From c85c1fdb8663763cbbe55f39aa88fa8ba0bf0a9b Mon Sep 17 00:00:00 2001 From: LukeGus Date: Wed, 23 Sep 2026 16:27:29 -0500 Subject: [PATCH] feat: convert file manager into a plugin --- docker/nginx-https.conf | 85 +- docker/nginx.conf | 83 +- drizzle/mysql/0036_dear_vapor.sql | 5 + drizzle/mysql/meta/0036_snapshot.json | 8691 +++++++++++++++++ drizzle/mysql/meta/_journal.json | 7 + drizzle/postgres/0037_dizzy_sage.sql | 5 + drizzle/postgres/meta/0037_snapshot.json | 8479 ++++++++++++++++ drizzle/postgres/meta/_journal.json | 7 + drizzle/sqlite/0033_careful_random.sql | 5 + drizzle/sqlite/meta/0033_snapshot.json | 8194 ++++++++++++++++ drizzle/sqlite/meta/_journal.json | 7 + packages/plugin-sdk/ARCHITECTURE.md | 26 +- packages/plugin-sdk/FINISH-LIST.md | 56 +- packages/plugin-sdk/src/backend.ts | 4 +- packages/plugin-sdk/src/frontend.ts | 203 +- plugins/docker/src/frontend/DockerManager.tsx | 6 +- .../frontend/components/ConsoleTerminal.tsx | 6 +- plugins/file-manager/locales/en.json | 414 + .../locales/translated/af_ZA.json | 405 + .../locales/translated/ar_SA.json | 405 + .../locales/translated/bg_BG.json | 405 + .../locales/translated/bn_BD.json | 405 + .../locales/translated/ca_ES.json | 405 + .../locales/translated/cs_CZ.json | 405 + .../locales/translated/da_DK.json | 405 + .../locales/translated/de_DE.json | 405 + .../locales/translated/el_GR.json | 405 + .../locales/translated/es_ES.json | 405 + .../locales/translated/fi_FI.json | 405 + .../locales/translated/fr_FR.json | 405 + .../locales/translated/he_IL.json | 405 + .../locales/translated/hi_IN.json | 405 + .../locales/translated/hu_HU.json | 405 + .../locales/translated/id_ID.json | 405 + .../locales/translated/it_IT.json | 405 + .../locales/translated/ja_JP.json | 405 + .../locales/translated/ko_KR.json | 405 + .../locales/translated/nl_NL.json | 405 + .../locales/translated/no_NO.json | 405 + .../locales/translated/pl_PL.json | 405 + .../locales/translated/pt_BR.json | 405 + .../locales/translated/pt_PT.json | 405 + .../locales/translated/ro_RO.json | 405 + .../locales/translated/ru_RU.json | 405 + .../locales/translated/sr_SP.json | 405 + .../locales/translated/sv_SE.json | 405 + .../locales/translated/th_TH.json | 405 + .../locales/translated/tr_TR.json | 405 + .../locales/translated/uk_UA.json | 405 + .../locales/translated/vi_VN.json | 405 + .../locales/translated/zh_CN.json | 405 + .../locales/translated/zh_TW.json | 405 + plugins/file-manager/manifest.json | 97 + .../mysql/0001_adopt_file_manager_tables.sql | 49 + .../0001_adopt_file_manager_tables.sql | 53 + plugins/file-manager/migrations/snapshot.json | 161 + .../sqlite/0001_adopt_file_manager_tables.sql | 53 + plugins/file-manager/package.json | 15 + .../file-manager/src/backend/action-routes.ts | 483 + .../src/backend/bookmark-routes.ts | 645 ++ .../src/backend/content-routes.ts | 1539 +++ .../src/backend/direct-transfer-routing.ts | 72 + .../src/backend/download-routes.ts | 316 + .../file-manager/src/backend/error-message.ts | 6 + plugins/file-manager/src/backend/index.ts | 2214 +++++ .../file-manager/src/backend/list-routes.ts | 465 + .../src/backend/operation-commands.ts | 39 + .../src/backend/operation-routes.ts | 864 ++ .../file-manager/src/backend/plugin-ctx.ts | 13 + plugins/file-manager/src/backend/service.ts | 143 + plugins/file-manager/src/backend/session.ts | 282 + .../src/backend/sftp-promisify.ts | 120 + plugins/file-manager/src/backend/ssh.ts | 79 + plugins/file-manager/src/backend/tables.ts | 105 + .../src/backend/transfer-engine.ts | 3411 +++++++ .../src/backend/transfer-errors.ts | 52 + .../src/backend/transfer-host-utils.ts | 59 + .../src/backend/transfer-integrity.ts | 54 + .../src/backend/transfer-paths.ts | 151 + .../src/backend/transfer-routing.ts | 205 + .../file-manager/src/backend/transfer-scan.ts | 89 + .../src/backend/transfer-segment-copy.ts | 40 + .../src/backend/transfer-sftp-dir.ts | 74 + .../src/backend/transfer-stats.ts | 157 + .../src/backend/transfer-tuning.ts | 388 + .../file-manager/src/backend/trash-service.ts | 234 + plugins/file-manager/src/backend/utils.ts | 164 + .../src/frontend}/FileManager.tsx | 32 +- .../src/frontend}/FileManagerApp.tsx | 5 +- .../src/frontend}/FileManagerContextMenu.tsx | 0 .../src/frontend}/FileManagerDialogs.tsx | 8 +- .../src/frontend}/FileManagerGrid.tsx | 0 .../src/frontend}/FileManagerSidebar.tsx | 8 +- .../src/frontend}/FileManagerToolbar.tsx | 6 +- .../src/frontend}/FileManagerTrashDialog.tsx | 6 +- .../src/frontend/HostFilesTab.tsx | 12 +- .../src/frontend}/LocalFileContextMenu.tsx | 0 .../src/frontend}/LocalFilePane.tsx | 3 +- .../src/frontend}/SftpTransferTab.tsx | 30 +- .../src/frontend}/SudoPasswordDialog.tsx | 6 +- .../src/frontend/TransferMonitor.tsx | 91 + .../frontend}/api/file-manager-data-api.ts | 20 +- .../src/frontend}/api/local-transfer-api.ts | 0 .../frontend}/api/ssh-file-operations-api.ts | 12 +- .../src/frontend}/components/AudioPreview.tsx | 0 .../src/frontend}/components/CodeEditor.tsx | 0 .../components/ColumnResizeHandle.tsx | 0 .../components/ColumnVisibilityMenu.tsx | 0 .../frontend}/components/CompressDialog.tsx | 8 +- .../src/frontend}/components/DiffViewer.tsx | 4 +- .../src/frontend}/components/DiffWindow.tsx | 0 .../components/DownloadProgressToast.tsx | 0 .../frontend}/components/DraggableWindow.tsx | 0 .../src/frontend}/components/FileViewer.tsx | 2 +- .../src/frontend}/components/FileWindow.tsx | 2 +- .../src/frontend}/components/ImagePreview.tsx | 2 +- .../components/LocalTransferProgressToast.tsx | 2 +- .../frontend}/components/MarkdownRenderer.tsx | 0 .../frontend}/components/PaneResizeHandle.tsx | 0 .../src/frontend}/components/PdfPreview.tsx | 11 +- .../components/PermissionsDialog.tsx | 6 +- .../frontend}/components/TerminalWindow.tsx | 0 .../components/TransferProgressBar.tsx | 0 .../components/TransferProgressToast.tsx | 2 +- .../components/TransferToHostDialog.tsx | 28 +- .../frontend}/components/WindowManager.tsx | 0 .../src/frontend}/file-manager-types.ts | 0 .../src/frontend}/file-manager-utils.ts | 0 .../src/frontend}/file-preview-preload.ts | 0 .../frontend/homepage}/FileManagerWidget.tsx | 20 +- .../homepage}/FileManagerWidgetEditForm.tsx | 2 +- .../src/frontend}/hooks/useDragAndDrop.ts | 0 .../src/frontend}/hooks/useDragToDesktop.ts | 2 +- .../frontend}/hooks/useDragToSystemDesktop.ts | 2 +- .../src/frontend}/hooks/useFileSelection.ts | 0 .../src/frontend}/hooks/useLocalTransfers.ts | 5 +- .../src/frontend}/hooks/usePaneWidth.ts | 0 .../frontend}/hooks/useResizableColumns.ts | 0 plugins/file-manager/src/frontend/index.tsx | 147 + .../lib/file-content-request-cache.ts | 2 +- .../frontend}/lib/file-list-request-cache.ts | 2 +- .../src/frontend}/local-transfer-utils.ts | 0 .../src/frontend}/optimistic-file-list.ts | 0 .../src/frontend}/sftp-transfer-utils.ts | 0 .../src/frontend}/transferMetricsFormat.ts | 0 .../frontend}/transferNotificationStore.ts | 0 .../src/frontend}/transferProgressMonitor.tsx | 2 +- plugins/file-manager/tests/backend/helpers.ts | 91 + .../tests/backend/migrations.test.ts | 167 + .../file-manager/tests/backend/routes.test.ts | 68 + .../tests/frontend}/LocalFilePane.test.tsx | 5 +- .../tests/frontend}/SftpTransferTab.test.tsx | 16 +- .../tests/frontend/activate.test.tsx | 80 + .../api/ssh-file-operations-api.test.ts | 2 +- .../components}/TransferProgressBar.test.tsx | 2 +- .../frontend}/file-manager-utils.test.ts | 2 +- .../frontend}/file-preview-preload.test.ts | 2 +- .../frontend}/hooks/useDragAndDrop.test.ts | 2 +- .../frontend}/hooks/useFileSelection.test.ts | 2 +- .../frontend}/hooks/usePaneWidth.test.ts | 2 +- .../hooks/useResizableColumns.test.ts | 2 +- .../lib/file-content-request-cache.test.ts | 2 +- .../frontend}/local-transfer-utils.test.ts | 2 +- .../frontend}/optimistic-file-list.test.ts | 2 +- .../frontend}/sftp-transfer-utils.test.ts | 2 +- .../frontend}/transfer-progress-toast.test.ts | 2 +- plugins/file-manager/tsconfig.json | 10 + plugins/file-manager/vitest.config.ts | 3 + .../src/frontend/HostMetricsTab.tsx | 2 +- .../src/frontend/stats/ProxmoxStatsTab.tsx | 2 +- .../src/frontend/stats/useConnectionRetry.ts | 193 - .../src/frontend/GuacamoleApp.tsx | 6 +- scripts/plugin-boundary-allowlist.json | 44 + scripts/shell-plugin-id-allowlist.json | 24 +- src/backend/database/database.ts | 188 +- src/backend/database/db/index.ts | 49 +- .../database/db/performance-indexes.ts | 23 +- src/backend/database/db/schema.mysql.ts | 87 +- src/backend/database/db/schema.pg.ts | 87 +- src/backend/database/db/schema.ts | 87 +- src/backend/database/repositories/factory.ts | 16 +- .../file-manager-bookmark-repository.ts | 522 - .../transfer-recent-repository.ts | 168 - .../database/routes/delete-user-data.ts | 8 +- .../host-file-manager-bookmark-routes.ts | 544 -- .../database/routes/host-folder-routes.ts | 12 +- src/backend/database/routes/host.ts | 96 +- .../routes/user-password-reset-routes.ts | 2 - src/backend/plugins/ctx-ssh-auth.ts | 2 + src/backend/starter.ts | 14 +- .../file-manager-bookmark-repository.test.ts | 193 - .../transfer-recent-repository.test.ts | 119 - .../routes/user-password-reset-routes.test.ts | 2 - .../file-manager-settings-migration.test.ts | 163 + .../file-manager-settings-migration.ts | 100 + src/backend/utils/user-data-export.ts | 45 +- src/main.tsx | 13 - src/ui/AppShell.tsx | 25 +- src/ui/api/file-manager-metadata-api.ts | 126 - .../features/file-manager/TransferMonitor.tsx | 88 - src/ui/features/homepage/HomepageCanvas.tsx | 1 - .../homepage/dialogs/WidgetEditDialog.tsx | 9 - .../homepage/quick-connect-targets.ts | 8 +- .../features/terminal/TerminalTabContent.tsx | 7 +- src/ui/lib/activity-types.ts | 7 +- src/ui/lib/useConnectionRetry.ts | 191 - src/ui/locales/en.json | 385 - src/ui/locales/translated/af_ZA.json | 385 - src/ui/locales/translated/ar_SA.json | 385 - src/ui/locales/translated/bg_BG.json | 385 - src/ui/locales/translated/bn_BD.json | 385 - src/ui/locales/translated/ca_ES.json | 385 - src/ui/locales/translated/cs_CZ.json | 385 - src/ui/locales/translated/da_DK.json | 385 - src/ui/locales/translated/de_DE.json | 385 - src/ui/locales/translated/el_GR.json | 385 - src/ui/locales/translated/es_ES.json | 385 - src/ui/locales/translated/fi_FI.json | 385 - src/ui/locales/translated/fr_FR.json | 385 - src/ui/locales/translated/he_IL.json | 385 - src/ui/locales/translated/hi_IN.json | 385 - src/ui/locales/translated/hu_HU.json | 385 - src/ui/locales/translated/id_ID.json | 385 - src/ui/locales/translated/it_IT.json | 385 - src/ui/locales/translated/ja_JP.json | 385 - src/ui/locales/translated/ko_KR.json | 385 - src/ui/locales/translated/nl_NL.json | 385 - src/ui/locales/translated/no_NO.json | 385 - src/ui/locales/translated/pl_PL.json | 385 - src/ui/locales/translated/pt_BR.json | 385 - src/ui/locales/translated/pt_PT.json | 385 - src/ui/locales/translated/ro_RO.json | 385 - src/ui/locales/translated/ru_RU.json | 385 - src/ui/locales/translated/sr_SP.json | 385 - src/ui/locales/translated/sv_SE.json | 385 - src/ui/locales/translated/th_TH.json | 385 - src/ui/locales/translated/tr_TR.json | 385 - src/ui/locales/translated/uk_UA.json | 385 - src/ui/locales/translated/vi_VN.json | 385 - src/ui/locales/translated/zh_CN.json | 385 - src/ui/locales/translated/zh_TW.json | 385 - src/ui/main-axios.ts | 95 +- src/ui/onboarding/steps/FeaturesStep.tsx | 3 +- src/ui/plugin-host/shell-bridge.ts | 2 - src/ui/plugin-host/testing-host.tsx | 2 - src/ui/shell/CommandPalette.tsx | 6 - src/ui/shell/shell-layout.ts | 2 - src/ui/shell/tab-registry.ts | 4 +- src/ui/shell/tabUtils.tsx | 38 - src/ui/sidebar/AppRail.tsx | 1 - src/ui/sidebar/HostEditor.tsx | 5 - src/ui/sidebar/HostManagerTabs.tsx | 12 +- src/ui/sidebar/UserProfilePanel.tsx | 40 +- src/ui/sidebar/rail-items.ts | 7 - src/ui/sidebar/tree/HostItem/HostItem.tsx | 22 - src/ui/tests/lib/useConnectionRetry.test.ts | 30 - src/ui/tests/shell/shell-layout.test.ts | 17 +- src/ui/tests/sidebar/rail-items.test.ts | 2 +- .../sidebar/tree/HostItem/HostItem.test.tsx | 10 +- 259 files changed, 54155 insertions(+), 16791 deletions(-) create mode 100644 drizzle/mysql/0036_dear_vapor.sql create mode 100644 drizzle/mysql/meta/0036_snapshot.json create mode 100644 drizzle/postgres/0037_dizzy_sage.sql create mode 100644 drizzle/postgres/meta/0037_snapshot.json create mode 100644 drizzle/sqlite/0033_careful_random.sql create mode 100644 drizzle/sqlite/meta/0033_snapshot.json create mode 100644 plugins/file-manager/locales/en.json create mode 100644 plugins/file-manager/locales/translated/af_ZA.json create mode 100644 plugins/file-manager/locales/translated/ar_SA.json create mode 100644 plugins/file-manager/locales/translated/bg_BG.json create mode 100644 plugins/file-manager/locales/translated/bn_BD.json create mode 100644 plugins/file-manager/locales/translated/ca_ES.json create mode 100644 plugins/file-manager/locales/translated/cs_CZ.json create mode 100644 plugins/file-manager/locales/translated/da_DK.json create mode 100644 plugins/file-manager/locales/translated/de_DE.json create mode 100644 plugins/file-manager/locales/translated/el_GR.json create mode 100644 plugins/file-manager/locales/translated/es_ES.json create mode 100644 plugins/file-manager/locales/translated/fi_FI.json create mode 100644 plugins/file-manager/locales/translated/fr_FR.json create mode 100644 plugins/file-manager/locales/translated/he_IL.json create mode 100644 plugins/file-manager/locales/translated/hi_IN.json create mode 100644 plugins/file-manager/locales/translated/hu_HU.json create mode 100644 plugins/file-manager/locales/translated/id_ID.json create mode 100644 plugins/file-manager/locales/translated/it_IT.json create mode 100644 plugins/file-manager/locales/translated/ja_JP.json create mode 100644 plugins/file-manager/locales/translated/ko_KR.json create mode 100644 plugins/file-manager/locales/translated/nl_NL.json create mode 100644 plugins/file-manager/locales/translated/no_NO.json create mode 100644 plugins/file-manager/locales/translated/pl_PL.json create mode 100644 plugins/file-manager/locales/translated/pt_BR.json create mode 100644 plugins/file-manager/locales/translated/pt_PT.json create mode 100644 plugins/file-manager/locales/translated/ro_RO.json create mode 100644 plugins/file-manager/locales/translated/ru_RU.json create mode 100644 plugins/file-manager/locales/translated/sr_SP.json create mode 100644 plugins/file-manager/locales/translated/sv_SE.json create mode 100644 plugins/file-manager/locales/translated/th_TH.json create mode 100644 plugins/file-manager/locales/translated/tr_TR.json create mode 100644 plugins/file-manager/locales/translated/uk_UA.json create mode 100644 plugins/file-manager/locales/translated/vi_VN.json create mode 100644 plugins/file-manager/locales/translated/zh_CN.json create mode 100644 plugins/file-manager/locales/translated/zh_TW.json create mode 100644 plugins/file-manager/manifest.json create mode 100644 plugins/file-manager/migrations/mysql/0001_adopt_file_manager_tables.sql create mode 100644 plugins/file-manager/migrations/postgres/0001_adopt_file_manager_tables.sql create mode 100644 plugins/file-manager/migrations/snapshot.json create mode 100644 plugins/file-manager/migrations/sqlite/0001_adopt_file_manager_tables.sql create mode 100644 plugins/file-manager/package.json create mode 100644 plugins/file-manager/src/backend/action-routes.ts create mode 100644 plugins/file-manager/src/backend/bookmark-routes.ts create mode 100644 plugins/file-manager/src/backend/content-routes.ts create mode 100644 plugins/file-manager/src/backend/direct-transfer-routing.ts create mode 100644 plugins/file-manager/src/backend/download-routes.ts create mode 100644 plugins/file-manager/src/backend/error-message.ts create mode 100644 plugins/file-manager/src/backend/index.ts create mode 100644 plugins/file-manager/src/backend/list-routes.ts create mode 100644 plugins/file-manager/src/backend/operation-commands.ts create mode 100644 plugins/file-manager/src/backend/operation-routes.ts create mode 100644 plugins/file-manager/src/backend/plugin-ctx.ts create mode 100644 plugins/file-manager/src/backend/service.ts create mode 100644 plugins/file-manager/src/backend/session.ts create mode 100644 plugins/file-manager/src/backend/sftp-promisify.ts create mode 100644 plugins/file-manager/src/backend/ssh.ts create mode 100644 plugins/file-manager/src/backend/tables.ts create mode 100644 plugins/file-manager/src/backend/transfer-engine.ts create mode 100644 plugins/file-manager/src/backend/transfer-errors.ts create mode 100644 plugins/file-manager/src/backend/transfer-host-utils.ts create mode 100644 plugins/file-manager/src/backend/transfer-integrity.ts create mode 100644 plugins/file-manager/src/backend/transfer-paths.ts create mode 100644 plugins/file-manager/src/backend/transfer-routing.ts create mode 100644 plugins/file-manager/src/backend/transfer-scan.ts create mode 100644 plugins/file-manager/src/backend/transfer-segment-copy.ts create mode 100644 plugins/file-manager/src/backend/transfer-sftp-dir.ts create mode 100644 plugins/file-manager/src/backend/transfer-stats.ts create mode 100644 plugins/file-manager/src/backend/transfer-tuning.ts create mode 100644 plugins/file-manager/src/backend/trash-service.ts create mode 100644 plugins/file-manager/src/backend/utils.ts rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManager.tsx (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManagerApp.tsx (83%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManagerContextMenu.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManagerDialogs.tsx (95%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManagerGrid.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManagerSidebar.tsx (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManagerToolbar.tsx (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/FileManagerTrashDialog.tsx (98%) rename src/ui/sidebar/HostEditorFilesTab.tsx => plugins/file-manager/src/frontend/HostFilesTab.tsx (87%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/LocalFileContextMenu.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/LocalFilePane.tsx (99%) rename {src/ui/features/sftp => plugins/file-manager/src/frontend}/SftpTransferTab.tsx (98%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/SudoPasswordDialog.tsx (95%) create mode 100644 plugins/file-manager/src/frontend/TransferMonitor.tsx rename {src/ui => plugins/file-manager/src/frontend}/api/file-manager-data-api.ts (80%) rename {src/ui => plugins/file-manager/src/frontend}/api/local-transfer-api.ts (100%) rename {src/ui => plugins/file-manager/src/frontend}/api/ssh-file-operations-api.ts (98%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/AudioPreview.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/CodeEditor.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/ColumnResizeHandle.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/ColumnVisibilityMenu.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/CompressDialog.tsx (96%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/DiffViewer.tsx (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/DiffWindow.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/DownloadProgressToast.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/DraggableWindow.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/FileViewer.tsx (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/FileWindow.tsx (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/ImagePreview.tsx (98%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/LocalTransferProgressToast.tsx (98%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/MarkdownRenderer.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/PaneResizeHandle.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/PdfPreview.tsx (91%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/PermissionsDialog.tsx (98%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/TerminalWindow.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/TransferProgressBar.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/TransferProgressToast.tsx (98%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/TransferToHostDialog.tsx (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/components/WindowManager.tsx (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/file-manager-types.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/file-manager-utils.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/file-preview-preload.ts (100%) rename {src/ui/features/homepage/widgets => plugins/file-manager/src/frontend/homepage}/FileManagerWidget.tsx (75%) rename {src/ui/features/homepage/dialogs => plugins/file-manager/src/frontend/homepage}/FileManagerWidgetEditForm.tsx (83%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/hooks/useDragAndDrop.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/hooks/useDragToDesktop.ts (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/hooks/useDragToSystemDesktop.ts (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/hooks/useFileSelection.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/hooks/useLocalTransfers.ts (99%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/hooks/usePaneWidth.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/hooks/useResizableColumns.ts (100%) create mode 100644 plugins/file-manager/src/frontend/index.tsx rename {src/ui => plugins/file-manager/src/frontend}/lib/file-content-request-cache.ts (92%) rename {src/ui => plugins/file-manager/src/frontend}/lib/file-list-request-cache.ts (93%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/local-transfer-utils.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/optimistic-file-list.ts (100%) rename {src/ui/features/sftp => plugins/file-manager/src/frontend}/sftp-transfer-utils.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/transferMetricsFormat.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/transferNotificationStore.ts (100%) rename {src/ui/features/file-manager => plugins/file-manager/src/frontend}/transferProgressMonitor.tsx (99%) create mode 100644 plugins/file-manager/tests/backend/helpers.ts create mode 100644 plugins/file-manager/tests/backend/migrations.test.ts create mode 100644 plugins/file-manager/tests/backend/routes.test.ts rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/LocalFilePane.test.tsx (98%) rename {src/ui/tests/features/sftp => plugins/file-manager/tests/frontend}/SftpTransferTab.test.tsx (95%) create mode 100644 plugins/file-manager/tests/frontend/activate.test.tsx rename {src/ui/tests => plugins/file-manager/tests/frontend}/api/ssh-file-operations-api.test.ts (96%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend/components}/TransferProgressBar.test.tsx (95%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/file-manager-utils.test.ts (96%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/file-preview-preload.test.ts (83%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/hooks/useDragAndDrop.test.ts (96%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/hooks/useFileSelection.test.ts (96%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/hooks/usePaneWidth.test.ts (96%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/hooks/useResizableColumns.test.ts (97%) rename {src/ui/tests => plugins/file-manager/tests/frontend}/lib/file-content-request-cache.test.ts (95%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/local-transfer-utils.test.ts (99%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/optimistic-file-list.test.ts (96%) rename {src/ui/tests/features/sftp => plugins/file-manager/tests/frontend}/sftp-transfer-utils.test.ts (94%) rename {src/ui/tests/features/file-manager => plugins/file-manager/tests/frontend}/transfer-progress-toast.test.ts (90%) create mode 100644 plugins/file-manager/tsconfig.json create mode 100644 plugins/file-manager/vitest.config.ts delete mode 100644 plugins/proxmox/src/frontend/stats/useConnectionRetry.ts delete mode 100644 src/backend/database/repositories/file-manager-bookmark-repository.ts delete mode 100644 src/backend/database/repositories/transfer-recent-repository.ts delete mode 100644 src/backend/database/routes/host-file-manager-bookmark-routes.ts delete mode 100644 src/backend/tests/database/repositories/file-manager-bookmark-repository.test.ts delete mode 100644 src/backend/tests/database/repositories/transfer-recent-repository.test.ts create mode 100644 src/backend/tests/utils/file-manager-settings-migration.test.ts create mode 100644 src/backend/utils/crypto-migration/file-manager-settings-migration.ts delete mode 100644 src/ui/api/file-manager-metadata-api.ts delete mode 100644 src/ui/features/file-manager/TransferMonitor.tsx delete mode 100644 src/ui/lib/useConnectionRetry.ts delete mode 100644 src/ui/tests/lib/useConnectionRetry.test.ts diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index ab821f300..56e01d9e5 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -161,7 +161,7 @@ http { root /app/html; index index.html index.htm; expires off; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-xKFwPOSyQDCFj701DTxJU9nyOtY2cN1n+z8W2Q5rnUw='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-mtjYp/6raD7Dv7qw+nVeK90WjCAjr1mYFcUy3FI8cm4='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; @@ -588,87 +588,8 @@ http { proxy_cache off; } - location /host/file_manager/recent { - 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; - } - - location /host/file_manager/pinned { - 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; - } - - location /host/file_manager/shortcuts { - 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; - } - - location /host/file_manager/sudo-password { - proxy_pass http://127.0.0.1:30004; - 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; - } - - location /ssh/file_manager/ { - client_max_body_size 5G; - client_body_timeout 300s; - - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Content-Type-Options nosniff always; - add_header Cache-Control "no-store, no-cache, must-revalidate" always; - - proxy_pass http://127.0.0.1:30004; - 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; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - - proxy_request_buffering off; - proxy_buffering off; - } - - location /host/file_manager/ssh/ { - client_max_body_size 5G; - client_body_timeout 300s; - - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Content-Type-Options nosniff always; - add_header Cache-Control "no-store, no-cache, must-revalidate" always; - - proxy_pass http://127.0.0.1:30004; - 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; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - - proxy_request_buffering off; - proxy_buffering off; - } + # File manager routes (recent/pinned/shortcuts, SFTP, transfers) moved + # to the file-manager plugin, served under /plugin-api/file-manager/. location /health { proxy_pass http://127.0.0.1:30001; diff --git a/docker/nginx.conf b/docker/nginx.conf index 1232cc034..9fa0157da 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -142,7 +142,7 @@ http { root /app/html; index index.html index.htm; expires off; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-xKFwPOSyQDCFj701DTxJU9nyOtY2cN1n+z8W2Q5rnUw='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-mtjYp/6raD7Dv7qw+nVeK90WjCAjr1mYFcUy3FI8cm4='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; @@ -566,85 +566,8 @@ http { proxy_cache off; } - location /host/file_manager/recent { - 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; - } - - location /host/file_manager/pinned { - 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; - } - - location /host/file_manager/shortcuts { - 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; - } - - location /host/file_manager/sudo-password { - proxy_pass http://127.0.0.1:30004; - 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; - } - - location /ssh/file_manager/ { - client_max_body_size 5G; - client_body_timeout 3600s; - - add_header X-Content-Type-Options nosniff always; - add_header Cache-Control "no-store, no-cache, must-revalidate" always; - - proxy_pass http://127.0.0.1:30004; - 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; - - proxy_connect_timeout 60s; - proxy_send_timeout 3600s; - proxy_read_timeout 3600s; - - proxy_request_buffering off; - proxy_buffering off; - } - - location /host/file_manager/ssh/ { - client_max_body_size 5G; - client_body_timeout 3600s; - - add_header X-Content-Type-Options nosniff always; - add_header Cache-Control "no-store, no-cache, must-revalidate" always; - - proxy_pass http://127.0.0.1:30004; - 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; - - proxy_connect_timeout 60s; - proxy_send_timeout 3600s; - proxy_read_timeout 3600s; - - proxy_request_buffering off; - proxy_buffering off; - } + # File manager routes (recent/pinned/shortcuts, SFTP, transfers) moved + # to the file-manager plugin, served under /plugin-api/file-manager/. location /health { proxy_pass http://127.0.0.1:30001; diff --git a/drizzle/mysql/0036_dear_vapor.sql b/drizzle/mysql/0036_dear_vapor.sql new file mode 100644 index 000000000..e060073f0 --- /dev/null +++ b/drizzle/mysql/0036_dear_vapor.sql @@ -0,0 +1,5 @@ +-- file_manager_pinned, file_manager_recent, file_manager_shortcuts and +-- transfer_recent are adopted (renamed, not dropped) by the file-manager +-- plugin's own migration, which runs at plugin activation, after core's +-- drizzle migrations. A DROP TABLE here would run first and delete the data. +SELECT 1; diff --git a/drizzle/mysql/meta/0036_snapshot.json b/drizzle/mysql/meta/0036_snapshot.json new file mode 100644 index 000000000..49f8aa7ae --- /dev/null +++ b/drizzle/mysql/meta/0036_snapshot.json @@ -0,0 +1,8691 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "fac3c8a9-dffd-43df-ae9d-83424da7b030", + "prevId": "f25c6bfb-45f9-43c5-9bbf-477b4fff2f78", + "tables": { + "ai_conversations": { + "name": "ai_conversations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_conversations_user": { + "name": "idx_ai_conversations_user", + "columns": [ + "user_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_conversations_user_id_users_id_fk": { + "name": "ai_conversations_user_id_users_id_fk", + "tableFrom": "ai_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_conversations_id": { + "name": "ai_conversations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ai_messages": { + "name": "ai_messages", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('')" + }, + "tool_calls": { + "name": "tool_calls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_messages_conversation": { + "name": "idx_ai_messages_conversation", + "columns": [ + "conversation_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_messages_conversation_id_ai_conversations_id_fk": { + "name": "ai_messages_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_messages", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_messages_id": { + "name": "ai_messages_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ai_proposals": { + "name": "ai_proposals", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{}')" + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_proposals_user": { + "name": "idx_ai_proposals_user", + "columns": [ + "user_id", + "status" + ], + "isUnique": false + }, + "idx_ai_proposals_conversation": { + "name": "idx_ai_proposals_conversation", + "columns": [ + "conversation_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_proposals_conversation_id_ai_conversations_id_fk": { + "name": "ai_proposals_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_proposals_user_id_users_id_fk": { + "name": "ai_proposals_user_id_users_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_proposals_id": { + "name": "ai_proposals_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ai_providers": { + "name": "ai_providers", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_key_prefix": { + "name": "api_key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ai_providers_user_label": { + "name": "idx_ai_providers_user_label", + "columns": [ + "user_id", + "label" + ], + "isUnique": true + } + }, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_providers_id": { + "name": "ai_providers_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "api_keys_id": { + "name": "api_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "audit_logs_id": { + "name": "audit_logs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_channels": { + "name": "automation_channels", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_channels_pair": { + "name": "idx_automation_channels_pair", + "columns": [ + "automation_id", + "channel_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_channels_automation_id_automations_id_fk": { + "name": "automation_channels_automation_id_automations_id_fk", + "tableFrom": "automation_channels", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_channels_channel_id_notification_channels_id_fk": { + "name": "automation_channels_channel_id_notification_channels_id_fk", + "tableFrom": "automation_channels", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_channels_id": { + "name": "automation_channels_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_run_steps": { + "name": "automation_run_steps", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "run_id": { + "name": "run_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_index": { + "name": "step_index", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_id": { + "name": "step_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_type": { + "name": "step_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "truncated": { + "name": "truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_automation_run_steps_run": { + "name": "idx_automation_run_steps_run", + "columns": [ + "run_id", + "step_index" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_run_steps_run_id_automation_runs_id_fk": { + "name": "automation_run_steps_run_id_automation_runs_id_fk", + "tableFrom": "automation_run_steps", + "tableTo": "automation_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_run_steps_id": { + "name": "automation_run_steps_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_runs": { + "name": "automation_runs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_context": { + "name": "trigger_context", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_runs_automation": { + "name": "idx_automation_runs_automation", + "columns": [ + "automation_id", + "started_at" + ], + "isUnique": false + }, + "idx_automation_runs_user": { + "name": "idx_automation_runs_user", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_user_id_users_id_fk": { + "name": "automation_runs_user_id_users_id_fk", + "tableFrom": "automation_runs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_runs_id": { + "name": "automation_runs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_schedules": { + "name": "automation_schedules", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_due_at": { + "name": "next_due_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_tick_at": { + "name": "last_tick_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_schedules_automation": { + "name": "idx_automation_schedules_automation", + "columns": [ + "automation_id" + ], + "isUnique": true + }, + "idx_automation_schedules_due": { + "name": "idx_automation_schedules_due", + "columns": [ + "next_due_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_schedules_automation_id_automations_id_fk": { + "name": "automation_schedules_automation_id_automations_id_fk", + "tableFrom": "automation_schedules", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_schedules_id": { + "name": "automation_schedules_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automation_trigger_state": { + "name": "automation_trigger_state", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state_key": { + "name": "state_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "breach_started_at": { + "name": "breach_started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_value": { + "name": "last_value", + "type": "double", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_observed_state": { + "name": "last_observed_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_automation_trigger_state_key": { + "name": "idx_automation_trigger_state_key", + "columns": [ + "automation_id", + "state_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_trigger_state_automation_id_automations_id_fk": { + "name": "automation_trigger_state_automation_id_automations_id_fk", + "tableFrom": "automation_trigger_state", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automation_trigger_state_id": { + "name": "automation_trigger_state_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "definition_version": { + "name": "definition_version", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('skip')" + }, + "max_run_seconds": { + "name": "max_run_seconds", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_automations_user": { + "name": "idx_automations_user", + "columns": [ + "user_id", + "enabled" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_user_id_users_id_fk": { + "name": "automations_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "automations_id": { + "name": "automations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "c2s_tunnel_presets": { + "name": "c2s_tunnel_presets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "computer_name": { + "name": "computer_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "c2s_tunnel_presets_user_id_users_id_fk": { + "name": "c2s_tunnel_presets_user_id_users_id_fk", + "tableFrom": "c2s_tunnel_presets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "c2s_tunnel_presets_id": { + "name": "c2s_tunnel_presets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "collab_room_members": { + "name": "collab_room_members", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "room_id": { + "name": "room_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_role": { + "name": "room_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('member')" + }, + "added_by": { + "name": "added_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_collab_room_members_room_user": { + "name": "idx_collab_room_members_room_user", + "columns": [ + "room_id", + "user_id" + ], + "isUnique": true + }, + "idx_collab_room_members_user": { + "name": "idx_collab_room_members_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "collab_room_members_room_id_collab_rooms_id_fk": { + "name": "collab_room_members_room_id_collab_rooms_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "collab_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_user_id_users_id_fk": { + "name": "collab_room_members_user_id_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_added_by_users_id_fk": { + "name": "collab_room_members_added_by_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "collab_room_members_id": { + "name": "collab_room_members_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "collab_rooms": { + "name": "collab_rooms", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "persistent": { + "name": "persistent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "presenter_user_id": { + "name": "presenter_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_protocol": { + "name": "stage_protocol", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_host_id": { + "name": "stage_host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_share_id": { + "name": "stage_share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_link_token": { + "name": "guest_link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_collab_rooms_owner": { + "name": "idx_collab_rooms_owner", + "columns": [ + "owner_user_id" + ], + "isUnique": false + }, + "idx_collab_rooms_guest_token": { + "name": "idx_collab_rooms_guest_token", + "columns": [ + "guest_link_token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "collab_rooms_owner_user_id_users_id_fk": { + "name": "collab_rooms_owner_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_rooms_presenter_user_id_users_id_fk": { + "name": "collab_rooms_presenter_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "presenter_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_host_id_ssh_data_id_fk": { + "name": "collab_rooms_stage_host_id_ssh_data_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "ssh_data", + "columnsFrom": [ + "stage_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_share_id_session_shares_id_fk": { + "name": "collab_rooms_stage_share_id_session_shares_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "session_shares", + "columnsFrom": [ + "stage_share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "collab_rooms_id": { + "name": "collab_rooms_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "command_history": { + "name": "command_history", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "executed_at": { + "name": "executed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_command_history_user_host": { + "name": "idx_command_history_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "command_history_user_id_users_id_fk": { + "name": "command_history_user_id_users_id_fk", + "tableFrom": "command_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "command_history_host_id_ssh_data_id_fk": { + "name": "command_history_host_id_ssh_data_id_fk", + "tableFrom": "command_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "command_history_id": { + "name": "command_history_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('use')" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_access_id": { + "name": "credential_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_sidebar_preferences_user_id": { + "name": "credential_sidebar_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "dashboard_service_links": { + "name": "dashboard_service_links", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_service_links_user_id_users_id_fk": { + "name": "dashboard_service_links_user_id_users_id_fk", + "tableFrom": "dashboard_service_links", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dashboard_service_links_id": { + "name": "dashboard_service_links_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "dashboard_service_links_sync_id_unique": { + "name": "dashboard_service_links_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "dismissed_alerts": { + "name": "dismissed_alerts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dismissed_alerts_id": { + "name": "dismissed_alerts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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": {} + }, + "session_recordings": { + "name": "session_recordings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_id": { + "name": "access_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commands": { + "name": "commands", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dangerous_actions": { + "name": "dangerous_actions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recording_path": { + "name": "recording_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('text')" + }, + "terminated_by_owner": { + "name": "terminated_by_owner", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "termination_reason": { + "name": "termination_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_session_recordings_user_started": { + "name": "idx_session_recordings_user_started", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + }, + "idx_session_recordings_host": { + "name": "idx_session_recordings_host", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_recordings_host_id_ssh_data_id_fk": { + "name": "session_recordings_host_id_ssh_data_id_fk", + "tableFrom": "session_recordings", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_recordings_user_id_users_id_fk": { + "name": "session_recordings_user_id_users_id_fk", + "tableFrom": "session_recordings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "session_recordings_access_id_host_access_id_fk": { + "name": "session_recordings_access_id_host_access_id_fk", + "tableFrom": "session_recordings", + "tableTo": "host_access", + "columnsFrom": [ + "access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_recordings_id": { + "name": "session_recordings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "session_share_participants": { + "name": "session_share_participants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "share_id": { + "name": "share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_label": { + "name": "guest_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "left_at": { + "name": "left_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_share_participants_share_id_session_shares_id_fk": { + "name": "session_share_participants_share_id_session_shares_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "session_shares", + "columnsFrom": [ + "share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_share_participants_user_id_users_id_fk": { + "name": "session_share_participants_user_id_users_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_share_participants_id": { + "name": "session_share_participants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "session_shares": { + "name": "session_shares", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_instance_id": { + "name": "tab_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_type": { + "name": "share_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "link_token": { + "name": "link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('read-only')" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_joined_at": { + "name": "last_joined_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "join_count": { + "name": "join_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_session_shares_session_id": { + "name": "idx_session_shares_session_id", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_session_shares_host_id": { + "name": "idx_session_shares_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_shares_host_id_ssh_data_id_fk": { + "name": "session_shares_host_id_ssh_data_id_fk", + "tableFrom": "session_shares", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_owner_user_id_users_id_fk": { + "name": "session_shares_owner_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_target_user_id_users_id_fk": { + "name": "session_shares_target_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_shares_id": { + "name": "session_shares_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "session_shares_link_token_unique": { + "name": "session_shares_link_token_unique", + "columns": [ + "link_token" + ] + } + }, + "checkConstraint": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sessions_id": { + "name": "sessions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "settings_key": { + "name": "settings_key", + "columns": [ + "key" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('password')" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_credential_secrets_id": { + "name": "shared_credential_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_auth_overrides_id": { + "name": "shared_host_auth_overrides_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('credential')" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_secrets_id": { + "name": "shared_host_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credential_usage_id": { + "name": "ssh_credential_usage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credentials_id": { + "name": "ssh_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_folders_id": { + "name": "ssh_folders_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "sso_providers": { + "name": "sso_providers", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "sso_providers_id": { + "name": "sso_providers_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sync_tombstones": { + "name": "sync_tombstones", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_tombstones_id": { + "name": "sync_tombstones_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "termix_identities": { + "name": "termix_identities", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identities_id": { + "name": "termix_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_ca_id": { + "name": "termix_identity_ca_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('manual')" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_keys_id": { + "name": "termix_identity_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "tmux_session_tags": { + "name": "tmux_session_tags", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_name": { + "name": "session_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "tmux_session_tags_user_id_users_id_fk": { + "name": "tmux_session_tags_user_id_users_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tmux_session_tags_host_id_ssh_data_id_fk": { + "name": "tmux_session_tags_host_id_ssh_data_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "tmux_session_tags_id": { + "name": "tmux_session_tags_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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 151d0edf2..ed2f4bf21 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -253,6 +253,13 @@ "when": 1790185584715, "tag": "0035_clumsy_leech", "breakpoints": true + }, + { + "idx": 36, + "version": "5", + "when": 1790189637531, + "tag": "0036_dear_vapor", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/postgres/0037_dizzy_sage.sql b/drizzle/postgres/0037_dizzy_sage.sql new file mode 100644 index 000000000..e060073f0 --- /dev/null +++ b/drizzle/postgres/0037_dizzy_sage.sql @@ -0,0 +1,5 @@ +-- file_manager_pinned, file_manager_recent, file_manager_shortcuts and +-- transfer_recent are adopted (renamed, not dropped) by the file-manager +-- plugin's own migration, which runs at plugin activation, after core's +-- drizzle migrations. A DROP TABLE here would run first and delete the data. +SELECT 1; diff --git a/drizzle/postgres/meta/0037_snapshot.json b/drizzle/postgres/meta/0037_snapshot.json new file mode 100644 index 000000000..664aee72c --- /dev/null +++ b/drizzle/postgres/meta/0037_snapshot.json @@ -0,0 +1,8479 @@ +{ + "id": "59f4eaac-ecc6-4b3a-a985-f51a3540fb4a", + "prevId": "c76efbc1-e863-4d39-82fc-b695350f9359", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ai_conversations": { + "name": "ai_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_conversations_user": { + "name": "idx_ai_conversations_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_conversations_user_id_users_id_fk": { + "name": "ai_conversations_user_id_users_id_fk", + "tableFrom": "ai_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_messages": { + "name": "ai_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "tool_calls": { + "name": "tool_calls", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_messages_conversation": { + "name": "idx_ai_messages_conversation", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_messages_conversation_id_ai_conversations_id_fk": { + "name": "ai_messages_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_messages", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_proposals": { + "name": "ai_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_proposals_user": { + "name": "idx_ai_proposals_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_proposals_conversation": { + "name": "idx_ai_proposals_conversation", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_proposals_conversation_id_ai_conversations_id_fk": { + "name": "ai_proposals_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_proposals_user_id_users_id_fk": { + "name": "ai_proposals_user_id_users_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_providers": { + "name": "ai_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_prefix": { + "name": "api_key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_providers_user_label": { + "name": "idx_ai_providers_user_label", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_channels": { + "name": "automation_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_automation_channels_pair": { + "name": "idx_automation_channels_pair", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_channels_automation_id_automations_id_fk": { + "name": "automation_channels_automation_id_automations_id_fk", + "tableFrom": "automation_channels", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_channels_channel_id_notification_channels_id_fk": { + "name": "automation_channels_channel_id_notification_channels_id_fk", + "tableFrom": "automation_channels", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_run_steps": { + "name": "automation_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "step_index": { + "name": "step_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "step_id": { + "name": "step_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "step_type": { + "name": "step_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "truncated": { + "name": "truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_automation_run_steps_run": { + "name": "idx_automation_run_steps_run", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "step_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_run_steps_run_id_automation_runs_id_fk": { + "name": "automation_run_steps_run_id_automation_runs_id_fk", + "tableFrom": "automation_run_steps", + "tableTo": "automation_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_runs": { + "name": "automation_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_context": { + "name": "trigger_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_automation_runs_automation": { + "name": "idx_automation_runs_automation", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_automation_runs_user": { + "name": "idx_automation_runs_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_user_id_users_id_fk": { + "name": "automation_runs_user_id_users_id_fk", + "tableFrom": "automation_runs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_schedules": { + "name": "automation_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_due_at": { + "name": "next_due_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_tick_at": { + "name": "last_tick_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_automation_schedules_automation": { + "name": "idx_automation_schedules_automation", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_automation_schedules_due": { + "name": "idx_automation_schedules_due", + "columns": [ + { + "expression": "next_due_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_schedules_automation_id_automations_id_fk": { + "name": "automation_schedules_automation_id_automations_id_fk", + "tableFrom": "automation_schedules", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_trigger_state": { + "name": "automation_trigger_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "state_key": { + "name": "state_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "breach_started_at": { + "name": "breach_started_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_value": { + "name": "last_value", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "last_observed_state": { + "name": "last_observed_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automation_trigger_state_key": { + "name": "idx_automation_trigger_state_key", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_trigger_state_automation_id_automations_id_fk": { + "name": "automation_trigger_state_automation_id_automations_id_fk", + "tableFrom": "automation_trigger_state", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automations": { + "name": "automations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition_version": { + "name": "definition_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip'" + }, + "max_run_seconds": { + "name": "max_run_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300 + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automations_user": { + "name": "idx_automations_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automations_user_id_users_id_fk": { + "name": "automations_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.c2s_tunnel_presets": { + "name": "c2s_tunnel_presets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "computer_name": { + "name": "computer_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "c2s_tunnel_presets_user_id_users_id_fk": { + "name": "c2s_tunnel_presets_user_id_users_id_fk", + "tableFrom": "c2s_tunnel_presets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collab_room_members": { + "name": "collab_room_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "room_role": { + "name": "room_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "added_by": { + "name": "added_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_collab_room_members_room_user": { + "name": "idx_collab_room_members_room_user", + "columns": [ + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_collab_room_members_user": { + "name": "idx_collab_room_members_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collab_room_members_room_id_collab_rooms_id_fk": { + "name": "collab_room_members_room_id_collab_rooms_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "collab_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_user_id_users_id_fk": { + "name": "collab_room_members_user_id_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_added_by_users_id_fk": { + "name": "collab_room_members_added_by_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collab_rooms": { + "name": "collab_rooms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "persistent": { + "name": "persistent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "presenter_user_id": { + "name": "presenter_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "stage_protocol": { + "name": "stage_protocol", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stage_host_id": { + "name": "stage_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stage_share_id": { + "name": "stage_share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "guest_link_token": { + "name": "guest_link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_collab_rooms_owner": { + "name": "idx_collab_rooms_owner", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_collab_rooms_guest_token": { + "name": "idx_collab_rooms_guest_token", + "columns": [ + { + "expression": "guest_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collab_rooms_owner_user_id_users_id_fk": { + "name": "collab_rooms_owner_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_rooms_presenter_user_id_users_id_fk": { + "name": "collab_rooms_presenter_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "presenter_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_host_id_ssh_data_id_fk": { + "name": "collab_rooms_stage_host_id_ssh_data_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "ssh_data", + "columnsFrom": [ + "stage_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_share_id_session_shares_id_fk": { + "name": "collab_rooms_stage_share_id_session_shares_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "session_shares", + "columnsFrom": [ + "stage_share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.command_history": { + "name": "command_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "executed_at": { + "name": "executed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_command_history_user_host": { + "name": "idx_command_history_user_host", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "command_history_user_id_users_id_fk": { + "name": "command_history_user_id_users_id_fk", + "tableFrom": "command_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "command_history_host_id_ssh_data_id_fk": { + "name": "command_history_host_id_ssh_data_id_fk", + "tableFrom": "command_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_access": { + "name": "credential_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_service_links": { + "name": "dashboard_service_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_service_links_user_id_users_id_fk": { + "name": "dashboard_service_links_user_id_users_id_fk", + "tableFrom": "dashboard_service_links", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dashboard_service_links_sync_id_unique": { + "name": "dashboard_service_links_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dismissed_alerts": { + "name": "dismissed_alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.session_recordings": { + "name": "session_recordings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_id": { + "name": "access_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "commands": { + "name": "commands", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dangerous_actions": { + "name": "dangerous_actions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recording_path": { + "name": "recording_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "terminated_by_owner": { + "name": "terminated_by_owner", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "termination_reason": { + "name": "termination_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_session_recordings_user_started": { + "name": "idx_session_recordings_user_started", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_session_recordings_host": { + "name": "idx_session_recordings_host", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_recordings_host_id_ssh_data_id_fk": { + "name": "session_recordings_host_id_ssh_data_id_fk", + "tableFrom": "session_recordings", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_recordings_user_id_users_id_fk": { + "name": "session_recordings_user_id_users_id_fk", + "tableFrom": "session_recordings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "session_recordings_access_id_host_access_id_fk": { + "name": "session_recordings_access_id_host_access_id_fk", + "tableFrom": "session_recordings", + "tableTo": "host_access", + "columnsFrom": [ + "access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_share_participants": { + "name": "session_share_participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "share_id": { + "name": "share_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "guest_label": { + "name": "guest_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "left_at": { + "name": "left_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_share_participants_share_id_session_shares_id_fk": { + "name": "session_share_participants_share_id_session_shares_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "session_shares", + "columnsFrom": [ + "share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_share_participants_user_id_users_id_fk": { + "name": "session_share_participants_user_id_users_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_shares": { + "name": "session_shares", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_instance_id": { + "name": "tab_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "share_type": { + "name": "share_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "link_token": { + "name": "link_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read-only'" + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_joined_at": { + "name": "last_joined_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "join_count": { + "name": "join_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "idx_session_shares_session_id": { + "name": "idx_session_shares_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_session_shares_host_id": { + "name": "idx_session_shares_host_id", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_shares_host_id_ssh_data_id_fk": { + "name": "session_shares_host_id_ssh_data_id_fk", + "tableFrom": "session_shares", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_owner_user_id_users_id_fk": { + "name": "session_shares_owner_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_target_user_id_users_id_fk": { + "name": "session_shares_target_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_shares_link_token_unique": { + "name": "session_shares_link_token_unique", + "nullsNotDistinct": false, + "columns": [ + "link_token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_credential_secrets": { + "name": "shared_credential_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + { + "expression": "credential_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_secrets": { + "name": "shared_host_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + { + "expression": "host_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credential_usage": { + "name": "ssh_credential_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credentials": { + "name": "ssh_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_folders": { + "name": "ssh_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_providers": { + "name": "sso_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_tombstones": { + "name": "sync_tombstones", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identities": { + "name": "termix_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "nullsNotDistinct": false, + "columns": [ + "handle" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_ca": { + "name": "termix_identity_ca", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "nullsNotDistinct": false, + "columns": [ + "identity_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_keys": { + "name": "termix_identity_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tmux_session_tags": { + "name": "tmux_session_tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_name": { + "name": "session_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "tmux_session_tags_user_id_users_id_fk": { + "name": "tmux_session_tags_user_id_users_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tmux_session_tags_host_id_ssh_data_id_fk": { + "name": "tmux_session_tags_host_id_ssh_data_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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 42382b39a..09cd90055 100644 --- a/drizzle/postgres/meta/_journal.json +++ b/drizzle/postgres/meta/_journal.json @@ -260,6 +260,13 @@ "when": 1790185584204, "tag": "0036_redundant_ender_wiggin", "breakpoints": true + }, + { + "idx": 37, + "version": "7", + "when": 1790189637011, + "tag": "0037_dizzy_sage", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0033_careful_random.sql b/drizzle/sqlite/0033_careful_random.sql new file mode 100644 index 000000000..e060073f0 --- /dev/null +++ b/drizzle/sqlite/0033_careful_random.sql @@ -0,0 +1,5 @@ +-- file_manager_pinned, file_manager_recent, file_manager_shortcuts and +-- transfer_recent are adopted (renamed, not dropped) by the file-manager +-- plugin's own migration, which runs at plugin activation, after core's +-- drizzle migrations. A DROP TABLE here would run first and delete the data. +SELECT 1; diff --git a/drizzle/sqlite/meta/0033_snapshot.json b/drizzle/sqlite/meta/0033_snapshot.json new file mode 100644 index 000000000..d0fc9b847 --- /dev/null +++ b/drizzle/sqlite/meta/0033_snapshot.json @@ -0,0 +1,8194 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f12056c6-ae9b-4f88-b3fd-a2be424850ca", + "prevId": "ef5ab6c3-9beb-4293-8abd-b00d937a5f64", + "tables": { + "ai_conversations": { + "name": "ai_conversations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_conversations_user": { + "name": "idx_ai_conversations_user", + "columns": [ + "user_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_conversations_user_id_users_id_fk": { + "name": "ai_conversations_user_id_users_id_fk", + "tableFrom": "ai_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ai_messages": { + "name": "ai_messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "tool_calls": { + "name": "tool_calls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_messages_conversation": { + "name": "idx_ai_messages_conversation", + "columns": [ + "conversation_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_messages_conversation_id_ai_conversations_id_fk": { + "name": "ai_messages_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_messages", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ai_proposals": { + "name": "ai_proposals", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_proposals_user": { + "name": "idx_ai_proposals_user", + "columns": [ + "user_id", + "status" + ], + "isUnique": false + }, + "idx_ai_proposals_conversation": { + "name": "idx_ai_proposals_conversation", + "columns": [ + "conversation_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ai_proposals_conversation_id_ai_conversations_id_fk": { + "name": "ai_proposals_conversation_id_ai_conversations_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "ai_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_proposals_user_id_users_id_fk": { + "name": "ai_proposals_user_id_users_id_fk", + "tableFrom": "ai_proposals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ai_providers": { + "name": "ai_providers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_key": { + "name": "api_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_key_prefix": { + "name": "api_key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ai_providers_user_label": { + "name": "idx_ai_providers_user_label", + "columns": [ + "user_id", + "label" + ], + "isUnique": true + } + }, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_channels": { + "name": "automation_channels", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_channels_pair": { + "name": "idx_automation_channels_pair", + "columns": [ + "automation_id", + "channel_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_channels_automation_id_automations_id_fk": { + "name": "automation_channels_automation_id_automations_id_fk", + "tableFrom": "automation_channels", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_channels_channel_id_notification_channels_id_fk": { + "name": "automation_channels_channel_id_notification_channels_id_fk", + "tableFrom": "automation_channels", + "tableTo": "notification_channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_run_steps": { + "name": "automation_run_steps", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_index": { + "name": "step_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_id": { + "name": "step_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_type": { + "name": "step_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "truncated": { + "name": "truncated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_automation_run_steps_run": { + "name": "idx_automation_run_steps_run", + "columns": [ + "run_id", + "step_index" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_run_steps_run_id_automation_runs_id_fk": { + "name": "automation_run_steps_run_id_automation_runs_id_fk", + "tableFrom": "automation_run_steps", + "tableTo": "automation_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_runs": { + "name": "automation_runs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger_context": { + "name": "trigger_context", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "finished_at": { + "name": "finished_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dry_run": { + "name": "dry_run", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_runs_automation": { + "name": "idx_automation_runs_automation", + "columns": [ + "automation_id", + "started_at" + ], + "isUnique": false + }, + "idx_automation_runs_user": { + "name": "idx_automation_runs_user", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_user_id_users_id_fk": { + "name": "automation_runs_user_id_users_id_fk", + "tableFrom": "automation_runs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_schedules": { + "name": "automation_schedules", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_due_at": { + "name": "next_due_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_tick_at": { + "name": "last_tick_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_schedules_automation": { + "name": "idx_automation_schedules_automation", + "columns": [ + "automation_id" + ], + "isUnique": true + }, + "idx_automation_schedules_due": { + "name": "idx_automation_schedules_due", + "columns": [ + "next_due_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automation_schedules_automation_id_automations_id_fk": { + "name": "automation_schedules_automation_id_automations_id_fk", + "tableFrom": "automation_schedules", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_trigger_state": { + "name": "automation_trigger_state", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "automation_id": { + "name": "automation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "breach_started_at": { + "name": "breach_started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_value": { + "name": "last_value", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_observed_state": { + "name": "last_observed_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automation_trigger_state_key": { + "name": "idx_automation_trigger_state_key", + "columns": [ + "automation_id", + "state_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "automation_trigger_state_automation_id_automations_id_fk": { + "name": "automation_trigger_state_automation_id_automations_id_fk", + "tableFrom": "automation_trigger_state", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "definition_version": { + "name": "definition_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'skip'" + }, + "max_run_seconds": { + "name": "max_run_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "dry_run": { + "name": "dry_run", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_automations_user": { + "name": "idx_automations_user", + "columns": [ + "user_id", + "enabled" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_user_id_users_id_fk": { + "name": "automations_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "c2s_tunnel_presets": { + "name": "c2s_tunnel_presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "computer_name": { + "name": "computer_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "c2s_tunnel_presets_user_id_users_id_fk": { + "name": "c2s_tunnel_presets_user_id_users_id_fk", + "tableFrom": "c2s_tunnel_presets", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "collab_room_members": { + "name": "collab_room_members", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_role": { + "name": "room_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_collab_room_members_room_user": { + "name": "idx_collab_room_members_room_user", + "columns": [ + "room_id", + "user_id" + ], + "isUnique": true + }, + "idx_collab_room_members_user": { + "name": "idx_collab_room_members_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "collab_room_members_room_id_collab_rooms_id_fk": { + "name": "collab_room_members_room_id_collab_rooms_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "collab_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_user_id_users_id_fk": { + "name": "collab_room_members_user_id_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_room_members_added_by_users_id_fk": { + "name": "collab_room_members_added_by_users_id_fk", + "tableFrom": "collab_room_members", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "collab_rooms": { + "name": "collab_rooms", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "persistent": { + "name": "persistent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "presenter_user_id": { + "name": "presenter_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_protocol": { + "name": "stage_protocol", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_host_id": { + "name": "stage_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stage_share_id": { + "name": "stage_share_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_link_token": { + "name": "guest_link_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_collab_rooms_owner": { + "name": "idx_collab_rooms_owner", + "columns": [ + "owner_user_id" + ], + "isUnique": false + }, + "idx_collab_rooms_guest_token": { + "name": "idx_collab_rooms_guest_token", + "columns": [ + "guest_link_token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "collab_rooms_owner_user_id_users_id_fk": { + "name": "collab_rooms_owner_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collab_rooms_presenter_user_id_users_id_fk": { + "name": "collab_rooms_presenter_user_id_users_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "users", + "columnsFrom": [ + "presenter_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_host_id_ssh_data_id_fk": { + "name": "collab_rooms_stage_host_id_ssh_data_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "ssh_data", + "columnsFrom": [ + "stage_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "collab_rooms_stage_share_id_session_shares_id_fk": { + "name": "collab_rooms_stage_share_id_session_shares_id_fk", + "tableFrom": "collab_rooms", + "tableTo": "session_shares", + "columnsFrom": [ + "stage_share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "command_history": { + "name": "command_history", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "executed_at": { + "name": "executed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_command_history_user_host": { + "name": "idx_command_history_user_host", + "columns": [ + "user_id", + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "command_history_user_id_users_id_fk": { + "name": "command_history_user_id_users_id_fk", + "tableFrom": "command_history", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "command_history_host_id_ssh_data_id_fk": { + "name": "command_history_host_id_ssh_data_id_fk", + "tableFrom": "command_history", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "dashboard_service_links": { + "name": "dashboard_service_links", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "dashboard_service_links_sync_id_unique": { + "name": "dashboard_service_links_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "dashboard_service_links_user_id_users_id_fk": { + "name": "dashboard_service_links_user_id_users_id_fk", + "tableFrom": "dashboard_service_links", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "dismissed_alerts": { + "name": "dismissed_alerts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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": {} + }, + "session_recordings": { + "name": "session_recordings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_id": { + "name": "access_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "ended_at": { + "name": "ended_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commands": { + "name": "commands", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dangerous_actions": { + "name": "dangerous_actions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recording_path": { + "name": "recording_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "terminated_by_owner": { + "name": "terminated_by_owner", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "termination_reason": { + "name": "termination_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_session_recordings_user_started": { + "name": "idx_session_recordings_user_started", + "columns": [ + "user_id", + "started_at" + ], + "isUnique": false + }, + "idx_session_recordings_host": { + "name": "idx_session_recordings_host", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_recordings_host_id_ssh_data_id_fk": { + "name": "session_recordings_host_id_ssh_data_id_fk", + "tableFrom": "session_recordings", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_recordings_user_id_users_id_fk": { + "name": "session_recordings_user_id_users_id_fk", + "tableFrom": "session_recordings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "session_recordings_access_id_host_access_id_fk": { + "name": "session_recordings_access_id_host_access_id_fk", + "tableFrom": "session_recordings", + "tableTo": "host_access", + "columnsFrom": [ + "access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_share_participants": { + "name": "session_share_participants", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "share_id": { + "name": "share_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guest_label": { + "name": "guest_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "left_at": { + "name": "left_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_share_participants_share_id_session_shares_id_fk": { + "name": "session_share_participants_share_id_session_shares_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "session_shares", + "columnsFrom": [ + "share_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_share_participants_user_id_users_id_fk": { + "name": "session_share_participants_user_id_users_id_fk", + "tableFrom": "session_share_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_shares": { + "name": "session_shares", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_instance_id": { + "name": "tab_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_type": { + "name": "share_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "link_token": { + "name": "link_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'read-only'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_joined_at": { + "name": "last_joined_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "join_count": { + "name": "join_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "session_shares_link_token_unique": { + "name": "session_shares_link_token_unique", + "columns": [ + "link_token" + ], + "isUnique": true + }, + "idx_session_shares_session_id": { + "name": "idx_session_shares_session_id", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_session_shares_host_id": { + "name": "idx_session_shares_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_shares_host_id_ssh_data_id_fk": { + "name": "session_shares_host_id_ssh_data_id_fk", + "tableFrom": "session_shares", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_owner_user_id_users_id_fk": { + "name": "session_shares_owner_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_shares_target_user_id_users_id_fk": { + "name": "session_shares_target_user_id_users_id_fk", + "tableFrom": "session_shares", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sso_providers": { + "name": "sso_providers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_tombstones": { + "name": "sync_tombstones", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identities": { + "name": "termix_identities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ], + "isUnique": true + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tmux_session_tags": { + "name": "tmux_session_tags", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_name": { + "name": "session_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "tmux_session_tags_user_id_users_id_fk": { + "name": "tmux_session_tags_user_id_users_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tmux_session_tags_host_id_ssh_data_id_fk": { + "name": "tmux_session_tags_host_id_ssh_data_id_fk", + "tableFrom": "tmux_session_tags", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 ca04292ab..e5ad7a8a1 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -232,6 +232,13 @@ "when": 1790185583660, "tag": "0032_square_jack_power", "breakpoints": true + }, + { + "idx": 33, + "version": "6", + "when": 1790189636471, + "tag": "0033_careful_random", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/plugin-sdk/ARCHITECTURE.md b/packages/plugin-sdk/ARCHITECTURE.md index cb75d5f88..3cbc75a30 100644 --- a/packages/plugin-sdk/ARCHITECTURE.md +++ b/packages/plugin-sdk/ARCHITECTURE.md @@ -1067,9 +1067,15 @@ connect pipeline instead of importing ssh2 helpers from core: a chain has no single resolved host of its own. - `prepare(host, { client, purpose })`, `openTransport`, `classifyKeyboardInteractive` and `autoResponses` are the lower level for a - transport with its own prompt flow (docker's console, host metrics). + transport with its own prompt flow (docker's console, host metrics, **B6**'s + interactive file-manager connect with its TOTP/Warpgate parking flow). - `requiresSecret(authType)` and `supportsBackground(authType)` ask the provider. +- **B6** added `"file-manager"` and `"file-transfer"` to `PluginSshPurpose` + (core's `SshConnectPurpose` already had them; the SDK type had lagged), + since the file-manager plugin's interactive connect route and its dedicated + transfer sessions both need their own keepalive/timeout defaults rather than + falling back to the generic `"plugin"` purpose. Each new connection is audited and runs as the current actor; pooled reuse is not. Connections and pool entries are closed on deactivate. @@ -1608,8 +1614,8 @@ What the lint fence enforces today, in `eslint.config.mjs`: | Core importing a plugin backend | **Error** | 0 | - | | A plugin backend importing frontend code or `@/` | **Error** | 0 | - | | The shell importing plugin code | **Error** | 0 | - | -| A plugin frontend importing core through `@/` | Warning | 108 files | D1 | -| A plugin importing core by relative path | Warning | 67 files | D1 | +| A plugin frontend importing core through `@/` | Warning | 138 files | D1 | +| A plugin importing core by relative path | Warning | 68 files | D1 | | A plugin importing another plugin's source | Warning | 3 files | B18 | A warning does not fail a build, so the counts are held by @@ -1639,11 +1645,17 @@ Known specifics: an `optionalDependency` (added in A2; it previously declared nothing, and the loader had no reason to order the two). - Core feature servers that are not plugins yet still own ports: tunnel 30003, - file-manager 30004, dashboard 30006, tmux 30010, serial 30011 and homepage 30012. Each keeps its nginx block until its own Phase B step. No plugin owns - a port any more (A4). + dashboard 30006, tmux 30010, serial 30011 and homepage 30012. Each keeps its + nginx block until its own Phase B step. No plugin owns a port any more (A4). + **B6** moved file-manager off port 30004 onto `/plugin-api/file-manager/`. - The terminal component and `TerminalTabContent` stay in core until the - terminal's Phase B step (session manager, split view and file-manager - callbacks). ssh-terminal registers them through the legacy alias. + terminal's Phase B step (session manager, split view). ssh-terminal + registers them through the legacy alias; **B6** switched every core caller + of the file manager over to `invokeAction("files.openHost"/"files.openEditor")` + instead of the `openFileManager`/`openFileInEditor` shell callbacks it + removed, so `TerminalWindow.tsx`'s embedding of the core terminal component + is now the only file-manager-adjacent legacy-core import left, tracked the + same way as any other plugin depending on an unconverted core component. - Host data columns (`enableDocker`, `enableRdp`, `guacamoleConfig` and so on) stay in core types until Phase B moves them. Only UI branches on them left the shell. diff --git a/packages/plugin-sdk/FINISH-LIST.md b/packages/plugin-sdk/FINISH-LIST.md index 0d9363d90..437197248 100644 --- a/packages/plugin-sdk/FINISH-LIST.md +++ b/packages/plugin-sdk/FINISH-LIST.md @@ -23,14 +23,16 @@ it's done. is the SDK copy fleets now uses; core's copy was left in place rather than turned into a re-export, since host-metrics's own conversion step is where its imports should move wholesale. Owner: host-metrics's Phase B step. -- **B5 (proxmox):** `useConnectionRetry`, `runAdaptivePolling` and `cn` are - duplicated in `plugins/proxmox/src/frontend/stats/` rather than shared with - core's `src/ui/lib/` originals, matching the same "add to the SDK once a - second plugin needs it" call B3 made for `ManagerCardShell`. host-metrics's - own conversion needs the same two hooks for its stats tab, so promote both - into `@termix/plugin-sdk/frontend` (and `cn` into `@termix/plugin-sdk/ui`) - the next time either is touched. Owner: D1, or host-metrics's Phase B step - if it lands first. +- **B5 (proxmox):** `runAdaptivePolling` and `cn` are duplicated in + `plugins/proxmox/src/frontend/stats/` rather than shared with core's + `src/ui/lib/` originals. `useConnectionRetry` (the third duplicate B5 left + here) is resolved: **B6** promoted it into `@termix/plugin-sdk/frontend` + once file-manager became its fourth caller (after proxmox, docker, + host-metrics, remote-desktop) and switched proxmox's own copy over too. + host-metrics's own conversion needs `runAdaptivePolling` for its stats tab, + so promote it into `@termix/plugin-sdk/frontend` (and `cn` into + `@termix/plugin-sdk/ui`) the next time either is touched. Owner: D1, or + host-metrics's Phase B step if it lands first. - **B5 (proxmox):** `runDueProxmoxAutoSyncs` in `plugins/proxmox/src/backend/routes.ts` still reaches `createCurrentPluginSettingsRepository`/`createCurrentHostRepository` by @@ -40,3 +42,41 @@ it's done. bulk read today. A `ctx.settings.listHostsWithKey` or similar SDK addition would close this, if a second plugin's background scan ever needs the same shape. Owner: D1. +- **B6 (file-manager):** `plugins/file-manager/src/frontend/components/PdfPreview.tsx` + runs pdfjs without a worker (main-thread parsing) because `termix-plugin +build`'s esbuild step has no static-asset-copy pipeline the way core's Vite + build resolves a `?url` import to a hashed `/assets/` file. Fine for the + small files this preview usually opens; restoring a real worker needs the + CLI (`packages/plugin-sdk/cli/commands/build.mjs`) to gain a way to emit an + extra file into `dist/` and have the frontend reference it by its served + `/plugin-assets//` path. Owner: D1, or whichever step first needs a + worker or other static asset from a plugin bundle. +- **B6 (file-manager):** `transfer-tuning.ts`'s adaptive-tuning profile still + persists to a JSON file under `DATA_DIR` rather than `ctx.kv`, and + `HostFilesTab.tsx`'s `t()` calls plus the manifest's `contributes.settings.host` + `labelKey`s deliberately point at two different key paths (`hosts.*` and + `settings.host.*`) for the same three fields, both carrying the same English + text - a minor duplication from keeping the host editor's exact existing + section verbatim (`registerHostEditorSection`) instead of switching it to + the schema-driven settings form. Neither blocks anything; flagging for + whoever next touches this plugin's settings UI. +- **B6 (file-manager):** `TabContext.tsx`'s `computeUniqueTitle` compares + `tabType === "file_manager"` (an activity-log type string, not a tab type - + the real tab type has always been `"files"`), so that branch has never + matched and silently falls through to the terminal title. Pre-existing, + unrelated to the conversion; noted rather than fixed per the lean-mode + rules. Owner: whoever next touches tab title logic. +- **B6 (file-manager):** `enable_file_manager`, `default_path` and + `scp_legacy` are still live `ssh_data` columns; only the copy-into-plugin- + settings migration shipped (`file-manager-settings-migration.ts`). Every + core read site the earlier exploration found (`host.ts`, + `host-normalizers.ts`, `host-bulk-routes.ts`, `credentials.ts`, + `host-internal-routes.ts`, `database.ts`'s encrypt/decrypt round trip, + `plugins/host-view.ts`, `ctx-hosts.ts`, plus the frontend's + `HostEditorData.ts`/`HostManagerData.ts`/`SidebarTree.tsx`/quick-connect + mapping) still reads the column directly rather than + `ctx.settings.getHost`. Switching every one of those and then dropping the + column in lockstep (`schema.ts`, `db/index.ts`, a drizzle migration per + dialect, `schema:generate`) is real, separate work this step didn't + attempt, since none of those reads block the plugin conversion itself. + Owner: a dedicated follow-up step, or D0. diff --git a/packages/plugin-sdk/src/backend.ts b/packages/plugin-sdk/src/backend.ts index e57900b2e..d48ef7ede 100644 --- a/packages/plugin-sdk/src/backend.ts +++ b/packages/plugin-sdk/src/backend.ts @@ -577,7 +577,9 @@ export type PluginSshPurpose = | "proxmox" | "fleet" | "remote-desktop" - | "tunnel"; + | "tunnel" + | "file-manager" + | "file-transfer"; export type PluginSshAuthOutcome = | { status: "ready" } diff --git a/packages/plugin-sdk/src/frontend.ts b/packages/plugin-sdk/src/frontend.ts index f32955c49..e2122ba49 100644 --- a/packages/plugin-sdk/src/frontend.ts +++ b/packages/plugin-sdk/src/frontend.ts @@ -13,6 +13,7 @@ * and every plugin bundle. */ +import { useCallback, useEffect, useRef, useState } from "react"; import type { ComponentType, ReactNode, Ref } from "react"; import type { PluginManifest } from "./manifest.js"; @@ -75,8 +76,6 @@ export interface ShellApi { openSingletonTab: (type: string, options?: OpenTabOptions) => void; closeTab: (tabId: string) => void; renameTab: (tabId: string, label: string) => void; - openFileInEditor: (host: PluginHostRecord, path: string) => void; - openFileManager: (host: PluginHostRecord, path?: string) => void; openTerminalTab: (host: PluginHostRecord, path?: string) => void; /** Opens a rail view in the left sidebar. */ openRailView: (id: string) => void; @@ -733,5 +732,205 @@ export function invokeAction(id: string, ...args: unknown[]): Promise { return requireHost().invokeAction(id, ...args); } +// --------------------------------------------------------------------------- +// Connection retry +// --------------------------------------------------------------------------- + +export type ConnectionRetryStatus = + "connecting" | "connected" | "error" | "disconnected"; + +export interface UseConnectionRetryOptions { + connect: () => void | Promise; + maxAttempts?: number; + baseDelayMs?: number; + maxDelayMs?: number; + enabled?: boolean; + autoStart?: boolean; +} + +export interface UseConnectionRetryResult { + status: ConnectionRetryStatus; + attempt: number; + maxAttempts: number; + nextRetryInMs: number | null; + markConnected: () => void; + markFailed: () => void; + retryNow: () => void; + reset: () => void; +} + +const COUNTDOWN_TICK_MS = 250; +const RETRY_JITTER_RATIO = 0.1; + +export function computeReconnectDelay( + attempt: number, + baseDelayMs: number, + maxDelayMs: number, + random = Math.random, +): number { + const base = Math.min( + baseDelayMs * Math.pow(2, Math.max(0, attempt - 1)), + maxDelayMs, + ); + const jitter = base * RETRY_JITTER_RATIO * (random() * 2 - 1); + return Math.max(baseDelayMs, Math.min(maxDelayMs, Math.round(base + jitter))); +} + +/** + * Exponential-backoff reconnect with jitter, for a plugin's own connect flow + * (docker's console, host-metrics polling, remote desktop tokens, the file + * manager). Promoted here once a fourth plugin needed its own copy of the + * same hook. + */ +export function useConnectionRetry({ + connect, + maxAttempts = 8, + baseDelayMs = 2000, + maxDelayMs = 8000, + enabled = true, + autoStart = true, +}: UseConnectionRetryOptions): UseConnectionRetryResult { + const [status, setStatus] = useState("connecting"); + const [attempt, setAttempt] = useState(0); + const [nextRetryInMs, setNextRetryInMs] = useState(null); + + const connectRef = useRef(connect); + connectRef.current = connect; + + const enabledRef = useRef(enabled); + enabledRef.current = enabled; + + const attemptRef = useRef(0); + const retryTimeoutRef = useRef | null>(null); + const countdownIntervalRef = useRef | null>( + null, + ); + const isMountedRef = useRef(true); + const markFailedRef = useRef<() => void>(() => {}); + + const clearTimers = useCallback(() => { + if (retryTimeoutRef.current) { + clearTimeout(retryTimeoutRef.current); + retryTimeoutRef.current = null; + } + if (countdownIntervalRef.current) { + clearInterval(countdownIntervalRef.current); + countdownIntervalRef.current = null; + } + }, []); + + const runConnect = useCallback(() => { + if (!isMountedRef.current) return; + setStatus("connecting"); + try { + void Promise.resolve(connectRef.current()).catch(() => { + markFailedRef.current(); + }); + } catch { + markFailedRef.current(); + } + }, []); + + const scheduleRetry = useCallback(() => { + if (attemptRef.current >= maxAttempts) { + setStatus("disconnected"); + setNextRetryInMs(null); + return; + } + + attemptRef.current += 1; + setAttempt(attemptRef.current); + + const delay = computeReconnectDelay( + attemptRef.current, + baseDelayMs, + maxDelayMs, + ); + + let remaining = delay; + setNextRetryInMs(remaining); + countdownIntervalRef.current = setInterval(() => { + remaining = Math.max(0, remaining - COUNTDOWN_TICK_MS); + setNextRetryInMs(remaining); + }, COUNTDOWN_TICK_MS); + + retryTimeoutRef.current = setTimeout(() => { + clearTimers(); + if (!isMountedRef.current || !enabledRef.current) return; + runConnect(); + }, delay); + }, [baseDelayMs, maxDelayMs, maxAttempts, clearTimers, runConnect]); + + const markConnected = useCallback(() => { + clearTimers(); + attemptRef.current = 0; + setAttempt(0); + setNextRetryInMs(null); + if (isMountedRef.current) setStatus("connected"); + }, [clearTimers]); + + const markFailed = useCallback(() => { + clearTimers(); + if (!isMountedRef.current) return; + setStatus("error"); + if (enabledRef.current) { + scheduleRetry(); + } else { + setNextRetryInMs(null); + } + }, [clearTimers, scheduleRetry]); + markFailedRef.current = markFailed; + + const retryNow = useCallback(() => { + clearTimers(); + attemptRef.current = 0; + setAttempt(0); + setNextRetryInMs(null); + runConnect(); + }, [clearTimers, runConnect]); + + const reset = useCallback(() => { + clearTimers(); + attemptRef.current = 0; + setAttempt(0); + setNextRetryInMs(null); + if (isMountedRef.current) setStatus("connecting"); + }, [clearTimers]); + + useEffect(() => { + isMountedRef.current = true; + if (autoStart) { + runConnect(); + } + return () => { + isMountedRef.current = false; + clearTimers(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (enabled && status === "error" && !retryTimeoutRef.current) { + scheduleRetry(); + } + if (!enabled) { + clearTimers(); + setNextRetryInMs(null); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [enabled]); + + return { + status, + attempt, + maxAttempts, + nextRetryInMs, + markConnected, + markFailed, + retryNow, + reset, + }; +} + export type { PluginManifest } from "./manifest.js"; export type { ReactNode }; diff --git a/plugins/docker/src/frontend/DockerManager.tsx b/plugins/docker/src/frontend/DockerManager.tsx index b5fb68991..00cf43d94 100644 --- a/plugins/docker/src/frontend/DockerManager.tsx +++ b/plugins/docker/src/frontend/DockerManager.tsx @@ -15,7 +15,10 @@ import { Search, } from "lucide-react"; -import { useTranslation } from "@termix/plugin-sdk/frontend"; +import { + useTranslation, + useConnectionRetry, +} from "@termix/plugin-sdk/frontend"; import type { SSHHost, DockerContainer, DockerValidation } from "@/types"; import { logActivity, getSSHHosts } from "@/main-axios"; import { @@ -43,7 +46,6 @@ import { useConnectionLog, } from "@/ssh/connection-log/ConnectionLogContext.tsx"; import { ConnectionScreen } from "@/components/connection/ConnectionScreen.tsx"; -import { useConnectionRetry } from "@/lib/useConnectionRetry.ts"; import { useAdaptivePolling } from "@/hooks/use-adaptive-polling.ts"; import type { LogEntry } from "@/types/connection-log.ts"; import { Select2 } from "@/components/select2"; diff --git a/plugins/docker/src/frontend/components/ConsoleTerminal.tsx b/plugins/docker/src/frontend/components/ConsoleTerminal.tsx index 366376482..23c85d56c 100644 --- a/plugins/docker/src/frontend/components/ConsoleTerminal.tsx +++ b/plugins/docker/src/frontend/components/ConsoleTerminal.tsx @@ -15,7 +15,10 @@ import { Terminal as TerminalIcon, Power, PowerOff } from "lucide-react"; import { toast } from "sonner"; import type { SSHHost } from "@/types"; import { isElectron } from "@/main-axios.ts"; -import { useTranslation } from "@termix/plugin-sdk/frontend"; +import { + useTranslation, + useConnectionRetry, +} from "@termix/plugin-sdk/frontend"; import { resolveTermixThemeColors } from "@/features/terminal/terminal-theme"; import { DEFAULT_TERMINAL_CONFIG, TERMINAL_FONTS } from "@/lib/terminal-themes"; import { ensureTerminalFontsLoaded } from "@/features/terminal/terminal-global-styles"; @@ -25,7 +28,6 @@ import { ConnectionLogProvider, useConnectionLog, } from "@/ssh/connection-log/ConnectionLogContext.tsx"; -import { useConnectionRetry } from "@/lib/useConnectionRetry.ts"; interface ConsoleTerminalProps { containerId: string; diff --git a/plugins/file-manager/locales/en.json b/plugins/file-manager/locales/en.json new file mode 100644 index 000000000..0b4b9b7f6 --- /dev/null +++ b/plugins/file-manager/locales/en.json @@ -0,0 +1,414 @@ +{ + "nav": { + "files": "Files", + "sftp": "SFTP" + }, + "permissions": { + "use": { + "title": "Use file manager", + "description": "Browse, edit and transfer files over SFTP." + } + }, + "settings": { + "host": { + "enable": { + "label": "Enable File Manager" + }, + "defaultPath": { + "label": "Default Path" + }, + "scpLegacy": { + "label": "SCP Legacy Mode" + } + } + }, + "hostCapability": { + "label": "File Manager" + }, + "fileManager": { + "noHostSelected": "No host selected", + "initializingEditor": "Initializing editor...", + "file": "File", + "folder": "Folder", + "uploadFile": "Upload File", + "downloadFile": "Download", + "extractArchive": "Extract Archive", + "extractingArchive": "Extracting {{name}}...", + "archiveExtractedSuccessfully": "{{name}} extracted successfully", + "extractFailed": "Extract failed", + "compressFile": "Compress File", + "compressFiles": "Compress Files", + "compressFilesDesc": "Compress {{count}} items into an archive", + "archiveName": "Archive Name", + "enterArchiveName": "Enter archive name...", + "compressionFormat": "Compression Format", + "selectedFiles": "Selected files", + "andMoreFiles": "and {{count}} more...", + "compress": "Compress", + "compressingFiles": "Compressing {{count}} items into {{name}}...", + "filesCompressedSuccessfully": "{{name}} created successfully", + "compressFailed": "Compression failed", + "edit": "Edit", + "preview": "Preview", + "previous": "Previous", + "next": "Next", + "pageXOfY": "Page {{current}} of {{total}}", + "zoomOut": "Zoom Out", + "zoomIn": "Zoom In", + "newFile": "New File", + "newFolder": "New Folder", + "rename": "Rename", + "uploading": "Uploading...", + "uploadingFile": "Uploading {{name}}...", + "fileName": "File Name", + "folderName": "Folder Name", + "fileUploadedSuccessfully": "File \"{{name}}\" uploaded successfully", + "failedToUploadFile": "Failed to upload file", + "uploadingFolderFiles": "Uploading {{count}} file(s)...", + "uploadedFolderFiles": "Uploaded {{count}} file(s) successfully", + "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", + "fileDownloadedSuccessfully": "File \"{{name}}\" downloaded successfully", + "failedToDownloadFile": "Failed to download file", + "fileCreatedSuccessfully": "File \"{{name}}\" created successfully", + "folderCreatedSuccessfully": "Folder \"{{name}}\" created successfully", + "failedToCreateItem": "Failed to create item", + "operationFailed": "{{operation}} operation failed for {{name}}: {{error}}", + "failedToResolveSymlink": "Failed to resolve symlink", + "itemsDeletedSuccessfully": "{{count}} items deleted successfully", + "failedToDeleteItems": "Failed to delete items", + "sudoPasswordRequired": "Administrator Password Required", + "enterSudoPassword": "Enter sudo password to continue this operation", + "sudoPassword": "Sudo password", + "sudoOperationFailed": "Sudo operation failed", + "sudoAuthFailed": "Sudo authentication failed", + "dragFilesToUpload": "Drop files here to upload", + "dropToUploadHere": "Drop to upload here", + "dropToDownloadHere": "Drop to download here", + "localFiles": "Local", + "showLocalFiles": "Show local files (split view)", + "hideLocalFiles": "Hide local files", + "localHome": "Home folder", + "localPathPlaceholder": "Local folder path", + "localShowHidden": "Show hidden files", + "localHideHidden": "Hide hidden files", + "localRevealInFileManager": "Reveal in system file manager", + "localCannotOpenFolder": "Cannot open folder", + "localItemCount": "{{count}} items", + "localSelectedCount": "{{count}} selected", + "back": "Back", + "forward": "Forward", + "up": "Up", + "kind": "Kind", + "localUploadingProgress": "Uploading {{current}} of {{total}}", + "localDownloadingProgress": "Downloading {{current}} of {{total}}", + "localTransferInFlight": "{{count}} at once", + "localPreparingDownload": "Preparing download...", + "localTransferCancelled": "Transfer cancelled", + "localNothingToTransfer": "Nothing to transfer", + "localUploadComplete": "Uploaded {{count}} file(s)", + "localUploadFailed": "Upload failed", + "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", + "localDownloadComplete": "Downloaded {{count}} file(s)", + "localDownloadFailed": "Download failed", + "localDownloadPartial": "Downloaded {{done}} file(s), {{failed}} failed", + "localReplaceExistingPrompt": "{{count}} item(s) already exist in this folder. Replace them?", + "localReplace": "Replace", + "localSkipExisting": "Skip", + "localSkippedExisting": "Skipped {{count}} existing item(s)", + "localUnsafeNamesSkipped": "Skipped {{count}} item(s) whose names cannot be used on this computer", + "localOpen": "Open", + "localOpenFolder": "Open folder", + "localUploadToRemote": "Upload to server", + "localUploadToRemoteMany": "Upload {{count}} items to server", + "localMoveToTrash": "Move to Trash", + "localMoveToTrashMany": "Move {{count}} items to Trash", + "localTrashConfirmSingle": "Move \"{{name}}\" to the Trash?", + "localTrashConfirmFolder": "Move the folder \"{{name}}\" and all its contents to the Trash?", + "localTrashConfirmMany": "Move {{count}} items to the Trash?", + "localTrashed": "Moved {{count}} item(s) to the Trash", + "localTrashFailed": "Could not move to the Trash", + "localRenameFailed": "Rename failed", + "localCreateFailed": "Could not create item", + "goToParentFolder": "Go to parent folder", + "resizeSidebar": "Drag to resize the sidebar (double-click to reset)", + "resizeLocalPane": "Drag to resize the local pane (double-click to reset)", + "columns": "Columns", + "columnsHint": "Right-click to choose columns", + "emptyFolder": "This folder is empty", + "searchFiles": "Search files...", + "upload": "Upload", + "selectHostToStart": "Select a host to start file management", + "sshRequiredForFileManager": "File manager requires SSH. This host does not have SSH enabled.", + "failedToConnect": "Failed to connect to SSH", + "failedToLoadDirectory": "Failed to load directory", + "noSSHConnection": "No SSH connection available", + "copy": "Copy", + "cut": "Cut", + "paste": "Paste", + "copyPath": "Copy Path", + "copyPaths": "Copy Paths", + "delete": "Delete", + "properties": "Properties", + "refresh": "Refresh", + "downloadFiles": "Download {{count}} files to Browser", + "copyFiles": "Copy {{count}} items", + "cutFiles": "Cut {{count}} items", + "deleteFiles": "Delete {{count}} items", + "filesCopiedToClipboard": "{{count}} items copied to clipboard", + "filesCutToClipboard": "{{count}} items cut to clipboard", + "pathCopiedToClipboard": "Path copied to clipboard", + "pathsCopiedToClipboard": "{{count}} paths copied to clipboard", + "failedToCopyPath": "Failed to copy path to clipboard", + "copyFolderLink": "Copy Link to Folder", + "copyCurrentFolderLink": "Copy Link to Current Folder", + "folderLinkCopied": "Folder link copied to clipboard", + "failedToCopyFolderLink": "Failed to copy folder link", + "movedItems": "Moved {{count}} items", + "failedToDeleteItem": "Failed to delete item", + "itemRenamedSuccessfully": "{{type}} renamed successfully", + "failedToRenameItem": "Failed to rename item", + "download": "Download", + "openExternalEditor": "Open externally", + "chooseExternalEditor": "Choose external editor", + "externalEditorSelected": "External editor selected", + "externalEditorDesktopOnly": "External editor is only available in the desktop app", + "externalEditorOpened": "Opened in external editor. Saving there will upload changes back to the server.", + "failedToOpenExternalEditor": "Failed to open external editor", + "failedToSelectExternalEditor": "Failed to select external editor", + "permissions": "Permissions", + "size": "Size", + "modified": "Modified", + "path": "Path", + "confirmDelete": "Are you sure you want to delete {{name}}?", + "permissionDenied": "Permission denied", + "serverError": "Server Error", + "fileSavedSuccessfully": "File saved successfully", + "failedToSaveFile": "Failed to save file", + "confirmDeleteSingleItem": "Are you sure you want to permanently delete \"{{name}}\"?", + "confirmDeleteMultipleItems": "Are you sure you want to permanently delete {{count}} items?", + "confirmDeleteMultipleItemsWithFolders": "Are you sure you want to permanently delete {{count}} items? This includes folders and their contents.", + "confirmDeleteFolder": "Are you sure you want to permanently delete the folder \"{{name}}\" and all its contents?", + "permanentDeleteWarning": "This action cannot be undone. The item(s) will be permanently deleted from the server.", + "moveToTrashWarning": "The item(s) will be moved to the Termix Trash and can be restored before the retention period expires.", + "itemsMovedToTrash": "{{count}} item(s) moved to Trash", + "trash": "Trash", + "trashDescription": "Deleted items are retained for {{days}} day(s) on this host.", + "trashLoadFailed": "Failed to load Trash", + "trashEmpty": "Trash is empty", + "trashRestore": "Restore", + "trashRestored": "Restored {{name}}", + "trashRestoreFailed": "Failed to restore item. The original path may already exist.", + "trashDeletePermanently": "Delete permanently", + "trashDeleteConfirm": "Permanently delete {{name}}? This cannot be undone.", + "trashPermanentlyDeleted": "Permanently deleted {{name}}", + "trashDeleteFailed": "Failed to permanently delete item", + "trashEmptyAction": "Empty Trash", + "trashEmptyConfirm": "Permanently delete every item in Trash? This cannot be undone.", + "trashEmptied": "Trash emptied", + "trashRetention": "Automatic cleanup after (days)", + "trashRetentionSaved": "Trash retention updated", + "trashRetentionFailed": "Failed to update Trash retention", + "trashUnavailableConfirm": "This item cannot be moved to Trash, usually because it is on another filesystem or requires elevated permissions. Delete it permanently instead?", + "recent": "Recent", + "pinned": "Pinned", + "folderShortcuts": "Folder Shortcuts", + "failedToReconnectSSH": "Failed to reconnect SSH session", + "openTerminalHere": "Open Terminal Here", + "run": "Run", + "openTerminalInFolder": "Open Terminal in This Folder", + "openTerminalInFileLocation": "Open Terminal at File Location", + "runningFile": "Running - {{file}}", + "onlyRunExecutableFiles": "Can only run executable files", + "directories": "Directories", + "removedFromRecentFiles": "Removed \"{{name}}\" from recent files", + "removeFailed": "Remove failed", + "unpinnedSuccessfully": "Unpinned \"{{name}}\" successfully", + "unpinFailed": "Unpin failed", + "removedShortcut": "Removed shortcut \"{{name}}\"", + "removeShortcutFailed": "Remove shortcut failed", + "clearedAllRecentFiles": "Cleared all recent files", + "clearFailed": "Clear failed", + "removeFromRecentFiles": "Remove from recent files", + "clearAllRecentFiles": "Clear all recent files", + "unpinFile": "Unpin file", + "removeShortcut": "Remove shortcut", + "pinFile": "Pin file", + "addToShortcuts": "Add to shortcuts", + "pasteFailed": "Paste failed", + "noUndoableActions": "No undoable actions", + "undoCopySuccess": "Undid copy operation: Deleted {{count}} copied files", + "undoCopyFailedDelete": "Undo failed: Could not delete any copied files", + "undoCopyFailedNoInfo": "Undo failed: Could not find copied file information", + "undoMoveSuccess": "Undid move operation: Moved {{count}} files back to original location", + "undoMoveFailedMove": "Undo failed: Could not move any files back", + "undoMoveFailedNoInfo": "Undo failed: Could not find moved file information", + "undoDeleteNotSupported": "Delete operation cannot be undone: Files have been permanently deleted from server", + "undoTypeNotSupported": "Unsupported undo operation type", + "undoOperationFailed": "Undo operation failed", + "unknownError": "Unknown error", + "confirm": "Confirm", + "find": "Find...", + "replace": "Replace", + "downloadInstead": "Download Instead", + "keyboardShortcuts": "Keyboard Shortcuts", + "searchAndReplace": "Search & Replace", + "editing": "Editing", + "search": "Search", + "findNext": "Find Next", + "findPrevious": "Find Previous", + "save": "Save", + "selectAll": "Select All", + "undo": "Undo", + "redo": "Redo", + "moveLineUp": "Move Line Up", + "moveLineDown": "Move Line Down", + "toggleComment": "Toggle Comment", + "autoComplete": "Auto Complete", + "imageLoadError": "Failed to load image", + "startTyping": "Start typing...", + "unknownSize": "Unknown size", + "fileIsEmpty": "File is empty", + "largeFileWarning": "Large File Warning", + "largeFileWarningDesc": "This file is {{size}} in size, which may cause performance issues when opened as text.", + "fileNotFoundAndRemoved": "File \"{{name}}\" not found and has been removed from recent/pinned files", + "failedToLoadFile": "Failed to load file: {{error}}", + "serverErrorOccurred": "Server error occurred. Please try again later.", + "autoSaveFailed": "Auto-save failed", + "fileAutoSaved": "File auto-saved", + "moveFileFailed": "Failed to move {{name}}", + "moveOperationFailed": "Move operation failed", + "canOnlyCompareFiles": "Can only compare two files", + "comparingFiles": "Comparing files: {{file1}} and {{file2}}", + "dragFailed": "Drag operation failed", + "filePinnedSuccessfully": "File \"{{name}}\" pinned successfully", + "pinFileFailed": "Failed to pin file", + "fileUnpinnedSuccessfully": "File \"{{name}}\" unpinned successfully", + "unpinFileFailed": "Failed to unpin file", + "shortcutAddedSuccessfully": "Folder shortcut \"{{name}}\" added successfully", + "addShortcutFailed": "Failed to add shortcut", + "operationCompletedSuccessfully": "{{operation}} {{count}} items successfully", + "operationCompleted": "{{operation}} {{count}} items", + "downloadFileSuccess": "File {{name}} downloaded successfully", + "downloadFileFailed": "Download failed", + "downloadingFile": "Downloading {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Move to {{name}}", + "diffCompareWith": "Diff compare with {{name}}", + "dragOutsideToDownload": "Drag outside window to download ({{count}} files)", + "newFolderDefault": "NewFolder", + "newFileDefault": "NewFile.txt", + "successfullyMovedItems": "Successfully moved {{count}} items to {{target}}", + "move": "Move", + "searchInFile": "Search in file (Ctrl+F)", + "showKeyboardShortcuts": "Show keyboard shortcuts", + "decreaseFontSize": "Decrease font size", + "increaseFontSize": "Increase font size", + "startWritingMarkdown": "Start writing your markdown content...", + "loadingFileComparison": "Loading file comparison...", + "reload": "Reload", + "compare": "Compare", + "sideBySide": "Side by Side", + "inline": "Inline", + "fileComparison": "File Comparison: {{file1}} vs {{file2}}", + "fileTooLarge": "File too large: {{error}}", + "sshConnectionFailed": "SSH connection failed. Please check your connection to {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Failed to load file: {{error}}", + "connecting": "Connecting...", + "connectedSuccessfully": "Connected successfully", + "totpVerificationFailed": "TOTP verification failed", + "warpgateVerificationFailed": "Warpgate authentication failed", + "authenticationFailed": "Authentication failed", + "incorrectPassphrase": "Incorrect passphrase. Please try again.", + "verificationCodePrompt": "Verification code:", + "changePermissions": "Change Permissions", + "currentPermissions": "Current Permissions", + "owner": "Owner", + "group": "Group", + "others": "Others", + "read": "Read", + "write": "Write", + "execute": "Execute", + "permissionsChangedSuccessfully": "Permissions changed successfully", + "failedToChangePermissions": "Failed to change permissions", + "name": "Name", + "sortByName": "Name", + "sortByDate": "Date Modified", + "sortBySize": "Size", + "ascending": "Ascending", + "descending": "Descending", + "root": "Root", + "new": "New", + "sortBy": "Sort By", + "items": "Items", + "selected": "Selected", + "editor": "Editor", + "octal": "Octal", + "storage": "Storage", + "disk": "Disk", + "used": "Used", + "of": "of", + "toggleSidebar": "Toggle Sidebar", + "compactLayout": "Use compact layout", + "comfortableLayout": "Use comfortable layout", + "cannotLoadPdf": "Cannot load PDF", + "pdfLoadError": "There was an error loading this PDF file.", + "loadingPdf": "Loading PDF...", + "loadingPage": "Loading page..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Transfer files between two SSH hosts.", + "sourceServer": "Source Server", + "destinationServer": "Destination Server", + "selectHost": "Select host", + "noHostSelected": "No host selected", + "move": "Move", + "copy": "Copy", + "upload": "Upload", + "itemsSelected": "{{count}} item selected", + "itemsSelected_other": "{{count}} items selected", + "ready": "Ready", + "loadingHosts": "Loading hosts...", + "name": "Name", + "modified": "Modified", + "size": "Size", + "noFiles": "No files", + "open": "Open", + "up": "Up", + "refresh": "Refresh", + "copyToTarget": "Copy to Target Directory", + "rename": "Rename", + "delete": "Delete", + "editPermissions": "Edit Permissions", + "createNewFolder": "Create New Folder", + "deleteSelectedItems": "Delete Selected Items", + "deleteConfirm": "Delete {{count}} selected item?", + "deleteConfirm_other": "Delete {{count}} selected items?", + "cancel": "Cancel", + "save": "Save", + "folderCreated": "Folder created", + "itemRenamed": "Item renamed", + "actionFailed": "Action failed", + "itemsDeleted": "{{count}} item deleted", + "itemsDeleted_other": "{{count}} items deleted", + "deleteFailed": "Delete failed", + "permissionsUpdated": "Permissions updated", + "noRemoteSessionConnected": "No remote session connected", + "failedToListFiles": "Failed to list files", + "failedToRefresh": "Failed to refresh", + "destinationInsideSource": "Destination cannot be inside the selected source path.", + "sameHostNoOp": "Source and destination are the same folder." + }, + "hosts": { + "fileManager": "File Manager", + "tabFiles": "Files", + "enableFileManagerMonitor": "Enable File Manager", + "enableFileManagerMonitorDesc": "Browse and manage files on this host over SFTP", + "scpLegacyLabel": "SCP Legacy Mode", + "scpLegacyDesc": "Use legacy file transfer for servers that do not support the SFTP subsystem (e.g. embedded or minimal SSH servers).", + "defaultPathLabel": "Default Path", + "fileManagerPathHint": "The directory to open when the file manager launches for this host.", + "fileManagerUrlCopied": "File Manager URL copied", + "copyFileManagerUrlAction": "Copy File Manager URL" + } +} diff --git a/plugins/file-manager/locales/translated/af_ZA.json b/plugins/file-manager/locales/translated/af_ZA.json new file mode 100644 index 000000000..29ef2325d --- /dev/null +++ b/plugins/file-manager/locales/translated/af_ZA.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Geen gasheer gekies nie", + "initializingEditor": "Inisialiseer redigeerder...", + "file": "Lêer", + "folder": "Vouer", + "uploadFile": "Laai lêer op", + "downloadFile": "Laai af", + "extractArchive": "Uittrekselargief", + "extractingArchive": "Onttrek tans {{name}}...", + "archiveExtractedSuccessfully": "{{name}} suksesvol onttrek", + "extractFailed": "Uittreksel het misluk", + "compressFile": "Komprimeer lêer", + "compressFiles": "Kompresseer lêers", + "compressFilesDesc": "Kompresseer {{count}} items in 'n argief", + "archiveName": "Argiefnaam", + "enterArchiveName": "Voer argiefnaam in...", + "compressionFormat": "Kompressieformaat", + "selectedFiles": "Geselekteerde lêers", + "andMoreFiles": "en {{count}} meer...", + "compress": "Kompressie", + "compressingFiles": "Komprimering van {{count}} items in {{name}}...", + "filesCompressedSuccessfully": "{{name}} suksesvol geskep", + "compressFailed": "Kompressie het misluk", + "edit": "Wysig", + "preview": "Voorskou", + "previous": "Vorige", + "next": "Volgende", + "pageXOfY": "Bladsy {{current}} van {{total}}", + "zoomOut": "Uitzoom", + "zoomIn": "Inzoem", + "newFile": "Nuwe Lêer", + "newFolder": "Nuwe Vouer", + "rename": "Hernoem", + "uploading": "Laai tans op...", + "uploadingFile": "Laai tans {{name}} op ...", + "fileName": "Lêernaam", + "folderName": "Vouernaam", + "fileUploadedSuccessfully": "Lêer \"{{name}}\" is suksesvol opgelaai", + "failedToUploadFile": "Kon nie lêer oplaai nie", + "uploadingFolderFiles": "Laai tans {{count}} lêer(s) op...", + "uploadedFolderFiles": "{{count}} lêer(s) suksesvol opgelaai", + "uploadedFolderPartial": "Opgelaaide {{uploaded}} lêer(s), {{failed}} het misluk", + "fileDownloadedSuccessfully": "Lêer \"{{name}}\" is suksesvol afgelaai", + "failedToDownloadFile": "Kon nie lêer aflaai nie", + "fileCreatedSuccessfully": "Lêer \"{{name}}\" suksesvol geskep", + "folderCreatedSuccessfully": "Lêer \"{{name}}\" suksesvol geskep", + "failedToCreateItem": "Kon nie item skep nie", + "operationFailed": "{{operation}} -bewerking het misluk vir {{name}}: {{error}}", + "failedToResolveSymlink": "Kon nie simskakel oplos nie", + "itemsDeletedSuccessfully": "{{count}} items is suksesvol verwyder", + "failedToDeleteItems": "Kon nie items uitvee nie", + "sudoPasswordRequired": "Administrateurwagwoord benodig", + "enterSudoPassword": "Voer die sudo-wagwoord in om hierdie operasie voort te sit", + "sudoPassword": "Sudo-wagwoord", + "sudoOperationFailed": "Sudo-bewerking het misluk", + "sudoAuthFailed": "Sudo-verifikasie het misluk", + "dragFilesToUpload": "Plaas lêers hier om op te laai", + "dropToUploadHere": "Laat val om hier op te laai", + "dropToDownloadHere": "Los om hier af te laai", + "localFiles": "Plaaslik", + "showLocalFiles": "Wys plaaslike lêers (gesplete aansig)", + "hideLocalFiles": "Versteek plaaslike lêers", + "localHome": "Tuisbladmap", + "localPathPlaceholder": "Plaaslike vouerpad", + "localShowHidden": "Wys versteekte lêers", + "localHideHidden": "Versteek versteekte lêers", + "localRevealInFileManager": "Wys in stelsellêerbestuurder", + "localCannotOpenFolder": "Kan nie lêer oopmaak nie", + "localItemCount": "{{count}} items", + "localSelectedCount": "{{count}} gekies", + "back": "Terug", + "forward": "Vorentoe", + "up": "Op", + "kind": "Soortgelyk", + "localUploadingProgress": "Laai tans {{current}} van {{total}} op", + "localDownloadingProgress": "Laai tans {{current}} van {{total}} af", + "localTransferInFlight": "{{count}} gelyktydig", + "localPreparingDownload": "Berei aflaai voor...", + "localTransferCancelled": "Oordrag gekanselleer", + "localNothingToTransfer": "Niks om oor te dra nie", + "localUploadComplete": "Opgelaaide {{count}} lêer(s)", + "localUploadFailed": "Oplaai het misluk", + "localUploadPartial": "Opgelaaide {{done}} lêer(s), {{failed}} het misluk", + "localDownloadComplete": "Afgelaaide {{count}} lêer(s)", + "localDownloadFailed": "Aflaai het misluk", + "localDownloadPartial": "Aflaai van {{done}} lêer(s), {{failed}} het misluk", + "localReplaceExistingPrompt": "{{count}} item(s) bestaan reeds in hierdie vouer. Vervang hulle?", + "localReplace": "Vervang", + "localSkipExisting": "Slaan oor", + "localSkippedExisting": "Oorgeslaan {{count}} bestaande item(s)", + "localUnsafeNamesSkipped": "Oorgeslaan {{count}} item(s) waarvan die name nie op hierdie rekenaar gebruik kan word nie", + "localOpen": "Oop", + "localOpenFolder": "Maak vouer oop", + "localUploadToRemote": "Laai op na bediener", + "localUploadToRemoteMany": "Laai {{count}} items na bediener op", + "localMoveToTrash": "Skuif na asblik", + "localMoveToTrashMany": "Skuif {{count}} items na die asblik", + "localTrashConfirmSingle": "Skuif \"{{name}}\" na die asblik?", + "localTrashConfirmFolder": "Skuif die vouer \"{{name}}\" en al sy inhoud na die asblik?", + "localTrashConfirmMany": "Skuif {{count}} items na die asblik?", + "localTrashed": "Het {{count}} item(s) na die asblik geskuif", + "localTrashFailed": "Kon nie na die asblik skuif nie", + "localRenameFailed": "Hernoem het misluk", + "localCreateFailed": "Kon nie item skep nie", + "goToParentFolder": "Gaan na ouermap", + "resizeSidebar": "Sleep om die sybalk se grootte te verander (dubbelklik om terug te stel)", + "resizeLocalPane": "Sleep om die plaaslike paneel se grootte te verander (dubbelklik om terug te stel)", + "columns": "Kolomme", + "columnsHint": "Regskliek om kolomme te kies", + "emptyFolder": "Hierdie vouer is leeg", + "searchFiles": "Soek lêers...", + "upload": "Oplaai", + "selectHostToStart": "Kies 'n gasheer om lêerbestuur te begin", + "sshRequiredForFileManager": "Lêerbestuurder benodig SSH. Hierdie gasheer het nie SSH geaktiveer nie.", + "failedToConnect": "Kon nie aan SSH koppel nie", + "failedToLoadDirectory": "Kon nie gids laai nie", + "noSSHConnection": "Geen SSH-verbinding beskikbaar nie", + "copy": "Kopieer", + "cut": "Sny", + "paste": "Plak", + "copyPath": "Kopieer Pad", + "copyPaths": "Kopieer paaie", + "delete": "Vee uit", + "properties": "Eienskappe", + "refresh": "Verfris", + "downloadFiles": "Laai {{count}} lêers na die blaaier af", + "copyFiles": "Kopieer {{count}} items", + "cutFiles": "Sny {{count}} items", + "deleteFiles": "Vee {{count}} items uit", + "filesCopiedToClipboard": "{{count}} items gekopieer na knipbord", + "filesCutToClipboard": "{{count}} items is na knipbord gesny", + "pathCopiedToClipboard": "Pad gekopieer na knipbord", + "pathsCopiedToClipboard": "{{count}} paaie gekopieer na knipbord", + "failedToCopyPath": "Kon nie pad na knipbord kopieer nie", + "copyFolderLink": "Kopieer skakel na vouer", + "copyCurrentFolderLink": "Kopieer skakel na huidige vouer", + "folderLinkCopied": "Vouerskakel gekopieer na knipbord", + "failedToCopyFolderLink": "Kon nie vouerskakel kopieer nie", + "movedItems": "Het {{count}} items geskuif", + "failedToDeleteItem": "Kon nie item verwyder nie", + "itemRenamedSuccessfully": "{{type}} suksesvol hernoem", + "failedToRenameItem": "Kon nie item hernoem nie", + "download": "Laai af", + "openExternalEditor": "Maak ekstern oop", + "chooseExternalEditor": "Kies eksterne redigeerder", + "externalEditorSelected": "Eksterne redigeerder gekies", + "externalEditorDesktopOnly": "Eksterne redigeerder is slegs beskikbaar in die rekenaarprogram", + "externalEditorOpened": "Oopgemaak in eksterne redigeerder. As jy dit daar stoor, sal dit veranderinge terug na die bediener oplaai.", + "failedToOpenExternalEditor": "Kon nie eksterne redigeerder oopmaak nie", + "failedToSelectExternalEditor": "Kon nie eksterne redigeerder kies nie", + "permissions": "Toestemmings", + "size": "Grootte", + "modified": "Gewysig", + "path": "Pad", + "confirmDelete": "Is jy seker jy wil {{name}} verwyder?", + "permissionDenied": "Toestemming geweier", + "serverError": "Bedienerfout", + "fileSavedSuccessfully": "Lêer suksesvol gestoor", + "failedToSaveFile": "Kon nie lêer stoor nie", + "confirmDeleteSingleItem": "Is jy seker jy wil \"{{name}} \" permanent verwyder?", + "confirmDeleteMultipleItems": "Is jy seker jy wil {{count}} items permanent verwyder?", + "confirmDeleteMultipleItemsWithFolders": "Is jy seker jy wil {{count}} items permanent verwyder? Dit sluit gidse en hul inhoud in.", + "confirmDeleteFolder": "Is jy seker jy wil die vouer \"{{name}}\" en al sy inhoud permanent verwyder?", + "permanentDeleteWarning": "Hierdie aksie kan nie ongedaan gemaak word nie. Die item(s) sal permanent van die bediener verwyder word.", + "moveToTrashWarning": "Die item(s) sal na die Termix-asblik geskuif word en kan herstel word voordat die bewaringstydperk verstryk.", + "itemsMovedToTrash": "{{count}} item(s) na die asblik geskuif", + "trash": "Asblik", + "trashDescription": "Geskrapte items word vir {{days}} dag(e) op hierdie gasheer behou.", + "trashLoadFailed": "Kon nie die asblik laai nie", + "trashEmpty": "Asblik is leeg", + "trashRestore": "Herstel", + "trashRestored": "Herstel {{name}}", + "trashRestoreFailed": "Kon nie item herstel nie. Die oorspronklike pad bestaan dalk reeds.", + "trashDeletePermanently": "Vee permanent uit", + "trashDeleteConfirm": "Wil jy {{name}}permanent verwyder? Dit kan nie ongedaan gemaak word nie.", + "trashPermanentlyDeleted": "Permanent verwyder {{name}}", + "trashDeleteFailed": "Kon nie item permanent verwyder nie", + "trashEmptyAction": "Maak asblik leeg", + "trashEmptyConfirm": "Wil jy elke item in die asblik permanent verwyder? Dit kan nie ongedaan gemaak word nie.", + "trashEmptied": "Asblik leeggemaak", + "trashRetention": "Outomatiese skoonmaak na (dae)", + "trashRetentionSaved": "Asblikbewaring opgedateer", + "trashRetentionFailed": "Kon nie asblikbehoud opdateer nie", + "trashUnavailableConfirm": "Hierdie item kan nie na die asblik geskuif word nie, gewoonlik omdat dit op 'n ander lêerstelsel is of verhoogde toestemmings benodig. Verwyder dit eerder permanent?", + "recent": "Onlangs", + "pinned": "Vasgepen", + "folderShortcuts": "Vouerkortpaaie", + "failedToReconnectSSH": "Kon nie SSH-sessie herkoppel nie", + "openTerminalHere": "Maak Terminaal Hier Oop", + "run": "Hardloop", + "openTerminalInFolder": "Maak Terminaal in Hierdie Vouer Oop", + "openTerminalInFileLocation": "Maak Terminaal oop by Lêerligging", + "runningFile": "Hardloop - {{file}}", + "onlyRunExecutableFiles": "Kan slegs uitvoerbare lêers uitvoer", + "directories": "Gidse", + "removedFromRecentFiles": "\"{{name}}\" is uit onlangse lêers verwyder", + "removeFailed": "Verwydering het misluk", + "unpinnedSuccessfully": "\"{{name}}\" suksesvol ontspeld", + "unpinFailed": "Ontspeld het misluk", + "removedShortcut": "Kortpad \"{{name}} \" verwyder", + "removeShortcutFailed": "Verwydering van kortpad het misluk", + "clearedAllRecentFiles": "Alle onlangse lêers is uitgevee", + "clearFailed": "Vee uit misluk", + "removeFromRecentFiles": "Verwyder uit onlangse lêers", + "clearAllRecentFiles": "Vee alle onlangse lêers uit", + "unpinFile": "Ontspeld lêer", + "removeShortcut": "Verwyder kortpad", + "pinFile": "Speld lêer vas", + "addToShortcuts": "Voeg by kortpaaie", + "pasteFailed": "Plak het misluk", + "noUndoableActions": "Geen ongedaanbare aksies nie", + "undoCopySuccess": "Kopieerbewerking ongedaan gemaak: Gekopieerde lêers is verwyder {{count}}", + "undoCopyFailedDelete": "Ongedaan maak het misluk: Kon geen gekopieerde lêers verwyder nie", + "undoCopyFailedNoInfo": "Ongedaan maak het misluk: Kon nie gekopieerde lêerinligting vind nie", + "undoMoveSuccess": "Ongedaan gemaak met skuifbewerking: {{count}} lêers teruggeskuif na oorspronklike ligging", + "undoMoveFailedMove": "Ongedaan maak het misluk: Kon geen lêers terugskuif nie", + "undoMoveFailedNoInfo": "Ongedaan maak het misluk: Kon nie inligting oor die verskuifde lêer vind nie", + "undoDeleteNotSupported": "Verwyderingsbewerking kan nie ongedaan gemaak word nie: Lêers is permanent van die bediener verwyder", + "undoTypeNotSupported": "Nie-ondersteunde ongedaanmaak-bewerkingtipe", + "undoOperationFailed": "Ongedaanmaak-bewerking het misluk", + "unknownError": "Onbekende fout", + "confirm": "Bevestig", + "find": "Vind...", + "replace": "Vervang", + "downloadInstead": "Laai eerder af", + "keyboardShortcuts": "Sleutelbordkortpaaie", + "searchAndReplace": "Soek en vervang", + "editing": "Redigering", + "search": "Soek", + "findNext": "Vind Volgende", + "findPrevious": "Vind Vorige", + "save": "Stoor", + "selectAll": "Kies Alles", + "undo": "Ongedaan maak", + "redo": "Herdoen", + "moveLineUp": "Skuif lyn op", + "moveLineDown": "Skuif lyn af", + "toggleComment": "Wissel kommentaar", + "autoComplete": "Outomatiese Voltooiing", + "imageLoadError": "Kon nie beeld laai nie", + "startTyping": "Begin tik...", + "unknownSize": "Onbekende grootte", + "fileIsEmpty": "Lêer is leeg", + "largeFileWarning": "Waarskuwing oor groot lêers", + "largeFileWarningDesc": "Hierdie lêer is {{size}} groot, wat werkverrigtingsprobleme kan veroorsaak wanneer dit as teks oopgemaak word.", + "fileNotFoundAndRemoved": "Lêer \"{{name}}\" nie gevind nie en is verwyder uit onlangse/vasgespelde lêers", + "failedToLoadFile": "Kon nie lêer laai nie: {{error}}", + "serverErrorOccurred": "Bedienerfout het voorgekom. Probeer asseblief later weer.", + "autoSaveFailed": "Outomatiese stoor het misluk", + "fileAutoSaved": "Lêer outomaties gestoor", + "moveFileFailed": "Kon nie {{name}} skuif nie", + "moveOperationFailed": "Skuifbewerking het misluk", + "canOnlyCompareFiles": "Kan slegs twee lêers vergelyk", + "comparingFiles": "Vergelyk lêers: {{file1}} en {{file2}}", + "dragFailed": "Sleepbewerking het misluk", + "filePinnedSuccessfully": "Lêer \"{{name}}\" suksesvol vasgepen", + "pinFileFailed": "Kon nie lêer vaspen nie", + "fileUnpinnedSuccessfully": "Lêer \"{{name}}\" suksesvol ontspeld", + "unpinFileFailed": "Kon nie lêer ontspeld nie", + "shortcutAddedSuccessfully": "Vouerkortpad \"{{name}}\" suksesvol bygevoeg", + "addShortcutFailed": "Kon nie kortpad byvoeg nie", + "operationCompletedSuccessfully": "{{operation}} {{count}} items suksesvol", + "operationCompleted": "{{operation}} {{count}} items", + "downloadFileSuccess": "Lêer {{name}} suksesvol afgelaai", + "downloadFileFailed": "Aflaai het misluk", + "downloadingFile": "Laai tans af {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Skuif na {{name}}", + "diffCompareWith": "Verskil vergelyking met {{name}}", + "dragOutsideToDownload": "Sleep buite die venster om af te laai ({{count}} lêers)", + "newFolderDefault": "NuweVouer", + "newFileDefault": "NuweLêer.txt", + "successfullyMovedItems": "Suksesvol {{count}} items na {{target}} geskuif", + "move": "Beweeg", + "searchInFile": "Soek in lêer (Ctrl+F)", + "showKeyboardShortcuts": "Wys sleutelbordkortpaaie", + "decreaseFontSize": "Verklein lettergrootte", + "increaseFontSize": "Vergroot lettergrootte", + "startWritingMarkdown": "Begin om jou afslaginhoud te skryf...", + "loadingFileComparison": "Laai lêervergelyking...", + "reload": "Herlaai", + "compare": "Vergelyk", + "sideBySide": "Sy aan Sy", + "inline": "Inlyn", + "fileComparison": "Lêervergelyking: {{file1}} vs {{file2}}", + "fileTooLarge": "Lêer te groot: {{error}}", + "sshConnectionFailed": "SSH-verbinding het misluk. Kontroleer asseblief jou verbinding met {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Kon nie lêer laai nie: {{error}}", + "connecting": "Verbind...", + "connectedSuccessfully": "Suksesvol gekoppel", + "totpVerificationFailed": "TOTP-verifikasie het misluk", + "warpgateVerificationFailed": "Warpgate-verifikasie het misluk", + "authenticationFailed": "Verifikasie het misluk", + "incorrectPassphrase": "Verkeerde wagwoordfrase. Probeer asseblief weer.", + "verificationCodePrompt": "Verifikasiekode:", + "changePermissions": "Verander Toestemmings", + "currentPermissions": "Huidige Toestemmings", + "owner": "Eienaar", + "group": "Groep", + "others": "Ander", + "read": "Lees", + "write": "Skryf", + "execute": "Voer uit", + "permissionsChangedSuccessfully": "Toestemmings suksesvol verander", + "failedToChangePermissions": "Kon nie toestemmings verander nie", + "name": "Naam", + "sortByName": "Naam", + "sortByDate": "Datum Gewysig", + "sortBySize": "Grootte", + "ascending": "Stygend", + "descending": "Dalend", + "root": "Wortel", + "new": "Nuut", + "sortBy": "Sorteer volgens", + "items": "Items", + "selected": "Gekies", + "editor": "Redakteur", + "octal": "Oktaal", + "storage": "Berging", + "disk": "Skyf", + "used": "Gebruik", + "of": "van", + "toggleSidebar": "Wissel sybalk", + "compactLayout": "Gebruik kompakte uitleg", + "comfortableLayout": "Gebruik gemaklike uitleg", + "cannotLoadPdf": "Kan nie PDF laai nie", + "pdfLoadError": "Daar was 'n fout tydens die laai van hierdie PDF-lêer.", + "loadingPdf": "Laai PDF...", + "loadingPage": "Laai bladsy..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Dra lêers oor tussen twee SSH-gashere.", + "sourceServer": "Bronbediener", + "destinationServer": "Bestemmingsbediener", + "selectHost": "Kies gasheer", + "noHostSelected": "Geen gasheer gekies nie", + "move": "Beweeg", + "copy": "Kopieer", + "upload": "Oplaai", + "itemsSelected": "{{count}} item gekies", + "itemsSelected_other": "{{count}} items gekies", + "ready": "Gereed", + "loadingHosts": "Laai gashere...", + "name": "Naam", + "modified": "Gewysig", + "size": "Grootte", + "noFiles": "Geen lêers nie", + "open": "Oop", + "up": "Op", + "refresh": "Verfris", + "copyToTarget": "Kopieer na teikengids", + "rename": "Hernoem", + "delete": "Vee uit", + "editPermissions": "Wysig Toestemmings", + "createNewFolder": "Skep Nuwe Vouer", + "deleteSelectedItems": "Vee geselekteerde items uit", + "deleteConfirm": "Vee {{count}} gekose item uit?", + "deleteConfirm_other": "Vee {{count}} gekose items uit?", + "cancel": "Kanselleer", + "save": "Stoor", + "folderCreated": "Vouer geskep", + "itemRenamed": "Item hernoem", + "actionFailed": "Aksie het misluk", + "itemsDeleted": "{{count}} item is verwyder", + "itemsDeleted_other": "{{count}} items is uitgevee", + "deleteFailed": "Verwydering het misluk", + "permissionsUpdated": "Toestemmings opgedateer", + "noRemoteSessionConnected": "Geen afstandsessie gekoppel nie", + "failedToListFiles": "Kon nie lêers lys nie", + "failedToRefresh": "Kon nie herlaai nie", + "destinationInsideSource": "Bestemming kan nie binne die gekose bronpad wees nie.", + "sameHostNoOp": "Bron en bestemming is dieselfde lêergids." + }, + "hosts": { + "fileManager": "Lêerbestuurder", + "tabFiles": "Lêers", + "enableFileManagerMonitor": "Aktiveer Lêerbestuurder", + "enableFileManagerMonitorDesc": "Blaai deur en bestuur lêers op hierdie gasheer via SFTP", + "scpLegacyLabel": "SCP Legacy-modus", + "scpLegacyDesc": "Gebruik ouer lêeroordrag vir bedieners wat nie die SFTP-substelsel ondersteun nie (bv. ingebedde of minimale SSH-bedieners).", + "defaultPathLabel": "Standaardpad", + "fileManagerPathHint": "Die gids om oop te maak wanneer die lêerbestuurder vir hierdie gasheer begin.", + "fileManagerUrlCopied": "Lêerbestuurder-URL gekopieer", + "copyFileManagerUrlAction": "Kopieer Lêerbestuurder URL" + }, + "settings": { + "host": { + "enable": { + "label": "Aktiveer Lêerbestuurder" + }, + "defaultPath": { + "label": "Standaardpad" + }, + "scpLegacy": { + "label": "SCP Legacy-modus" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Lêers" + } +} diff --git a/plugins/file-manager/locales/translated/ar_SA.json b/plugins/file-manager/locales/translated/ar_SA.json new file mode 100644 index 000000000..49ffc930b --- /dev/null +++ b/plugins/file-manager/locales/translated/ar_SA.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "لم يتم اختيار مضيف", + "initializingEditor": "جارٍ تهيئة المحرر...", + "file": "ملف", + "folder": "مجلد", + "uploadFile": "رفع ملف", + "downloadFile": "تنزيل", + "extractArchive": "استخراج الأرشيف", + "extractingArchive": "جارٍ استخراج {{name}}...", + "archiveExtractedSuccessfully": "تم استخراج {{name}} بنجاح", + "extractFailed": "فشل الاستخراج", + "compressFile": "ضغط ملف", + "compressFiles": "ضغط ملفات", + "compressFilesDesc": "ضغط {{count}} عنصر في أرشيف", + "archiveName": "اسم الأرشيف", + "enterArchiveName": "أدخل اسم الأرشيف...", + "compressionFormat": "صيغة الضغط", + "selectedFiles": "الملفات المحددة", + "andMoreFiles": "و {{count}} المزيد...", + "compress": "ضغط", + "compressingFiles": "جارٍ ضغط {{count}} عنصر في {{name}}...", + "filesCompressedSuccessfully": "تم إنشاء {{name}} بنجاح", + "compressFailed": "فشل الضغط", + "edit": "تحرير", + "preview": "معاينة", + "previous": "السابق", + "next": "التالي", + "pageXOfY": "صفحة {{current}} من {{total}}", + "zoomOut": "تصغير", + "zoomIn": "تكبير", + "newFile": "ملف جديد", + "newFolder": "مجلد جديد", + "rename": "إعادة تسمية", + "uploading": "جارٍ الرفع...", + "uploadingFile": "جارٍ رفع {{name}}...", + "fileName": "اسم الملف", + "folderName": "اسم المجلد", + "fileUploadedSuccessfully": "تم رفع الملف \"{{name}}\" بنجاح", + "failedToUploadFile": "فشل رفع الملف", + "uploadingFolderFiles": "جارٍ تحميل {{count}} ملف(ملفات)...", + "uploadedFolderFiles": "تم تحميل {{count}} ملف بنجاح", + "uploadedFolderPartial": "تم تحميل {{uploaded}} ملفًا، {{failed}} فشل", + "fileDownloadedSuccessfully": "تم تنزيل الملف \"{{name}}\" بنجاح", + "failedToDownloadFile": "فشل تنزيل الملف", + "fileCreatedSuccessfully": "تم إنشاء الملف \"{{name}}\" بنجاح", + "folderCreatedSuccessfully": "تم إنشاء المجلد \"{{name}}\" بنجاح", + "failedToCreateItem": "فشل إنشاء العنصر", + "operationFailed": "فشلت عملية {{operation}} لـ {{name}}: {{error}}", + "failedToResolveSymlink": "فشل حل الرابط الرمزي", + "itemsDeletedSuccessfully": "تم حذف {{count}} عناصر بنجاح", + "failedToDeleteItems": "فشل حذف العناصر", + "sudoPasswordRequired": "كلمة مرور المسؤول مطلوبة", + "enterSudoPassword": "أدخل كلمة مرور sudo لمتابعة هذه العملية", + "sudoPassword": "كلمة مرور sudo", + "sudoOperationFailed": "فشلت عملية sudo", + "sudoAuthFailed": "فشلت مصادقة sudo", + "dragFilesToUpload": "أفلت الملفات هنا لرفعها", + "dropToUploadHere": "قم بإسقاط الملف هنا للتحميل", + "dropToDownloadHere": "قم بالتنزيل من هنا", + "localFiles": "محلي", + "showLocalFiles": "عرض الملفات المحلية (عرض مقسم)", + "hideLocalFiles": "إخفاء الملفات المحلية", + "localHome": "المجلد الرئيسي", + "localPathPlaceholder": "مسار المجلد المحلي", + "localShowHidden": "إظهار الملفات المخفية", + "localHideHidden": "إخفاء الملفات المخفية", + "localRevealInFileManager": "إظهار في مدير ملفات النظام", + "localCannotOpenFolder": "لا يمكن فتح المجلد", + "localItemCount": "{{count}} عناصر", + "localSelectedCount": "{{count}} تم الاختيار", + "back": "خلف", + "forward": "إلى الأمام", + "up": "أعلى", + "kind": "عطوف", + "localUploadingProgress": "جارٍ تحميل {{current}} من {{total}}", + "localDownloadingProgress": "جارٍ تنزيل {{current}} من {{total}}", + "localTransferInFlight": "{{count}} دفعة واحدة", + "localPreparingDownload": "جارٍ تحضير التنزيل...", + "localTransferCancelled": "تم إلغاء التحويل", + "localNothingToTransfer": "لا يوجد شيء لنقله", + "localUploadComplete": "تم تحميل {{count}} ملف(ملفات)", + "localUploadFailed": "فشل التحميل", + "localUploadPartial": "تم تحميل {{done}} ملفًا، {{failed}} فشل", + "localDownloadComplete": "تم تنزيل {{count}} ملفًا(ملفات)", + "localDownloadFailed": "فشل التنزيل", + "localDownloadPartial": "تم تنزيل {{done}} ملفًا، {{failed}} فشل", + "localReplaceExistingPrompt": "{{count}} العنصر (العناصر) موجودة بالفعل في هذا المجلد. هل تريد استبدالها؟", + "localReplace": "يستبدل", + "localSkipExisting": "يتخطى", + "localSkippedExisting": "تم تخطي {{count}} عنصر (عناصر) موجودة", + "localUnsafeNamesSkipped": "تم تخطي {{count}} عنصرًا لا يمكن استخدام أسمائها على هذا الكمبيوتر", + "localOpen": "يفتح", + "localOpenFolder": "افتح المجلد", + "localUploadToRemote": "التحميل إلى الخادم", + "localUploadToRemoteMany": "قم بتحميل {{count}} عنصرًا إلى الخادم", + "localMoveToTrash": "انقل إلى سلة المهملات", + "localMoveToTrashMany": "انقل {{count}} عنصرًا إلى سلة المهملات", + "localTrashConfirmSingle": "هل تريد نقل \"{{name}}\" إلى سلة المهملات؟", + "localTrashConfirmFolder": "هل تريد نقل المجلد \"{{name}}\" وجميع محتوياته إلى سلة المهملات؟", + "localTrashConfirmMany": "هل تريد نقل العناصر {{count}} إلى سلة المهملات؟", + "localTrashed": "تم نقل {{count}} عنصرًا إلى سلة المهملات", + "localTrashFailed": "لم يتمكن من الانتقال إلى سلة المهملات", + "localRenameFailed": "فشلت عملية إعادة التسمية", + "localCreateFailed": "تعذر إنشاء العنصر", + "goToParentFolder": "انتقل إلى المجلد الرئيسي", + "resizeSidebar": "اسحب لتغيير حجم الشريط الجانبي (انقر نقراً مزدوجاً لإعادة التعيين)", + "resizeLocalPane": "اسحب لتغيير حجم اللوحة المحلية (انقر نقراً مزدوجاً لإعادة التعيين)", + "columns": "الأعمدة", + "columnsHint": "انقر بزر الماوس الأيمن لاختيار الأعمدة", + "emptyFolder": "هذا المجلد فارغ", + "searchFiles": "بحث في الملفات...", + "upload": "رفع", + "selectHostToStart": "حدد مضيفًا لبدء إدارة الملفات", + "sshRequiredForFileManager": "يتطلب مدير الملفات SSH. هذا المضيف لم يتم تمكين SSH عليه.", + "failedToConnect": "فشل الاتصال بـ SSH", + "failedToLoadDirectory": "فشل تحميل المجلد", + "noSSHConnection": "لا يوجد اتصال SSH متاح", + "copy": "نسخ", + "cut": "قص", + "paste": "لصق", + "copyPath": "نسخ المسار", + "copyPaths": "نسخ المسارات", + "delete": "حذف", + "properties": "خصائص", + "refresh": "تحديث", + "downloadFiles": "تنزيل {{count}} ملفات إلى المتصفح", + "copyFiles": "نسخ {{count}} عناصر", + "cutFiles": "قص {{count}} عناصر", + "deleteFiles": "حذف {{count}} عناصر", + "filesCopiedToClipboard": "تم نسخ {{count}} عناصر إلى الحافظة", + "filesCutToClipboard": "تم قص {{count}} عناصر إلى الحافظة", + "pathCopiedToClipboard": "تم نسخ المسار إلى الحافظة", + "pathsCopiedToClipboard": "تم نسخ {{count}} مسارات إلى الحافظة", + "failedToCopyPath": "فشل نسخ المسار إلى الحافظة", + "copyFolderLink": "نسخ رابط المجلد", + "copyCurrentFolderLink": "نسخ رابط المجلد الحالي", + "folderLinkCopied": "تم نسخ رابط المجلد إلى الحافظة", + "failedToCopyFolderLink": "فشل نسخ رابط المجلد", + "movedItems": "تم نقل {{count}} عناصر", + "failedToDeleteItem": "فشل حذف العنصر", + "itemRenamedSuccessfully": "تمت إعادة تسمية {{type}} بنجاح", + "failedToRenameItem": "فشلت إعادة تسمية العنصر", + "download": "تنزيل", + "openExternalEditor": "فتح خارجيًا", + "chooseExternalEditor": "اختيار محرر خارجي", + "externalEditorSelected": "تم اختيار المحرر الخارجي", + "externalEditorDesktopOnly": "المحرر الخارجي متاح فقط في تطبيق سطح المكتب", + "externalEditorOpened": "تم الفتح في المحرر الخارجي. سيؤدي الحفظ هناك إلى رفع التغييرات إلى الخادم.", + "failedToOpenExternalEditor": "فشل فتح المحرر الخارجي", + "failedToSelectExternalEditor": "فشل اختيار المحرر الخارجي", + "permissions": "الصلاحيات", + "size": "الحجم", + "modified": "تاريخ التعديل", + "path": "المسار", + "confirmDelete": "هل أنت متأكد أنك تريد حذف {{name}}؟", + "permissionDenied": "تم رفض الإذن", + "serverError": "خطأ في الخادم", + "fileSavedSuccessfully": "تم حفظ الملف بنجاح", + "failedToSaveFile": "فشل حفظ الملف", + "confirmDeleteSingleItem": "هل أنت متأكد أنك تريد حذف \"{{name}}\" نهائيًا؟", + "confirmDeleteMultipleItems": "هل أنت متأكد أنك تريد حذف {{count}} عناصر نهائيًا؟", + "confirmDeleteMultipleItemsWithFolders": "هل أنت متأكد أنك تريد حذف {{count}} عناصر نهائيًا؟ يتضمن ذلك المجلدات ومحتوياتها.", + "confirmDeleteFolder": "هل أنت متأكد أنك تريد حذف المجلد \"{{name}}\" وجميع محتوياته نهائيًا؟", + "permanentDeleteWarning": "لا يمكن التراجع عن هذا الإجراء. سيتم حذف العنصر (العناصر) نهائيًا من الخادم.", + "moveToTrashWarning": "سيتم نقل العنصر (العناصر) إلى سلة مهملات Termix ويمكن استعادته قبل انتهاء فترة الاحتفاظ.", + "itemsMovedToTrash": "تم نقل العنصر (العناصر) إلى سلة المهملات {{count}}", + "trash": "نفاية", + "trashDescription": "يتم الاحتفاظ بالعناصر المحذوفة لمدة {{days}} يوم (أيام) على هذا المضيف.", + "trashLoadFailed": "فشل تحميل سلة المهملات", + "trashEmpty": "سلة المهملات فارغة", + "trashRestore": "يعيد", + "trashRestored": "تمت الاستعادة {{name}}", + "trashRestoreFailed": "فشلت عملية استعادة العنصر. قد يكون المسار الأصلي موجودًا بالفعل.", + "trashDeletePermanently": "حذف نهائياً", + "trashDeleteConfirm": "حذف {{name}}نهائياً؟ لا يمكن التراجع عن هذا الإجراء.", + "trashPermanentlyDeleted": "تم الحذف نهائياً {{name}}", + "trashDeleteFailed": "فشل حذف العنصر نهائياً", + "trashEmptyAction": "قم بإفراغ سلة المهملات", + "trashEmptyConfirm": "هل تريد حذف جميع العناصر الموجودة في سلة المهملات نهائياً؟ لا يمكن التراجع عن هذا الإجراء.", + "trashEmptied": "تم إفراغ سلة المهملات", + "trashRetention": "التنظيف التلقائي بعد (أيام)", + "trashRetentionSaved": "تحديث نظام الاحتفاظ بالنفايات", + "trashRetentionFailed": "فشل تحديث الاحتفاظ بالنفايات", + "trashUnavailableConfirm": "لا يمكن نقل هذا العنصر إلى سلة المهملات، عادةً لأنه موجود على نظام ملفات آخر أو يتطلب صلاحيات إدارية. هل تريد حذفه نهائياً بدلاً من ذلك؟", + "recent": "الأخيرة", + "pinned": "المثبتة", + "folderShortcuts": "اختصارات المجلدات", + "failedToReconnectSSH": "فشل إعادة الاتصال بجلسة SSH", + "openTerminalHere": "افتح الطرفية هنا", + "run": "تشغيل", + "openTerminalInFolder": "افتح الطرفية في هذا المجلد", + "openTerminalInFileLocation": "افتح الطرفية في موقع الملف", + "runningFile": "قيد التشغيل - {{file}}", + "onlyRunExecutableFiles": "يمكن تشغيل الملفات التنفيذية فقط", + "directories": "المجلدات", + "removedFromRecentFiles": "تمت إزالة \"{{name}}\" من الملفات الحديثة", + "removeFailed": "فشلت الإزالة", + "unpinnedSuccessfully": "تم إلغاء تثبيت \"{{name}}\" بنجاح", + "unpinFailed": "فشل إلغاء التثبيت", + "removedShortcut": "تمت إزالة الاختصار \"{{name}}\"", + "removeShortcutFailed": "فشلت إزالة الاختصار", + "clearedAllRecentFiles": "تم مسح جميع الملفات الحديثة", + "clearFailed": "فشل المسح", + "removeFromRecentFiles": "إزالة من الملفات الحديثة", + "clearAllRecentFiles": "مسح جميع الملفات الحديثة", + "unpinFile": "إلغاء تثبيت الملف", + "removeShortcut": "إزالة الاختصار", + "pinFile": "تثبيت الملف", + "addToShortcuts": "إضافة إلى الاختصارات", + "pasteFailed": "فشل اللصق", + "noUndoableActions": "لا توجد إجراءات يمكن التراجع عنها", + "undoCopySuccess": "تم التراجع عن عملية النسخ: حُذف {{count}} ملفًا منسوخًا", + "undoCopyFailedDelete": "فشل التراجع: تعذر حذف أي ملفات منسوخة", + "undoCopyFailedNoInfo": "فشل التراجع: تعذر العثور على معلومات الملفات المنسوخة", + "undoMoveSuccess": "تم التراجع عن عملية النقل: تم نقل {{count}} ملفًا إلى الموقع الأصلي", + "undoMoveFailedMove": "فشل التراجع: تعذر نقل أي ملفات للخلف", + "undoMoveFailedNoInfo": "فشل التراجع: تعذر العثور على معلومات الملفات المنقولة", + "undoDeleteNotSupported": "لا يمكن التراجع عن عملية الحذف: تم حذف الملفات نهائيًا من الخادم", + "undoTypeNotSupported": "نوع عملية التراجع غير مدعوم", + "undoOperationFailed": "فشلت عملية التراجع", + "unknownError": "خطأ غير معروف", + "confirm": "تأكيد", + "find": "بحث...", + "replace": "استبدال", + "downloadInstead": "تنزيل بدلاً من ذلك", + "keyboardShortcuts": "اختصارات لوحة المفاتيح", + "searchAndReplace": "بحث واستبدال", + "editing": "تعديل", + "search": "بحث", + "findNext": "بحث عن التالي", + "findPrevious": "بحث عن السابق", + "save": "حفظ", + "selectAll": "تحديد الكل", + "undo": "تراجع", + "redo": "إعادة", + "moveLineUp": "نقل السطر لأعلى", + "moveLineDown": "نقل السطر لأسفل", + "toggleComment": "تبديل التعليق", + "autoComplete": "إكمال تلقائي", + "imageLoadError": "فشل تحميل الصورة", + "startTyping": "ابدأ الكتابة...", + "unknownSize": "حجم غير معروف", + "fileIsEmpty": "الملف فارغ", + "largeFileWarning": "تحذير ملف كبير", + "largeFileWarningDesc": "هذا الملف بحجم {{size}}، مما قد يسبب مشكلات في الأداء عند فتحه كنص.", + "fileNotFoundAndRemoved": "الملف \"{{name}}\" غير موجود وتمت إزالته من الملفات الأخيرة/المثبتة", + "failedToLoadFile": "فشل تحميل الملف: {{error}}", + "serverErrorOccurred": "حدث خطأ في الخادم. يرجى المحاولة لاحقًا.", + "autoSaveFailed": "فشل الحفظ التلقائي", + "fileAutoSaved": "تم حفظ الملف تلقائيًا", + "moveFileFailed": "فشل نقل {{name}}", + "moveOperationFailed": "فشلت عملية النقل", + "canOnlyCompareFiles": "يمكن مقارنة ملفين فقط", + "comparingFiles": "مقارنة الملفات: {{file1}} و {{file2}}", + "dragFailed": "فشلت عملية السحب", + "filePinnedSuccessfully": "تم تثبيت الملف \"{{name}}\" بنجاح", + "pinFileFailed": "فشل تثبيت الملف", + "fileUnpinnedSuccessfully": "تم إلغاء تثبيت الملف \"{{name}}\" بنجاح", + "unpinFileFailed": "فشل إلغاء تثبيت الملف", + "shortcutAddedSuccessfully": "تمت إضافة اختصار المجلد \"{{name}}\" بنجاح", + "addShortcutFailed": "فشلت إضافة الاختصار", + "operationCompletedSuccessfully": "تمت {{operation}} {{count}} عناصر بنجاح", + "operationCompleted": "{{operation}} {{count}} عناصر", + "downloadFileSuccess": "تم تنزيل الملف {{name}} بنجاح", + "downloadFileFailed": "فشل التنزيل", + "downloadingFile": "جارٍ التنزيل {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "نقل إلى {{name}}", + "diffCompareWith": "مقارنة الاختلافات مع {{name}}", + "dragOutsideToDownload": "اسحب خارج النافذة للتنزيل ({{count}} ملفات)", + "newFolderDefault": "مجلد جديد", + "newFileDefault": "ملف جديد.txt", + "successfullyMovedItems": "تم نقل {{count}} عناصر بنجاح إلى {{target}}", + "move": "نقل", + "searchInFile": "بحث في الملف (Ctrl+F)", + "showKeyboardShortcuts": "إظهار اختصارات لوحة المفاتيح", + "decreaseFontSize": "تصغير حجم الخط", + "increaseFontSize": "تكبير حجم الخط", + "startWritingMarkdown": "ابدأ كتابة محتوى Markdown...", + "loadingFileComparison": "جارٍ تحميل مقارنة الملفات...", + "reload": "إعادة تحميل", + "compare": "مقارنة", + "sideBySide": "جنباً إلى جنب", + "inline": "مضمن", + "fileComparison": "مقارنة الملفات: {{file1}} مقابل {{file2}}", + "fileTooLarge": "الملف كبير جداً: {{error}}", + "sshConnectionFailed": "فشل اتصال SSH. يرجى التحقق من الاتصال بـ {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "فشل تحميل الملف: {{error}}", + "connecting": "جارٍ الاتصال...", + "connectedSuccessfully": "تم الاتصال بنجاح", + "totpVerificationFailed": "فشل التحقق من TOTP", + "warpgateVerificationFailed": "فشلت مصادقة Warpgate", + "authenticationFailed": "فشلت المصادقة", + "incorrectPassphrase": "عبارة المرور غير صحيحة. يرجى المحاولة مرة أخرى.", + "verificationCodePrompt": "رمز التحقق:", + "changePermissions": "تغيير الصلاحيات", + "currentPermissions": "الصلاحيات الحالية", + "owner": "المالك", + "group": "المجموعة", + "others": "الآخرون", + "read": "قراءة", + "write": "كتابة", + "execute": "تنفيذ", + "permissionsChangedSuccessfully": "تم تغيير الصلاحيات بنجاح", + "failedToChangePermissions": "فشل تغيير الصلاحيات", + "name": "الاسم", + "sortByName": "الاسم", + "sortByDate": "تاريخ التعديل", + "sortBySize": "الحجم", + "ascending": "تصاعدي", + "descending": "تنازلي", + "root": "الجذر", + "new": "جديد", + "sortBy": "ترتيب حسب", + "items": "عناصر", + "selected": "محدد", + "editor": "محرر", + "octal": "ثماني", + "storage": "التخزين", + "disk": "القرص", + "used": "مستخدم", + "of": "من", + "toggleSidebar": "إظهار/إخفاء الشريط الجانبي", + "compactLayout": "استخدم تصميمًا مضغوطًا", + "comfortableLayout": "استخدم تصميمًا مريحًا", + "cannotLoadPdf": "لا يمكن تحميل PDF", + "pdfLoadError": "حدث خطأ أثناء تحميل ملف PDF هذا.", + "loadingPdf": "جارٍ تحميل PDF...", + "loadingPage": "جارٍ تحميل الصفحة..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "نقل الملفات بين مضيفين عبر بروتوكول SSH.", + "sourceServer": "خادم المصدر", + "destinationServer": "خادم الوجهة", + "selectHost": "حدد المضيف", + "noHostSelected": "لم يتم تحديد أي مضيف", + "move": "يتحرك", + "copy": "ينسخ", + "upload": "رفع", + "itemsSelected": "تم تحديد العنصر {{count}}", + "itemsSelected_other": "تم تحديد {{count}} عنصر", + "ready": "مستعد", + "loadingHosts": "جارٍ تحميل المضيفين...", + "name": "اسم", + "modified": "معدل", + "size": "مقاس", + "noFiles": "لا توجد ملفات", + "open": "يفتح", + "up": "أعلى", + "refresh": "ينعش", + "copyToTarget": "انسخ إلى الدليل الهدف", + "rename": "إعادة تسمية", + "delete": "يمسح", + "editPermissions": "تعديل الأذونات", + "createNewFolder": "إنشاء مجلد جديد", + "deleteSelectedItems": "حذف العناصر المحددة", + "deleteConfirm": "حذف العنصر المحدد {{count}}؟", + "deleteConfirm_other": "حذف {{count}} من العناصر المحددة؟", + "cancel": "يلغي", + "save": "يحفظ", + "folderCreated": "تم إنشاء المجلد", + "itemRenamed": "تمت إعادة تسمية العنصر", + "actionFailed": "فشلت العملية", + "itemsDeleted": "تم حذف العنصر {{count}}", + "itemsDeleted_other": "تم حذف {{count}} عنصر", + "deleteFailed": "فشلت عملية الحذف", + "permissionsUpdated": "تم تحديث الأذونات", + "noRemoteSessionConnected": "لم يتم الاتصال بأي جلسة عن بعد", + "failedToListFiles": "فشل في عرض قائمة الملفات", + "failedToRefresh": "فشل التحديث", + "destinationInsideSource": "لا يمكن أن تكون الوجهة داخل مسار المصدر المحدد.", + "sameHostNoOp": "المصدر والوجهة هما نفس المجلد." + }, + "hosts": { + "fileManager": "مدير الملفات", + "tabFiles": "الملفات", + "enableFileManagerMonitor": "تمكين مدير الملفات", + "enableFileManagerMonitorDesc": "استعراض وإدارة الملفات على هذا المضيف عبر SFTP", + "scpLegacyLabel": "وضع SCP القديم", + "scpLegacyDesc": "استخدم وضع النقل القديم للملفات للخوادم التي لا تدعم النظام الفرعي SFTP (مثل خوادم SSH المدمجة أو ذات الإمكانيات المحدودة).", + "defaultPathLabel": "المسار الافتراضي", + "fileManagerPathHint": "الدليل الذي سيتم فتحه عند تشغيل مدير الملفات لهذا المضيف.", + "fileManagerUrlCopied": "تم نسخ رابط مدير الملفات", + "copyFileManagerUrlAction": "نسخ رابط مدير الملفات" + }, + "settings": { + "host": { + "enable": { + "label": "تمكين مدير الملفات" + }, + "defaultPath": { + "label": "المسار الافتراضي" + }, + "scpLegacy": { + "label": "وضع SCP القديم" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "الملفات" + } +} diff --git a/plugins/file-manager/locales/translated/bg_BG.json b/plugins/file-manager/locales/translated/bg_BG.json new file mode 100644 index 000000000..6b453431c --- /dev/null +++ b/plugins/file-manager/locales/translated/bg_BG.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Няма избран хост", + "initializingEditor": "Инициализиране на редактора...", + "file": "Файл", + "folder": "Папка", + "uploadFile": "Качване на файл", + "downloadFile": "Изтегляне", + "extractArchive": "Извличане на архив", + "extractingArchive": "Извличане на {{name}}...", + "archiveExtractedSuccessfully": "{{name}} е извлечено успешно", + "extractFailed": "Извличането не бе успешно", + "compressFile": "Компресиране на файл", + "compressFiles": "Компресиране на файлове", + "compressFilesDesc": "Компресиране на {{count}} елементи в архив", + "archiveName": "Име на архива", + "enterArchiveName": "Въведете име на архива...", + "compressionFormat": "Формат на компресия", + "selectedFiles": "Избрани файлове", + "andMoreFiles": "и още {{count}}...", + "compress": "Компресиране", + "compressingFiles": "Компресиране на {{count}} елементи в {{name}}...", + "filesCompressedSuccessfully": "{{name}} е създадено успешно", + "compressFailed": "Компресията не беше успешна", + "edit": "Редактиране", + "preview": "Преглед", + "previous": "Предишен", + "next": "Следващо", + "pageXOfY": "Страница {{current}} от {{total}}", + "zoomOut": "Намаляване на мащаба", + "zoomIn": "Увеличаване на мащаба", + "newFile": "Нов файл", + "newFolder": "Нова папка", + "rename": "Преименуване", + "uploading": "Качване...", + "uploadingFile": "Качване {{name}}...", + "fileName": "Име на файл", + "folderName": "Име на папката", + "fileUploadedSuccessfully": "Файлът „{{name}}“ е качен успешно", + "failedToUploadFile": "Качването на файла не бе успешно", + "uploadingFolderFiles": "Качване на {{count}} файл(а)...", + "uploadedFolderFiles": "Качени {{count}} файла(а) успешно", + "uploadedFolderPartial": "Качени {{uploaded}} файла(а), {{failed}} неуспешно", + "fileDownloadedSuccessfully": "Файлът „{{name}}“ е изтеглен успешно", + "failedToDownloadFile": "Изтеглянето на файла не бе успешно", + "fileCreatedSuccessfully": "Файлът „{{name}}“ е създаден успешно", + "folderCreatedSuccessfully": "Папка „{{name}}“ е създадена успешно", + "failedToCreateItem": "Създаването на елемент не бе успешно", + "operationFailed": "Операцията {{operation}} е неуспешна за {{name}}: {{error}}", + "failedToResolveSymlink": "Неуспешно разрешаване на символната връзка", + "itemsDeletedSuccessfully": "{{count}} елементи са изтрити успешно", + "failedToDeleteItems": "Изтриването на елементи не бе успешно", + "sudoPasswordRequired": "Изисква се администраторска парола", + "enterSudoPassword": "Въведете паролата за sudo, за да продължите тази операция", + "sudoPassword": "Парола за Sudo", + "sudoOperationFailed": "Sudo операцията не беше успешна", + "sudoAuthFailed": "Sudo удостоверяването не беше успешно", + "dragFilesToUpload": "Пуснете файлове тук, за да ги качите", + "dropToUploadHere": "Пуснете, за да качите тук", + "dropToDownloadHere": "Пуснете, за да изтеглите тук", + "localFiles": "Местно", + "showLocalFiles": "Показване на локални файлове (разделен изглед)", + "hideLocalFiles": "Скриване на локални файлове", + "localHome": "Начална папка", + "localPathPlaceholder": "Път към локалната папка", + "localShowHidden": "Показване на скритите файлове", + "localHideHidden": "Скриване на скрити файлове", + "localRevealInFileManager": "Разкриване в системния файлов мениджър", + "localCannotOpenFolder": "Не може да се отвори папка", + "localItemCount": "{{count}} артикули", + "localSelectedCount": "{{count}} избрано", + "back": "Обратно", + "forward": "Напред", + "up": "Нагоре", + "kind": "Вид", + "localUploadingProgress": "Качване на {{current}} от {{total}}", + "localDownloadingProgress": "Изтегляне на {{current}} от {{total}}", + "localTransferInFlight": "{{count}} наведнъж", + "localPreparingDownload": "Подготовка за изтегляне...", + "localTransferCancelled": "Прехвърлянето е анулирано", + "localNothingToTransfer": "Няма нищо за прехвърляне", + "localUploadComplete": "Качени {{count}} файла(а)", + "localUploadFailed": "Качването не бе успешно", + "localUploadPartial": "Качени {{done}} файла(а), {{failed}} неуспешно", + "localDownloadComplete": "Изтеглени {{count}} файлове", + "localDownloadFailed": "Изтеглянето не бе успешно", + "localDownloadPartial": "Изтеглени {{done}} файлове, {{failed}} неуспешно", + "localReplaceExistingPrompt": "{{count}} елемент(а) вече съществуват в тази папка. Да се заменят ли?", + "localReplace": "Замяна", + "localSkipExisting": "Пропускане", + "localSkippedExisting": "Пропуснати {{count}} съществуващи елементи", + "localUnsafeNamesSkipped": "Пропуснати {{count}} елементи, чиито имена не могат да се използват на този компютър", + "localOpen": "Отворено", + "localOpenFolder": "Отвори папка", + "localUploadToRemote": "Качване на сървъра", + "localUploadToRemoteMany": "Качване на {{count}} елементи на сървъра", + "localMoveToTrash": "Премести в кошчето", + "localMoveToTrashMany": "Преместване на {{count}} елемента в кошчето", + "localTrashConfirmSingle": "Да се премести ли „{{name}}“ в кошчето?", + "localTrashConfirmFolder": "Да се премести ли папката „{{name}}“ и цялото ѝ съдържание в кошчето?", + "localTrashConfirmMany": "Да се преместят ли {{count}} елементи в кошчето?", + "localTrashed": "Преместени {{count}} елемент(а) в кошчето", + "localTrashFailed": "Не можа да се премести в кошчето", + "localRenameFailed": "Преименуването не бе успешно", + "localCreateFailed": "Не можа да се създаде елемент", + "goToParentFolder": "Отиди до родителската папка", + "resizeSidebar": "Плъзнете, за да промените размера на страничната лента (щракнете двукратно за нулиране)", + "resizeLocalPane": "Плъзнете, за да промените размера на локалния панел (щракнете двукратно за нулиране)", + "columns": "Колони", + "columnsHint": "Щракнете с десния бутон, за да изберете колони", + "emptyFolder": "Тази папка е празна", + "searchFiles": "Търсене на файлове...", + "upload": "Качване", + "selectHostToStart": "Изберете хост, за да започнете управлението на файлове", + "sshRequiredForFileManager": "Файловият мениджър изисква SSH. Този хост не е активиран за SSH.", + "failedToConnect": "Неуспешно свързване към SSH", + "failedToLoadDirectory": "Неуспешно зареждане на директорията", + "noSSHConnection": "Няма налична SSH връзка", + "copy": "Копиране", + "cut": "Изрязване", + "paste": "Паста", + "copyPath": "Копиране на пътя", + "copyPaths": "Копиране на пътища", + "delete": "Изтриване", + "properties": "Имоти", + "refresh": "Обновяване", + "downloadFiles": "Изтегляне на {{count}} файлове в браузъра", + "copyFiles": "Копиране на {{count}} елементи", + "cutFiles": "Изрязване на {{count}} елементи", + "deleteFiles": "Изтриване на {{count}} елементи", + "filesCopiedToClipboard": "{{count}} елементи са копирани в клипборда", + "filesCutToClipboard": "{{count}} елементи, изрязани в клипборда", + "pathCopiedToClipboard": "Пътят е копиран в клипборда", + "pathsCopiedToClipboard": "{{count}} пътища, копирани в клипборда", + "failedToCopyPath": "Копирането на пътя в клипборда не бе успешно", + "copyFolderLink": "Копиране на връзката към папката", + "copyCurrentFolderLink": "Копиране на връзката към текущата папка", + "folderLinkCopied": "Връзката към папката е копирана в клипборда", + "failedToCopyFolderLink": "Копирането на връзката към папката не бе успешно", + "movedItems": "Преместени {{count}} елементи", + "failedToDeleteItem": "Изтриването на елемента не бе успешно", + "itemRenamedSuccessfully": "{{type}} преименуван успешно", + "failedToRenameItem": "Преименуването на елемента не бе успешно", + "download": "Изтегляне", + "openExternalEditor": "Отворено външно", + "chooseExternalEditor": "Изберете външен редактор", + "externalEditorSelected": "Избран външен редактор", + "externalEditorDesktopOnly": "Външният редактор е наличен само в настолното приложение", + "externalEditorOpened": "Отворено във външен редактор. Запазването там ще качи промените обратно на сървъра.", + "failedToOpenExternalEditor": "Неуспешно отваряне на външен редактор", + "failedToSelectExternalEditor": "Избирането на външен редактор не бе успешно", + "permissions": "Разрешения", + "size": "Размер", + "modified": "Модифицирано", + "path": "Път", + "confirmDelete": "Сигурни ли сте, че искате да изтриете {{name}}?", + "permissionDenied": "Разрешението е отказано", + "serverError": "Грешка на сървъра", + "fileSavedSuccessfully": "Файлът е запазен успешно", + "failedToSaveFile": "Запазването на файла не бе успешно", + "confirmDeleteSingleItem": "Сигурни ли сте, че искате да изтриете за постоянно „{{name}}“?", + "confirmDeleteMultipleItems": "Сигурни ли сте, че искате да изтриете за постоянно {{count}} елементи?", + "confirmDeleteMultipleItemsWithFolders": "Наистина ли искате да изтриете за постоянно {{count}} елементи? Това включва папки и тяхното съдържание.", + "confirmDeleteFolder": "Сигурни ли сте, че искате да изтриете окончателно папката „{{name}}“ и цялото ѝ съдържание?", + "permanentDeleteWarning": "Това действие не може да бъде отменено. Елементът(ите) ще бъдат изтрити за постоянно от сървъра.", + "moveToTrashWarning": "Елементът(ите) ще бъдат преместени в кошчето на Termix и могат да бъдат възстановени преди изтичане на срока на съхранение.", + "itemsMovedToTrash": "{{count}} елемент(а) са преместени в кошчето", + "trash": "Кошче", + "trashDescription": "Изтритите елементи се запазват за {{days}} дни на този хост.", + "trashLoadFailed": "Зареждането на кошчето не бе успешно", + "trashEmpty": "Кошчето е празно", + "trashRestore": "Възстановяване", + "trashRestored": "Възстановено {{name}}", + "trashRestoreFailed": "Възстановяването на елемента не бе успешно. Възможно е оригиналният път вече да съществува.", + "trashDeletePermanently": "Изтрий за постоянно", + "trashDeleteConfirm": "Да се изтрие ли за постоянно {{name}}? Това не може да се отмени.", + "trashPermanentlyDeleted": "За постоянно изтрито {{name}}", + "trashDeleteFailed": "Неуспешно трайно изтриване на елемента", + "trashEmptyAction": "Изпразване на кошчето", + "trashEmptyConfirm": "Да се изтрие ли за постоянно всеки елемент в кошчето? Това не може да се отмени.", + "trashEmptied": "Кошчето е изпразнено", + "trashRetention": "Автоматично почистване след (дни)", + "trashRetentionSaved": "Запазването на кошчето е актуализирано", + "trashRetentionFailed": "Актуализирането на задържането на кошчето не бе успешно", + "trashUnavailableConfirm": "Този елемент не може да бъде преместен в кошчето, обикновено защото е на друга файлова система или изисква повишени права. Искате ли да го изтриете за постоянно вместо това?", + "recent": "Скорошни", + "pinned": "Закачено", + "folderShortcuts": "Преки пътища към папки", + "failedToReconnectSSH": "Неуспешно повторно свързване на SSH сесията", + "openTerminalHere": "Отворете терминала тук", + "run": "Бягане", + "openTerminalInFolder": "Отвори терминала в тази папка", + "openTerminalInFileLocation": "Отваряне на терминала на местоположението на файла", + "runningFile": "Бягане - {{file}}", + "onlyRunExecutableFiles": "Може да изпълнява само изпълними файлове", + "directories": "Директории", + "removedFromRecentFiles": "Премахнато е „{{name}}“ от последните файлове", + "removeFailed": "Премахването не бе успешно", + "unpinnedSuccessfully": "Откачено е успешно „{{name}}“", + "unpinFailed": "Откачването не бе успешно", + "removedShortcut": "Премахнат е пряк път „{{name}}“", + "removeShortcutFailed": "Премахването на пряк път не бе успешно", + "clearedAllRecentFiles": "Изчистени са всички скорошни файлове", + "clearFailed": "Изчистването не бе успешно", + "removeFromRecentFiles": "Премахване от скорошни файлове", + "clearAllRecentFiles": "Изчистване на всички скорошни файлове", + "unpinFile": "Откачи файл", + "removeShortcut": "Премахване на пряк път", + "pinFile": "Закачи файл", + "addToShortcuts": "Добавяне към преките пътища", + "pasteFailed": "Поставянето не бе успешно", + "noUndoableActions": "Няма отменими действия", + "undoCopySuccess": "Отменено копиране: Изтрити {{count}} копирани файлове", + "undoCopyFailedDelete": "Отмяната не бе успешна: Не можаха да бъдат изтрити копирани файлове", + "undoCopyFailedNoInfo": "Отмяната не бе успешна: Не можа да бъде намерена информация за копирания файл", + "undoMoveSuccess": "Отменена операция по преместване: {{count}} файлове са преместени обратно в оригиналното местоположение", + "undoMoveFailedMove": "Отмяната не бе успешна: Не можах да преместя файловете обратно", + "undoMoveFailedNoInfo": "Отмяната не бе успешна: Не можа да бъде намерена информация за преместения файл", + "undoDeleteNotSupported": "Операцията по изтриване не може да бъде отменена: Файловете са изтрити за постоянно от сървъра", + "undoTypeNotSupported": "Неподдържан тип операция за отмяна", + "undoOperationFailed": "Отмяната не бе успешна", + "unknownError": "Неизвестна грешка", + "confirm": "Потвърди", + "find": "Намерете...", + "replace": "Замяна", + "downloadInstead": "Изтеглете вместо това", + "keyboardShortcuts": "Клавишни комбинации", + "searchAndReplace": "Търсене и замяна", + "editing": "Редактиране", + "search": "Търсене", + "findNext": "Намери следващото", + "findPrevious": "Намери предишното", + "save": "Запазване", + "selectAll": "Избери всички", + "undo": "Отмяна", + "redo": "Преработи", + "moveLineUp": "Премести линията нагоре", + "moveLineDown": "Премести реда надолу", + "toggleComment": "Превключване на коментара", + "autoComplete": "Автоматично завършване", + "imageLoadError": "Зареждането на изображението не бе успешно", + "startTyping": "Започнете да пишете...", + "unknownSize": "Неизвестен размер", + "fileIsEmpty": "Файлът е празен", + "largeFileWarning": "Предупреждение за голям файл", + "largeFileWarningDesc": "Размерът на този файл е {{size}} , което може да причини проблеми с производителността, когато се отвори като текст.", + "fileNotFoundAndRemoved": "Файлът „{{name}}“ не е намерен и е премахнат от скорошни/закачени файлове", + "failedToLoadFile": "Неуспешно зареждане на файл: {{error}}", + "serverErrorOccurred": "Възникна грешка в сървъра. Моля, опитайте отново по-късно.", + "autoSaveFailed": "Автоматичното запазване не бе успешно", + "fileAutoSaved": "Файлът е автоматично запазен", + "moveFileFailed": "Преместването не бе успешно {{name}}", + "moveOperationFailed": "Операцията по преместване не бе успешна", + "canOnlyCompareFiles": "Може да се сравняват само два файла", + "comparingFiles": "Сравняване на файлове: {{file1}} и {{file2}}", + "dragFailed": "Плъзгането не беше успешно", + "filePinnedSuccessfully": "Файлът „{{name}}“ е успешно закачен", + "pinFileFailed": "Закрепването на файла не бе успешно", + "fileUnpinnedSuccessfully": "Файлът „{{name}}“ е откачен успешно", + "unpinFileFailed": "Откачването на файла не бе успешно", + "shortcutAddedSuccessfully": "Прякият път към папка „{{name}}“ е добавен успешно", + "addShortcutFailed": "Добавянето на пряк път не бе успешно", + "operationCompletedSuccessfully": "{{operation}} {{count}} елементи успешно", + "operationCompleted": "{{operation}} {{count}} артикули", + "downloadFileSuccess": "Файлът {{name}} е изтеглен успешно", + "downloadFileFailed": "Изтеглянето не бе успешно", + "downloadingFile": "Изтегляне {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Премести се в {{name}}", + "diffCompareWith": "Разлика в сравнение с {{name}}", + "dragOutsideToDownload": "Плъзнете извън прозореца, за да изтеглите ({{count}} файлове)", + "newFolderDefault": "НоваПапка", + "newFileDefault": "NewFile.txt", + "successfullyMovedItems": "Успешно преместени {{count}} елементи в {{target}}", + "move": "Преместване", + "searchInFile": "Търсене във файл (Ctrl+F)", + "showKeyboardShortcuts": "Показване на клавишни комбинации", + "decreaseFontSize": "Намаляване на размера на шрифта", + "increaseFontSize": "Увеличаване на размера на шрифта", + "startWritingMarkdown": "Започнете да пишете съдържанието си за markdown...", + "loadingFileComparison": "Зареждане на сравнението на файлове...", + "reload": "Презареждане", + "compare": "Сравни", + "sideBySide": "Рамо до рамо", + "inline": "Вграден", + "fileComparison": "Сравнение на файлове: {{file1}} срещу {{file2}}", + "fileTooLarge": "Файлът е твърде голям: {{error}}", + "sshConnectionFailed": "SSH връзката не бе успешна. Моля, проверете връзката си с {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Неуспешно зареждане на файл: {{error}}", + "connecting": "Свързване...", + "connectedSuccessfully": "Свързаването е осъществено успешно", + "totpVerificationFailed": "Проверката на TOTP не бе успешна", + "warpgateVerificationFailed": "Удостоверяването на Warpgate не беше успешно", + "authenticationFailed": "Удостоверяването не бе успешно", + "incorrectPassphrase": "Неправилна парола. Моля, опитайте отново.", + "verificationCodePrompt": "Код за потвърждение:", + "changePermissions": "Промяна на разрешенията", + "currentPermissions": "Текущи разрешения", + "owner": "Собственик", + "group": "Група", + "others": "Други", + "read": "Прочетете", + "write": "Пишете", + "execute": "Изпълнение", + "permissionsChangedSuccessfully": "Разрешенията са променени успешно", + "failedToChangePermissions": "Промяната на разрешенията не бе успешна", + "name": "Име", + "sortByName": "Име", + "sortByDate": "Дата на промяна", + "sortBySize": "Размер", + "ascending": "Възходящ", + "descending": "Низходящо", + "root": "Корен", + "new": "Ново", + "sortBy": "Сортиране по", + "items": "Елементи", + "selected": "Избрано", + "editor": "Редактор", + "octal": "Осмичен", + "storage": "Съхранение", + "disk": "Диск", + "used": "Използван", + "of": "от", + "toggleSidebar": "Превключване на страничната лента", + "compactLayout": "Използвайте компактно оформление", + "comfortableLayout": "Използвайте удобно оформление", + "cannotLoadPdf": "Не може да се зареди PDF файл", + "pdfLoadError": "Възникна грешка при зареждането на този PDF файл.", + "loadingPdf": "Зареждане на PDF файл...", + "loadingPage": "Зареждане на страницата..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Прехвърляне на файлове между два SSH хоста.", + "sourceServer": "Източник на сървъра", + "destinationServer": "Целеви сървър", + "selectHost": "Изберете хост", + "noHostSelected": "Няма избран хост", + "move": "Преместване", + "copy": "Копиране", + "upload": "Качване", + "itemsSelected": "{{count}} избран елемент", + "itemsSelected_other": "{{count}} избрани елементи", + "ready": "Готов", + "loadingHosts": "Зареждане на хостове...", + "name": "Име", + "modified": "Модифицирано", + "size": "Размер", + "noFiles": "Няма файлове", + "open": "Отворено", + "up": "Нагоре", + "refresh": "Опресняване", + "copyToTarget": "Копиране в целевата директория", + "rename": "Преименуване", + "delete": "Изтриване", + "editPermissions": "Редактиране на разрешения", + "createNewFolder": "Създаване на нова папка", + "deleteSelectedItems": "Изтриване на избраните елементи", + "deleteConfirm": "Да се изтрие ли {{count}} избран елемент?", + "deleteConfirm_other": "Да се изтрият ли {{count}} избрани елементи?", + "cancel": "Отказ", + "save": "Запазване", + "folderCreated": "Папката е създадена", + "itemRenamed": "Елементът е преименуван", + "actionFailed": "Действието е неуспешно", + "itemsDeleted": "{{count}} елементът е изтрит", + "itemsDeleted_other": "{{count}} елементи са изтрити", + "deleteFailed": "Изтриването не бе успешно", + "permissionsUpdated": "Разрешенията са актуализирани", + "noRemoteSessionConnected": "Няма свързана отдалечена сесия", + "failedToListFiles": "Неуспешно изброяване на файловете", + "failedToRefresh": "Опресняването не бе успешно", + "destinationInsideSource": "Дестинацията не може да бъде в избрания път на източника.", + "sameHostNoOp": "Източникът и местоназначението са една и съща папка." + }, + "hosts": { + "fileManager": "Файлов мениджър", + "tabFiles": "Файлове", + "enableFileManagerMonitor": "Активиране на файловия мениджър", + "enableFileManagerMonitorDesc": "Разглеждайте и управлявайте файлове на този хост през SFTP", + "scpLegacyLabel": "SCP Legacy режим", + "scpLegacyDesc": "Използвайте наследствен трансфер на файлове за сървъри, които не поддържат SFTP подсистемата (напр. вградени или минимални SSH сървъри).", + "defaultPathLabel": "Път по подразбиране", + "fileManagerPathHint": "Директорията, която да се отвори при стартиране на файловия мениджър за този хост.", + "fileManagerUrlCopied": "URL адресът на файловия мениджър е копиран", + "copyFileManagerUrlAction": "Копиране на URL адреса на файловия мениджър" + }, + "settings": { + "host": { + "enable": { + "label": "Активиране на файловия мениджър" + }, + "defaultPath": { + "label": "Път по подразбиране" + }, + "scpLegacy": { + "label": "SCP Legacy режим" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Файлове" + } +} diff --git a/plugins/file-manager/locales/translated/bn_BD.json b/plugins/file-manager/locales/translated/bn_BD.json new file mode 100644 index 000000000..fd169f6a5 --- /dev/null +++ b/plugins/file-manager/locales/translated/bn_BD.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "কোন হোস্ট নির্বাচন করা হয়নি", + "initializingEditor": "এডিটর চালু করা হচ্ছে...", + "file": "ফাইল", + "folder": "ফোল্ডার", + "uploadFile": "ফাইল আপলোড করুন", + "downloadFile": "ডাউনলোড করুন", + "extractArchive": "আর্কাইভ নিষ্কাশন করুন", + "extractingArchive": "{{name}} নিষ্কাশন করা হচ্ছে ...", + "archiveExtractedSuccessfully": "{{name}} সফলভাবে নিষ্কাশন করা হয়েছে", + "extractFailed": "নিষ্কাশন ব্যর্থ হয়েছে", + "compressFile": "ফাইল সংকুচিত করুন", + "compressFiles": "ফাইলগুলি সংকুচিত করুন", + "compressFilesDesc": "{{count}} টি আইটেমকে আর্কাইভে সংকুচিত করুন", + "archiveName": "আর্কাইভের নাম", + "enterArchiveName": "আর্কাইভের নাম লিখুন...", + "compressionFormat": "কম্প্রেশন ফরম্যাট", + "selectedFiles": "নির্বাচিত ফাইল", + "andMoreFiles": "এবং {{count}} আরও...", + "compress": "সংকুচিত করুন", + "compressingFiles": "{{count}} টি আইটেমকে {{name}} টি আইটেমে সংকুচিত করা হচ্ছে...", + "filesCompressedSuccessfully": "{{name}} সফলভাবে তৈরি হয়েছে", + "compressFailed": "সংকোচন ব্যর্থ হয়েছে", + "edit": "সম্পাদনা", + "preview": "প্রিভিউ", + "previous": "পূর্ববর্তী", + "next": "পরবর্তী", + "pageXOfY": "{{current}} এর {{total}} পৃষ্ঠা", + "zoomOut": "জুম আউট", + "zoomIn": "জুম ইন", + "newFile": "নতুন ফাইল", + "newFolder": "নতুন ফোল্ডার", + "rename": "পুনঃনামকরণ", + "uploading": "আপলোড হচ্ছে...", + "uploadingFile": "{{name}} আপলোড হচ্ছে ...", + "fileName": "ফাইলের নাম", + "folderName": "ফোল্ডারের নাম", + "fileUploadedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে আপলোড হয়েছে", + "failedToUploadFile": "ফাইল আপলোড করতে ব্যর্থ হয়েছে", + "uploadingFolderFiles": "{{count}} টি ফাইল আপলোড করা হচ্ছে...", + "uploadedFolderFiles": "{{count}} টি ফাইল সফলভাবে আপলোড করা হয়েছে", + "uploadedFolderPartial": "{{uploaded}} টি ফাইল আপলোড করা হয়েছে, {{failed}} টি ফাইল আপলোড ব্যর্থ হয়েছে", + "fileDownloadedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে ডাউনলোড হয়েছে", + "failedToDownloadFile": "ফাইল ডাউনলোড করতে ব্যর্থ হয়েছে", + "fileCreatedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে তৈরি হয়েছে", + "folderCreatedSuccessfully": "\"{{name}}\" ফোল্ডারটি সফলভাবে তৈরি করা হয়েছে", + "failedToCreateItem": "আইটেম তৈরি করতে ব্যর্থ হয়েছে", + "operationFailed": "{{operation}} অপারেশনটি {{name}}এর জন্য ব্যর্থ হয়েছে: {{error}}", + "failedToResolveSymlink": "সিমলিঙ্ক সমাধান করতে ব্যর্থ হয়েছে", + "itemsDeletedSuccessfully": "{{count}} আইটেম সফলভাবে মুছে ফেলা হয়েছে", + "failedToDeleteItems": "আইটেমগুলি মুছে ফেলতে ব্যর্থ হয়েছে", + "sudoPasswordRequired": "প্রশাসক পাসওয়ার্ড আবশ্যক", + "enterSudoPassword": "এই অপারেশনটি চালিয়ে যেতে sudo পাসওয়ার্ড দিন।", + "sudoPassword": "সুডো পাসওয়ার্ড", + "sudoOperationFailed": "Sudo অপারেশন ব্যর্থ হয়েছে", + "sudoAuthFailed": "Sudo প্রমাণীকরণ ব্যর্থ হয়েছে", + "dragFilesToUpload": "আপলোড করার জন্য ফাইলগুলো এখানে রাখুন।", + "dropToUploadHere": "এখানে আপলোড করতে ড্রপ করুন", + "dropToDownloadHere": "ডাউনলোড করতে এখানে ক্লিক করুন", + "localFiles": "স্থানীয়", + "showLocalFiles": "স্থানীয় ফাইলগুলি দেখান (বিভক্ত দৃশ্য)", + "hideLocalFiles": "স্থানীয় ফাইলগুলি লুকান", + "localHome": "হোম ফোল্ডার", + "localPathPlaceholder": "স্থানীয় ফোল্ডার পাথ", + "localShowHidden": "লুকানো ফাইলগুলি দেখান", + "localHideHidden": "লুকানো ফাইলগুলি লুকান", + "localRevealInFileManager": "সিস্টেম ফাইল ম্যানেজারে প্রকাশ করুন", + "localCannotOpenFolder": "ফোল্ডার খোলা যাচ্ছে না", + "localItemCount": "{{count}} আইটেম", + "localSelectedCount": "{{count}} নির্বাচিত", + "back": "ফিরে যান", + "forward": "ফরোয়ার্ড", + "up": "উপরে", + "kind": "ধরনের", + "localUploadingProgress": "{{total}} এর {{current}} আপলোড করা হচ্ছে", + "localDownloadingProgress": "{{total}} এর {{current}} ডাউনলোড হচ্ছে", + "localTransferInFlight": "{{count}} একবারে", + "localPreparingDownload": "ডাউনলোড প্রস্তুত হচ্ছে...", + "localTransferCancelled": "স্থানান্তর বাতিল করা হয়েছে", + "localNothingToTransfer": "স্থানান্তর করার কিছু নেই", + "localUploadComplete": "{{count}} টি ফাইল আপলোড করা হয়েছে", + "localUploadFailed": "আপলোড ব্যর্থ হয়েছে", + "localUploadPartial": "{{done}} টি ফাইল আপলোড করা হয়েছে, {{failed}} টি ফাইল আপলোড ব্যর্থ হয়েছে", + "localDownloadComplete": "{{count}} টি ফাইল ডাউনলোড করা হয়েছে", + "localDownloadFailed": "ডাউনলোড ব্যর্থ হয়েছে", + "localDownloadPartial": "{{done}} টি ফাইল ডাউনলোড করা হয়েছে, {{failed}} টি ফাইল ডাউনলোড ব্যর্থ হয়েছে", + "localReplaceExistingPrompt": "এই ফোল্ডারে {{count}} টি আইটেম ইতিমধ্যেই বিদ্যমান। এগুলো প্রতিস্থাপন করবেন?", + "localReplace": "প্রতিস্থাপন করুন", + "localSkipExisting": "এড়িয়ে যান", + "localSkippedExisting": "{{count}} টি বিদ্যমান আইটেম বাদ দেওয়া হয়েছে", + "localUnsafeNamesSkipped": "{{count}} টি আইটেম বাদ দেওয়া হয়েছে, যেগুলোর নাম এই কম্পিউটারে ব্যবহার করা যাবে না।", + "localOpen": "খোলা", + "localOpenFolder": "ফোল্ডার খুলুন", + "localUploadToRemote": "সার্ভারে আপলোড করুন", + "localUploadToRemoteMany": "সার্ভারে {{count}} টি আইটেম আপলোড করুন", + "localMoveToTrash": "ট্র্যাশে সরান", + "localMoveToTrashMany": "{{count}} টি আইটেম ট্র্যাশে সরান", + "localTrashConfirmSingle": "\"{{name}}\" ট্র্যাশে সরাবেন?", + "localTrashConfirmFolder": "\"{{name}}\" ফোল্ডারটি এবং এর সমস্ত বিষয়বস্তু ট্র্যাশে সরান?", + "localTrashConfirmMany": "{{count}} টি আইটেম ট্র্যাশে সরাবেন?", + "localTrashed": "ট্র্যাশে {{count}} টি আইটেম সরানো হয়েছে", + "localTrashFailed": "ট্র্যাশে সরানো যায়নি", + "localRenameFailed": "নাম পরিবর্তন ব্যর্থ হয়েছে", + "localCreateFailed": "আইটেমটি তৈরি করা যায়নি", + "goToParentFolder": "প্যারেন্ট ফোল্ডারে যান", + "resizeSidebar": "সাইডবারের আকার পরিবর্তন করতে ড্র্যাগ করুন (রিসেট করতে ডাবল-ক্লিক করুন)", + "resizeLocalPane": "স্থানীয় প্যানেলের আকার পরিবর্তন করতে ড্র্যাগ করুন (রিসেট করতে ডাবল-ক্লিক করুন)", + "columns": "কলাম", + "columnsHint": "কলাম বাছাই করতে ডান-ক্লিক করুন", + "emptyFolder": "এই ফোল্ডারটি খালি", + "searchFiles": "ফাইলগুলি অনুসন্ধান করুন...", + "upload": "আপলোড", + "selectHostToStart": "ফাইল ব্যবস্থাপনা শুরু করতে একটি হোস্ট নির্বাচন করুন", + "sshRequiredForFileManager": "ফাইল ম্যানেজারের জন্য SSH প্রয়োজন। এই হোস্টে SSH সক্রিয় করা নেই।", + "failedToConnect": "SSH-এর সাথে সংযোগ করতে ব্যর্থ হয়েছে", + "failedToLoadDirectory": "ডিরেক্টরি লোড করতে ব্যর্থ হয়েছে", + "noSSHConnection": "কোন SSH সংযোগ উপলব্ধ নেই", + "copy": "অনুলিপি", + "cut": "কাটা", + "paste": "পেস্ট", + "copyPath": "কপি পাথ", + "copyPaths": "কপি পাথ", + "delete": "মুছে ফেলুন", + "properties": "বৈশিষ্ট্য", + "refresh": "রিফ্রেশ", + "downloadFiles": "ব্রাউজারে {{count}} ফাইলগুলো ডাউনলোড করুন", + "copyFiles": "{{count}} আইটেম কপি করুন", + "cutFiles": "{{count}} টি আইটেম কাটুন", + "deleteFiles": "{{count}} টি আইটেম মুছুন", + "filesCopiedToClipboard": "{{count}} আইটেম ক্লিপবোর্ডে কপি করা হয়েছে", + "filesCutToClipboard": "{{count}} আইটেমগুলো ক্লিপবোর্ডে কাটা হয়েছে", + "pathCopiedToClipboard": "পাথটি ক্লিপবোর্ডে কপি করা হয়েছে", + "pathsCopiedToClipboard": "{{count}} পাথগুলো ক্লিপবোর্ডে কপি করা হয়েছে", + "failedToCopyPath": "ক্লিপবোর্ডে পাথ কপি করতে ব্যর্থ হয়েছে", + "copyFolderLink": "ফোল্ডারে লিঙ্ক কপি করুন", + "copyCurrentFolderLink": "বর্তমান ফোল্ডারে লিঙ্ক কপি করুন", + "folderLinkCopied": "ফোল্ডারের লিঙ্ক ক্লিপবোর্ডে কপি করা হয়েছে", + "failedToCopyFolderLink": "ফোল্ডার লিঙ্ক কপি করতে ব্যর্থ হয়েছে", + "movedItems": "{{count}} টি আইটেম সরানো হয়েছে", + "failedToDeleteItem": "আইটেমটি মুছে ফেলতে ব্যর্থ হয়েছে", + "itemRenamedSuccessfully": "{{type}} সফলভাবে নাম পরিবর্তন করা হয়েছে", + "failedToRenameItem": "আইটেমের নাম পরিবর্তন করতে ব্যর্থ হয়েছে", + "download": "ডাউনলোড করুন", + "openExternalEditor": "বাইরে খুলুন", + "chooseExternalEditor": "বাহ্যিক সম্পাদক নির্বাচন করুন", + "externalEditorSelected": "বাহ্যিক সম্পাদক নির্বাচিত", + "externalEditorDesktopOnly": "এক্সটার্নাল এডিটর শুধুমাত্র ডেস্কটপ অ্যাপে উপলব্ধ।", + "externalEditorOpened": "বাহ্যিক এডিটরে খোলা হয়েছে। সেখানে সেভ করলে পরিবর্তনগুলো সার্ভারে আপলোড হয়ে যাবে।", + "failedToOpenExternalEditor": "বাহ্যিক সম্পাদক খুলতে ব্যর্থ হয়েছে", + "failedToSelectExternalEditor": "বাহ্যিক সম্পাদক নির্বাচন করতে ব্যর্থ হয়েছে", + "permissions": "অনুমতি", + "size": "আকার", + "modified": "পরিবর্তিত", + "path": "পথ", + "confirmDelete": "আপনি কি {{name}} মুছে ফেলতে নিশ্চিত?", + "permissionDenied": "অনুমতি প্রত্যাখ্যান করা হয়েছে", + "serverError": "সার্ভার ত্রুটি", + "fileSavedSuccessfully": "ফাইল সফলভাবে সংরক্ষিত হয়েছে।", + "failedToSaveFile": "ফাইল সংরক্ষণ করতে ব্যর্থ হয়েছে", + "confirmDeleteSingleItem": "আপনি কি \"{{name}} \" স্থায়ীভাবে মুছে ফেলতে চান?", + "confirmDeleteMultipleItems": "আপনি কি {{count}} টি আইটেম স্থায়ীভাবে মুছে ফেলতে চান?", + "confirmDeleteMultipleItemsWithFolders": "আপনি কি {{count}} টি আইটেম স্থায়ীভাবে মুছে ফেলতে নিশ্চিত? এর মধ্যে ফোল্ডার এবং তার ভেতরের সবকিছু অন্তর্ভুক্ত।", + "confirmDeleteFolder": "আপনি কি \"{{name}}\" ফোল্ডারটি এবং এর ভেতরের সবকিছু স্থায়ীভাবে মুছে ফেলতে চান?", + "permanentDeleteWarning": "এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না। আইটেম(গুলি) সার্ভার থেকে স্থায়ীভাবে মুছে ফেলা হবে।", + "moveToTrashWarning": "আইটেমটি টারমিক্স ট্র্যাশে স্থানান্তরিত হবে এবং সংরক্ষণের সময়সীমা শেষ হওয়ার আগে তা পুনরুদ্ধার করা যাবে।", + "itemsMovedToTrash": "{{count}} আইটেম(গুলি) ট্র্যাশে সরানো হয়েছে", + "trash": "আবর্জনা", + "trashDescription": "এই হোস্টে মুছে ফেলা আইটেমগুলি {{days}} দিন পর্যন্ত সংরক্ষিত থাকে।", + "trashLoadFailed": "ট্র্যাশ লোড করতে ব্যর্থ হয়েছে", + "trashEmpty": "ময়লার ঝুড়ি খালি", + "trashRestore": "পুনরুদ্ধার করুন", + "trashRestored": "পুনরুদ্ধার করা হয়েছে {{name}}", + "trashRestoreFailed": "আইটেমটি পুনরুদ্ধার করা সম্ভব হয়নি। মূল পাথটি হয়তো ইতিমধ্যেই বিদ্যমান আছে।", + "trashDeletePermanently": "স্থায়ীভাবে মুছে ফেলুন", + "trashDeleteConfirm": "{{name}}স্থায়ীভাবে মুছে ফেলবেন? এটি আর পূর্বাবস্থায় ফেরানো যাবে না।", + "trashPermanentlyDeleted": "স্থায়ীভাবে মুছে ফেলা হয়েছে {{name}}", + "trashDeleteFailed": "আইটেমটি স্থায়ীভাবে মুছে ফেলতে ব্যর্থ হয়েছে", + "trashEmptyAction": "ময়লা খালি করুন", + "trashEmptyConfirm": "ট্র্যাশের প্রতিটি আইটেম স্থায়ীভাবে মুছে ফেলবেন? এটি আর পূর্বাবস্থায় ফেরানো যাবে না।", + "trashEmptied": "ময়লা খালি করা হয়েছে", + "trashRetention": "(দিন) পর স্বয়ংক্রিয়ভাবে পরিষ্কার করা হবে", + "trashRetentionSaved": "আবর্জনা সংরক্ষণের সময় আপডেট করা হয়েছে", + "trashRetentionFailed": "ট্র্যাশ রিটেনশন আপডেট করতে ব্যর্থ হয়েছে", + "trashUnavailableConfirm": "এই আইটেমটি ট্র্যাশে সরানো যাচ্ছে না, কারণ এটি সাধারণত অন্য ফাইলসিস্টেমে রয়েছে অথবা এর জন্য বিশেষ অনুমতির প্রয়োজন। এর পরিবর্তে এটি স্থায়ীভাবে মুছে ফেলবেন?", + "recent": "সাম্প্রতিক", + "pinned": "পিন করা", + "folderShortcuts": "ফোল্ডার শর্টকাট", + "failedToReconnectSSH": "SSH সেশন পুনরায় সংযোগ করতে ব্যর্থ হয়েছে", + "openTerminalHere": "এখানে টার্মিনাল খুলুন", + "run": "দৌড়", + "openTerminalInFolder": "এই ফোল্ডারে টার্মিনাল খুলুন", + "openTerminalInFileLocation": "ফাইল লোকেশনে টার্মিনাল খুলুন", + "runningFile": "দৌড়ানো - {{file}}", + "onlyRunExecutableFiles": "শুধুমাত্র এক্সিকিউটেবল ফাইল চালানো যাবে", + "directories": "ডিরেক্টরি", + "removedFromRecentFiles": "সাম্প্রতিক ফাইলগুলি থেকে \"{{name}}\" সরানো হয়েছে", + "removeFailed": "ব্যর্থ অপসারণ", + "unpinnedSuccessfully": "\"{{name}}\" সফলভাবে আনপিন করা হয়েছে", + "unpinFailed": "আনপিন ব্যর্থ হয়েছে", + "removedShortcut": "\"{{name}} \" শর্টকাটটি সরানো হয়েছে", + "removeShortcutFailed": "শর্টকাট অপসারণ ব্যর্থ হয়েছে", + "clearedAllRecentFiles": "সাম্প্রতিক সমস্ত ফাইল মুছে ফেলা হয়েছে", + "clearFailed": "পরিষ্কার করতে ব্যর্থ হয়েছে", + "removeFromRecentFiles": "সাম্প্রতিক ফাইলগুলি থেকে সরান", + "clearAllRecentFiles": "সাম্প্রতিক সমস্ত ফাইল মুছে ফেলুন", + "unpinFile": "ফাইলটি আনপিন করুন", + "removeShortcut": "শর্টকাট সরান", + "pinFile": "পিন ফাইল", + "addToShortcuts": "শর্টকাটে যোগ করুন", + "pasteFailed": "পেস্ট ব্যর্থ হয়েছে", + "noUndoableActions": "কোনো পূর্বাবস্থায় ফেরানো যায় না এমন কাজ নেই।", + "undoCopySuccess": "কপি অপারেশন বাতিল করা হয়েছে: {{count}} টি কপি করা ফাইল মুছে ফেলা হয়েছে", + "undoCopyFailedDelete": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: কপি করা কোনো ফাইল মুছে ফেলা সম্ভব হয়নি", + "undoCopyFailedNoInfo": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: কপি করা ফাইলের তথ্য খুঁজে পাওয়া যায়নি", + "undoMoveSuccess": "স্থানান্তর প্রক্রিয়া বাতিল করা হয়েছে: {{count}} টি ফাইল মূল অবস্থানে ফিরিয়ে আনা হয়েছে", + "undoMoveFailedMove": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: কোনো ফাইল আগের জায়গায় সরানো সম্ভব হয়নি", + "undoMoveFailedNoInfo": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: স্থানান্তরিত ফাইলের তথ্য খুঁজে পাওয়া যায়নি", + "undoDeleteNotSupported": "মুছে ফেলার প্রক্রিয়াটি পূর্বাবস্থায় ফেরানো যাবে না: ফাইলগুলো সার্ভার থেকে স্থায়ীভাবে মুছে ফেলা হয়েছে।", + "undoTypeNotSupported": "অসমর্থিত পূর্বাবস্থায় ফেরানোর অপারেশনের ধরণ", + "undoOperationFailed": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে", + "unknownError": "অজানা ত্রুটি", + "confirm": "নিশ্চিত করুন", + "find": "খুঁজুন...", + "replace": "প্রতিস্থাপন করুন", + "downloadInstead": "পরিবর্তে ডাউনলোড করুন", + "keyboardShortcuts": "কীবোর্ড শর্টকাট", + "searchAndReplace": "অনুসন্ধান ও প্রতিস্থাপন", + "editing": "সম্পাদনা", + "search": "অনুসন্ধান", + "findNext": "পরবর্তী খুঁজুন", + "findPrevious": "পূর্ববর্তী খুঁজুন", + "save": "সংরক্ষণ করুন", + "selectAll": "সব নির্বাচন করুন", + "undo": "পূর্বাবস্থায় ফেরান", + "redo": "পুনরায় করুন", + "moveLineUp": "লাইন আপ সরান", + "moveLineDown": "লাইন নিচে সরান", + "toggleComment": "মন্তব্য টগল করুন", + "autoComplete": "স্বয়ংক্রিয়ভাবে সম্পূর্ণ", + "imageLoadError": "ছবি লোড করতে ব্যর্থ হয়েছে", + "startTyping": "টাইপ করা শুরু করুন...", + "unknownSize": "অজানা আকার", + "fileIsEmpty": "ফাইলটি খালি", + "largeFileWarning": "বড় ফাইলের সতর্কতা", + "largeFileWarningDesc": "এই ফাইলটির আকার {{size}} , যা টেক্সট হিসেবে খুললে পারফরম্যান্সের সমস্যা সৃষ্টি করতে পারে।", + "fileNotFoundAndRemoved": "\"{{name}}\" ফাইলটি খুঁজে পাওয়া যায়নি এবং সাম্প্রতিক/পিন করা ফাইলগুলো থেকে সরিয়ে ফেলা হয়েছে।", + "failedToLoadFile": "ফাইল লোড করতে ব্যর্থ: {{error}}", + "serverErrorOccurred": "সার্ভার ত্রুটি ঘটেছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন।", + "autoSaveFailed": "স্বয়ংক্রিয় সংরক্ষণ ব্যর্থ হয়েছে", + "fileAutoSaved": "ফাইল স্বয়ংক্রিয়ভাবে সংরক্ষিত হয়েছে", + "moveFileFailed": "{{name}} সরাতে ব্যর্থ", + "moveOperationFailed": "স্থানান্তর প্রক্রিয়া ব্যর্থ হয়েছে", + "canOnlyCompareFiles": "শুধুমাত্র দুটি ফাইল তুলনা করা যাবে।", + "comparingFiles": "ফাইলগুলোর তুলনা: {{file1}} এবং {{file2}}", + "dragFailed": "ড্র্যাগ অপারেশন ব্যর্থ হয়েছে", + "filePinnedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে পিন করা হয়েছে", + "pinFileFailed": "ফাইল পিন করতে ব্যর্থ হয়েছে", + "fileUnpinnedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে আনপিন করা হয়েছে", + "unpinFileFailed": "ফাইল আনপিন করতে ব্যর্থ হয়েছে", + "shortcutAddedSuccessfully": "ফোল্ডার শর্টকাট \"{{name}}\" সফলভাবে যোগ করা হয়েছে", + "addShortcutFailed": "শর্টকাট যোগ করতে ব্যর্থ হয়েছে", + "operationCompletedSuccessfully": "{{operation}} {{count}} আইটেম সফলভাবে", + "operationCompleted": "{{operation}} {{count}} আইটেম", + "downloadFileSuccess": "ফাইল {{name}} সফলভাবে ডাউনলোড হয়েছে", + "downloadFileFailed": "ডাউনলোড ব্যর্থ হয়েছে", + "downloadingFile": "ডাউনলোড হচ্ছে {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "{{name}} এ যান", + "diffCompareWith": "{{name}} এর সাথে পার্থক্য তুলনা করুন", + "dragOutsideToDownload": "ডাউনলোড করতে উইন্ডোর বাইরে টেনে আনুন ({{count}} ফাইলগুলো)", + "newFolderDefault": "নতুন ফোল্ডার", + "newFileDefault": "NewFile.txt", + "successfullyMovedItems": "{{count}} টি আইটেম সফলভাবে {{target}} টি আইটেমে সরানো হয়েছে", + "move": "স্থানান্তর", + "searchInFile": "ফাইলে অনুসন্ধান করুন (Ctrl+F)", + "showKeyboardShortcuts": "কিবোর্ড শর্টকাট দেখান", + "decreaseFontSize": "ফন্টের আকার কমান", + "increaseFontSize": "ফন্টের আকার বাড়ান", + "startWritingMarkdown": "আপনার মার্কডাউন কন্টেন্ট লেখা শুরু করুন...", + "loadingFileComparison": "ফাইল তুলনা লোড হচ্ছে...", + "reload": "পুনরায় লোড করুন", + "compare": "তুলনা করুন", + "sideBySide": "পাশাপাশি", + "inline": "ইনলাইন", + "fileComparison": "ফাইল তুলনা: {{file1}} বনাম {{file2}}", + "fileTooLarge": "ফাইলের আকার অনেক বড়: {{error}}", + "sshConnectionFailed": "SSH সংযোগ ব্যর্থ হয়েছে। অনুগ্রহ করে {{name}} ({{ip}}:{{port}} ) -এ আপনার সংযোগ পরীক্ষা করুন।", + "loadFileFailed": "ফাইল লোড করতে ব্যর্থ: {{error}}", + "connecting": "সংযোগ স্থাপন...", + "connectedSuccessfully": "সফলভাবে সংযুক্ত হয়েছে", + "totpVerificationFailed": "TOTP যাচাইকরণ ব্যর্থ হয়েছে", + "warpgateVerificationFailed": "ওয়ার্পগেট প্রমাণীকরণ ব্যর্থ হয়েছে", + "authenticationFailed": "প্রমাণীকরণ ব্যর্থ হয়েছে", + "incorrectPassphrase": "ভুল পাসফ্রেজ। অনুগ্রহ করে আবার চেষ্টা করুন।", + "verificationCodePrompt": "যাচাইকরণ কোড:", + "changePermissions": "অনুমতি পরিবর্তন করুন", + "currentPermissions": "বর্তমান অনুমতি", + "owner": "মালিক", + "group": "গ্রুপ", + "others": "অন্যান্য", + "read": "পড়ুন", + "write": "লিখুন", + "execute": "কার্যকর করুন", + "permissionsChangedSuccessfully": "অনুমতি সফলভাবে পরিবর্তন করা হয়েছে", + "failedToChangePermissions": "অনুমতি পরিবর্তন করতে ব্যর্থ হয়েছে", + "name": "নাম", + "sortByName": "নাম", + "sortByDate": "পরিবর্তিত তারিখ", + "sortBySize": "আকার", + "ascending": "আরোহী", + "descending": "অবতরণ", + "root": "মূল", + "new": "নতুন", + "sortBy": "অনুসারে সাজান", + "items": "আইটেম", + "selected": "নির্বাচিত", + "editor": "সম্পাদক", + "octal": "অক্টাল", + "storage": "স্টোরেজ", + "disk": "ডিস্ক", + "used": "ব্যবহৃত", + "of": "এর", + "toggleSidebar": "সাইডবার টগল করুন", + "compactLayout": "সংক্ষিপ্ত বিন্যাস ব্যবহার করুন", + "comfortableLayout": "আরামদায়ক বিন্যাস ব্যবহার করুন", + "cannotLoadPdf": "পিডিএফ লোড করা যাচ্ছে না", + "pdfLoadError": "এই পিডিএফ ফাইলটি লোড করতে ত্রুটি হয়েছে।", + "loadingPdf": "পিডিএফ লোড হচ্ছে...", + "loadingPage": "পৃষ্ঠা লোড হচ্ছে..." + }, + "sftpTransfer": { + "title": "এসএফটিপি", + "description": "দুটি SSH হোস্টের মধ্যে ফাইল স্থানান্তর করুন।", + "sourceServer": "উৎস সার্ভার", + "destinationServer": "গন্তব্য সার্ভার", + "selectHost": "হোস্ট নির্বাচন করুন", + "noHostSelected": "কোন হোস্ট নির্বাচন করা হয়নি", + "move": "স্থানান্তর", + "copy": "অনুলিপি", + "upload": "আপলোড", + "itemsSelected": "{{count}} আইটেম নির্বাচিত", + "itemsSelected_other": "{{count}} নির্বাচিত আইটেম", + "ready": "প্রস্তুত", + "loadingHosts": "হোস্ট লোড হচ্ছে...", + "name": "নাম", + "modified": "পরিবর্তিত", + "size": "আকার", + "noFiles": "কোন ফাইল নেই", + "open": "খোলা", + "up": "উপরে", + "refresh": "রিফ্রেশ", + "copyToTarget": "লক্ষ্য ডিরেক্টরিতে অনুলিপি করুন", + "rename": "নাম পরিবর্তন", + "delete": "মুছে ফেলুন", + "editPermissions": "সম্পাদনার অনুমতি", + "createNewFolder": "নতুন ফোল্ডার তৈরি করুন", + "deleteSelectedItems": "নির্বাচিত আইটেমগুলি মুছুন", + "deleteConfirm": "{{count}} নির্বাচিত আইটেমটি মুছবেন?", + "deleteConfirm_other": "{{count}} নির্বাচিত আইটেমগুলি মুছবেন?", + "cancel": "বাতিল করুন", + "save": "সংরক্ষণ করুন", + "folderCreated": "ফোল্ডার তৈরি করা হয়েছে", + "itemRenamed": "আইটেমের নাম পরিবর্তন করা হয়েছে", + "actionFailed": "পদক্ষেপ ব্যর্থ হয়েছে", + "itemsDeleted": "{{count}} আইটেমটি মুছে ফেলা হয়েছে", + "itemsDeleted_other": "{{count}} আইটেম মুছে ফেলা হয়েছে", + "deleteFailed": "মুছে ফেলা ব্যর্থ হয়েছে", + "permissionsUpdated": "অনুমতি আপডেট করা হয়েছে", + "noRemoteSessionConnected": "কোন রিমোট সেশন সংযুক্ত নেই", + "failedToListFiles": "ফাইলগুলি তালিকাভুক্ত করতে ব্যর্থ হয়েছে", + "failedToRefresh": "রিফ্রেশ করতে ব্যর্থ হয়েছে", + "destinationInsideSource": "গন্তব্য নির্বাচিত উৎস পথের মধ্যে থাকতে পারবে না।", + "sameHostNoOp": "উৎস এবং গন্তব্য একই ফোল্ডার।" + }, + "hosts": { + "fileManager": "ফাইল ম্যানেজার", + "tabFiles": "ফাইল", + "enableFileManagerMonitor": "ফাইল ম্যানেজার সক্রিয় করুন", + "enableFileManagerMonitorDesc": "SFTP-এর মাধ্যমে এই হোস্টের ফাইলগুলি ব্রাউজ ও পরিচালনা করুন।", + "scpLegacyLabel": "SCP লিগ্যাসি মোড", + "scpLegacyDesc": "যেসব সার্ভার SFTP সাবসিস্টেম সমর্থন করে না (যেমন এমবেডেড বা মিনিমাল SSH সার্ভার), সেগুলোর জন্য লিগ্যাসি ফাইল ট্রান্সফার ব্যবহার করুন।", + "defaultPathLabel": "ডিফল্ট পথ", + "fileManagerPathHint": "এই হোস্টের জন্য ফাইল ম্যানেজার চালু হলে যে ডিরেক্টরিটি খুলবে।", + "fileManagerUrlCopied": "ফাইল ম্যানেজার ইউআরএল কপি করা হয়েছে", + "copyFileManagerUrlAction": "ফাইল ম্যানেজার ইউআরএল কপি করুন" + }, + "settings": { + "host": { + "enable": { + "label": "ফাইল ম্যানেজার সক্রিয় করুন" + }, + "defaultPath": { + "label": "ডিফল্ট পথ" + }, + "scpLegacy": { + "label": "SCP লিগ্যাসি মোড" + } + } + }, + "nav": { + "sftp": "এসএফটিপি", + "files": "ফাইল" + } +} diff --git a/plugins/file-manager/locales/translated/ca_ES.json b/plugins/file-manager/locales/translated/ca_ES.json new file mode 100644 index 000000000..229c95ebb --- /dev/null +++ b/plugins/file-manager/locales/translated/ca_ES.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "No s'ha seleccionat cap amfitrió", + "initializingEditor": "Inicialitzant l'editor...", + "file": "Fitxer", + "folder": "Carpeta", + "uploadFile": "Puja el fitxer", + "downloadFile": "Descarrega", + "extractArchive": "Extreure l'arxiu", + "extractingArchive": "Extraient {{name}}...", + "archiveExtractedSuccessfully": "{{name}} s'ha extret correctament", + "extractFailed": "L'extracció ha fallat", + "compressFile": "Comprimir fitxer", + "compressFiles": "Comprimir fitxers", + "compressFilesDesc": "Comprimir elements {{count}} en un arxiu", + "archiveName": "Nom de l'arxiu", + "enterArchiveName": "Introduïu el nom de l'arxiu...", + "compressionFormat": "Format de compressió", + "selectedFiles": "Fitxers seleccionats", + "andMoreFiles": "i {{count}} més...", + "compress": "Comprimir", + "compressingFiles": "Comprimint elements {{count}} en {{name}}...", + "filesCompressedSuccessfully": "{{name}} creat correctament", + "compressFailed": "La compressió ha fallat", + "edit": "Edita", + "preview": "Vista prèvia", + "previous": "Anterior", + "next": "Següent", + "pageXOfY": "Pàgina {{current}} de {{total}}", + "zoomOut": "Allunyar", + "zoomIn": "Ampliar zoom", + "newFile": "Fitxer nou", + "newFolder": "Nova carpeta", + "rename": "Canvia el nom", + "uploading": "S'està carregant...", + "uploadingFile": "S'està carregant {{name}}...", + "fileName": "Nom del fitxer", + "folderName": "Nom de la carpeta", + "fileUploadedSuccessfully": "El fitxer \"{{name}}\" s'ha carregat correctament", + "failedToUploadFile": "No s'ha pogut carregar el fitxer", + "uploadingFolderFiles": "S'estan pujant {{count}} fitxer(s)...", + "uploadedFolderFiles": "S'han carregat correctament els fitxers {{count}}", + "uploadedFolderPartial": "No s'ha pogut pujar el(s) fitxer(s) {{uploaded}} , {{failed}}", + "fileDownloadedSuccessfully": "Fitxer \"{{name}}\" descarregat correctament", + "failedToDownloadFile": "No s'ha pogut descarregar el fitxer", + "fileCreatedSuccessfully": "Fitxer \"{{name}}\" creat correctament", + "folderCreatedSuccessfully": "La carpeta \"{{name}}\" s'ha creat correctament", + "failedToCreateItem": "No s'ha pogut crear l'element", + "operationFailed": "L'operació {{operation}} ha fallat per a {{name}}: {{error}}", + "failedToResolveSymlink": "No s'ha pogut resoldre l'enllaç simbòlic", + "itemsDeletedSuccessfully": "{{count}} elements suprimits correctament", + "failedToDeleteItems": "No s'han pogut suprimir els elements", + "sudoPasswordRequired": "Contrasenya d'administrador requerida", + "enterSudoPassword": "Introduïu la contrasenya sudo per continuar amb aquesta operació", + "sudoPassword": "Contrasenya de Sudo", + "sudoOperationFailed": "L'operació Sudo ha fallat.", + "sudoAuthFailed": "L'autenticació de Sudo ha fallat.", + "dragFilesToUpload": "Deixa anar els fitxers aquí per pujar-los", + "dropToUploadHere": "Deixa anar per pujar aquí", + "dropToDownloadHere": "Deixa anar per descarregar aquí", + "localFiles": "Local", + "showLocalFiles": "Mostra els fitxers locals (vista dividida)", + "hideLocalFiles": "Amaga els fitxers locals", + "localHome": "Carpeta d'inici", + "localPathPlaceholder": "Ruta de la carpeta local", + "localShowHidden": "Mostra els fitxers ocults", + "localHideHidden": "Amaga els fitxers ocults", + "localRevealInFileManager": "Mostra al gestor de fitxers del sistema", + "localCannotOpenFolder": "No es pot obrir la carpeta", + "localItemCount": "{{count}} elements", + "localSelectedCount": "{{count}} seleccionat", + "back": "Enrere", + "forward": "Endavant", + "up": "Amunt", + "kind": "Amable", + "localUploadingProgress": "S'està carregant {{current}} de {{total}}", + "localDownloadingProgress": "Descarregant {{current}} de {{total}}", + "localTransferInFlight": "{{count}} alhora", + "localPreparingDownload": "Preparant la descàrrega...", + "localTransferCancelled": "Transferència cancel·lada", + "localNothingToTransfer": "Res a transferir", + "localUploadComplete": "Fitxer(s) {{count}} penjat(s)", + "localUploadFailed": "La pujada ha fallat", + "localUploadPartial": "No s'ha pogut pujar el(s) fitxer(s) {{done}} , {{failed}}", + "localDownloadComplete": "Fitxer(s) descarregat(s) {{count}}", + "localDownloadFailed": "La descàrrega ha fallat", + "localDownloadPartial": "No s'ha pogut descarregar el(s) fitxer(s) {{done}} , {{failed}}.", + "localReplaceExistingPrompt": "Ja existeixen elements {{count}} en aquesta carpeta. Voleu substituir-los?", + "localReplace": "Substitueix", + "localSkipExisting": "Omet", + "localSkippedExisting": "S'han omès {{count}} elements existents", + "localUnsafeNamesSkipped": "S'han omès {{count}} elements els noms dels quals no es poden utilitzar en aquest ordinador", + "localOpen": "Obre", + "localOpenFolder": "Obre la carpeta", + "localUploadToRemote": "Puja al servidor", + "localUploadToRemoteMany": "Puja {{count}} elements al servidor", + "localMoveToTrash": "Mou a la paperera", + "localMoveToTrashMany": "Mou {{count}} elements a la paperera", + "localTrashConfirmSingle": "Voleu moure \"{{name}}\" a la paperera?", + "localTrashConfirmFolder": "Voleu moure la carpeta \"{{name}}\" i tot el seu contingut a la paperera?", + "localTrashConfirmMany": "Voleu moure {{count}} elements a la paperera?", + "localTrashed": "S'han mogut {{count}} elements a la paperera.", + "localTrashFailed": "No s'ha pogut moure a la paperera", + "localRenameFailed": "No s'ha pogut canviar el nom", + "localCreateFailed": "No s'ha pogut crear l'element", + "goToParentFolder": "Anar a la carpeta principal", + "resizeSidebar": "Arrossegueu per canviar la mida de la barra lateral (feu doble clic per restablir-la)", + "resizeLocalPane": "Arrossegueu per canviar la mida del panell local (feu doble clic per restablir-lo)", + "columns": "Columnes", + "columnsHint": "Feu clic amb el botó dret per triar columnes", + "emptyFolder": "Aquesta carpeta està buida", + "searchFiles": "Cerca fitxers...", + "upload": "Pujada", + "selectHostToStart": "Seleccioneu un amfitrió per iniciar la gestió de fitxers", + "sshRequiredForFileManager": "El gestor de fitxers requereix SSH. Aquest amfitrió no té SSH habilitat.", + "failedToConnect": "No s'ha pogut connectar a SSH", + "failedToLoadDirectory": "No s'ha pogut carregar el directori", + "noSSHConnection": "No hi ha connexió SSH disponible", + "copy": "Còpia", + "cut": "Tallar", + "paste": "Enganxa", + "copyPath": "Copia la ruta", + "copyPaths": "Copiar camins", + "delete": "Suprimeix", + "properties": "Propietats", + "refresh": "Actualitza", + "downloadFiles": "Baixeu els fitxers {{count}} al navegador", + "copyFiles": "Copia elements {{count}}", + "cutFiles": "Retalla elements {{count}}", + "deleteFiles": "Suprimeix elements {{count}}", + "filesCopiedToClipboard": "{{count}} elements copiats al porta-retalls", + "filesCutToClipboard": "{{count}} elements retallats al porta-retalls", + "pathCopiedToClipboard": "La ruta s'ha copiat al porta-retalls", + "pathsCopiedToClipboard": "{{count}} camins copiats al porta-retalls", + "failedToCopyPath": "No s'ha pogut copiar la ruta al porta-retalls", + "copyFolderLink": "Copia l'enllaç a la carpeta", + "copyCurrentFolderLink": "Copia l'enllaç a la carpeta actual", + "folderLinkCopied": "Enllaç de carpeta copiat al porta-retalls", + "failedToCopyFolderLink": "No s'ha pogut copiar l'enllaç de la carpeta", + "movedItems": "Elements moguts {{count}}", + "failedToDeleteItem": "No s'ha pogut suprimir l'element", + "itemRenamedSuccessfully": "{{type}} s'ha canviat el nom correctament", + "failedToRenameItem": "No s'ha pogut canviar el nom de l'element", + "download": "Descarrega", + "openExternalEditor": "Obre externament", + "chooseExternalEditor": "Trieu un editor extern", + "externalEditorSelected": "Editor extern seleccionat", + "externalEditorDesktopOnly": "L'editor extern només està disponible a l'aplicació d'escriptori", + "externalEditorOpened": "Obert en un editor extern. Si deseu-ho, els canvis es tornaran a carregar al servidor.", + "failedToOpenExternalEditor": "No s'ha pogut obrir l'editor extern", + "failedToSelectExternalEditor": "No s'ha pogut seleccionar l'editor extern", + "permissions": "Permisos", + "size": "Mida", + "modified": "Modificat", + "path": "Camí", + "confirmDelete": "Esteu segur que voleu suprimir {{name}}?", + "permissionDenied": "Permís denegat", + "serverError": "Error del servidor", + "fileSavedSuccessfully": "Fitxer desat correctament", + "failedToSaveFile": "No s'ha pogut desar el fitxer", + "confirmDeleteSingleItem": "Esteu segur que voleu suprimir permanentment \"{{name}}\"?", + "confirmDeleteMultipleItems": "Esteu segur que voleu suprimir permanentment els elements {{count}}?", + "confirmDeleteMultipleItemsWithFolders": "Esteu segur que voleu suprimir permanentment els elements {{count}} ? Això inclou les carpetes i el seu contingut.", + "confirmDeleteFolder": "Esteu segur que voleu suprimir permanentment la carpeta \"{{name}}\" i tot el seu contingut?", + "permanentDeleteWarning": "Aquesta acció no es pot desfer. Els elements s'eliminaran permanentment del servidor.", + "moveToTrashWarning": "Els elements es mouran a la paperera de Termix i es podran restaurar abans que expiri el període de retenció.", + "itemsMovedToTrash": "{{count}} element(s) mogut(s) a la paperera", + "trash": "Escombraries", + "trashDescription": "Els elements suprimits es conserven durant {{days}} dies en aquest amfitrió.", + "trashLoadFailed": "No s'ha pogut carregar la paperera", + "trashEmpty": "La paperera és buida", + "trashRestore": "Restaurar", + "trashRestored": "Restaurat {{name}}", + "trashRestoreFailed": "No s'ha pogut restaurar l'element. És possible que la ruta original ja existeixi.", + "trashDeletePermanently": "Suprimeix permanentment", + "trashDeleteConfirm": "Voleu suprimir permanentment {{name}}? Això no es pot desfer.", + "trashPermanentlyDeleted": "Suprimit permanentment {{name}}", + "trashDeleteFailed": "No s'ha pogut suprimir l'element permanentment", + "trashEmptyAction": "Buida la paperera", + "trashEmptyConfirm": "Voleu suprimir permanentment tots els elements de la paperera? Això no es pot desfer.", + "trashEmptied": "Paperera buida", + "trashRetention": "Neteja automàtica després de (dies)", + "trashRetentionSaved": "Retenció de brossa actualitzada", + "trashRetentionFailed": "No s'ha pogut actualitzar la retenció de la paperera", + "trashUnavailableConfirm": "Aquest element no es pot moure a la paperera, normalment perquè es troba en un altre sistema de fitxers o requereix permisos elevats. Voleu suprimir-lo permanentment?", + "recent": "Recent", + "pinned": "Fixat", + "folderShortcuts": "Dreceres de carpeta", + "failedToReconnectSSH": "No s'ha pogut reconnectar la sessió SSH", + "openTerminalHere": "Obre el terminal aquí", + "run": "Corre", + "openTerminalInFolder": "Obre el terminal en aquesta carpeta", + "openTerminalInFileLocation": "Obre el terminal a la ubicació del fitxer", + "runningFile": "Córrer - {{file}}", + "onlyRunExecutableFiles": "Només pot executar fitxers executables", + "directories": "Directoris", + "removedFromRecentFiles": "S'ha eliminat \"{{name}}\" dels fitxers recents", + "removeFailed": "No s'ha pogut eliminar l'opció.", + "unpinnedSuccessfully": "S'ha desfixat \"{{name}}\" correctament", + "unpinFailed": "No s'ha pogut desancorar la fixació.", + "removedShortcut": "S'ha eliminat la drecera \"{{name}}\"", + "removeShortcutFailed": "No s'ha pogut eliminar la drecera.", + "clearedAllRecentFiles": "S'han esborrat tots els fitxers recents", + "clearFailed": "Error en esborrar", + "removeFromRecentFiles": "Elimina dels fitxers recents", + "clearAllRecentFiles": "Esborra tots els fitxers recents", + "unpinFile": "Desfixar fitxer", + "removeShortcut": "Elimina la drecera", + "pinFile": "Fitxer fixat", + "addToShortcuts": "Afegeix a les dreceres", + "pasteFailed": "No s'ha pogut enganxar", + "noUndoableActions": "Sense accions desfaibles", + "undoCopySuccess": "Operació de còpia desfeta: S'han suprimit {{count}} fitxers copiats", + "undoCopyFailedDelete": "Error en desfer: no s'han pogut suprimir els fitxers copiats.", + "undoCopyFailedNoInfo": "Error en desfer: no s'ha pogut trobar la informació del fitxer copiat", + "undoMoveSuccess": "Operació de moviment desfeta: S'han tornat a moure els fitxers {{count}} a la ubicació original.", + "undoMoveFailedMove": "Desfer ha fallat: no s'han pogut moure cap fitxer enrere", + "undoMoveFailedNoInfo": "Desfer ha fallat: no s'ha pogut trobar la informació del fitxer mogut", + "undoDeleteNotSupported": "L'operació de supressió no es pot desfer: els fitxers s'han suprimit permanentment del servidor.", + "undoTypeNotSupported": "Tipus d'operació de desfer no compatible", + "undoOperationFailed": "L'operació de desfer ha fallat", + "unknownError": "Error desconegut", + "confirm": "Confirma", + "find": "Troba...", + "replace": "Substitueix", + "downloadInstead": "Descarrega en comptes d'això", + "keyboardShortcuts": "Dreceres de teclat", + "searchAndReplace": "Cerca i substitueix", + "editing": "Edició", + "search": "Cerca", + "findNext": "Cerca el següent", + "findPrevious": "Cerca anterior", + "save": "Desa", + "selectAll": "Selecciona-ho tot", + "undo": "Desfer", + "redo": "Refer", + "moveLineUp": "Mou la línia cap amunt", + "moveLineDown": "Mou la línia cap avall", + "toggleComment": "Activa/desactiva el comentari", + "autoComplete": "Completar automàticament", + "imageLoadError": "No s'ha pogut carregar la imatge", + "startTyping": "Comença a escriure...", + "unknownSize": "Mida desconeguda", + "fileIsEmpty": "El fitxer està buit", + "largeFileWarning": "Avís de fitxer gran", + "largeFileWarningDesc": "Aquest fitxer té una mida de {{size}} , cosa que pot causar problemes de rendiment quan s'obre com a text.", + "fileNotFoundAndRemoved": "No s'ha trobat el fitxer \"{{name}}\" i s'ha eliminat dels fitxers recents/fixats.", + "failedToLoadFile": "No s'ha pogut carregar el fitxer: {{error}}", + "serverErrorOccurred": "S'ha produït un error del servidor. Torna-ho a provar més tard.", + "autoSaveFailed": "El desament automàtic ha fallat", + "fileAutoSaved": "Fitxer desat automàticament", + "moveFileFailed": "No s'ha pogut moure {{name}}", + "moveOperationFailed": "L'operació de moviment ha fallat", + "canOnlyCompareFiles": "Només es poden comparar dos fitxers", + "comparingFiles": "Comparació de fitxers: {{file1}} i {{file2}}", + "dragFailed": "L'operació d'arrossegament ha fallat", + "filePinnedSuccessfully": "Fitxer \"{{name}}\" fixat correctament", + "pinFileFailed": "No s'ha pogut fixar el fitxer", + "fileUnpinnedSuccessfully": "El fitxer \"{{name}}\" s'ha desanclat correctament", + "unpinFileFailed": "No s'ha pogut desancorar el fitxer", + "shortcutAddedSuccessfully": "Drecera de carpeta \"{{name}}\" afegida correctament", + "addShortcutFailed": "No s'ha pogut afegir la drecera", + "operationCompletedSuccessfully": "{{operation}} {{count}} elements correctament", + "operationCompleted": "{{operation}} {{count}} elements", + "downloadFileSuccess": "Fitxer {{name}} descarregat correctament", + "downloadFileFailed": "La descàrrega ha fallat", + "downloadingFile": "Descarregant {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Mou a {{name}}", + "diffCompareWith": "Comparació de diferències amb {{name}}", + "dragOutsideToDownload": "Arrossega fora de la finestra per descarregar (fitxers{{count}})", + "newFolderDefault": "NovaCarpeta", + "newFileDefault": "NouFitxer.txt", + "successfullyMovedItems": "S'han mogut correctament {{count}} elements a {{target}}", + "move": "Moure", + "searchInFile": "Cerca en un fitxer (Ctrl+F)", + "showKeyboardShortcuts": "Mostra les dreceres de teclat", + "decreaseFontSize": "Reduir la mida de la lletra", + "increaseFontSize": "Augmenta la mida de la lletra", + "startWritingMarkdown": "Comença a escriure el teu contingut de rebaixes...", + "loadingFileComparison": "S'està carregant la comparació de fitxers...", + "reload": "Torna a carregar", + "compare": "Comparar", + "sideBySide": "Costat al costat", + "inline": "En línia", + "fileComparison": "Comparació de fitxers: {{file1}} vs {{file2}}", + "fileTooLarge": "Fitxer massa gran: {{error}}", + "sshConnectionFailed": "La connexió SSH ha fallat. Si us plau, comproveu la connexió a {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "No s'ha pogut carregar el fitxer: {{error}}", + "connecting": "Connectant...", + "connectedSuccessfully": "Connexió correcta", + "totpVerificationFailed": "La verificació TOTP ha fallat", + "warpgateVerificationFailed": "L'autenticació de Warpgate ha fallat", + "authenticationFailed": "L'autenticació ha fallat", + "incorrectPassphrase": "Contrasenya incorrecta. Si us plau, torneu-ho a intentar.", + "verificationCodePrompt": "Codi de verificació:", + "changePermissions": "Canvia els permisos", + "currentPermissions": "Permisos actuals", + "owner": "propietari", + "group": "Grup", + "others": "Altres", + "read": "Llegir", + "write": "Escriure", + "execute": "Executar", + "permissionsChangedSuccessfully": "Els permisos s'han canviat correctament", + "failedToChangePermissions": "No s'han pogut canviar els permisos", + "name": "Nom", + "sortByName": "Nom", + "sortByDate": "Data de modificació", + "sortBySize": "Mida", + "ascending": "Ascendent", + "descending": "Descendent", + "root": "Arrel", + "new": "Nou", + "sortBy": "Ordena per", + "items": "Articles", + "selected": "Seleccionat", + "editor": "Editor", + "octal": "Octal", + "storage": "Emmagatzematge", + "disk": "Disc", + "used": "Usat", + "of": "de", + "toggleSidebar": "Obre/tanca la barra lateral", + "compactLayout": "Utilitza un disseny compacte", + "comfortableLayout": "Utilitza un disseny còmode", + "cannotLoadPdf": "No es pot carregar el PDF", + "pdfLoadError": "S'ha produït un error en carregar aquest fitxer PDF.", + "loadingPdf": "S'està carregant el PDF...", + "loadingPage": "S'està carregant la pàgina..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Transferir fitxers entre dos hosts SSH.", + "sourceServer": "Servidor d'origen", + "destinationServer": "Servidor de destinació", + "selectHost": "Selecciona l'amfitrió", + "noHostSelected": "No s'ha seleccionat cap amfitrió", + "move": "Moure", + "copy": "Còpia", + "upload": "Pujada", + "itemsSelected": "{{count}} element seleccionat", + "itemsSelected_other": "{{count}} elements seleccionats", + "ready": "Llest", + "loadingHosts": "S'estan carregant els amfitrions...", + "name": "Nom", + "modified": "Modificat", + "size": "Mida", + "noFiles": "Sense fitxers", + "open": "Obre", + "up": "Amunt", + "refresh": "Actualitza", + "copyToTarget": "Copia al directori de destinació", + "rename": "Canvia el nom", + "delete": "Suprimeix", + "editPermissions": "Edita els permisos", + "createNewFolder": "Crea una carpeta nova", + "deleteSelectedItems": "Suprimeix els elements seleccionats", + "deleteConfirm": "Voleu suprimir l'element seleccionat {{count}}?", + "deleteConfirm_other": "Voleu suprimir els elements seleccionats {{count}}?", + "cancel": "Cancel·la", + "save": "Desa", + "folderCreated": "Carpeta creada", + "itemRenamed": "L'element s'ha rebatejat", + "actionFailed": "L'acció ha fallat", + "itemsDeleted": "{{count}} element suprimit", + "itemsDeleted_other": "{{count}} elements suprimits", + "deleteFailed": "No s'ha pogut eliminar l'opció.", + "permissionsUpdated": "Permisos actualitzats", + "noRemoteSessionConnected": "No hi ha cap sessió remota connectada", + "failedToListFiles": "No s'han pogut llistar els fitxers", + "failedToRefresh": "No s'ha pogut actualitzar", + "destinationInsideSource": "La destinació no pot ser dins de la ruta d'origen seleccionada.", + "sameHostNoOp": "L'origen i la destinació són la mateixa carpeta." + }, + "hosts": { + "fileManager": "Gestor de fitxers", + "tabFiles": "Fitxers", + "enableFileManagerMonitor": "Habilita el gestor de fitxers", + "enableFileManagerMonitorDesc": "Navega i gestiona fitxers en aquest amfitrió mitjançant SFTP", + "scpLegacyLabel": "Mode Llegat de SCP", + "scpLegacyDesc": "Feu servir la transferència de fitxers antiga per a servidors que no admeten el subsistema SFTP (per exemple, servidors SSH integrats o mínims).", + "defaultPathLabel": "Camí predeterminat", + "fileManagerPathHint": "El directori que s'obrirà quan s'iniciï el gestor de fitxers per a aquest amfitrió.", + "fileManagerUrlCopied": "S'ha copiat l'URL del gestor de fitxers", + "copyFileManagerUrlAction": "Copia l'URL del gestor de fitxers" + }, + "settings": { + "host": { + "enable": { + "label": "Habilita el gestor de fitxers" + }, + "defaultPath": { + "label": "Camí predeterminat" + }, + "scpLegacy": { + "label": "Mode Llegat de SCP" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Fitxers" + } +} diff --git a/plugins/file-manager/locales/translated/cs_CZ.json b/plugins/file-manager/locales/translated/cs_CZ.json new file mode 100644 index 000000000..a6ae45cbd --- /dev/null +++ b/plugins/file-manager/locales/translated/cs_CZ.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Nebyl vybrán žádný hostitel", + "initializingEditor": "Inicializace editoru...", + "file": "Soubor", + "folder": "Složka", + "uploadFile": "Nahrát soubor", + "downloadFile": "Stáhnout", + "extractArchive": "Extrahovat archiv", + "extractingArchive": "Extrahování {{name}}...", + "archiveExtractedSuccessfully": "{{name}} byl úspěšně extrahován", + "extractFailed": "Extrakce se nezdařila", + "compressFile": "Komprimovat soubor", + "compressFiles": "Komprimovat soubory", + "compressFilesDesc": "Komprimovat {{count}} položek do archivu", + "archiveName": "Název archivu", + "enterArchiveName": "Zadejte název archivu...", + "compressionFormat": "Kompresní formát", + "selectedFiles": "Vybrané soubory", + "andMoreFiles": "a {{count}} dalších...", + "compress": "Komprimovat", + "compressingFiles": "Komprimace {{count}} položek do {{name}}...", + "filesCompressedSuccessfully": "{{name}} úspěšně vytvořeno", + "compressFailed": "Komprese selhala", + "edit": "Upravit", + "preview": "Náhled", + "previous": "Předchozí", + "next": "Další", + "pageXOfY": "Strana {{current}} z {{total}}", + "zoomOut": "Oddálit", + "zoomIn": "Přiblížit", + "newFile": "Nový soubor", + "newFolder": "Nová složka", + "rename": "Přejmenovat", + "uploading": "Nahrávání...", + "uploadingFile": "Nahrávání {{name}}...", + "fileName": "Název souboru", + "folderName": "Název složky", + "fileUploadedSuccessfully": "Soubor „{{name}}“ byl úspěšně nahrán", + "failedToUploadFile": "Nahrávání souboru se nezdařilo", + "uploadingFolderFiles": "Nahrávání {{count}} souborů...", + "uploadedFolderFiles": "Úspěšně nahráno {{count}} souborů", + "uploadedFolderPartial": "Nahrání souboru (souborů) {{uploaded}} se nezdařilo. {{failed}}", + "fileDownloadedSuccessfully": "Soubor „{{name}}“ byl úspěšně stažen", + "failedToDownloadFile": "Stažení souboru se nezdařilo", + "fileCreatedSuccessfully": "Soubor „{{name}}“ byl úspěšně vytvořen", + "folderCreatedSuccessfully": "Složka „{{name}}“ byla úspěšně vytvořena", + "failedToCreateItem": "Vytvoření položky se nezdařilo", + "operationFailed": "Operace {{operation}} se nezdařila pro {{name}}: {{error}}", + "failedToResolveSymlink": "Nepodařilo se vyřešit symbolický odkaz", + "itemsDeletedSuccessfully": "{{count}} položek bylo úspěšně smazáno", + "failedToDeleteItems": "Položky se nepodařilo smazat", + "sudoPasswordRequired": "Vyžadováno heslo správce", + "enterSudoPassword": "Pro pokračování v této operaci zadejte heslo sudo.", + "sudoPassword": "Heslo Sudo", + "sudoOperationFailed": "Operace Sudo selhala", + "sudoAuthFailed": "Ověření Sudo selhalo", + "dragFilesToUpload": "Přetáhněte sem soubory k nahrání", + "dropToUploadHere": "Přetáhněte sem pro nahrání", + "dropToDownloadHere": "Přetáhněte pro stažení sem", + "localFiles": "Místní", + "showLocalFiles": "Zobrazit lokální soubory (rozdělené zobrazení)", + "hideLocalFiles": "Skrýt lokální soubory", + "localHome": "Domovská složka", + "localPathPlaceholder": "Cesta k místní složce", + "localShowHidden": "Zobrazit skryté soubory", + "localHideHidden": "Skrýt skryté soubory", + "localRevealInFileManager": "Zobrazit ve správci systémových souborů", + "localCannotOpenFolder": "Nelze otevřít složku", + "localItemCount": "{{count}} položek", + "localSelectedCount": "{{count}} vybráno", + "back": "Zadní", + "forward": "Vpřed", + "up": "Nahoru", + "kind": "Druh", + "localUploadingProgress": "Nahrávání {{current}} z {{total}}", + "localDownloadingProgress": "Stahování {{current}} z {{total}}", + "localTransferInFlight": "{{count}} najednou", + "localPreparingDownload": "Příprava stahování...", + "localTransferCancelled": "Převod zrušen", + "localNothingToTransfer": "Nic k převodu", + "localUploadComplete": "Nahráno {{count}} souborů", + "localUploadFailed": "Nahrávání se nezdařilo", + "localUploadPartial": "Nahrání souboru (souborů) {{done}} se nezdařilo. {{failed}}", + "localDownloadComplete": "Staženo {{count}} souborů", + "localDownloadFailed": "Stahování se nezdařilo", + "localDownloadPartial": "Stažení souboru (souborů) {{done}} se nezdařilo. {{failed}}", + "localReplaceExistingPrompt": "V této složce již existuje {{count}} položek. Nahradit je?", + "localReplace": "Nahradit", + "localSkipExisting": "Přeskočit", + "localSkippedExisting": "Přeskočeno {{count}} existujících položek", + "localUnsafeNamesSkipped": "Přeskočeno {{count}} položek, jejichž názvy nelze na tomto počítači použít", + "localOpen": "OTEVŘENO", + "localOpenFolder": "Otevřít složku", + "localUploadToRemote": "Nahrát na server", + "localUploadToRemoteMany": "Nahrát {{count}} položek na server", + "localMoveToTrash": "Přesunout do koše", + "localMoveToTrashMany": "Přesunout {{count}} položek do koše", + "localTrashConfirmSingle": "Přesunout „{{name}}“ do koše?", + "localTrashConfirmFolder": "Přesunout složku „{{name}}“ a veškerý její obsah do koše?", + "localTrashConfirmMany": "Přesunout {{count}} položek do koše?", + "localTrashed": "Přesunuto {{count}} položek do koše", + "localTrashFailed": "Nepodařilo se přesunout do koše", + "localRenameFailed": "Přejmenování se nezdařilo", + "localCreateFailed": "Nepodařilo se vytvořit položku", + "goToParentFolder": "Přejít do nadřazené složky", + "resizeSidebar": "Přetažením změníte velikost postranního panelu (dvojitým kliknutím resetujete)", + "resizeLocalPane": "Přetažením změníte velikost lokálního panelu (dvojitým kliknutím resetujete).", + "columns": "Sloupce", + "columnsHint": "Kliknutím pravým tlačítkem myši vyberete sloupce", + "emptyFolder": "Tato složka je prázdná", + "searchFiles": "Hledat soubory...", + "upload": "Nahrát", + "selectHostToStart": "Vyberte hostitele pro spuštění správy souborů", + "sshRequiredForFileManager": "Správce souborů vyžaduje SSH. Tento hostitel nemá SSH povoleno.", + "failedToConnect": "Nepodařilo se připojit k SSH", + "failedToLoadDirectory": "Nepodařilo se načíst adresář", + "noSSHConnection": "Není k dispozici žádné SSH připojení", + "copy": "Kopie", + "cut": "Střih", + "paste": "Pasta", + "copyPath": "Kopírovat cestu", + "copyPaths": "Kopírovat cesty", + "delete": "Vymazat", + "properties": "Nemovitosti", + "refresh": "Obnovit", + "downloadFiles": "Stáhnout {{count}} souborů do prohlížeče", + "copyFiles": "Kopírovat {{count}} položek", + "cutFiles": "Vyjmout {{count}} položek", + "deleteFiles": "Smazat {{count}} položek", + "filesCopiedToClipboard": "{{count}} položek zkopírováno do schránky", + "filesCutToClipboard": "{{count}} položek vyjmutých do schránky", + "pathCopiedToClipboard": "Cesta zkopírována do schránky", + "pathsCopiedToClipboard": "{{count}} cest zkopírováno do schránky", + "failedToCopyPath": "Nepodařilo se zkopírovat cestu do schránky", + "copyFolderLink": "Kopírovat odkaz do složky", + "copyCurrentFolderLink": "Kopírovat odkaz do aktuální složky", + "folderLinkCopied": "Odkaz na složku byl zkopírován do schránky", + "failedToCopyFolderLink": "Nepodařilo se zkopírovat odkaz na složku", + "movedItems": "Přesunuto {{count}} položek", + "failedToDeleteItem": "Položku se nepodařilo smazat", + "itemRenamedSuccessfully": "{{type}} byl úspěšně přejmenován", + "failedToRenameItem": "Přejmenování položky se nezdařilo", + "download": "Stáhnout", + "openExternalEditor": "Otevřít externě", + "chooseExternalEditor": "Vyberte externího editora", + "externalEditorSelected": "Vybrán externí editor", + "externalEditorDesktopOnly": "Externí editor je k dispozici pouze v desktopové aplikaci", + "externalEditorOpened": "Otevřeno v externím editoru. Uložením tam se změny nahrají zpět na server.", + "failedToOpenExternalEditor": "Nepodařilo se otevřít externí editor", + "failedToSelectExternalEditor": "Nepodařilo se vybrat externí editor", + "permissions": "Oprávnění", + "size": "Velikost", + "modified": "Upraveno", + "path": "Cesta", + "confirmDelete": "Opravdu chcete smazat {{name}}?", + "permissionDenied": "Oprávnění zamítnuto", + "serverError": "Chyba serveru", + "fileSavedSuccessfully": "Soubor byl úspěšně uložen", + "failedToSaveFile": "Uložení souboru se nezdařilo", + "confirmDeleteSingleItem": "Opravdu chcete trvale smazat „{{name}}“?", + "confirmDeleteMultipleItems": "Opravdu chcete trvale smazat {{count}} položek?", + "confirmDeleteMultipleItemsWithFolders": "Opravdu chcete trvale smazat {{count}} položek? To zahrnuje i složky a jejich obsah.", + "confirmDeleteFolder": "Opravdu chcete trvale smazat složku „{{name}}“ a veškerý její obsah?", + "permanentDeleteWarning": "Tuto akci nelze vrátit zpět. Položka(y) bude(budou) trvale smazána(y) ze serveru.", + "moveToTrashWarning": "Položka(y) bude(budou) přesunuty(y) do koše Termix a bude(budou) možné je obnovit před uplynutím doby uchování.", + "itemsMovedToTrash": "{{count}} položek přesunutých do koše", + "trash": "Odpadky", + "trashDescription": "Smazané položky jsou na tomto hostiteli uchovávány po dobu {{days}} dní.", + "trashLoadFailed": "Načtení koše se nezdařilo", + "trashEmpty": "Koš je prázdný", + "trashRestore": "Obnovit", + "trashRestored": "Obnoveno {{name}}", + "trashRestoreFailed": "Obnovení položky se nezdařilo. Původní cesta již pravděpodobně existuje.", + "trashDeletePermanently": "Trvale smazat", + "trashDeleteConfirm": "Trvale smazat {{name}}? Tuto akci nelze vrátit zpět.", + "trashPermanentlyDeleted": "Trvale smazáno {{name}}", + "trashDeleteFailed": "Trvalé smazání položky se nezdařilo", + "trashEmptyAction": "Vysypat koš", + "trashEmptyConfirm": "Trvale smazat všechny položky v koši? Tuto akci nelze vrátit zpět.", + "trashEmptied": "Koš vyprázdněn", + "trashRetention": "Automatické čištění po (dnech)", + "trashRetentionSaved": "Uchovávání koše aktualizováno", + "trashRetentionFailed": "Aktualizace uchovávání v koši se nepodařilo", + "trashUnavailableConfirm": "Tuto položku nelze přesunout do koše, obvykle proto, že se nachází v jiném souborovém systému nebo vyžaduje zvýšená oprávnění. Chcete ji místo toho trvale smazat?", + "recent": "Nedávné", + "pinned": "Připnuto", + "folderShortcuts": "Zkratky složek", + "failedToReconnectSSH": "Nepodařilo se znovu připojit relaci SSH", + "openTerminalHere": "Otevřít terminál zde", + "run": "Běh", + "openTerminalInFolder": "Otevřít terminál v této složce", + "openTerminalInFileLocation": "Otevřít terminál v umístění souboru", + "runningFile": "Běh - {{file}}", + "onlyRunExecutableFiles": "Lze spustit pouze spustitelné soubory", + "directories": "Adresáře", + "removedFromRecentFiles": "Z posledních souborů odstraněno „{{name}}“", + "removeFailed": "Odebrání se nezdařilo", + "unpinnedSuccessfully": "Úspěšně odepnuto „{{name}}“", + "unpinFailed": "Odepnout se nepodařilo", + "removedShortcut": "Odstraněna zkratka „{{name}}“", + "removeShortcutFailed": "Odebrání zástupce se nezdařilo", + "clearedAllRecentFiles": "Vymazány všechny nedávné soubory", + "clearFailed": "Vymazání se nezdařilo", + "removeFromRecentFiles": "Odebrat z posledních souborů", + "clearAllRecentFiles": "Vymazat všechny nedávné soubory", + "unpinFile": "Odepnout soubor", + "removeShortcut": "Odebrat zkratku", + "pinFile": "Připnout soubor", + "addToShortcuts": "Přidat do zkratek", + "pasteFailed": "Vložení se nezdařilo", + "noUndoableActions": "Žádné akce, které lze vrátit zpět", + "undoCopySuccess": "Zrušená operace kopírování: Smazáno {{count}} zkopírovaných souborů", + "undoCopyFailedDelete": "Vrácení se nezdařilo: Nepodařilo se smazat žádné zkopírované soubory.", + "undoCopyFailedNoInfo": "Vrácení se nezdařilo: Nelze najít informace o zkopírovaném souboru.", + "undoMoveSuccess": "Zrušená operace přesunutí: {{count}} souborů bylo přesunuto zpět do původního umístění.", + "undoMoveFailedMove": "Vrácení se nezdařilo: Nepodařilo se přesunout žádné soubory zpět.", + "undoMoveFailedNoInfo": "Vrácení se nezdařilo: Nelze najít informace o přesunutém souboru.", + "undoDeleteNotSupported": "Operaci smazání nelze vrátit zpět: Soubory byly trvale smazány ze serveru.", + "undoTypeNotSupported": "Nepodporovaný typ operace vrácení zpět", + "undoOperationFailed": "Operace vrácení se nezdařila", + "unknownError": "Neznámá chyba", + "confirm": "Potvrdit", + "find": "Nalézt...", + "replace": "Nahradit", + "downloadInstead": "Stáhnout místo toho", + "keyboardShortcuts": "Klávesové zkratky", + "searchAndReplace": "Hledat a nahradit", + "editing": "Editace", + "search": "Vyhledávání", + "findNext": "Najít další", + "findPrevious": "Najít předchozí", + "save": "Uložit", + "selectAll": "Vybrat vše", + "undo": "Zpět", + "redo": "Předělat", + "moveLineUp": "Posunout seřadit nahoru", + "moveLineDown": "Posunout řádek dolů", + "toggleComment": "Přepnout komentář", + "autoComplete": "Automatické dokončení", + "imageLoadError": "Nepodařilo se načíst obrázek", + "startTyping": "Začněte psát...", + "unknownSize": "Neznámá velikost", + "fileIsEmpty": "Soubor je prázdný", + "largeFileWarning": "Varování před velkým souborem", + "largeFileWarningDesc": "Velikost tohoto souboru je {{size}} , což může při otevření jako text způsobit problémy s výkonem.", + "fileNotFoundAndRemoved": "Soubor „{{name}}“ nebyl nalezen a byl odstraněn z nedávných/připnutých souborů.", + "failedToLoadFile": "Nepodařilo se načíst soubor: {{error}}", + "serverErrorOccurred": "Došlo k chybě serveru. Zkuste to prosím znovu později.", + "autoSaveFailed": "Automatické ukládání se nezdařilo", + "fileAutoSaved": "Soubor se automaticky ukládá", + "moveFileFailed": "Přesun se nezdařilo {{name}}", + "moveOperationFailed": "Operace přesunu se nezdařila", + "canOnlyCompareFiles": "Lze porovnat pouze dva soubory", + "comparingFiles": "Porovnání souborů: {{file1}} a {{file2}}", + "dragFailed": "Operace přetažení se nezdařila", + "filePinnedSuccessfully": "Soubor „{{name}}“ byl úspěšně připnut", + "pinFileFailed": "Připnutí souboru se nezdařilo", + "fileUnpinnedSuccessfully": "Soubor „{{name}}“ byl úspěšně odepnut", + "unpinFileFailed": "Nepodařilo se odepnout soubor", + "shortcutAddedSuccessfully": "Zástupce složky „{{name}}“ byl úspěšně přidán", + "addShortcutFailed": "Přidání zástupce se nezdařilo", + "operationCompletedSuccessfully": "{{operation}} {{count}} položek úspěšně", + "operationCompleted": "{{operation}} {{count}} položek", + "downloadFileSuccess": "Soubor {{name}} byl úspěšně stažen", + "downloadFileFailed": "Stahování se nezdařilo", + "downloadingFile": "Stahování {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Přesunout na {{name}}", + "diffCompareWith": "Rozdíl ve srovnání s {{name}}", + "dragOutsideToDownload": "Přetáhněte mimo okno pro stažení ({{count}} souborů)", + "newFolderDefault": "Nová složka", + "newFileDefault": "NovýSoubor.txt", + "successfullyMovedItems": "Položky {{count}} byly úspěšně přesunuty do {{target}}", + "move": "Pohyb", + "searchInFile": "Hledat v souboru (Ctrl+F)", + "showKeyboardShortcuts": "Zobrazit klávesové zkratky", + "decreaseFontSize": "Zmenšit velikost písma", + "increaseFontSize": "Zvětšit velikost písma", + "startWritingMarkdown": "Začněte psát svůj obsah v Markdownu...", + "loadingFileComparison": "Načítání porovnání souborů...", + "reload": "Znovu načíst", + "compare": "Porovnat", + "sideBySide": "Bok po boku", + "inline": "Vložený", + "fileComparison": "Porovnání souborů: {{file1}} vs. {{file2}}", + "fileTooLarge": "Soubor je příliš velký: {{error}}", + "sshConnectionFailed": "SSH připojení selhalo. Zkontrolujte prosím připojení k {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Nepodařilo se načíst soubor: {{error}}", + "connecting": "Spojovací...", + "connectedSuccessfully": "Úspěšné připojení", + "totpVerificationFailed": "Ověření TOTP se nezdařilo", + "warpgateVerificationFailed": "Ověření Warpgate selhalo", + "authenticationFailed": "Ověření se nezdařilo", + "incorrectPassphrase": "Nesprávné heslo. Zkuste to prosím znovu.", + "verificationCodePrompt": "Ověřovací kód:", + "changePermissions": "Změnit oprávnění", + "currentPermissions": "Aktuální oprávnění", + "owner": "Majitel", + "group": "Skupina", + "others": "Ostatní", + "read": "Číst", + "write": "Napsat", + "execute": "Vykonat", + "permissionsChangedSuccessfully": "Oprávnění byla úspěšně změněna", + "failedToChangePermissions": "Nepodařilo se změnit oprávnění", + "name": "Jméno", + "sortByName": "Jméno", + "sortByDate": "Datum úpravy", + "sortBySize": "Velikost", + "ascending": "Vzestupně", + "descending": "Klesající", + "root": "Vykořenit", + "new": "Nový", + "sortBy": "Seřadit podle", + "items": "Položky", + "selected": "Vybraný", + "editor": "Editor", + "octal": "Osmičkové", + "storage": "Skladování", + "disk": "Disk", + "used": "Použitý", + "of": "z", + "toggleSidebar": "Přepnout postranní panel", + "compactLayout": "Použít kompaktní rozvržení", + "comfortableLayout": "Použijte pohodlné rozvržení", + "cannotLoadPdf": "Nelze načíst PDF", + "pdfLoadError": "Při načítání tohoto PDF souboru došlo k chybě.", + "loadingPdf": "Načítání PDF...", + "loadingPage": "Načítání stránky..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Přenos souborů mezi dvěma SSH hostiteli.", + "sourceServer": "Zdrojový server", + "destinationServer": "Cílový server", + "selectHost": "Vyberte hostitele", + "noHostSelected": "Nebyl vybrán žádný hostitel", + "move": "Pohyb", + "copy": "Kopie", + "upload": "Nahrát", + "itemsSelected": "Vybrána položka {{count}}", + "itemsSelected_other": "Vybráno {{count}} položek", + "ready": "Připraveno", + "loadingHosts": "Načítání hostitelů...", + "name": "Jméno", + "modified": "Upraveno", + "size": "Velikost", + "noFiles": "Žádné soubory", + "open": "OTEVŘENO", + "up": "Nahoru", + "refresh": "Obnovit", + "copyToTarget": "Kopírovat do cílového adresáře", + "rename": "Přejmenovat", + "delete": "Vymazat", + "editPermissions": "Upravit oprávnění", + "createNewFolder": "Vytvořit novou složku", + "deleteSelectedItems": "Smazat vybrané položky", + "deleteConfirm": "Smazat {{count}} vybranou položku?", + "deleteConfirm_other": "Smazat {{count}} vybraných položek?", + "cancel": "Zrušit", + "save": "Uložit", + "folderCreated": "Složka vytvořena", + "itemRenamed": "Položka přejmenována", + "actionFailed": "Akce se nezdařila", + "itemsDeleted": "{{count}} položka smazána", + "itemsDeleted_other": "{{count}} položek smazáno", + "deleteFailed": "Smazání se nezdařilo", + "permissionsUpdated": "Oprávnění aktualizována", + "noRemoteSessionConnected": "Není připojena žádná vzdálená relace", + "failedToListFiles": "Seznam souborů se nepodařilo zobrazit", + "failedToRefresh": "Obnovení se nezdařilo", + "destinationInsideSource": "Cíl nemůže být uvnitř vybrané zdrojové cesty.", + "sameHostNoOp": "Zdroj a cíl jsou stejná složka." + }, + "hosts": { + "fileManager": "Správce souborů", + "tabFiles": "Soubory", + "enableFileManagerMonitor": "Povolit Správce souborů", + "enableFileManagerMonitorDesc": "Procházení a správa souborů na tomto hostiteli přes SFTP", + "scpLegacyLabel": "Starší režim SCP", + "scpLegacyDesc": "Pro servery, které nepodporují subsystém SFTP (např. vestavěné nebo minimální SSH servery), použijte starší přenos souborů.", + "defaultPathLabel": "Výchozí cesta", + "fileManagerPathHint": "Adresář, který se má otevřít při spuštění správce souborů pro tento hostitel.", + "fileManagerUrlCopied": "URL adresa Správce souborů zkopírována", + "copyFileManagerUrlAction": "Kopírovat URL Správce souborů" + }, + "settings": { + "host": { + "enable": { + "label": "Povolit Správce souborů" + }, + "defaultPath": { + "label": "Výchozí cesta" + }, + "scpLegacy": { + "label": "Starší režim SCP" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Soubory" + } +} diff --git a/plugins/file-manager/locales/translated/da_DK.json b/plugins/file-manager/locales/translated/da_DK.json new file mode 100644 index 000000000..d1d436168 --- /dev/null +++ b/plugins/file-manager/locales/translated/da_DK.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Ingen vært valgt", + "initializingEditor": "Initialiserer editor...", + "file": "Fil", + "folder": "Folder", + "uploadFile": "Upload fil", + "downloadFile": "Download", + "extractArchive": "Uddrag af arkivet", + "extractingArchive": "Udtrækker {{name}}...", + "archiveExtractedSuccessfully": "{{name}} blev udpakket", + "extractFailed": "Udpakning mislykkedes", + "compressFile": "Komprimer fil", + "compressFiles": "Komprimer filer", + "compressFilesDesc": "Komprimer {{count}} elementer til et arkiv", + "archiveName": "Arkivnavn", + "enterArchiveName": "Indtast arkivnavn...", + "compressionFormat": "Kompressionsformat", + "selectedFiles": "Valgte filer", + "andMoreFiles": "og {{count}} mere...", + "compress": "Sammenpresse", + "compressingFiles": "Komprimerer {{count}} elementer til {{name}}...", + "filesCompressedSuccessfully": "{{name}} er oprettet", + "compressFailed": "Komprimering mislykkedes", + "edit": "Redigere", + "preview": "Forhåndsvisning", + "previous": "Tidligere", + "next": "Næste", + "pageXOfY": "Side {{current}} af {{total}}", + "zoomOut": "Zoom ud", + "zoomIn": "Zoom ind", + "newFile": "Ny fil", + "newFolder": "Ny mappe", + "rename": "Omdøb", + "uploading": "Uploader...", + "uploadingFile": "Uploader {{name}}...", + "fileName": "Filnavn", + "folderName": "Mappenavn", + "fileUploadedSuccessfully": "Filen \"{{name}}\" er blevet uploadet", + "failedToUploadFile": "Kunne ikke uploade filen", + "uploadingFolderFiles": "Uploader {{count}} fil(er)...", + "uploadedFolderFiles": "Uploadede {{count}} fil(er)", + "uploadedFolderPartial": "Upload af {{uploaded}} fil(er), {{failed}} mislykkedes", + "fileDownloadedSuccessfully": "Filen \"{{name}}\" er blevet downloadet", + "failedToDownloadFile": "Kunne ikke downloade filen", + "fileCreatedSuccessfully": "Filen \"{{name}}\" er blevet oprettet", + "folderCreatedSuccessfully": "Mappen \"{{name}}\" er blevet oprettet", + "failedToCreateItem": "Kunne ikke oprette elementet", + "operationFailed": "{{operation}} handling mislykkedes for {{name}}: {{error}}", + "failedToResolveSymlink": "Kunne ikke løse symbolsk link", + "itemsDeletedSuccessfully": "{{count}} elementer er blevet slettet", + "failedToDeleteItems": "Kunne ikke slette elementer", + "sudoPasswordRequired": "Administratoradgangskode påkrævet", + "enterSudoPassword": "Indtast sudo-adgangskoden for at fortsætte denne handling", + "sudoPassword": "Sudo-adgangskode", + "sudoOperationFailed": "Sudo-handlingen mislykkedes", + "sudoAuthFailed": "Sudo-godkendelse mislykkedes", + "dragFilesToUpload": "Slip filer her for at uploade", + "dropToUploadHere": "Slip for at uploade her", + "dropToDownloadHere": "Slip for at downloade her", + "localFiles": "Lokal", + "showLocalFiles": "Vis lokale filer (delt visning)", + "hideLocalFiles": "Skjul lokale filer", + "localHome": "Hjemmemappe", + "localPathPlaceholder": "Lokal mappesti", + "localShowHidden": "Vis skjulte filer", + "localHideHidden": "Skjul skjulte filer", + "localRevealInFileManager": "Vis i systemfilhåndtering", + "localCannotOpenFolder": "Kan ikke åbne mappen", + "localItemCount": "{{count}} elementer", + "localSelectedCount": "{{count}} valgt", + "back": "Tilbage", + "forward": "Forward", + "up": "Op", + "kind": "Slags", + "localUploadingProgress": "Uploader {{current}} af {{total}}", + "localDownloadingProgress": "Downloader {{current}} af {{total}}", + "localTransferInFlight": "{{count}} på én gang", + "localPreparingDownload": "Forbereder download...", + "localTransferCancelled": "Overførsel annulleret", + "localNothingToTransfer": "Intet at overføre", + "localUploadComplete": "Uploadede {{count}} fil(er)", + "localUploadFailed": "Upload mislykkedes", + "localUploadPartial": "Upload af {{done}} fil(er), {{failed}} mislykkedes", + "localDownloadComplete": "Downloadede {{count}} fil(er)", + "localDownloadFailed": "Download mislykkedes", + "localDownloadPartial": "Download af {{done}} fil(er), {{failed}} mislykkedes", + "localReplaceExistingPrompt": "{{count}} elementer findes allerede i denne mappe. Skal de erstattes?", + "localReplace": "Erstatte", + "localSkipExisting": "Springe", + "localSkippedExisting": "Springede {{count}} eksisterende element(er) over", + "localUnsafeNamesSkipped": "Springede {{count}} elementer over, hvis navne ikke kan bruges på denne computer", + "localOpen": "Åben", + "localOpenFolder": "Åbn mappe", + "localUploadToRemote": "Upload til server", + "localUploadToRemoteMany": "Upload {{count}} elementer til serveren", + "localMoveToTrash": "Flyt til papirkurven", + "localMoveToTrashMany": "Flyt {{count}} elementer til papirkurven", + "localTrashConfirmSingle": "Flytte \"{{name}}\" til papirkurven?", + "localTrashConfirmFolder": "Flytte mappen \"{{name}}\" og alt dens indhold til papirkurven?", + "localTrashConfirmMany": "Flytte {{count}} elementer til papirkurven?", + "localTrashed": "Flyttede {{count}} element(er) til papirkurven", + "localTrashFailed": "Kunne ikke flytte til papirkurven", + "localRenameFailed": "Omdøbning mislykkedes", + "localCreateFailed": "Kunne ikke oprette elementet", + "goToParentFolder": "Gå til overordnet mappe", + "resizeSidebar": "Træk for at ændre størrelsen på sidebjælken (dobbeltklik for at nulstille)", + "resizeLocalPane": "Træk for at ændre størrelsen på den lokale rude (dobbeltklik for at nulstille)", + "columns": "Kolonner", + "columnsHint": "Højreklik for at vælge kolonner", + "emptyFolder": "Denne mappe er tom", + "searchFiles": "Søg i filer...", + "upload": "Upload", + "selectHostToStart": "Vælg en vært for at starte filhåndtering", + "sshRequiredForFileManager": "Filhåndtering kræver SSH. Denne vært har ikke SSH aktiveret.", + "failedToConnect": "Kunne ikke oprette forbindelse til SSH", + "failedToLoadDirectory": "Kunne ikke indlæse mappen", + "noSSHConnection": "Ingen SSH-forbindelse tilgængelig", + "copy": "Kopi", + "cut": "Skære", + "paste": "Indsæt", + "copyPath": "Kopiér sti", + "copyPaths": "Kopier stier", + "delete": "Slet", + "properties": "Ejendomme", + "refresh": "Opfriske", + "downloadFiles": "Download {{count}} filer til browseren", + "copyFiles": "Kopiér {{count}} elementer", + "cutFiles": "Klip {{count}} elementer", + "deleteFiles": "Slet {{count}} elementer", + "filesCopiedToClipboard": "{{count}} elementer kopieret til udklipsholderen", + "filesCutToClipboard": "{{count}} elementer klippet til udklipsholder", + "pathCopiedToClipboard": "Sti kopieret til udklipsholder", + "pathsCopiedToClipboard": "{{count}} stier kopieret til udklipsholder", + "failedToCopyPath": "Kunne ikke kopiere stien til udklipsholderen", + "copyFolderLink": "Kopiér link til mappe", + "copyCurrentFolderLink": "Kopiér link til aktuel mappe", + "folderLinkCopied": "Mappelink kopieret til udklipsholder", + "failedToCopyFolderLink": "Kunne ikke kopiere mappelinket", + "movedItems": "Flyttede {{count}} elementer", + "failedToDeleteItem": "Kunne ikke slette elementet", + "itemRenamedSuccessfully": "{{type}} er omdøbt", + "failedToRenameItem": "Kunne ikke omdøbe elementet", + "download": "Download", + "openExternalEditor": "Åbn eksternt", + "chooseExternalEditor": "Vælg ekstern editor", + "externalEditorSelected": "Ekstern editor valgt", + "externalEditorDesktopOnly": "Ekstern editor er kun tilgængelig i desktop-appen", + "externalEditorOpened": "Åbnet i ekstern editor. Hvis du gemmer der, uploades ændringerne tilbage til serveren.", + "failedToOpenExternalEditor": "Kunne ikke åbne den eksterne editor", + "failedToSelectExternalEditor": "Kunne ikke vælge ekstern editor", + "permissions": "Tilladelser", + "size": "Størrelse", + "modified": "Ændret", + "path": "Sti", + "confirmDelete": "Er du sikker på, at du vil slette {{name}}?", + "permissionDenied": "Tilladelse nægtet", + "serverError": "Serverfejl", + "fileSavedSuccessfully": "Filen er gemt", + "failedToSaveFile": "Kunne ikke gemme filen", + "confirmDeleteSingleItem": "Er du sikker på, at du vil slette \"{{name}} \" permanent?", + "confirmDeleteMultipleItems": "Er du sikker på, at du vil slette {{count}} elementer permanent?", + "confirmDeleteMultipleItemsWithFolders": "Er du sikker på, at du vil slette {{count}} elementer permanent? Dette inkluderer mapper og deres indhold.", + "confirmDeleteFolder": "Er du sikker på, at du vil slette mappen \"{{name}}\" og alt dens indhold permanent?", + "permanentDeleteWarning": "Denne handling kan ikke fortrydes. Elementet/elementerne vil blive permanent slettet fra serveren.", + "moveToTrashWarning": "Elementet/elementerne vil blive flyttet til Termix-papirkurven og kan gendannes, inden opbevaringsperioden udløber.", + "itemsMovedToTrash": "{{count}} element(er) flyttet til papirkurven", + "trash": "Skrald", + "trashDescription": "Slettede elementer gemmes i {{days}} dag(e) på denne vært.", + "trashLoadFailed": "Kunne ikke indlæse papirkurven", + "trashEmpty": "Papirkurven er tom", + "trashRestore": "Gendan", + "trashRestored": "Gendannet {{name}}", + "trashRestoreFailed": "Elementet kunne ikke gendannes. Den oprindelige sti findes muligvis allerede.", + "trashDeletePermanently": "Slet permanent", + "trashDeleteConfirm": "Vil du slette {{name}}permanent? Dette kan ikke fortrydes.", + "trashPermanentlyDeleted": "Permanent slettet {{name}}", + "trashDeleteFailed": "Kunne ikke slette elementet permanent", + "trashEmptyAction": "Tøm papirkurven", + "trashEmptyConfirm": "Vil du slette alle elementer i papirkurven permanent? Dette kan ikke fortrydes.", + "trashEmptied": "Skraldespanden tømt", + "trashRetention": "Automatisk oprydning efter (dage)", + "trashRetentionSaved": "Papirkurvsopbevaring opdateret", + "trashRetentionFailed": "Kunne ikke opdatere opbevaring af papirkurven", + "trashUnavailableConfirm": "Dette element kan ikke flyttes til papirkurven, normalt fordi det er på et andet filsystem eller kræver forhøjede tilladelser. Slet det permanent i stedet?", + "recent": "Nylig", + "pinned": "Fastgjort", + "folderShortcuts": "Mappegenveje", + "failedToReconnectSSH": "Kunne ikke genoprette forbindelsen til SSH-sessionen", + "openTerminalHere": "Åbn terminalen her", + "run": "Løbe", + "openTerminalInFolder": "Åbn Terminal i denne mappe", + "openTerminalInFileLocation": "Åbn terminalen på filplaceringen", + "runningFile": "Løb - {{file}}", + "onlyRunExecutableFiles": "Kan kun køre eksekverbare filer", + "directories": "Kataloger", + "removedFromRecentFiles": "Fjernede \"{{name}}\" fra seneste filer", + "removeFailed": "Fjernelse mislykkedes", + "unpinnedSuccessfully": "\"{{name}}\" blev frigjort", + "unpinFailed": "Frigørelse mislykkedes", + "removedShortcut": "Fjernede genvejen \"{{name}}\"", + "removeShortcutFailed": "Fjernelse af genvej mislykkedes", + "clearedAllRecentFiles": "Rydde alle seneste filer", + "clearFailed": "Rydning mislykkedes", + "removeFromRecentFiles": "Fjern fra seneste filer", + "clearAllRecentFiles": "Ryd alle seneste filer", + "unpinFile": "Frigør fil", + "removeShortcut": "Fjern genvej", + "pinFile": "Fastgør fil", + "addToShortcuts": "Føj til genveje", + "pasteFailed": "Indsætning mislykkedes", + "noUndoableActions": "Ingen handlinger, der kan fortrydes", + "undoCopySuccess": "Fortrydt kopiering: Slettede {{count}} kopierede filer", + "undoCopyFailedDelete": "Fortryd mislykkedes: Kunne ikke slette nogen kopierede filer", + "undoCopyFailedNoInfo": "Fortryd mislykkedes: Kunne ikke finde kopierede filoplysninger", + "undoMoveSuccess": "Fortrydt flyttehandling: Flyttede {{count}} filer tilbage til den oprindelige placering", + "undoMoveFailedMove": "Fortryd mislykkedes: Kunne ikke flytte nogen filer tilbage", + "undoMoveFailedNoInfo": "Fortryd mislykkedes: Kunne ikke finde oplysninger om den flyttede fil", + "undoDeleteNotSupported": "Sletningen kan ikke fortrydes: Filerne er blevet permanent slettet fra serveren", + "undoTypeNotSupported": "Ikke-understøttet fortrydelseshandlingstype", + "undoOperationFailed": "Fortrydelseshandling mislykkedes", + "unknownError": "Ukendt fejl", + "confirm": "Bekræfte", + "find": "Finde...", + "replace": "Erstatte", + "downloadInstead": "Download i stedet", + "keyboardShortcuts": "Tastaturgenveje", + "searchAndReplace": "Søg og erstat", + "editing": "Redigering", + "search": "Søge", + "findNext": "Find næste", + "findPrevious": "Find forrige", + "save": "Spare", + "selectAll": "Vælg alle", + "undo": "Fortryd", + "redo": "Gentag", + "moveLineUp": "Flyt linje op", + "moveLineDown": "Flyt linje ned", + "toggleComment": "Slå kommentar til/fra", + "autoComplete": "Autofuldførelse", + "imageLoadError": "Billedet kunne ikke indlæses", + "startTyping": "Begynd at skrive...", + "unknownSize": "Ukendt størrelse", + "fileIsEmpty": "Filen er tom", + "largeFileWarning": "Advarsel om stor fil", + "largeFileWarningDesc": "Denne fil er {{size}} stor, hvilket kan forårsage problemer med ydeevnen, når den åbnes som tekst.", + "fileNotFoundAndRemoved": "Filen \"{{name}}\" blev ikke fundet og er blevet fjernet fra seneste/fastgjorte filer", + "failedToLoadFile": "Kunne ikke indlæse filen: {{error}}", + "serverErrorOccurred": "Der opstod en serverfejl. Prøv igen senere.", + "autoSaveFailed": "Automatisk lagring mislykkedes", + "fileAutoSaved": "Fil gemt automatisk", + "moveFileFailed": "Kunne ikke flytte {{name}}", + "moveOperationFailed": "Flytteoperationen mislykkedes", + "canOnlyCompareFiles": "Kan kun sammenligne to filer", + "comparingFiles": "Sammenligning af filer: {{file1}} og {{file2}}", + "dragFailed": "Trækhandlingen mislykkedes", + "filePinnedSuccessfully": "Filen \"{{name}}\" er blevet fastgjort", + "pinFileFailed": "Kunne ikke fastlåse filen", + "fileUnpinnedSuccessfully": "Filen \"{{name}}\" blev frigjort", + "unpinFileFailed": "Kunne ikke frigøre filen", + "shortcutAddedSuccessfully": "Mappens genvej \"{{name}}\" blev tilføjet", + "addShortcutFailed": "Kunne ikke tilføje genvej", + "operationCompletedSuccessfully": "{{operation}} {{count}} elementer er blevet genanvendt", + "operationCompleted": "{{operation}} {{count}} elementer", + "downloadFileSuccess": "Fil {{name}} er blevet downloadet", + "downloadFileFailed": "Download mislykkedes", + "downloadingFile": "Downloader {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Flyt til {{name}}", + "diffCompareWith": "Differens sammenligning med {{name}}", + "dragOutsideToDownload": "Træk uden for vinduet for at downloade ({{count}} filer)", + "newFolderDefault": "NyMappe", + "newFileDefault": "Nyfil.txt", + "successfullyMovedItems": "{{count}} elementer er flyttet til {{target}}", + "move": "Flytte", + "searchInFile": "Søg i filen (Ctrl+F)", + "showKeyboardShortcuts": "Vis tastaturgenveje", + "decreaseFontSize": "Formindsk skriftstørrelsen", + "increaseFontSize": "Øg skriftstørrelsen", + "startWritingMarkdown": "Begynd at skrive dit indhold til nedskrivning...", + "loadingFileComparison": "Indlæser filsammenligning...", + "reload": "Genindlæs", + "compare": "Sammenligne", + "sideBySide": "Side om side", + "inline": "Indlejret", + "fileComparison": "Filsammenligning: {{file1}} vs. {{file2}}", + "fileTooLarge": "Filen er for stor: {{error}}", + "sshConnectionFailed": "SSH-forbindelse mislykkedes. Kontroller venligst din forbindelse til {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Kunne ikke indlæse filen: {{error}}", + "connecting": "Forbinder...", + "connectedSuccessfully": "Forbindelsen er oprettet", + "totpVerificationFailed": "TOTP-bekræftelse mislykkedes", + "warpgateVerificationFailed": "Warpgate-godkendelse mislykkedes", + "authenticationFailed": "Godkendelse mislykkedes", + "incorrectPassphrase": "Forkert adgangskode. Prøv igen.", + "verificationCodePrompt": "Bekræftelseskode:", + "changePermissions": "Ændre tilladelser", + "currentPermissions": "Nuværende tilladelser", + "owner": "Ejer", + "group": "Gruppe", + "others": "Andre", + "read": "Læse", + "write": "Skrive", + "execute": "Udfør", + "permissionsChangedSuccessfully": "Tilladelserne er ændret", + "failedToChangePermissions": "Kunne ikke ændre tilladelser", + "name": "Navn", + "sortByName": "Navn", + "sortByDate": "Dato ændret", + "sortBySize": "Størrelse", + "ascending": "Stigende", + "descending": "Faldende", + "root": "Rod", + "new": "Ny", + "sortBy": "Sortér efter", + "items": "Varer", + "selected": "Valgt", + "editor": "Redaktør", + "octal": "Oktal", + "storage": "Opbevaring", + "disk": "Disk", + "used": "Brugt", + "of": "af", + "toggleSidebar": "Slå sidebjælken til/fra", + "compactLayout": "Brug kompakt layout", + "comfortableLayout": "Brug et komfortabelt layout", + "cannotLoadPdf": "Kan ikke indlæse PDF'en", + "pdfLoadError": "Der opstod en fejl under indlæsning af denne PDF-fil.", + "loadingPdf": "Indlæser PDF...", + "loadingPage": "Indlæser side..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Overfør filer mellem to SSH-værter.", + "sourceServer": "Kildeserver", + "destinationServer": "Destinationsserver", + "selectHost": "Vælg vært", + "noHostSelected": "Ingen vært valgt", + "move": "Flytte", + "copy": "Kopi", + "upload": "Upload", + "itemsSelected": "{{count}} element valgt", + "itemsSelected_other": "{{count}} elementer valgt", + "ready": "Parat", + "loadingHosts": "Indlæser værter...", + "name": "Navn", + "modified": "Ændret", + "size": "Størrelse", + "noFiles": "Ingen filer", + "open": "Åben", + "up": "Op", + "refresh": "Opfriske", + "copyToTarget": "Kopiér til målmappe", + "rename": "Omdøb", + "delete": "Slet", + "editPermissions": "Rediger tilladelser", + "createNewFolder": "Opret ny mappe", + "deleteSelectedItems": "Slet valgte elementer", + "deleteConfirm": "Slet {{count}} det valgte element?", + "deleteConfirm_other": "Slet {{count}} valgte elementer?", + "cancel": "Ophæve", + "save": "Spare", + "folderCreated": "Mappe oprettet", + "itemRenamed": "Varen er omdøbt", + "actionFailed": "Handlingen mislykkedes", + "itemsDeleted": "{{count}} element slettet", + "itemsDeleted_other": "{{count}} elementer slettet", + "deleteFailed": "Sletning mislykkedes", + "permissionsUpdated": "Tilladelser opdateret", + "noRemoteSessionConnected": "Ingen fjernsession tilsluttet", + "failedToListFiles": "Kunne ikke liste filer", + "failedToRefresh": "Kunne ikke opdatere", + "destinationInsideSource": "Destinationen må ikke være inden for den valgte kildesti.", + "sameHostNoOp": "Kilde og destination er den samme mappe." + }, + "hosts": { + "fileManager": "Filhåndtering", + "tabFiles": "Filer", + "enableFileManagerMonitor": "Aktivér Filhåndtering", + "enableFileManagerMonitorDesc": "Gennemse og administrer filer på denne vært via SFTP", + "scpLegacyLabel": "SCP Legacy-tilstand", + "scpLegacyDesc": "Brug ældre filoverførsel til servere, der ikke understøtter SFTP-undersystemet (f.eks. integrerede eller minimale SSH-servere).", + "defaultPathLabel": "Standardsti", + "fileManagerPathHint": "Den mappe, der skal åbnes, når filhåndteringen starter for denne vært.", + "fileManagerUrlCopied": "URL'en til filhåndtering er kopieret", + "copyFileManagerUrlAction": "Kopiér URL til filhåndtering" + }, + "settings": { + "host": { + "enable": { + "label": "Aktivér Filhåndtering" + }, + "defaultPath": { + "label": "Standardsti" + }, + "scpLegacy": { + "label": "SCP Legacy-tilstand" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Filer" + } +} diff --git a/plugins/file-manager/locales/translated/de_DE.json b/plugins/file-manager/locales/translated/de_DE.json new file mode 100644 index 000000000..f05b2c527 --- /dev/null +++ b/plugins/file-manager/locales/translated/de_DE.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Kein Host ausgewählt", + "initializingEditor": "Editor wird initialisiert...", + "file": "Datei", + "folder": "Ordner", + "uploadFile": "Datei hochladen", + "downloadFile": "Herunterladen", + "extractArchive": "Archiv entpacken", + "extractingArchive": "Extrahiere {{name}}...", + "archiveExtractedSuccessfully": "{{name}} erfolgreich extrahiert", + "extractFailed": "Extrahieren fehlgeschlagen", + "compressFile": "Datei komprimieren", + "compressFiles": "Dateien komprimieren", + "compressFilesDesc": "{{count}} Elemente in ein Archiv komprimieren", + "archiveName": "Archivname", + "enterArchiveName": "Archivname eingeben...", + "compressionFormat": "Komprimierungsformat", + "selectedFiles": "Ausgewählte Dateien", + "andMoreFiles": "und {{count}} weitere...", + "compress": "Komprimieren", + "compressingFiles": "Komprimiere {{count}} Elemente in {{name}}...", + "filesCompressedSuccessfully": "{{name}} erfolgreich erstellt", + "compressFailed": "Komprimierung fehlgeschlagen", + "edit": "Bearbeiten", + "preview": "Vorschau", + "previous": "Zurück", + "next": "Weiter", + "pageXOfY": "Seite {{current}} von {{total}}", + "zoomOut": "Verkleinern", + "zoomIn": "Vergrößern", + "newFile": "Neue Datei", + "newFolder": "Neuer Ordner", + "rename": "Umbenennen", + "uploading": "Lade hoch...", + "uploadingFile": "Lade {{name}} hoch...", + "fileName": "Dateiname", + "folderName": "Ordnername", + "fileUploadedSuccessfully": "Datei \"{{name}}\" erfolgreich hochgeladen", + "failedToUploadFile": "Hochladen der Datei fehlgeschlagen", + "uploadingFolderFiles": "Uploading {{count}} file(s)...", + "uploadedFolderFiles": "{{count}} Datei(en) erfolgreich hochgeladen", + "uploadedFolderPartial": "Hochladen von {{uploaded}} Datei(en), {{failed}} fehlgeschlagen", + "fileDownloadedSuccessfully": "Datei \"{{name}}\" erfolgreich heruntergeladen", + "failedToDownloadFile": "Herunterladen der Datei fehlgeschlagen", + "fileCreatedSuccessfully": "Datei \"{{name}}\" erfolgreich erstellt", + "folderCreatedSuccessfully": "Ordner \"{{name}}\" erfolgreich erstellt", + "failedToCreateItem": "Fehler beim Erstellen des Elements", + "operationFailed": "{{operation}}-Operation für {{name}} fehlgeschlagen: {{error}}", + "failedToResolveSymlink": "Fehler beim Auflösen des Symlinks", + "itemsDeletedSuccessfully": "{{count}} Elemente erfolgreich gelöscht", + "failedToDeleteItems": "Fehler beim Löschen der Elemente", + "sudoPasswordRequired": "Sudo-Passwort erforderlich", + "enterSudoPassword": "Sudo-Passwort eingeben, um diesen Vorgang fortzusetzen", + "sudoPassword": "Sudo-Passwort", + "sudoOperationFailed": "Sudo-Vorgang fehlgeschlagen", + "sudoAuthFailed": "Sudo-Authentifizierung fehlgeschlagen", + "dragFilesToUpload": "Dateien hier ablegen, um sie hochzuladen", + "dropToUploadHere": "Hier zum Hochladen ablegen", + "dropToDownloadHere": "Hier klicken zum Herunterladen", + "localFiles": "Lokal", + "showLocalFiles": "Lokale Dateien anzeigen (geteilte Ansicht)", + "hideLocalFiles": "Lokale Dateien ausblenden", + "localHome": "Startseitenordner", + "localPathPlaceholder": "Lokaler Ordnerpfad", + "localShowHidden": "Versteckte Dateien anzeigen", + "localHideHidden": "Versteckte Dateien ausblenden", + "localRevealInFileManager": "Im Systemdateimanager anzeigen", + "localCannotOpenFolder": "Ordner kann nicht geöffnet werden", + "localItemCount": "{{count}} Artikel", + "localSelectedCount": "{{count}} ausgewählt", + "back": "Zurück", + "forward": "Nach vorne", + "up": "Hoch", + "kind": "Art", + "localUploadingProgress": "Hochladen von {{current}} von {{total}}", + "localDownloadingProgress": "Herunterladen von {{current}} von {{total}}", + "localTransferInFlight": "{{count}} gleichzeitig", + "localPreparingDownload": "Download wird vorbereitet...", + "localTransferCancelled": "Transfer storniert", + "localNothingToTransfer": "Nichts zu übertragen", + "localUploadComplete": "Es wurden {{count}} Dateien hochgeladen", + "localUploadFailed": "Hochladen fehlgeschlagen", + "localUploadPartial": "Hochladen von {{done}} Datei(en), {{failed}} fehlgeschlagen", + "localDownloadComplete": "Heruntergeladene Datei(en) {{count}}", + "localDownloadFailed": "Download fehlgeschlagen", + "localDownloadPartial": "Download der Datei(en) {{done}} , {{failed}} fehlgeschlagen", + "localReplaceExistingPrompt": "{{count}} In diesem Ordner sind bereits Elemente vorhanden. Sollen sie ersetzt werden?", + "localReplace": "Ersetzen", + "localSkipExisting": "Überspringen", + "localSkippedExisting": "Übersprungene {{count}} vorhandene Elemente", + "localUnsafeNamesSkipped": "{{count}} Elemente übersprungen, deren Namen auf diesem Computer nicht verwendet werden können", + "localOpen": "Offen", + "localOpenFolder": "Ordner öffnen", + "localUploadToRemote": "Auf Server hochladen", + "localUploadToRemoteMany": "{{count}} Elemente auf den Server hochladen", + "localMoveToTrash": "In den Papierkorb verschieben", + "localMoveToTrashMany": "Verschiebe {{count}} Objekte in den Papierkorb.", + "localTrashConfirmSingle": "\"{{name}}\" in den Papierkorb verschieben?", + "localTrashConfirmFolder": "Den Ordner \"{{name}}\" und seinen gesamten Inhalt in den Papierkorb verschieben?", + "localTrashConfirmMany": "{{count}} Elemente in den Papierkorb verschieben?", + "localTrashed": "{{count}} Elemente wurden in den Papierkorb verschoben.", + "localTrashFailed": "Konnte nicht in den Mülleimer bewegt werden.", + "localRenameFailed": "Umbenennen fehlgeschlagen", + "localCreateFailed": "Element konnte nicht erstellt werden", + "goToParentFolder": "Gehe zum übergeordneten Ordner", + "resizeSidebar": "Ziehen Sie, um die Seitenleiste in der Größe anzupassen (Doppelklick zum Zurücksetzen).", + "resizeLocalPane": "Ziehen Sie, um die Größe des lokalen Fensters zu ändern (Doppelklick zum Zurücksetzen).", + "columns": "Spalten", + "columnsHint": "Klicken Sie mit der rechten Maustaste, um Spalten auszuwählen.", + "emptyFolder": "Dieser Ordner ist leer", + "searchFiles": "Dateien durchsuchen...", + "upload": "Hochladen", + "selectHostToStart": "Wählen Sie einen Host aus, um die Dateiverwaltung zu starten", + "sshRequiredForFileManager": "Dateimanager erfordert SSH. SSH ist auf diesem Host nicht aktiviert.", + "failedToConnect": "Verbindung zu SSH fehlgeschlagen", + "failedToLoadDirectory": "Fehler beim Laden des Verzeichnisses", + "noSSHConnection": "Keine SSH-Verbindung verfügbar", + "copy": "Kopieren", + "cut": "Ausschneiden", + "paste": "Einfügen", + "copyPath": "Pfad kopieren", + "copyPaths": "Pfade kopieren", + "delete": "Löschen", + "properties": "Eigenschaften", + "refresh": "Aktualisieren", + "downloadFiles": "{{count}} Dateien über den Browser herunterladen", + "copyFiles": "{{count}} Elemente kopieren", + "cutFiles": "{{count}} Elemente ausschneiden", + "deleteFiles": "{{count}} Elemente löschen", + "filesCopiedToClipboard": "{{count}} Elemente in die Zwischenablage kopiert", + "filesCutToClipboard": "{{count}} Elemente in die Zwischenablage ausgeschnitten", + "pathCopiedToClipboard": "Pfad in die Zwischenablage kopiert", + "pathsCopiedToClipboard": "{{count}} Pfade in die Zwischenablage kopiert", + "failedToCopyPath": "Fehler beim Kopieren des Pfads in die Zwischenablage", + "copyFolderLink": "Link zum Ordner kopieren", + "copyCurrentFolderLink": "Link zum aktuellen Ordner kopieren", + "folderLinkCopied": "Ordner-Link in die Zwischenablage kopiert", + "failedToCopyFolderLink": "Fehler beim Kopieren des Ordner-Links", + "movedItems": "{{count}} Elemente verschoben", + "failedToDeleteItem": "Fehler beim Löschen des Elements", + "itemRenamedSuccessfully": "{{type}} erfolgreich umbenannt", + "failedToRenameItem": "Fehler beim Umbenennen des Elements", + "download": "Herunterladen", + "openExternalEditor": "Extern öffnen", + "chooseExternalEditor": "Externen Editor auswählen", + "externalEditorSelected": "Externer Editor ausgewählt", + "externalEditorDesktopOnly": "Externer Editor ist nur in der Desktop-App verfügbar", + "externalEditorOpened": "Im externen Editor geöffnet. Beim Speichern werden die Änderungen auf den Server hochgeladen.", + "failedToOpenExternalEditor": "Fehler beim Öffnen des externen Editors", + "failedToSelectExternalEditor": "Fehler bei der Auswahl des externen Editors", + "permissions": "Berechtigungen", + "size": "Größe", + "modified": "Geändert", + "path": "Pfad", + "confirmDelete": "Sind Sie sicher, dass Sie {{name}} löschen möchten?", + "permissionDenied": "Zugriff verweigert", + "serverError": "Serverfehler", + "fileSavedSuccessfully": "Datei erfolgreich gespeichert", + "failedToSaveFile": "Fehler beim Speichern der Datei", + "confirmDeleteSingleItem": "Sind Sie sicher, dass Sie \"{{name}}\" endgültig löschen möchten?", + "confirmDeleteMultipleItems": "Sind Sie sicher, dass Sie {{count}} Elemente endgültig löschen möchten?", + "confirmDeleteMultipleItemsWithFolders": "Sind Sie sicher, dass Sie {{count}} Elemente endgültig löschen möchten? Dies beinhaltet auch Ordner und deren Inhalte.", + "confirmDeleteFolder": "Sind Sie sicher, dass Sie den Ordner \"{{name}}\" und seinen gesamten Inhalt endgültig löschen möchten?", + "permanentDeleteWarning": "Diese Aktion kann nicht rückgängig gemacht werden. Die Elemente werden endgültig vom Server gelöscht.", + "moveToTrashWarning": "Die Elemente werden in den Termix-Papierkorb verschoben und können vor Ablauf der Aufbewahrungsfrist wiederhergestellt werden.", + "itemsMovedToTrash": "{{count}} Elemente in den Papierkorb verschoben", + "trash": "Müll", + "trashDescription": "Gelöschte Elemente werden auf diesem Host für {{days}} Tage aufbewahrt.", + "trashLoadFailed": "Der Papierkorb konnte nicht geladen werden.", + "trashEmpty": "Der Mülleimer ist leer", + "trashRestore": "Wiederherstellen", + "trashRestored": "Wiederhergestellt {{name}}", + "trashRestoreFailed": "Das Element konnte nicht wiederhergestellt werden. Der ursprüngliche Pfad existiert möglicherweise bereits.", + "trashDeletePermanently": "Endgültig löschen", + "trashDeleteConfirm": "{{name}}endgültig löschen? Dies kann nicht rückgängig gemacht werden.", + "trashPermanentlyDeleted": "Endgültig gelöscht {{name}}", + "trashDeleteFailed": "Fehler beim endgültigen Löschen des Eintrags", + "trashEmptyAction": "Mülleimer leeren", + "trashEmptyConfirm": "Alle Elemente im Papierkorb endgültig löschen? Dies kann nicht rückgängig gemacht werden.", + "trashEmptied": "Müll geleert", + "trashRetention": "Automatische Bereinigung nach (Tagen)", + "trashRetentionSaved": "Müllspeicherung aktualisiert", + "trashRetentionFailed": "Aktualisierung der Papierkorb-Aufbewahrung fehlgeschlagen", + "trashUnavailableConfirm": "Dieses Element kann nicht in den Papierkorb verschoben werden, üblicherweise weil es sich auf einem anderen Dateisystem befindet oder erhöhte Berechtigungen erfordert. Soll es stattdessen endgültig gelöscht werden?", + "recent": "Zuletzt verwendet", + "pinned": "Angepinnt", + "folderShortcuts": "Ordner-Verknüpfungen", + "failedToReconnectSSH": "Fehler beim Wiederherstellen der SSH-Sitzung", + "openTerminalHere": "Terminal hier öffnen", + "run": "Ausführen", + "openTerminalInFolder": "Terminal in diesem Ordner öffnen", + "openTerminalInFileLocation": "Terminal am Dateispeicherort öffnen", + "runningFile": "Läuft - {{file}}", + "onlyRunExecutableFiles": "Nur ausführbare Dateien können ausgeführt werden", + "directories": "Verzeichnisse", + "removedFromRecentFiles": "\"{{name}}\" aus den zuletzt verwendeten Dateien entfernt", + "removeFailed": "Entfernen fehlgeschlagen", + "unpinnedSuccessfully": "\"{{name}}\" erfolgreich gelöst", + "unpinFailed": "Lösen fehlgeschlagen", + "removedShortcut": "Verknüpfung \"{{name}}\" entfernt", + "removeShortcutFailed": "Entfernen der Verknüpfung fehlgeschlagen", + "clearedAllRecentFiles": "Alle zuletzt verwendeten Dateien gelöscht", + "clearFailed": "Löschen fehlgeschlagen", + "removeFromRecentFiles": "Aus zuletzt verwendeten Dateien entfernen", + "clearAllRecentFiles": "Alle zuletzt verwendeten Dateien löschen", + "unpinFile": "Datei lösen", + "removeShortcut": "Verknüpfung entfernen", + "pinFile": "Datei anheften", + "addToShortcuts": "Zu Verknüpfungen hinzufügen", + "pasteFailed": "Einfügen fehlgeschlagen", + "noUndoableActions": "Keine rückgängig zu machenden Aktionen", + "undoCopySuccess": "Kopiervorgang rückgängig gemacht: {{count}} kopierte Dateien gelöscht", + "undoCopyFailedDelete": "Rückgängig fehlgeschlagen: Konnte keine kopierten Dateien löschen", + "undoCopyFailedNoInfo": "Rückgängig fehlgeschlagen: Informationen zu kopierten Dateien nicht gefunden", + "undoMoveSuccess": "Verschiebevorgang rückgängig gemacht: {{count}} Dateien an den ursprünglichen Ort zurückverschoben", + "undoMoveFailedMove": "Rückgängig fehlgeschlagen: Konnte keine Dateien zurückverschieben", + "undoMoveFailedNoInfo": "Rückgängig fehlgeschlagen: Informationen zu verschobenen Dateien nicht gefunden", + "undoDeleteNotSupported": "Löschvorgang kann nicht rückgängig gemacht werden: Dateien wurden dauerhaft vom Server gelöscht", + "undoTypeNotSupported": "Nicht unterstützter Typ für Rückgängig-Vorgang", + "undoOperationFailed": "Rückgängig-Vorgang fehlgeschlagen", + "unknownError": "Unbekannter Fehler", + "confirm": "Bestätigen", + "find": "Suchen...", + "replace": "Ersetzen", + "downloadInstead": "Stattdessen herunterladen", + "keyboardShortcuts": "Tastaturkürzel", + "searchAndReplace": "Suchen & Ersetzen", + "editing": "Bearbeiten", + "search": "Suchen", + "findNext": "Weitersuchen", + "findPrevious": "Vorheriges suchen", + "save": "Speichern", + "selectAll": "Alles auswählen", + "undo": "Rückgängig", + "redo": "Wiederholen", + "moveLineUp": "Zeile nach oben verschieben", + "moveLineDown": "Zeile nach unten verschieben", + "toggleComment": "Kommentar umschalten", + "autoComplete": "Autovervollständigung", + "imageLoadError": "Bild konnte nicht geladen werden", + "startTyping": "Mit der Eingabe beginnen...", + "unknownSize": "Unbekannte Größe", + "fileIsEmpty": "Datei ist leer", + "largeFileWarning": "Warnung bei großen Dateien", + "largeFileWarningDesc": "Diese Datei ist {{size}} groß, was beim Öffnen als Text zu Leistungsproblemen führen kann.", + "fileNotFoundAndRemoved": "Datei \"{{name}}\" nicht gefunden und wurde aus den zuletzt verwendeten/angehefteten Dateien entfernt.", + "failedToLoadFile": "Datei konnte nicht geladen werden: {{error}}", + "serverErrorOccurred": "Ein Serverfehler ist aufgetreten. Bitte versuchen Sie es später erneut.", + "autoSaveFailed": "Automatisches Speichern fehlgeschlagen", + "fileAutoSaved": "Datei automatisch gespeichert", + "moveFileFailed": "Verschieben von {{name}} fehlgeschlagen", + "moveOperationFailed": "Verschiebevorgang fehlgeschlagen", + "canOnlyCompareFiles": "Es können nur zwei Dateien verglichen werden", + "comparingFiles": "Vergleiche Dateien: {{file1}} und {{file2}}", + "dragFailed": "Ziehvorgang fehlgeschlagen", + "filePinnedSuccessfully": "Datei \"{{name}}\" wurde erfolgreich angeheftet", + "pinFileFailed": "Anheften der Datei fehlgeschlagen", + "fileUnpinnedSuccessfully": "Datei \"{{name}}\" wurde erfolgreich abgeheftet", + "unpinFileFailed": "Loslösen der Datei fehlgeschlagen", + "shortcutAddedSuccessfully": "Ordnerverknüpfung \"{{name}}\" erfolgreich hinzugefügt", + "addShortcutFailed": "Hinzufügen der Verknüpfung fehlgeschlagen", + "operationCompletedSuccessfully": "{{operation}} {{count}} Elemente erfolgreich abgeschlossen", + "operationCompleted": "{{operation}} {{count}} Elemente", + "downloadFileSuccess": "Datei {{name}} erfolgreich heruntergeladen", + "downloadFileFailed": "Download fehlgeschlagen", + "downloadingFile": "Herunterladen {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Verschieben nach {{name}}", + "diffCompareWith": "Diff-Vergleich mit {{name}}", + "dragOutsideToDownload": "Zum Herunterladen aus dem Fenster ziehen ({{count}} Dateien)", + "newFolderDefault": "Neuer Ordner", + "newFileDefault": "Neue Datei.txt", + "successfullyMovedItems": "{{count}} Elemente erfolgreich nach {{target}} verschoben", + "move": "Verschieben", + "searchInFile": "In Datei suchen (Strg+F)", + "showKeyboardShortcuts": "Tastaturkürzel anzeigen", + "decreaseFontSize": "Schriftgröße verkleinern", + "increaseFontSize": "Schriftgröße vergrößern", + "startWritingMarkdown": "Beginnen Sie mit der Eingabe Ihres Markdown-Inhalts...", + "loadingFileComparison": "Dateivergleich wird geladen...", + "reload": "Neu laden", + "compare": "Vergleichen", + "sideBySide": "Nebeneinander", + "inline": "Inline", + "fileComparison": "Dateivergleich: {{file1}} vs. {{file2}}", + "fileTooLarge": "Datei zu groß: {{error}}", + "sshConnectionFailed": "SSH-Verbindung fehlgeschlagen. Bitte überprüfen Sie Ihre Verbindung zu {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Fehler beim Laden der Datei: {{error}}", + "connecting": "Verbindung wird hergestellt...", + "connectedSuccessfully": "Erfolgreich verbunden", + "totpVerificationFailed": "TOTP-Überprüfung fehlgeschlagen", + "warpgateVerificationFailed": "Warpgate-Authentifizierung fehlgeschlagen", + "authenticationFailed": "Authentifizierung fehlgeschlagen", + "incorrectPassphrase": "Falsche Passphrase. Bitte versuchen Sie es erneut.", + "verificationCodePrompt": "Verifizierungscode:", + "changePermissions": "Berechtigungen ändern", + "currentPermissions": "Aktuelle Berechtigungen", + "owner": "Besitzer", + "group": "Gruppe", + "others": "Andere", + "read": "Lesen", + "write": "Schreiben", + "execute": "Ausführen", + "permissionsChangedSuccessfully": "Berechtigungen erfolgreich geändert", + "failedToChangePermissions": "Berechtigungen konnten nicht geändert werden", + "name": "Name", + "sortByName": "Name", + "sortByDate": "Änderungsdatum", + "sortBySize": "Größe", + "ascending": "Aufsteigend", + "descending": "Absteigend", + "root": "Stammverzeichnis", + "new": "Neu", + "sortBy": "Sortieren nach", + "items": "Elemente", + "selected": "Ausgewählt", + "editor": "Editor", + "octal": "Oktal", + "storage": "Speicher", + "disk": "Datenträger", + "used": "Belegt", + "of": "von", + "toggleSidebar": "Seitenleiste umschalten", + "compactLayout": "Kompaktes Layout verwenden", + "comfortableLayout": "Nutzen Sie ein komfortables Layout", + "cannotLoadPdf": "PDF kann nicht geladen werden", + "pdfLoadError": "Beim Laden dieser PDF-Datei ist ein Fehler aufgetreten.", + "loadingPdf": "PDF wird geladen...", + "loadingPage": "Seite wird geladen..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Dateien zwischen zwei SSH-Hosts übertragen.", + "sourceServer": "Quellserver", + "destinationServer": "Zielserver", + "selectHost": "Host auswählen", + "noHostSelected": "Kein Host ausgewählt", + "move": "Bewegen", + "copy": "Kopie", + "upload": "Hochladen", + "itemsSelected": "{{count}} Element ausgewählt", + "itemsSelected_other": "{{count}} ausgewählte Elemente", + "ready": "Bereit", + "loadingHosts": "Hosts werden geladen...", + "name": "Name", + "modified": "Modifiziert", + "size": "Größe", + "noFiles": "Keine Dateien", + "open": "Offen", + "up": "Hoch", + "refresh": "Aktualisieren", + "copyToTarget": "In Zielverzeichnis kopieren", + "rename": "Umbenennen", + "delete": "Löschen", + "editPermissions": "Berechtigungen bearbeiten", + "createNewFolder": "Neuen Ordner erstellen", + "deleteSelectedItems": "Ausgewählte Elemente löschen", + "deleteConfirm": "Delete {{count}} selected item?", + "deleteConfirm_other": "{{count}} ausgewählte Elemente löschen?", + "cancel": "Stornieren", + "save": "Speichern", + "folderCreated": "Ordner erstellt", + "itemRenamed": "Artikel umbenannt", + "actionFailed": "Aktion fehlgeschlagen", + "itemsDeleted": "{{count}} Element gelöscht", + "itemsDeleted_other": "{{count}} gelöschte Elemente", + "deleteFailed": "Löschen fehlgeschlagen", + "permissionsUpdated": "Berechtigungen aktualisiert", + "noRemoteSessionConnected": "Keine Remote-Sitzung verbunden", + "failedToListFiles": "Fehler beim Auflisten der Dateien", + "failedToRefresh": "Aktualisierung fehlgeschlagen", + "destinationInsideSource": "Das Ziel darf nicht innerhalb des ausgewählten Quellpfads liegen.", + "sameHostNoOp": "Quell- und Zielordner sind derselbe Ordner." + }, + "hosts": { + "fileManager": "Dateimanager", + "tabFiles": "Dateien", + "enableFileManagerMonitor": "Dateimanager aktivieren", + "enableFileManagerMonitorDesc": "Dateien auf diesem Host über SFTP durchsuchen und verwalten", + "scpLegacyLabel": "SCP-Legacy-Modus", + "scpLegacyDesc": "Verwenden Sie die Legacy-Dateiübertragung für Server, die das SFTP-Subsystem nicht unterstützen (z. B. eingebettete oder minimale SSH-Server).", + "defaultPathLabel": "Standardpfad", + "fileManagerPathHint": "Das Verzeichnis, das geöffnet wird, wenn der Dateimanager für diesen Host gestartet wird.", + "fileManagerUrlCopied": "Dateimanager-URL kopiert", + "copyFileManagerUrlAction": "Dateimanager-URL kopieren" + }, + "settings": { + "host": { + "enable": { + "label": "Dateimanager aktivieren" + }, + "defaultPath": { + "label": "Standardpfad" + }, + "scpLegacy": { + "label": "SCP-Legacy-Modus" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Dateien" + } +} diff --git a/plugins/file-manager/locales/translated/el_GR.json b/plugins/file-manager/locales/translated/el_GR.json new file mode 100644 index 000000000..235b1a8da --- /dev/null +++ b/plugins/file-manager/locales/translated/el_GR.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Δεν έχει επιλεγεί κεντρικός υπολογιστής", + "initializingEditor": "Αρχικοποίηση προγράμματος επεξεργασίας...", + "file": "Αρχείο", + "folder": "Ντοσιέ", + "uploadFile": "Μεταφόρτωση αρχείου", + "downloadFile": "Λήψη", + "extractArchive": "Εξαγωγή αρχείου", + "extractingArchive": "Εξαγωγή {{name}}...", + "archiveExtractedSuccessfully": "{{name}} εξήχθη με επιτυχία", + "extractFailed": "Η εξαγωγή απέτυχε.", + "compressFile": "Συμπίεση αρχείου", + "compressFiles": "Συμπίεση αρχείων", + "compressFilesDesc": "Συμπίεση στοιχείων {{count}} σε ένα αρχείο", + "archiveName": "Όνομα αρχείου", + "enterArchiveName": "Εισαγάγετε όνομα αρχείου...", + "compressionFormat": "Μορφή συμπίεσης", + "selectedFiles": "Επιλεγμένα αρχεία", + "andMoreFiles": "και {{count}} περισσότερα...", + "compress": "Κομπρέσα", + "compressingFiles": "Συμπίεση στοιχείων {{count}} σε {{name}}...", + "filesCompressedSuccessfully": "{{name}} δημιουργήθηκε με επιτυχία", + "compressFailed": "Η συμπίεση απέτυχε", + "edit": "Εκδίδω", + "preview": "Πρεμιέρα", + "previous": "Προηγούμενος", + "next": "Επόμενος", + "pageXOfY": "Σελίδα {{current}} από {{total}}", + "zoomOut": "Σμίκρυνση", + "zoomIn": "Μεγέθυνση", + "newFile": "Νέο αρχείο", + "newFolder": "Νέος φάκελος", + "rename": "Μετονομάζω", + "uploading": "Μεταφόρτωση...", + "uploadingFile": "Μεταφόρτωση {{name}}...", + "fileName": "Όνομα αρχείου", + "folderName": "Όνομα φακέλου", + "fileUploadedSuccessfully": "Το αρχείο \"{{name}}\" μεταφορτώθηκε με επιτυχία", + "failedToUploadFile": "Η μεταφόρτωση του αρχείου απέτυχε", + "uploadingFolderFiles": "Μεταφόρτωση {{count}} αρχείου(ων)...", + "uploadedFolderFiles": "Μεταφορτώθηκαν με επιτυχία τα {{count}} αρχεία", + "uploadedFolderPartial": "Η μεταφόρτωση {{uploaded}} αρχείου(ων), {{failed}} απέτυχε", + "fileDownloadedSuccessfully": "Το αρχείο \"{{name}}\" λήφθηκε με επιτυχία.", + "failedToDownloadFile": "Αποτυχία λήψης αρχείου", + "fileCreatedSuccessfully": "Το αρχείο \"{{name}}\" δημιουργήθηκε με επιτυχία", + "folderCreatedSuccessfully": "Ο φάκελος \"{{name}}\" δημιουργήθηκε με επιτυχία", + "failedToCreateItem": "Η δημιουργία στοιχείου απέτυχε", + "operationFailed": "Η λειτουργία {{operation}} απέτυχε για {{name}}: {{error}}", + "failedToResolveSymlink": "Αποτυχία επίλυσης συμβολικού συνδέσμου", + "itemsDeletedSuccessfully": "{{count}} στοιχεία διαγράφηκαν με επιτυχία", + "failedToDeleteItems": "Η διαγραφή στοιχείων απέτυχε", + "sudoPasswordRequired": "Απαιτείται κωδικός πρόσβασης διαχειριστή", + "enterSudoPassword": "Εισαγάγετε τον κωδικό πρόσβασης sudo για να συνεχίσετε αυτήν τη λειτουργία", + "sudoPassword": "Κωδικός πρόσβασης Sudo", + "sudoOperationFailed": "Η λειτουργία Sudo απέτυχε", + "sudoAuthFailed": "Ο έλεγχος ταυτότητας Sudo απέτυχε.", + "dragFilesToUpload": "Αποθέστε αρχεία εδώ για μεταφόρτωση", + "dropToUploadHere": "Αποθέστε εδώ για μεταφόρτωση", + "dropToDownloadHere": "Αποθέστε για λήψη εδώ", + "localFiles": "Τοπικός", + "showLocalFiles": "Εμφάνιση τοπικών αρχείων (διαιρεμένη προβολή)", + "hideLocalFiles": "Απόκρυψη τοπικών αρχείων", + "localHome": "Αρχική σελίδα", + "localPathPlaceholder": "Διαδρομή τοπικού φακέλου", + "localShowHidden": "Εμφάνιση κρυφών αρχείων", + "localHideHidden": "Απόκρυψη κρυφών αρχείων", + "localRevealInFileManager": "Εμφάνιση στον διαχειριστή αρχείων συστήματος", + "localCannotOpenFolder": "Δεν είναι δυνατό το άνοιγμα του φακέλου", + "localItemCount": "{{count}} στοιχεία", + "localSelectedCount": "επιλεγμένο {{count}}", + "back": "Πίσω", + "forward": "Προς τα εμπρός", + "up": "Επάνω", + "kind": "Είδος", + "localUploadingProgress": "Μεταφόρτωση {{current}} από {{total}}", + "localDownloadingProgress": "Λήψη {{current}} από {{total}}", + "localTransferInFlight": "{{count}} ταυτόχρονα", + "localPreparingDownload": "Προετοιμασία λήψης...", + "localTransferCancelled": "Η μεταφορά ακυρώθηκε.", + "localNothingToTransfer": "Δεν υπάρχει τίποτα προς μεταφορά", + "localUploadComplete": "Μεταφορτώθηκε {{count}} αρχείο(α)", + "localUploadFailed": "Η μεταφόρτωση απέτυχε.", + "localUploadPartial": "Η μεταφόρτωση {{done}} αρχείου(ων), {{failed}} απέτυχε", + "localDownloadComplete": "Λήψη {{count}} αρχείου(ων)", + "localDownloadFailed": "Η λήψη απέτυχε.", + "localDownloadPartial": "Η λήψη του/των {{done}} αρχείου/αρχείων απέτυχε, το {{failed}}", + "localReplaceExistingPrompt": "{{count}} στοιχεία υπάρχουν ήδη σε αυτόν τον φάκελο. Να αντικατασταθούν;", + "localReplace": "Αντικαθιστώ", + "localSkipExisting": "Παραλείπω", + "localSkippedExisting": "Παραλείφθηκαν {{count}} υπάρχοντα στοιχεία", + "localUnsafeNamesSkipped": "Παραλείφθηκαν {{count}} στοιχεία των οποίων τα ονόματα δεν μπορούν να χρησιμοποιηθούν σε αυτόν τον υπολογιστή", + "localOpen": "Ανοιχτό", + "localOpenFolder": "Άνοιγμα φακέλου", + "localUploadToRemote": "Μεταφόρτωση στον διακομιστή", + "localUploadToRemoteMany": "Μεταφόρτωση {{count}} στοιχείων στον διακομιστή", + "localMoveToTrash": "Μετακίνηση στον Κάδο", + "localMoveToTrashMany": "Μετακίνηση {{count}} στοιχείων στον Κάδο Απορριμμάτων", + "localTrashConfirmSingle": "Μετακίνηση του \"{{name}}\" στον Κάδο;", + "localTrashConfirmFolder": "Μετακίνηση του φακέλου \"{{name}}\" και όλου του περιεχομένου του στον Κάδο Απορριμμάτων;", + "localTrashConfirmMany": "Μετακίνηση {{count}} στοιχείων στον Κάδο;", + "localTrashed": "Μετακινήθηκαν {{count}} στοιχεία στον Κάδο Απορριμμάτων", + "localTrashFailed": "Δεν ήταν δυνατή η μετακίνηση στον Κάδο", + "localRenameFailed": "Η μετονομασία απέτυχε.", + "localCreateFailed": "Δεν ήταν δυνατή η δημιουργία στοιχείου", + "goToParentFolder": "Μετάβαση στον γονικό φάκελο", + "resizeSidebar": "Σύρετε για να αλλάξετε το μέγεθος της πλαϊνής γραμμής (κάντε διπλό κλικ για επαναφορά)", + "resizeLocalPane": "Σύρετε για να αλλάξετε το μέγεθος του τοπικού παραθύρου (κάντε διπλό κλικ για επαναφορά)", + "columns": "Στήλες", + "columnsHint": "Κάντε δεξί κλικ για να επιλέξετε στήλες", + "emptyFolder": "Αυτός ο φάκελος είναι κενός", + "searchFiles": "Αναζήτηση αρχείων...", + "upload": "Μεταφόρτωση", + "selectHostToStart": "Επιλέξτε έναν κεντρικό υπολογιστή για να ξεκινήσετε τη διαχείριση αρχείων", + "sshRequiredForFileManager": "Ο διαχειριστής αρχείων απαιτεί SSH. Αυτός ο κεντρικός υπολογιστής δεν έχει ενεργοποιημένο SSH.", + "failedToConnect": "Αποτυχία σύνδεσης με SSH", + "failedToLoadDirectory": "Αποτυχία φόρτωσης καταλόγου", + "noSSHConnection": "Δεν υπάρχει διαθέσιμη σύνδεση SSH", + "copy": "Αντίγραφο", + "cut": "Τομή", + "paste": "Πάστα", + "copyPath": "Αντιγραφή διαδρομής", + "copyPaths": "Διαδρομές αντιγραφής", + "delete": "Διαγράφω", + "properties": "Σκηνικά θέατρου", + "refresh": "Φρεσκάρω", + "downloadFiles": "Λήψη αρχείων {{count}} στο πρόγραμμα περιήγησης", + "copyFiles": "Αντιγραφή {{count}} στοιχείων", + "cutFiles": "Αποκοπή {{count}} στοιχείων", + "deleteFiles": "Διαγραφή {{count}} στοιχείων", + "filesCopiedToClipboard": "{{count}} στοιχεία αντιγράφηκαν στο πρόχειρο", + "filesCutToClipboard": "{{count}} στοιχεία αποκόπηκαν στο πρόχειρο", + "pathCopiedToClipboard": "Η διαδρομή αντιγράφηκε στο πρόχειρο", + "pathsCopiedToClipboard": "{{count}} διαδρομές αντιγράφηκαν στο πρόχειρο", + "failedToCopyPath": "Αποτυχία αντιγραφής διαδρομής στο πρόχειρο", + "copyFolderLink": "Αντιγραφή συνδέσμου σε φάκελο", + "copyCurrentFolderLink": "Αντιγραφή συνδέσμου στον τρέχοντα φάκελο", + "folderLinkCopied": "Ο σύνδεσμος φακέλου αντιγράφηκε στο πρόχειρο", + "failedToCopyFolderLink": "Αποτυχία αντιγραφής συνδέσμου φακέλου", + "movedItems": "Μετακινήθηκαν {{count}} στοιχεία", + "failedToDeleteItem": "Η διαγραφή του στοιχείου απέτυχε", + "itemRenamedSuccessfully": "Το {{type}} μετονομάστηκε με επιτυχία", + "failedToRenameItem": "Η μετονομασία του στοιχείου απέτυχε", + "download": "Λήψη", + "openExternalEditor": "Άνοιγμα εξωτερικά", + "chooseExternalEditor": "Επιλογή εξωτερικού επεξεργαστή", + "externalEditorSelected": "Επιλέχθηκε εξωτερικός επεξεργαστής", + "externalEditorDesktopOnly": "Ο εξωτερικός επεξεργαστής είναι διαθέσιμος μόνο στην εφαρμογή για υπολογιστές", + "externalEditorOpened": "Άνοιξε σε εξωτερικό πρόγραμμα επεξεργασίας. Η αποθήκευση εκεί θα μεταφορτώσει τις αλλαγές πίσω στον διακομιστή.", + "failedToOpenExternalEditor": "Αποτυχία ανοίγματος εξωτερικού προγράμματος επεξεργασίας", + "failedToSelectExternalEditor": "Αποτυχία επιλογής εξωτερικού προγράμματος επεξεργασίας", + "permissions": "Δικαιώματα", + "size": "Μέγεθος", + "modified": "Τροποποιημένο", + "path": "Μονοπάτι", + "confirmDelete": "Είστε σίγουροι ότι θέλετε να διαγράψετε το {{name}};", + "permissionDenied": "Η άδεια απορρίφθηκε", + "serverError": "Σφάλμα διακομιστή", + "fileSavedSuccessfully": "Το αρχείο αποθηκεύτηκε με επιτυχία", + "failedToSaveFile": "Η αποθήκευση του αρχείου απέτυχε", + "confirmDeleteSingleItem": "Είστε σίγουροι ότι θέλετε να διαγράψετε οριστικά το \"{{name}}\";", + "confirmDeleteMultipleItems": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά τα στοιχεία {{count}};", + "confirmDeleteMultipleItemsWithFolders": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά τα στοιχεία {{count}} ; Αυτό περιλαμβάνει τους φακέλους και το περιεχόμενό τους.", + "confirmDeleteFolder": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά τον φάκελο \"{{name}}\" και όλα τα περιεχόμενά του;", + "permanentDeleteWarning": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Τα στοιχεία θα διαγραφούν οριστικά από τον διακομιστή.", + "moveToTrashWarning": "Τα στοιχεία θα μετακινηθούν στον Κάδο Απορριμμάτων του Termix και μπορούν να αποκατασταθούν πριν λήξει η περίοδος διατήρησης.", + "itemsMovedToTrash": "{{count}} στοιχείο(α) μετακινήθηκε(αν) στον Κάδο", + "trash": "Σκουπίδια", + "trashDescription": "Τα διαγραμμένα στοιχεία διατηρούνται για {{days}} ημέρα(ες) σε αυτόν τον κεντρικό υπολογιστή.", + "trashLoadFailed": "Αποτυχία φόρτωσης κάδου απορριμμάτων", + "trashEmpty": "Ο κάδος απορριμμάτων είναι άδειος", + "trashRestore": "Επαναφέρω", + "trashRestored": "Αποκαταστάθηκε {{name}}", + "trashRestoreFailed": "Αποτυχία επαναφοράς στοιχείου. Η αρχική διαδρομή ενδέχεται να υπάρχει ήδη.", + "trashDeletePermanently": "Οριστική διαγραφή", + "trashDeleteConfirm": "Να διαγραφεί οριστικά το {{name}}; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "trashPermanentlyDeleted": "Διαγράφηκε οριστικά {{name}}", + "trashDeleteFailed": "Η οριστική διαγραφή του στοιχείου απέτυχε", + "trashEmptyAction": "Άδειασμα Κάδου", + "trashEmptyConfirm": "Να διαγραφούν οριστικά όλα τα στοιχεία στον Κάδο; Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας.", + "trashEmptied": "Ο κάδος αδειάστηκε", + "trashRetention": "Αυτόματος καθαρισμός μετά από (ημέρες)", + "trashRetentionSaved": "Η διατήρηση απορριμμάτων ενημερώθηκε", + "trashRetentionFailed": "Αποτυχία ενημέρωσης διατήρησης κάδου απορριμμάτων", + "trashUnavailableConfirm": "Αυτό το στοιχείο δεν μπορεί να μετακινηθεί στον Κάδο, συνήθως επειδή βρίσκεται σε άλλο σύστημα αρχείων ή απαιτεί αυξημένα δικαιώματα. Να διαγραφεί οριστικά;", + "recent": "Πρόσφατος", + "pinned": "Καρφιτσωμένο", + "folderShortcuts": "Συντομεύσεις φακέλων", + "failedToReconnectSSH": "Αποτυχία επανασύνδεσης συνεδρίας SSH", + "openTerminalHere": "Άνοιγμα τερματικού εδώ", + "run": "Τρέξιμο", + "openTerminalInFolder": "Άνοιγμα τερματικού σε αυτόν τον φάκελο", + "openTerminalInFileLocation": "Άνοιγμα τερματικού στη θέση αρχείου", + "runningFile": "Τρέξιμο - {{file}}", + "onlyRunExecutableFiles": "Μπορεί να εκτελέσει μόνο εκτελέσιμα αρχεία", + "directories": "Κατάλογοι", + "removedFromRecentFiles": "Αφαιρέθηκε το \"{{name}}\" από πρόσφατα αρχεία", + "removeFailed": "Η κατάργηση απέτυχε.", + "unpinnedSuccessfully": "Το \"{{name}}\" ξεκαρφιτσώθηκε με επιτυχία", + "unpinFailed": "Το ξεκαρφίτσωμα απέτυχε.", + "removedShortcut": "Αφαιρέθηκε η συντόμευση \"{{name}}\"", + "removeShortcutFailed": "Η κατάργηση της συντόμευσης απέτυχε.", + "clearedAllRecentFiles": "Διαγράφηκαν όλα τα πρόσφατα αρχεία", + "clearFailed": "Η διαγραφή απέτυχε.", + "removeFromRecentFiles": "Κατάργηση από πρόσφατα αρχεία", + "clearAllRecentFiles": "Διαγραφή όλων των πρόσφατων αρχείων", + "unpinFile": "Ξεκαρφίτσωμα αρχείου", + "removeShortcut": "Κατάργηση συντόμευσης", + "pinFile": "Καρφίτσωμα αρχείου", + "addToShortcuts": "Προσθήκη στις συντομεύσεις", + "pasteFailed": "Η επικόλληση απέτυχε.", + "noUndoableActions": "Δεν υπάρχουν ενέργειες που μπορούν να αναιρεθούν", + "undoCopySuccess": "Αναίρεση λειτουργίας αντιγραφής: Διαγράφηκαν {{count}} αντιγραμμένα αρχεία", + "undoCopyFailedDelete": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η διαγραφή των αντιγραμμένων αρχείων", + "undoCopyFailedNoInfo": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η εύρεση των πληροφοριών του αντιγραμμένου αρχείου", + "undoMoveSuccess": "Αναίρεση λειτουργίας μετακίνησης: Μετακινήθηκαν τα αρχεία {{count}} πίσω στην αρχική τους θέση", + "undoMoveFailedMove": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η επαναφορά κανενός αρχείου", + "undoMoveFailedNoInfo": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η εύρεση των πληροφοριών του μετακινημένου αρχείου", + "undoDeleteNotSupported": "Η λειτουργία διαγραφής δεν μπορεί να αναιρεθεί: Τα αρχεία έχουν διαγραφεί οριστικά από τον διακομιστή", + "undoTypeNotSupported": "Μη υποστηριζόμενος τύπος λειτουργίας αναίρεσης", + "undoOperationFailed": "Η λειτουργία αναίρεσης απέτυχε", + "unknownError": "Άγνωστο σφάλμα", + "confirm": "Επιβεβαιώνω", + "find": "Εύρημα...", + "replace": "Αντικαθιστώ", + "downloadInstead": "Λήψη εναλλακτικά", + "keyboardShortcuts": "Συντομεύσεις πληκτρολογίου", + "searchAndReplace": "Αναζήτηση & Αντικατάσταση", + "editing": "Επεξεργασία", + "search": "Ερευνα", + "findNext": "Εύρεση επόμενου", + "findPrevious": "Εύρεση προηγούμενου", + "save": "Εκτός", + "selectAll": "Επιλογή όλων", + "undo": "Ξεκάνω", + "redo": "Ξανακάνω", + "moveLineUp": "Μετακίνηση γραμμής προς τα πάνω", + "moveLineDown": "Μετακίνηση γραμμής προς τα κάτω", + "toggleComment": "Εναλλαγή σχολίου", + "autoComplete": "Αυτόματη συμπλήρωση", + "imageLoadError": "Αποτυχία φόρτωσης εικόνας", + "startTyping": "Ξεκινήστε να πληκτρολογείτε...", + "unknownSize": "Άγνωστο μέγεθος", + "fileIsEmpty": "Το αρχείο είναι κενό", + "largeFileWarning": "Προειδοποίηση για μεγάλο αρχείο", + "largeFileWarningDesc": "Αυτό το αρχείο έχει μέγεθος {{size}} , το οποίο ενδέχεται να προκαλέσει προβλήματα απόδοσης όταν ανοιχτεί ως κείμενο.", + "fileNotFoundAndRemoved": "Το αρχείο \"{{name}}\" δεν βρέθηκε και έχει αφαιρεθεί από τα πρόσφατα/καρφιτσωμένα αρχεία", + "failedToLoadFile": "Αποτυχία φόρτωσης αρχείου: {{error}}", + "serverErrorOccurred": "Παρουσιάστηκε σφάλμα διακομιστή. Δοκιμάστε ξανά αργότερα.", + "autoSaveFailed": "Η αυτόματη αποθήκευση απέτυχε", + "fileAutoSaved": "Το αρχείο αποθηκεύτηκε αυτόματα", + "moveFileFailed": "Αποτυχία μετακίνησης {{name}}", + "moveOperationFailed": "Η λειτουργία μετακίνησης απέτυχε", + "canOnlyCompareFiles": "Μπορεί να συγκρίνει μόνο δύο αρχεία", + "comparingFiles": "Σύγκριση αρχείων: {{file1}} και {{file2}}", + "dragFailed": "Η λειτουργία μεταφοράς απέτυχε", + "filePinnedSuccessfully": "Το αρχείο \"{{name}}\" καρφιτσώθηκε με επιτυχία", + "pinFileFailed": "Αποτυχία καρφιτσώματος αρχείου", + "fileUnpinnedSuccessfully": "Το αρχείο \"{{name}}\" ξεκαρφιτσώθηκε με επιτυχία", + "unpinFileFailed": "Αποτυχία ξεκαρφιτσώματος αρχείου", + "shortcutAddedSuccessfully": "Η συντόμευση φακέλου \"{{name}}\" προστέθηκε με επιτυχία", + "addShortcutFailed": "Η προσθήκη συντόμευσης απέτυχε", + "operationCompletedSuccessfully": "{{operation}} {{count}} στοιχεία με επιτυχία", + "operationCompleted": "{{operation}} {{count}} στοιχεία", + "downloadFileSuccess": "Το αρχείο {{name}} λήφθηκε με επιτυχία.", + "downloadFileFailed": "Η λήψη απέτυχε.", + "downloadingFile": "Λήψη {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Μετακίνηση σε {{name}}", + "diffCompareWith": "Διαφορά σε σύγκριση με {{name}}", + "dragOutsideToDownload": "Σύρετε έξω από το παράθυρο για λήψη ({{count}} αρχείων)", + "newFolderDefault": "ΝέοςΦάκελος", + "newFileDefault": "ΝέοΑρχείο.txt", + "successfullyMovedItems": "Επιτυχής μετακίνηση {{count}} στοιχείων στο {{target}}", + "move": "Κίνηση", + "searchInFile": "Αναζήτηση σε αρχείο (Ctrl+F)", + "showKeyboardShortcuts": "Εμφάνιση συντομεύσεων πληκτρολογίου", + "decreaseFontSize": "Μείωση μεγέθους γραμματοσειράς", + "increaseFontSize": "Αύξηση μεγέθους γραμματοσειράς", + "startWritingMarkdown": "Ξεκινήστε να γράφετε το περιεχόμενό σας με έκπτωση...", + "loadingFileComparison": "Φόρτωση σύγκρισης αρχείων...", + "reload": "Γεμίζω πάλι", + "compare": "Συγκρίνω", + "sideBySide": "Πλάι πλάι", + "inline": "Ενσωματωμένο", + "fileComparison": "Σύγκριση αρχείων: {{file1}} έναντι {{file2}}", + "fileTooLarge": "Το αρχείο είναι πολύ μεγάλο: {{error}}", + "sshConnectionFailed": "Η σύνδεση SSH απέτυχε. Ελέγξτε τη σύνδεσή σας με {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Αποτυχία φόρτωσης αρχείου: {{error}}", + "connecting": "Συνδετικός...", + "connectedSuccessfully": "Η σύνδεση ολοκληρώθηκε με επιτυχία", + "totpVerificationFailed": "Η επαλήθευση TOTP απέτυχε.", + "warpgateVerificationFailed": "Η πιστοποίηση Warpgate απέτυχε.", + "authenticationFailed": "Η επαλήθευση ταυτότητας απέτυχε", + "incorrectPassphrase": "Λανθασμένη φράση πρόσβασης. Δοκιμάστε ξανά.", + "verificationCodePrompt": "Κωδικός επαλήθευσης:", + "changePermissions": "Αλλαγή Δικαιωμάτων", + "currentPermissions": "Τρέχοντα Δικαιώματα", + "owner": "Ιδιοκτήτης", + "group": "Ομάδα", + "others": "Άλλοι", + "read": "Ανάγνωση", + "write": "Γράφω", + "execute": "Εκτελώ", + "permissionsChangedSuccessfully": "Τα δικαιώματα άλλαξαν με επιτυχία", + "failedToChangePermissions": "Αποτυχία αλλαγής δικαιωμάτων", + "name": "Ονομα", + "sortByName": "Ονομα", + "sortByDate": "Ημερομηνία τροποποίησης", + "sortBySize": "Μέγεθος", + "ascending": "Αύξουσα", + "descending": "Φθίνων", + "root": "Ρίζα", + "new": "Νέος", + "sortBy": "Ταξινόμηση κατά", + "items": "Αντικείμενα", + "selected": "Επιλεγμένο", + "editor": "Συντάκτης", + "octal": "Οκτάεδρος", + "storage": "Αποθήκευση", + "disk": "Δίσκος", + "used": "Μεταχειρισμένος", + "of": "του", + "toggleSidebar": "Εναλλαγή πλευρικής γραμμής", + "compactLayout": "Χρήση συμπαγούς διάταξης", + "comfortableLayout": "Χρησιμοποιήστε άνετη διάταξη", + "cannotLoadPdf": "Δεν είναι δυνατή η φόρτωση του PDF", + "pdfLoadError": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση αυτού του αρχείου PDF.", + "loadingPdf": "Φόρτωση PDF...", + "loadingPage": "Φόρτωση σελίδας..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Μεταφορά αρχείων μεταξύ δύο κεντρικών υπολογιστών SSH.", + "sourceServer": "Διακομιστής πηγής", + "destinationServer": "Διακομιστής προορισμού", + "selectHost": "Επιλογή κεντρικού υπολογιστή", + "noHostSelected": "Δεν έχει επιλεγεί κεντρικός υπολογιστής", + "move": "Κίνηση", + "copy": "Αντίγραφο", + "upload": "Μεταφόρτωση", + "itemsSelected": "Επιλέχθηκε {{count}} στοιχείο", + "itemsSelected_other": "Επιλέχθηκαν {{count}} στοιχεία", + "ready": "Ετοιμος", + "loadingHosts": "Φόρτωση κεντρικών υπολογιστών...", + "name": "Ονομα", + "modified": "Τροποποιημένο", + "size": "Μέγεθος", + "noFiles": "Δεν υπάρχουν αρχεία", + "open": "Ανοιχτό", + "up": "Επάνω", + "refresh": "Φρεσκάρω", + "copyToTarget": "Αντιγραφή στον κατάλογο προορισμού", + "rename": "Μετονομάζω", + "delete": "Διαγράφω", + "editPermissions": "Επεξεργασία δικαιωμάτων", + "createNewFolder": "Δημιουργία νέου φακέλου", + "deleteSelectedItems": "Διαγραφή επιλεγμένων στοιχείων", + "deleteConfirm": "Διαγραφή του επιλεγμένου {{count}} στοιχείου;", + "deleteConfirm_other": "Διαγραφή {{count}} επιλεγμένων στοιχείων;", + "cancel": "Ματαίωση", + "save": "Εκτός", + "folderCreated": "Ο φάκελος δημιουργήθηκε", + "itemRenamed": "Το στοιχείο μετονομάστηκε", + "actionFailed": "Η ενέργεια απέτυχε", + "itemsDeleted": "{{count}} στοιχείο διαγράφηκε", + "itemsDeleted_other": "{{count}} στοιχεία διαγράφηκαν", + "deleteFailed": "Η διαγραφή απέτυχε.", + "permissionsUpdated": "Τα δικαιώματα ενημερώθηκαν", + "noRemoteSessionConnected": "Δεν έχει συνδεθεί απομακρυσμένη συνεδρία", + "failedToListFiles": "Αποτυχία καταχώρισης αρχείων", + "failedToRefresh": "Η ανανέωση απέτυχε", + "destinationInsideSource": "Ο προορισμός δεν μπορεί να βρίσκεται εντός της επιλεγμένης διαδρομής προέλευσης.", + "sameHostNoOp": "Η προέλευση και ο προορισμός είναι ο ίδιος φάκελος." + }, + "hosts": { + "fileManager": "Διαχειριστής αρχείων", + "tabFiles": "Αρχεία", + "enableFileManagerMonitor": "Ενεργοποίηση Διαχείρισης Αρχείων", + "enableFileManagerMonitorDesc": "Περιηγηθείτε και διαχειριστείτε αρχεία σε αυτόν τον κεντρικό υπολογιστή μέσω SFTP", + "scpLegacyLabel": "Λειτουργία παλαιού τύπου SCP", + "scpLegacyDesc": "Χρησιμοποιήστε μεταφορά αρχείων παλαιού τύπου για διακομιστές που δεν υποστηρίζουν το υποσύστημα SFTP (π.χ. ενσωματωμένοι ή ελάχιστοι διακομιστές SSH).", + "defaultPathLabel": "Προεπιλεγμένη διαδρομή", + "fileManagerPathHint": "Ο κατάλογος που θα ανοίξει κατά την εκκίνηση του διαχειριστή αρχείων για αυτόν τον κεντρικό υπολογιστή.", + "fileManagerUrlCopied": "Η διεύθυνση URL του διαχειριστή αρχείων αντιγράφηκε", + "copyFileManagerUrlAction": "Αντιγραφή URL Διαχείρισης Αρχείων" + }, + "settings": { + "host": { + "enable": { + "label": "Ενεργοποίηση Διαχείρισης Αρχείων" + }, + "defaultPath": { + "label": "Προεπιλεγμένη διαδρομή" + }, + "scpLegacy": { + "label": "Λειτουργία παλαιού τύπου SCP" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Αρχεία" + } +} diff --git a/plugins/file-manager/locales/translated/es_ES.json b/plugins/file-manager/locales/translated/es_ES.json new file mode 100644 index 000000000..6ca422054 --- /dev/null +++ b/plugins/file-manager/locales/translated/es_ES.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Ningún host seleccionado", + "initializingEditor": "Inicializando editor...", + "file": "Archivo", + "folder": "Carpeta", + "uploadFile": "Subir archivo", + "downloadFile": "Descargar", + "extractArchive": "Extraer archivo", + "extractingArchive": "Extrayendo {{name}}...", + "archiveExtractedSuccessfully": "{{name}} extraído correctamente", + "extractFailed": "Extracción fallida", + "compressFile": "Comprimir archivo", + "compressFiles": "Comprimir archivos", + "compressFilesDesc": "Comprimir {{count}} elementos en un archivo", + "archiveName": "Nombre del archivo", + "enterArchiveName": "Introduzca el nombre del archivo...", + "compressionFormat": "Formato de compresión", + "selectedFiles": "Archivos seleccionados", + "andMoreFiles": "y {{count}} más...", + "compress": "Comprimir", + "compressingFiles": "Comprimiendo {{count}} elementos en {{name}}...", + "filesCompressedSuccessfully": "{{name}} creado correctamente", + "compressFailed": "La compresión falló", + "edit": "Editar", + "preview": "Vista previa", + "previous": "Anterior", + "next": "Siguiente", + "pageXOfY": "Página {{current}} de {{total}}", + "zoomOut": "Alejar", + "zoomIn": "Acercar", + "newFile": "Nuevo archivo", + "newFolder": "Nueva carpeta", + "rename": "Renombrar", + "uploading": "Subiendo...", + "uploadingFile": "Subiendo {{name}}...", + "fileName": "Nombre del archivo", + "folderName": "Nombre de la carpeta", + "fileUploadedSuccessfully": "Archivo \"{{name}}\" subido correctamente", + "failedToUploadFile": "Error al subir el archivo", + "uploadingFolderFiles": "Subiendo {{count}} archivo(s)...", + "uploadedFolderFiles": "Se subieron correctamente los archivos {{count}}", + "uploadedFolderPartial": "Se subieron {{uploaded}} archivos, {{failed}} falló.", + "fileDownloadedSuccessfully": "Archivo \"{{name}}\" descargado correctamente", + "failedToDownloadFile": "Error al descargar el archivo", + "fileCreatedSuccessfully": "Archivo \"{{name}}\" creado correctamente", + "folderCreatedSuccessfully": "Carpeta \"{{name}}\" creada correctamente", + "failedToCreateItem": "No se pudo crear el elemento", + "operationFailed": "La operación {{operation}} falló para {{name}}: {{error}}", + "failedToResolveSymlink": "No se pudo resolver el enlace simbólico", + "itemsDeletedSuccessfully": "{{count}} elementos eliminados correctamente", + "failedToDeleteItems": "No se pudieron eliminar los elementos", + "sudoPasswordRequired": "Se requiere contraseña de administrador", + "enterSudoPassword": "Introduzca la contraseña sudo para continuar esta operación", + "sudoPassword": "Contraseña sudo", + "sudoOperationFailed": "La operación sudo falló", + "sudoAuthFailed": "La autenticación sudo falló", + "dragFilesToUpload": "Suelte los archivos aquí para subirlos", + "dropToUploadHere": "Arrastra para subir aquí", + "dropToDownloadHere": "Haz clic aquí para descargar", + "localFiles": "Local", + "showLocalFiles": "Mostrar archivos locales (vista dividida)", + "hideLocalFiles": "Ocultar archivos locales", + "localHome": "Carpeta de inicio", + "localPathPlaceholder": "Ruta de la carpeta local", + "localShowHidden": "Mostrar archivos ocultos", + "localHideHidden": "Ocultar archivos ocultos", + "localRevealInFileManager": "Mostrar en el administrador de archivos del sistema", + "localCannotOpenFolder": "No se puede abrir la carpeta", + "localItemCount": "{{count}} elementos", + "localSelectedCount": "{{count}} seleccionado", + "back": "Atrás", + "forward": "Adelante", + "up": "Arriba", + "kind": "Amable", + "localUploadingProgress": "Subiendo {{current}} de {{total}}", + "localDownloadingProgress": "Descargando {{current}} de {{total}}", + "localTransferInFlight": "{{count}} al instante", + "localPreparingDownload": "Preparando la descarga...", + "localTransferCancelled": "Transferencia cancelada", + "localNothingToTransfer": "Nada que transferir", + "localUploadComplete": "Archivo(s) subido(s) {{count}}", + "localUploadFailed": "Error al cargar la página", + "localUploadPartial": "Se subieron {{done}} archivos, {{failed}} falló.", + "localDownloadComplete": "Archivo(s) descargado(s) {{count}}", + "localDownloadFailed": "Falló la descarga.", + "localDownloadPartial": "Se descargaron {{done}} archivos, {{failed}} falló.", + "localReplaceExistingPrompt": "Ya existen {{count}} elementos en esta carpeta. ¿Desea reemplazarlos?", + "localReplace": "Reemplazar", + "localSkipExisting": "Saltar", + "localSkippedExisting": "Se omitieron {{count}} elementos existentes", + "localUnsafeNamesSkipped": "Se omitieron {{count}} elementos cuyos nombres no se pueden usar en este ordenador.", + "localOpen": "Abierto", + "localOpenFolder": "Abrir carpeta", + "localUploadToRemote": "Subir al servidor", + "localUploadToRemoteMany": "Sube {{count}} elementos al servidor", + "localMoveToTrash": "Mover a la basura", + "localMoveToTrashMany": "Mover {{count}} elementos a la Papelera", + "localTrashConfirmSingle": "¿Mover \"{{name}}\" a la papelera?", + "localTrashConfirmFolder": "¿Mover la carpeta \"{{name}}\" y todo su contenido a la Papelera?", + "localTrashConfirmMany": "¿Mover {{count}} elementos a la Papelera?", + "localTrashed": "Se movieron {{count}} elementos a la Papelera", + "localTrashFailed": "No se pudo mover a la papelera", + "localRenameFailed": "Error al cambiar el nombre", + "localCreateFailed": "No se pudo crear el artículo.", + "goToParentFolder": "Ir a la carpeta principal", + "resizeSidebar": "Arrastra para cambiar el tamaño de la barra lateral (haz doble clic para restablecer).", + "resizeLocalPane": "Arrastra para cambiar el tamaño del panel local (haz doble clic para restablecer).", + "columns": "Columnas", + "columnsHint": "Haz clic con el botón derecho para elegir las columnas.", + "emptyFolder": "Esta carpeta está vacía", + "searchFiles": "Buscar archivos...", + "upload": "Subir", + "selectHostToStart": "Seleccione un host para iniciar la gestión de archivos", + "sshRequiredForFileManager": "El gestor de archivos requiere SSH. Este host no tiene SSH habilitado.", + "failedToConnect": "No se pudo conectar a SSH", + "failedToLoadDirectory": "No se pudo cargar el directorio", + "noSSHConnection": "No hay conexión SSH disponible", + "copy": "Copiar", + "cut": "Cortar", + "paste": "Pegar", + "copyPath": "Copiar ruta", + "copyPaths": "Copiar rutas", + "delete": "Eliminar", + "properties": "Propiedades", + "refresh": "Actualizar", + "downloadFiles": "Descargar {{count}} archivos al navegador", + "copyFiles": "Copiar {{count}} elementos", + "cutFiles": "Cortar {{count}} elementos", + "deleteFiles": "Eliminar {{count}} elementos", + "filesCopiedToClipboard": "{{count}} elementos copiados al portapapeles", + "filesCutToClipboard": "{{count}} elementos cortados al portapapeles", + "pathCopiedToClipboard": "Ruta copiada al portapapeles", + "pathsCopiedToClipboard": "{{count}} rutas copiadas al portapapeles", + "failedToCopyPath": "Error al copiar la ruta al portapapeles", + "copyFolderLink": "Copiar enlace a la carpeta", + "copyCurrentFolderLink": "Copiar enlace a la carpeta actual", + "folderLinkCopied": "Enlace a la carpeta copiado al portapapeles", + "failedToCopyFolderLink": "Error al copiar el enlace a la carpeta", + "movedItems": "{{count}} elementos movidos", + "failedToDeleteItem": "Error al eliminar el elemento", + "itemRenamedSuccessfully": "{{type}} renombrado con éxito", + "failedToRenameItem": "Error al renombrar el elemento", + "download": "Descargar", + "openExternalEditor": "Abrir externamente", + "chooseExternalEditor": "Elegir editor externo", + "externalEditorSelected": "Editor externo seleccionado", + "externalEditorDesktopOnly": "El editor externo solo está disponible en la aplicación de escritorio", + "externalEditorOpened": "Abierto en el editor externo. Al guardar allí se subirán los cambios al servidor.", + "failedToOpenExternalEditor": "Error al abrir el editor externo", + "failedToSelectExternalEditor": "Error al seleccionar el editor externo", + "permissions": "Permisos", + "size": "Tamaño", + "modified": "Modificado", + "path": "Ruta", + "confirmDelete": "¿Está seguro de que desea eliminar {{name}}?", + "permissionDenied": "Permiso denegado", + "serverError": "Error del servidor", + "fileSavedSuccessfully": "Archivo guardado con éxito", + "failedToSaveFile": "Error al guardar el archivo", + "confirmDeleteSingleItem": "¿Está seguro de que desea eliminar permanentemente \"{{name}}\"?", + "confirmDeleteMultipleItems": "¿Está seguro de que desea eliminar permanentemente {{count}} elementos?", + "confirmDeleteMultipleItemsWithFolders": "¿Está seguro de que desea eliminar permanentemente {{count}} elementos? Esto incluye carpetas y su contenido.", + "confirmDeleteFolder": "¿Está seguro de que desea eliminar permanentemente la carpeta \"{{name}}\" y todo su contenido?", + "permanentDeleteWarning": "Esta acción no se puede deshacer. Los elementos se eliminarán permanentemente del servidor.", + "moveToTrashWarning": "El/los elemento(s) se moverán a la Papelera de Termix y se podrán restaurar antes de que expire el período de retención.", + "itemsMovedToTrash": "{{count}} elemento(s) movido(s) a la Papelera", + "trash": "Basura", + "trashDescription": "Los elementos eliminados se conservan durante {{days}} día(s) en este host.", + "trashLoadFailed": "No se pudo cargar la papelera.", + "trashEmpty": "La papelera está vacía", + "trashRestore": "Restaurar", + "trashRestored": "Restaurado {{name}}", + "trashRestoreFailed": "No se pudo restaurar el elemento. Es posible que la ruta original ya exista.", + "trashDeletePermanently": "Eliminar permanentemente", + "trashDeleteConfirm": "¿Eliminar permanentemente {{name}}? Esto no se puede deshacer.", + "trashPermanentlyDeleted": "Eliminado permanentemente {{name}}", + "trashDeleteFailed": "No se pudo eliminar el elemento de forma permanente.", + "trashEmptyAction": "Vaciar la basura", + "trashEmptyConfirm": "¿Eliminar permanentemente todos los elementos de la Papelera? Esta acción es irreversible.", + "trashEmptied": "Basura vaciada", + "trashRetention": "Limpieza automática después de (días)", + "trashRetentionSaved": "Se ha actualizado la retención de basura.", + "trashRetentionFailed": "Error al actualizar la retención de la papelera", + "trashUnavailableConfirm": "Este elemento no se puede mover a la Papelera, normalmente porque se encuentra en otro sistema de archivos o requiere permisos de administrador. ¿Desea eliminarlo permanentemente?", + "recent": "Recientes", + "pinned": "Fijados", + "folderShortcuts": "Accesos directos a carpetas", + "failedToReconnectSSH": "Error al reconectar la sesión SSH", + "openTerminalHere": "Abrir terminal aquí", + "run": "Ejecutar", + "openTerminalInFolder": "Abrir terminal en esta carpeta", + "openTerminalInFileLocation": "Abrir terminal en la ubicación del archivo", + "runningFile": "Ejecutando - {{file}}", + "onlyRunExecutableFiles": "Solo se pueden ejecutar archivos ejecutables", + "directories": "Directorios", + "removedFromRecentFiles": "Se eliminó \"{{name}}\" de los archivos recientes", + "removeFailed": "Error al eliminar", + "unpinnedSuccessfully": "Se desancló \"{{name}}\" correctamente", + "unpinFailed": "Error al desanclar", + "removedShortcut": "Se eliminó el acceso directo \"{{name}}\"", + "removeShortcutFailed": "Error al eliminar el acceso directo", + "clearedAllRecentFiles": "Se borraron todos los archivos recientes", + "clearFailed": "Error al borrar", + "removeFromRecentFiles": "Eliminar de archivos recientes", + "clearAllRecentFiles": "Borrar todos los archivos recientes", + "unpinFile": "Desanclar archivo", + "removeShortcut": "Eliminar acceso directo", + "pinFile": "Anclar archivo", + "addToShortcuts": "Añadir a accesos directos", + "pasteFailed": "Error al pegar", + "noUndoableActions": "No hay acciones para deshacer", + "undoCopySuccess": "Se deshizo la copia: se eliminaron {{count}} archivos copiados", + "undoCopyFailedDelete": "Error al deshacer: no se pudo eliminar ningún archivo copiado", + "undoCopyFailedNoInfo": "Error al deshacer: no se encontró información de los archivos copiados", + "undoMoveSuccess": "Se deshizo el movimiento: se movieron {{count}} archivos a su ubicación original", + "undoMoveFailedMove": "Error al deshacer: no se pudo mover ningún archivo de vuelta", + "undoMoveFailedNoInfo": "Error al deshacer: no se encontró información de los archivos movidos", + "undoDeleteNotSupported": "La operación de eliminación no se puede deshacer: los archivos se han eliminado permanentemente del servidor", + "undoTypeNotSupported": "Tipo de operación de deshacer no compatible", + "undoOperationFailed": "Error en la operación de deshacer", + "unknownError": "Error desconocido", + "confirm": "Confirmar", + "find": "Buscar...", + "replace": "Reemplazar", + "downloadInstead": "Descargar en su lugar", + "keyboardShortcuts": "Atajos de teclado", + "searchAndReplace": "Buscar y reemplazar", + "editing": "Edición", + "search": "Buscar", + "findNext": "Buscar siguiente", + "findPrevious": "Buscar anterior", + "save": "Guardar", + "selectAll": "Seleccionar todo", + "undo": "Deshacer", + "redo": "Rehacer", + "moveLineUp": "Mover línea hacia arriba", + "moveLineDown": "Mover línea hacia abajo", + "toggleComment": "Alternar comentario", + "autoComplete": "Autocompletar", + "imageLoadError": "Error al cargar la imagen", + "startTyping": "Empiece a escribir...", + "unknownSize": "Tamaño desconocido", + "fileIsEmpty": "El archivo está vacío", + "largeFileWarning": "Advertencia de archivo grande", + "largeFileWarningDesc": "Este archivo tiene un tamaño de {{size}}, lo que puede causar problemas de rendimiento al abrirlo como texto.", + "fileNotFoundAndRemoved": "El archivo \"{{name}}\" no se encontró y se ha eliminado de los archivos recientes/fijados.", + "failedToLoadFile": "Error al cargar el archivo: {{error}}", + "serverErrorOccurred": "Se ha producido un error en el servidor. Vuelva a intentarlo más tarde.", + "autoSaveFailed": "Error en el autoguardado", + "fileAutoSaved": "Archivo autoguardado", + "moveFileFailed": "Error al mover {{name}}", + "moveOperationFailed": "Error en la operación de mover", + "canOnlyCompareFiles": "Solo se pueden comparar dos archivos", + "comparingFiles": "Comparando archivos: {{file1}} y {{file2}}", + "dragFailed": "Error en la operación de arrastre", + "filePinnedSuccessfully": "Archivo \"{{name}}\" fijado correctamente", + "pinFileFailed": "Error al fijar el archivo", + "fileUnpinnedSuccessfully": "Archivo \"{{name}}\" desfijado correctamente", + "unpinFileFailed": "No se pudo desanclar el archivo", + "shortcutAddedSuccessfully": "Acceso directo a la carpeta \"{{name}}\" añadido correctamente", + "addShortcutFailed": "No se pudo añadir el acceso directo", + "operationCompletedSuccessfully": "{{operation}} {{count}} elementos correctamente", + "operationCompleted": "{{operation}} {{count}} elementos", + "downloadFileSuccess": "Archivo {{name}} descargado correctamente", + "downloadFileFailed": "Error en la descarga", + "downloadingFile": "Descargando {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Mover a {{name}}", + "diffCompareWith": "Comparar diferencias con {{name}}", + "dragOutsideToDownload": "Arrastrar fuera de la ventana para descargar ({{count}} archivos)", + "newFolderDefault": "NuevaCarpeta", + "newFileDefault": "NuevoArchivo.txt", + "successfullyMovedItems": "{{count}} elementos movidos correctamente a {{target}}", + "move": "Mover", + "searchInFile": "Buscar en archivo (Ctrl+F)", + "showKeyboardShortcuts": "Mostrar atajos de teclado", + "decreaseFontSize": "Reducir tamaño de fuente", + "increaseFontSize": "Aumentar tamaño de fuente", + "startWritingMarkdown": "Empiece a escribir su contenido Markdown...", + "loadingFileComparison": "Cargando comparación de archivos...", + "reload": "Recargar", + "compare": "Comparar", + "sideBySide": "Lado a lado", + "inline": "En línea", + "fileComparison": "Comparación de archivos: {{file1}} vs {{file2}}", + "fileTooLarge": "Archivo demasiado grande: {{error}}", + "sshConnectionFailed": "Error de conexión SSH. Verifique su conexión a {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Error al cargar el archivo: {{error}}", + "connecting": "Conectando...", + "connectedSuccessfully": "Conectado correctamente", + "totpVerificationFailed": "Error en la verificación TOTP", + "warpgateVerificationFailed": "Error de autenticación de Warpgate", + "authenticationFailed": "Error de autenticación", + "incorrectPassphrase": "Frase de contraseña incorrecta. Inténtelo de nuevo.", + "verificationCodePrompt": "Código de verificación:", + "changePermissions": "Cambiar permisos", + "currentPermissions": "Permisos actuales", + "owner": "Propietario", + "group": "Grupo", + "others": "Otros", + "read": "Lectura", + "write": "Escritura", + "execute": "Ejecución", + "permissionsChangedSuccessfully": "Permisos cambiados correctamente", + "failedToChangePermissions": "Error al cambiar los permisos", + "name": "Nombre", + "sortByName": "Nombre", + "sortByDate": "Fecha de modificación", + "sortBySize": "Tamaño", + "ascending": "Ascendente", + "descending": "Descendente", + "root": "Raíz", + "new": "Nuevo", + "sortBy": "Ordenar por", + "items": "Elementos", + "selected": "Seleccionados", + "editor": "Editor", + "octal": "Octal", + "storage": "Almacenamiento", + "disk": "Disco", + "used": "Usado", + "of": "de", + "toggleSidebar": "Alternar barra lateral", + "compactLayout": "Utilice un diseño compacto.", + "comfortableLayout": "Utilice una disposición cómoda", + "cannotLoadPdf": "No se puede cargar el PDF", + "pdfLoadError": "Se produjo un error al cargar este archivo PDF.", + "loadingPdf": "Cargando PDF...", + "loadingPage": "Cargando página..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Transferir archivos entre dos hosts SSH.", + "sourceServer": "Servidor de origen", + "destinationServer": "Servidor de destino", + "selectHost": "Seleccione el host", + "noHostSelected": "No se ha seleccionado ningún host.", + "move": "Mover", + "copy": "Copiar", + "upload": "Subir", + "itemsSelected": "{{count}} elemento seleccionado", + "itemsSelected_other": "{{count}} elementos seleccionados", + "ready": "Listo", + "loadingHosts": "Cargando hosts...", + "name": "Nombre", + "modified": "Modificado", + "size": "Tamaño", + "noFiles": "No hay archivos", + "open": "Abierto", + "up": "Arriba", + "refresh": "Refrescar", + "copyToTarget": "Copiar al directorio de destino", + "rename": "Rebautizar", + "delete": "Borrar", + "editPermissions": "Editar permisos", + "createNewFolder": "Crear nueva carpeta", + "deleteSelectedItems": "Eliminar elementos seleccionados", + "deleteConfirm": "¿Eliminar {{count}} elemento seleccionado?", + "deleteConfirm_other": "¿Eliminar {{count}} elementos seleccionados?", + "cancel": "Cancelar", + "save": "Ahorrar", + "folderCreated": "Carpeta creada", + "itemRenamed": "Artículo renombrado", + "actionFailed": "La acción fracasó", + "itemsDeleted": "{{count}} elemento eliminado", + "itemsDeleted_other": "{{count}} elementos eliminados", + "deleteFailed": "Error al eliminar", + "permissionsUpdated": "Permisos actualizados", + "noRemoteSessionConnected": "No hay ninguna sesión remota conectada.", + "failedToListFiles": "No se pudieron listar los archivos.", + "failedToRefresh": "No se pudo actualizar", + "destinationInsideSource": "El destino no puede estar dentro de la ruta de origen seleccionada.", + "sameHostNoOp": "La carpeta de origen y la de destino son la misma." + }, + "hosts": { + "fileManager": "Gestor de archivos", + "tabFiles": "Archivos", + "enableFileManagerMonitor": "Habilitar Gestor de archivos", + "enableFileManagerMonitorDesc": "Explorar y gestionar archivos en este host mediante SFTP", + "scpLegacyLabel": "Modo SCP heredado", + "scpLegacyDesc": "Usar transferencia de archivos heredada para servidores que no admiten el subsistema SFTP (p. ej., servidores SSH integrados o mínimos).", + "defaultPathLabel": "Ruta predeterminada", + "fileManagerPathHint": "Directorio que se abre al iniciar el gestor de archivos para este host.", + "fileManagerUrlCopied": "URL del gestor de archivos copiada", + "copyFileManagerUrlAction": "Copiar URL del gestor de archivos" + }, + "settings": { + "host": { + "enable": { + "label": "Habilitar Gestor de archivos" + }, + "defaultPath": { + "label": "Ruta predeterminada" + }, + "scpLegacy": { + "label": "Modo SCP heredado" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Archivos" + } +} diff --git a/plugins/file-manager/locales/translated/fi_FI.json b/plugins/file-manager/locales/translated/fi_FI.json new file mode 100644 index 000000000..733cd3eeb --- /dev/null +++ b/plugins/file-manager/locales/translated/fi_FI.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Ei valittua isäntää", + "initializingEditor": "Editorin alustaminen...", + "file": "Tiedosto", + "folder": "Kansio", + "uploadFile": "Lähetä tiedosto", + "downloadFile": "Lataa", + "extractArchive": "Pura arkisto", + "extractingArchive": "Puretaan {{name}}...", + "archiveExtractedSuccessfully": "{{name}} uutettu onnistuneesti", + "extractFailed": "Poiminta epäonnistui", + "compressFile": "Pakkaa tiedosto", + "compressFiles": "Pakkaa tiedostot", + "compressFilesDesc": "Pakkaa {{count}} kohdetta arkistoon", + "archiveName": "Arkiston nimi", + "enterArchiveName": "Anna arkiston nimi...", + "compressionFormat": "Pakkausmuoto", + "selectedFiles": "Valitut tiedostot", + "andMoreFiles": "ja {{count}} lisää...", + "compress": "Pakkaa", + "compressingFiles": "Pakkaamalla {{count}} alkiota {{name}}...", + "filesCompressedSuccessfully": "{{name}} luotu onnistuneesti", + "compressFailed": "Pakkaus epäonnistui", + "edit": "Muokata", + "preview": "Esikatselu", + "previous": "Edellinen", + "next": "Seuraava", + "pageXOfY": "Sivu {{current}} / {{total}}", + "zoomOut": "Loitonna", + "zoomIn": "Lähennä", + "newFile": "Uusi tiedosto", + "newFolder": "Uusi kansio", + "rename": "Nimeä uudelleen", + "uploading": "Ladataan...", + "uploadingFile": "Ladataan {{name}}...", + "fileName": "Tiedoston nimi", + "folderName": "Kansion nimi", + "fileUploadedSuccessfully": "Tiedosto \"{{name}}\" ladattu onnistuneesti", + "failedToUploadFile": "Tiedoston lataaminen epäonnistui", + "uploadingFolderFiles": "Ladataan {{count}} tiedostoa/tiedostoja...", + "uploadedFolderFiles": "{{count}} tiedostoa ladattu onnistuneesti", + "uploadedFolderPartial": "{{uploaded}} tiedoston/tiedostojen {{failed}} lataaminen epäonnistui", + "fileDownloadedSuccessfully": "Tiedosto \"{{name}}\" ladattu onnistuneesti", + "failedToDownloadFile": "Tiedoston lataaminen epäonnistui", + "fileCreatedSuccessfully": "Tiedosto \"{{name}}\" luotu onnistuneesti", + "folderCreatedSuccessfully": "Kansio \"{{name}}\" luotu onnistuneesti", + "failedToCreateItem": "Kohteen luominen epäonnistui", + "operationFailed": "{{operation}} toiminto epäonnistui kohteelle {{name}}: {{error}}", + "failedToResolveSymlink": "Symlinkin ratkaiseminen epäonnistui", + "itemsDeletedSuccessfully": "{{count}} kohdetta poistettu onnistuneesti", + "failedToDeleteItems": "Kohteiden poistaminen epäonnistui", + "sudoPasswordRequired": "Järjestelmänvalvojan salasana vaaditaan", + "enterSudoPassword": "Anna sudo-salasana jatkaaksesi tätä toimintoa", + "sudoPassword": "Sudo-salasana", + "sudoOperationFailed": "Sudo-toiminto epäonnistui", + "sudoAuthFailed": "Sudo-todennus epäonnistui", + "dragFilesToUpload": "Pudota tiedostot tähän ladataksesi", + "dropToUploadHere": "Pudota ladataksesi tähän", + "dropToDownloadHere": "Pudota ladataksesi tästä", + "localFiles": "Paikallinen", + "showLocalFiles": "Näytä paikalliset tiedostot (jaettu näkymä)", + "hideLocalFiles": "Piilota paikalliset tiedostot", + "localHome": "Kotikansio", + "localPathPlaceholder": "Paikallisen kansion polku", + "localShowHidden": "Näytä piilotetut tiedostot", + "localHideHidden": "Piilota piilotetut tiedostot", + "localRevealInFileManager": "Näytä järjestelmän tiedostonhallinnassa", + "localCannotOpenFolder": "Kansiota ei voi avata", + "localItemCount": "{{count}} kohdetta", + "localSelectedCount": "{{count}} valittu", + "back": "Takaisin", + "forward": "Eteenpäin", + "up": "Ylös", + "kind": "Ystävällinen", + "localUploadingProgress": "Ladataan {{current}} / {{total}}", + "localDownloadingProgress": "Ladataan {{current}} / {{total}}", + "localTransferInFlight": "{{count}} kerralla", + "localPreparingDownload": "Valmistellaan latausta...", + "localTransferCancelled": "Siirto peruttu", + "localNothingToTransfer": "Ei mitään siirrettävää", + "localUploadComplete": "Ladatut {{count}} tiedostot", + "localUploadFailed": "Lataus epäonnistui", + "localUploadPartial": "{{done}} tiedoston/tiedostojen {{failed}} lataaminen epäonnistui", + "localDownloadComplete": "Ladatut {{count}} tiedostot", + "localDownloadFailed": "Lataus epäonnistui", + "localDownloadPartial": "{{done}} tiedoston/tiedostojen {{failed}} lataus epäonnistui", + "localReplaceExistingPrompt": "{{count}} kohdetta on jo tässä kansiossa. Korvataanko ne?", + "localReplace": "Korvata", + "localSkipExisting": "Ohita", + "localSkippedExisting": "Ohitettu {{count}} olemassa olevaa kohdetta/kohteita", + "localUnsafeNamesSkipped": "Ohitettiin {{count}} kohdetta, joiden nimiä ei voi käyttää tässä tietokoneessa", + "localOpen": "Avata", + "localOpenFolder": "Avaa kansio", + "localUploadToRemote": "Lähetä palvelimelle", + "localUploadToRemoteMany": "Lataa {{count}} kohdetta palvelimelle", + "localMoveToTrash": "Siirrä roskakoriin", + "localMoveToTrashMany": "Siirrä {{count}} kohdetta roskakoriin", + "localTrashConfirmSingle": "Siirretäänkö \"{{name}}\" roskakoriin?", + "localTrashConfirmFolder": "Siirretäänkö kansio \"{{name}}\" ja kaikki sen sisältö roskakoriin?", + "localTrashConfirmMany": "Siirretäänkö {{count}} kohdetta roskakoriin?", + "localTrashed": "Siirretty {{count}} kohdetta roskakoriin", + "localTrashFailed": "Roskakoriin siirtäminen epäonnistui", + "localRenameFailed": "Uudelleennimeäminen epäonnistui", + "localCreateFailed": "Kohteen luominen epäonnistui", + "goToParentFolder": "Siirry pääkansioon", + "resizeSidebar": "Muuta sivupalkin kokoa vetämällä (nollaa kaksoisnapsauttamalla)", + "resizeLocalPane": "Vedä muuttaaksesi paikallisen ruudun kokoa (kaksoisnapsauta palauttaaksesi alkuperäisen)", + "columns": "Sarakkeet", + "columnsHint": "Valitse sarakkeet napsauttamalla hiiren kakkospainikkeella", + "emptyFolder": "Tämä kansio on tyhjä", + "searchFiles": "Hae tiedostoja...", + "upload": "Lataa", + "selectHostToStart": "Valitse isäntä tiedostonhallinnan aloittamiseksi", + "sshRequiredForFileManager": "Tiedostonhallinta vaatii SSH:n. Tässä isännässä SSH ei ole käytössä.", + "failedToConnect": "SSH-yhteyden muodostaminen epäonnistui", + "failedToLoadDirectory": "Hakemiston lataaminen epäonnistui", + "noSSHConnection": "SSH-yhteyttä ei ole saatavilla", + "copy": "Kopioida", + "cut": "Leikata", + "paste": "Liitä", + "copyPath": "Kopioi polku", + "copyPaths": "Kopioi polut", + "delete": "Poistaa", + "properties": "Ominaisuudet", + "refresh": "Päivitä", + "downloadFiles": "Lataa {{count}} tiedostoa selaimeen", + "copyFiles": "Kopioi {{count}} kohdetta", + "cutFiles": "Leikkaa {{count}} kohdetta", + "deleteFiles": "Poista {{count}} kohdetta", + "filesCopiedToClipboard": "{{count}} kohdetta kopioitiin leikepöydälle", + "filesCutToClipboard": "{{count}} kohdetta leikataan leikepöydälle", + "pathCopiedToClipboard": "Polku kopioitu leikepöydälle", + "pathsCopiedToClipboard": "{{count}} polut kopioitu leikepöydälle", + "failedToCopyPath": "Polun kopioiminen leikepöydälle epäonnistui", + "copyFolderLink": "Kopioi linkki kansioon", + "copyCurrentFolderLink": "Kopioi linkki nykyiseen kansioon", + "folderLinkCopied": "Kansiolinkki kopioitu leikepöydälle", + "failedToCopyFolderLink": "Kansiolinkin kopiointi epäonnistui", + "movedItems": "Siirretty {{count}} kohdetta", + "failedToDeleteItem": "Kohteen poistaminen epäonnistui", + "itemRenamedSuccessfully": "{{type}} nimettiin uudelleen onnistuneesti", + "failedToRenameItem": "Kohteen uudelleennimeäminen epäonnistui", + "download": "Lataa", + "openExternalEditor": "Avaa ulkopuolelta", + "chooseExternalEditor": "Valitse ulkoinen editori", + "externalEditorSelected": "Ulkoinen editori valittu", + "externalEditorDesktopOnly": "Ulkoinen editori on käytettävissä vain työpöytäsovelluksessa", + "externalEditorOpened": "Avattu ulkoisessa editorissa. Tallentamalla sinne muutokset latautuvat takaisin palvelimelle.", + "failedToOpenExternalEditor": "Ulkoisen editorin avaaminen epäonnistui", + "failedToSelectExternalEditor": "Ulkoisen editorin valinta epäonnistui", + "permissions": "Käyttöoikeudet", + "size": "Koko", + "modified": "Muokattu", + "path": "Polku", + "confirmDelete": "Oletko varma, että haluat poistaa {{name}}?", + "permissionDenied": "Lupa evätty", + "serverError": "Palvelinvirhe", + "fileSavedSuccessfully": "Tiedosto tallennettu onnistuneesti", + "failedToSaveFile": "Tiedoston tallennus epäonnistui", + "confirmDeleteSingleItem": "Oletko varma, että haluat poistaa pysyvästi \"{{name}}\"?", + "confirmDeleteMultipleItems": "Oletko varma, että haluat poistaa pysyvästi {{count}} kohdetta?", + "confirmDeleteMultipleItemsWithFolders": "Haluatko varmasti poistaa pysyvästi {{count}} kohdetta? Tämä koskee myös kansioita ja niiden sisältöä.", + "confirmDeleteFolder": "Oletko varma, että haluat poistaa pysyvästi kansion \"{{name}}\" ja kaiken sen sisällön?", + "permanentDeleteWarning": "Tätä toimintoa ei voi perua. Kohde/kohteet poistetaan pysyvästi palvelimelta.", + "moveToTrashWarning": "Kohde(et) siirretään Termixin roskakoriin ja ne voidaan palauttaa ennen säilytysajan päättymistä.", + "itemsMovedToTrash": "{{count}} kohdetta siirretty roskakoriin", + "trash": "Roska", + "trashDescription": "Poistetut kohteet säilytetään tällä isännöinnillä {{days}} päivää.", + "trashLoadFailed": "Roskakorin lataaminen epäonnistui", + "trashEmpty": "Roskakori on tyhjä", + "trashRestore": "Palauttaa", + "trashRestored": "Palautettu {{name}}", + "trashRestoreFailed": "Kohteen palauttaminen epäonnistui. Alkuperäinen polku saattaa jo olla olemassa.", + "trashDeletePermanently": "Poista pysyvästi", + "trashDeleteConfirm": "Poistetaanko {{name}}pysyvästi? Tätä ei voi perua.", + "trashPermanentlyDeleted": "Poistettu pysyvästi {{name}}", + "trashDeleteFailed": "Kohteen pysyvä poistaminen epäonnistui", + "trashEmptyAction": "Tyhjennä roskakori", + "trashEmptyConfirm": "Poistetaanko kaikki roskakorissa olevat kohteet pysyvästi? Tätä ei voi perua.", + "trashEmptied": "Roskakori tyhjennetty", + "trashRetention": "Automaattinen puhdistus (päivän) kuluttua", + "trashRetentionSaved": "Roskakorin säilytys päivitetty", + "trashRetentionFailed": "Roskakorin säilytystietojen päivittäminen epäonnistui.", + "trashUnavailableConfirm": "Tätä kohdetta ei voida siirtää roskakoriin, yleensä siksi, että se on toisessa tiedostojärjestelmässä tai vaatii laajennetut käyttöoikeudet. Poistetaanko se pysyvästi?", + "recent": "Viimeaikaiset", + "pinned": "Kiinnitetty", + "folderShortcuts": "Kansion pikavalinnat", + "failedToReconnectSSH": "SSH-istunnon uudelleenyhdistäminen epäonnistui", + "openTerminalHere": "Avaa terminaali tästä", + "run": "Juokse", + "openTerminalInFolder": "Avaa pääte tässä kansiossa", + "openTerminalInFileLocation": "Avaa pääte tiedoston sijainnissa", + "runningFile": "Juokseminen - {{file}}", + "onlyRunExecutableFiles": "Voi suorittaa vain suoritettavia tiedostoja", + "directories": "Hakemistot", + "removedFromRecentFiles": "Poistettu \"{{name}}\" viimeisimmistä tiedostoista", + "removeFailed": "Poisto epäonnistui", + "unpinnedSuccessfully": "Pinnauksen \"{{name}}\" irrotettu onnistuneesti", + "unpinFailed": "Irrottaminen epäonnistui", + "removedShortcut": "Pikakuvake \"{{name}} \" poistettu", + "removeShortcutFailed": "Pikakuvakkeen poistaminen epäonnistui", + "clearedAllRecentFiles": "Tyhjensi kaikki viimeisimmät tiedostot", + "clearFailed": "Tyhjennys epäonnistui", + "removeFromRecentFiles": "Poista viimeisimmistä tiedostoista", + "clearAllRecentFiles": "Tyhjennä kaikki viimeisimmät tiedostot", + "unpinFile": "Irrota tiedosto", + "removeShortcut": "Poista pikakuvake", + "pinFile": "Kiinnitä tiedosto", + "addToShortcuts": "Lisää pikakuvakkeisiin", + "pasteFailed": "Liittäminen epäonnistui", + "noUndoableActions": "Ei peruuttamattomia toimintoja", + "undoCopySuccess": "Kopioinnin peruutus: Poistettu {{count}} kopioitua tiedostoa", + "undoCopyFailedDelete": "Kumoaminen epäonnistui: Kopioituja tiedostoja ei voitu poistaa", + "undoCopyFailedNoInfo": "Kumoaminen epäonnistui: Kopioituja tiedostotietoja ei löytynyt", + "undoMoveSuccess": "Peruttu siirto: Siirsi {{count}} tiedostoa takaisin alkuperäiseen sijaintiinsa", + "undoMoveFailedMove": "Kumoaminen epäonnistui: Tiedostoja ei voitu siirtää takaisin", + "undoMoveFailedNoInfo": "Kumoaminen epäonnistui: Siirretyn tiedoston tietoja ei löytynyt", + "undoDeleteNotSupported": "Poistoa ei voi perua: Tiedostot on poistettu pysyvästi palvelimelta", + "undoTypeNotSupported": "Ei-tuettu kumoamistoimintotyyppi", + "undoOperationFailed": "Kumoamistoiminto epäonnistui", + "unknownError": "Tuntematon virhe", + "confirm": "Vahvistaa", + "find": "Löytää...", + "replace": "Korvata", + "downloadInstead": "Lataa sen sijaan", + "keyboardShortcuts": "Pikanäppäimet", + "searchAndReplace": "Etsi ja korvaa", + "editing": "Muokkaus", + "search": "Haku", + "findNext": "Etsi seuraava", + "findPrevious": "Etsi edellinen", + "save": "Tallentaa", + "selectAll": "Valitse kaikki", + "undo": "Kumoa", + "redo": "Tee uudelleen", + "moveLineUp": "Siirrä riviä ylös", + "moveLineDown": "Siirrä rivi alas", + "toggleComment": "Näytä kommentti", + "autoComplete": "Automaattinen täydennys", + "imageLoadError": "Kuvan lataaminen epäonnistui", + "startTyping": "Aloita kirjoittaminen...", + "unknownSize": "Tuntematon koko", + "fileIsEmpty": "Tiedosto on tyhjä", + "largeFileWarning": "Varoitus suuresta tiedostosta", + "largeFileWarningDesc": "Tiedoston koko on {{size}} , mikä voi aiheuttaa suorituskykyongelmia avattaessa sitä tekstinä.", + "fileNotFoundAndRemoved": "Tiedostoa \"{{name}}\" ei löytynyt ja se on poistettu viimeksi käytettyjen/kiinnitettyjen tiedostojen joukosta.", + "failedToLoadFile": "Tiedoston lataus epäonnistui: {{error}}", + "serverErrorOccurred": "Palvelinvirhe. Yritä myöhemmin uudelleen.", + "autoSaveFailed": "Automaattinen tallennus epäonnistui", + "fileAutoSaved": "Tiedosto tallennettu automaattisesti", + "moveFileFailed": "Siirto epäonnistui {{name}}", + "moveOperationFailed": "Siirto epäonnistui", + "canOnlyCompareFiles": "Voi verrata vain kahta tiedostoa", + "comparingFiles": "Tiedostojen vertailu: {{file1}} ja {{file2}}", + "dragFailed": "Vetotoiminto epäonnistui", + "filePinnedSuccessfully": "Tiedosto \"{{name}}\" kiinnitetty onnistuneesti", + "pinFileFailed": "Tiedoston kiinnittäminen epäonnistui", + "fileUnpinnedSuccessfully": "Tiedosto \"{{name}}\" irrotettu onnistuneesti", + "unpinFileFailed": "Tiedoston irrottaminen epäonnistui", + "shortcutAddedSuccessfully": "Kansion pikakuvake \"{{name}}\" lisätty onnistuneesti", + "addShortcutFailed": "Pikakuvakkeen lisääminen epäonnistui", + "operationCompletedSuccessfully": "{{operation}} {{count}} kohteita onnistuneesti", + "operationCompleted": "{{operation}} {{count}} kohdetta", + "downloadFileSuccess": "Tiedosto {{name}} ladattu onnistuneesti", + "downloadFileFailed": "Lataus epäonnistui", + "downloadingFile": "Ladataan {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Siirrä kohtaan {{name}}", + "diffCompareWith": "Vertaa eroa kohtaan {{name}}", + "dragOutsideToDownload": "Vedä ikkunan ulkopuolelle ladataksesi ({{count}} tiedostoa)", + "newFolderDefault": "Uusi kansio", + "newFileDefault": "UusiTiedosto.txt", + "successfullyMovedItems": "{{count}} kohdetta siirretty onnistuneesti kohteeseen {{target}}", + "move": "Liikkua", + "searchInFile": "Hae tiedostosta (Ctrl+F)", + "showKeyboardShortcuts": "Näytä pikanäppäimet", + "decreaseFontSize": "Pienennä fonttikokoa", + "increaseFontSize": "Suurenna fonttikokoa", + "startWritingMarkdown": "Aloita Markdown-sisältösi kirjoittaminen...", + "loadingFileComparison": "Tiedostojen vertailua ladataan...", + "reload": "Lataa uudelleen", + "compare": "Vertaa", + "sideBySide": "Rinnakkain", + "inline": "Rivissä", + "fileComparison": "Tiedostovertailu: {{file1}} vs. {{file2}}", + "fileTooLarge": "Tiedosto liian suuri: {{error}}", + "sshConnectionFailed": "SSH-yhteys epäonnistui. Tarkista yhteytesi kohteeseen {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Tiedoston lataus epäonnistui: {{error}}", + "connecting": "Yhdistetään...", + "connectedSuccessfully": "Yhteys onnistui", + "totpVerificationFailed": "TOTP-vahvistus epäonnistui", + "warpgateVerificationFailed": "Warpgate-todennus epäonnistui", + "authenticationFailed": "Todennus epäonnistui", + "incorrectPassphrase": "Virheellinen salasana. Yritä uudelleen.", + "verificationCodePrompt": "Vahvistuskoodi:", + "changePermissions": "Muuta käyttöoikeuksia", + "currentPermissions": "Nykyiset käyttöoikeudet", + "owner": "Omistaja", + "group": "Ryhmä", + "others": "Muut", + "read": "Lukea", + "write": "Kirjoittaa", + "execute": "Suorittaa", + "permissionsChangedSuccessfully": "Käyttöoikeudet muutettu onnistuneesti", + "failedToChangePermissions": "Käyttöoikeuksien muuttaminen epäonnistui", + "name": "Nimi", + "sortByName": "Nimi", + "sortByDate": "Muokkauspäivämäärä", + "sortBySize": "Koko", + "ascending": "Nouseva", + "descending": "Laskeva", + "root": "Juuri", + "new": "Uusi", + "sortBy": "Lajittele", + "items": "Kohteet", + "selected": "Valittu", + "editor": "Toimittaja", + "octal": "Oktaali", + "storage": "Säilytys", + "disk": "Levy", + "used": "Käytetty", + "of": "jostakin", + "toggleSidebar": "Näytä/piilota sivupalkki", + "compactLayout": "Käytä kompaktia asettelua", + "comfortableLayout": "Käytä mukavaa asettelua", + "cannotLoadPdf": "PDF-tiedoston lataaminen ei onnistu", + "pdfLoadError": "PDF-tiedoston lataamisessa tapahtui virhe.", + "loadingPdf": "Ladataan PDF-tiedostoa...", + "loadingPage": "Sivua ladataan..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Siirrä tiedostoja kahden SSH-isännän välillä.", + "sourceServer": "Lähdepalvelin", + "destinationServer": "Kohdepalvelin", + "selectHost": "Valitse isäntä", + "noHostSelected": "Ei valittua isäntää", + "move": "Liikkua", + "copy": "Kopioida", + "upload": "Lataa", + "itemsSelected": "{{count}} kohde valittu", + "itemsSelected_other": "{{count}} kohdetta valittu", + "ready": "Valmis", + "loadingHosts": "Ladataan isäntiä...", + "name": "Nimi", + "modified": "Muokattu", + "size": "Koko", + "noFiles": "Ei tiedostoja", + "open": "Avata", + "up": "Ylös", + "refresh": "Päivitä", + "copyToTarget": "Kopioi kohdehakemistoon", + "rename": "Nimeä uudelleen", + "delete": "Poistaa", + "editPermissions": "Muokkausoikeudet", + "createNewFolder": "Luo uusi kansio", + "deleteSelectedItems": "Poista valitut kohteet", + "deleteConfirm": "Poistetaanko {{count}} valittu kohde?", + "deleteConfirm_other": "Poistetaanko {{count}} valitut kohteet?", + "cancel": "Peruuttaa", + "save": "Tallentaa", + "folderCreated": "Kansio luotu", + "itemRenamed": "Kohteen nimi on muutettu", + "actionFailed": "Toiminto epäonnistui", + "itemsDeleted": "{{count}} kohde poistettu", + "itemsDeleted_other": "{{count}} kohdetta poistettu", + "deleteFailed": "Poisto epäonnistui", + "permissionsUpdated": "Käyttöoikeudet päivitetty", + "noRemoteSessionConnected": "Ei etäistuntoa yhdistettynä", + "failedToListFiles": "Tiedostojen listaaminen epäonnistui", + "failedToRefresh": "Päivitys epäonnistui", + "destinationInsideSource": "Kohde ei voi olla valitun lähdepolun sisällä.", + "sameHostNoOp": "Lähde- ja kohdekansiot ovat samassa kansiossa." + }, + "hosts": { + "fileManager": "Tiedostonhallinta", + "tabFiles": "Tiedostot", + "enableFileManagerMonitor": "Ota tiedostonhallinta käyttöön", + "enableFileManagerMonitorDesc": "Selaa ja hallinnoi tiedostoja tällä isännöinnillä SFTP:n kautta", + "scpLegacyLabel": "SCP:n vanha tila", + "scpLegacyDesc": "Käytä vanhaa tiedostonsiirtoa palvelimilla, jotka eivät tue SFTP-alijärjestelmää (esim. sulautetut tai minimaaliset SSH-palvelimet).", + "defaultPathLabel": "Oletuspolku", + "fileManagerPathHint": "Hakemisto, joka avataan, kun tiedostonhallinta käynnistyy tälle isännälle.", + "fileManagerUrlCopied": "Tiedostonhallinnan URL-osoite kopioitu", + "copyFileManagerUrlAction": "Kopioi tiedostonhallinnan URL-osoite" + }, + "settings": { + "host": { + "enable": { + "label": "Ota tiedostonhallinta käyttöön" + }, + "defaultPath": { + "label": "Oletuspolku" + }, + "scpLegacy": { + "label": "SCP:n vanha tila" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Tiedostot" + } +} diff --git a/plugins/file-manager/locales/translated/fr_FR.json b/plugins/file-manager/locales/translated/fr_FR.json new file mode 100644 index 000000000..2c5d4947b --- /dev/null +++ b/plugins/file-manager/locales/translated/fr_FR.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Aucun hôte sélectionné", + "initializingEditor": "Initialisation de l'éditeur...", + "file": "Fichier", + "folder": "Dossier", + "uploadFile": "Téléverser un fichier", + "downloadFile": "Télécharger", + "extractArchive": "Extraire l'archive", + "extractingArchive": "Extraction de {{name}}...", + "archiveExtractedSuccessfully": "{{name}} extrait avec succès", + "extractFailed": "Échec de l'extraction", + "compressFile": "Compresser le fichier", + "compressFiles": "Compresser les fichiers", + "compressFilesDesc": "Compresser {{count}} éléments en une archive", + "archiveName": "Nom de l'archive", + "enterArchiveName": "Entrez le nom de l'archive...", + "compressionFormat": "Format de compression", + "selectedFiles": "Fichiers sélectionnés", + "andMoreFiles": "et {{count}} de plus...", + "compress": "Compresser", + "compressingFiles": "Compression de {{count}} éléments en {{name}}...", + "filesCompressedSuccessfully": "{{name}} créé avec succès", + "compressFailed": "Échec de la compression", + "edit": "Modifier", + "preview": "Aperçu", + "previous": "Précédent", + "next": "Suivant", + "pageXOfY": "Page {{current}} sur {{total}}", + "zoomOut": "Zoom arrière", + "zoomIn": "Zoom avant", + "newFile": "Nouveau fichier", + "newFolder": "Nouveau dossier", + "rename": "Renommer", + "uploading": "Téléversement...", + "uploadingFile": "Téléversement de {{name}}...", + "fileName": "Nom du fichier", + "folderName": "Nom du dossier", + "fileUploadedSuccessfully": "Fichier \"{{name}}\" téléversé avec succès", + "failedToUploadFile": "Échec du téléversement du fichier", + "uploadingFolderFiles": "Téléchargement du ou des fichiers {{count}}...", + "uploadedFolderFiles": "Fichier(s) {{count}} téléchargé(s) avec succès", + "uploadedFolderPartial": "Le(s) fichier(s) {{uploaded}} téléchargé(s), {{failed}} a échoué", + "fileDownloadedSuccessfully": "Fichier \"{{name}}\" téléchargé avec succès", + "failedToDownloadFile": "Échec du téléchargement du fichier", + "fileCreatedSuccessfully": "Fichier \"{{name}}\" créé avec succès", + "folderCreatedSuccessfully": "Dossier \"{{name}}\" créé avec succès", + "failedToCreateItem": "Échec de la création de l'élément", + "operationFailed": "L'opération {{operation}} a échoué pour {{name}} : {{error}}", + "failedToResolveSymlink": "Échec de la résolution du lien symbolique", + "itemsDeletedSuccessfully": "{{count}} éléments supprimés avec succès", + "failedToDeleteItems": "Échec de la suppression des éléments", + "sudoPasswordRequired": "Mot de passe administrateur requis", + "enterSudoPassword": "Entrez le mot de passe sudo pour continuer cette opération", + "sudoPassword": "Mot de passe sudo", + "sudoOperationFailed": "L'opération sudo a échoué", + "sudoAuthFailed": "L'authentification sudo a échoué", + "dragFilesToUpload": "Déposez les fichiers ici pour les téléverser", + "dropToUploadHere": "Déposez votre fichier ici.", + "dropToDownloadHere": "Cliquez ici pour télécharger", + "localFiles": "Locale", + "showLocalFiles": "Afficher les fichiers locaux (vue fractionnée)", + "hideLocalFiles": "Masquer les fichiers locaux", + "localHome": "Dossier personnel", + "localPathPlaceholder": "chemin du dossier local", + "localShowHidden": "Afficher les fichiers cachés", + "localHideHidden": "Masquer les fichiers cachés", + "localRevealInFileManager": "Afficher dans le gestionnaire de fichiers système", + "localCannotOpenFolder": "Impossible d'ouvrir le dossier", + "localItemCount": "{{count}} articles", + "localSelectedCount": "{{count}} sélectionné", + "back": "Dos", + "forward": "Avant", + "up": "En haut", + "kind": "Gentil", + "localUploadingProgress": "Téléchargement de {{current}} de {{total}}", + "localDownloadingProgress": "Téléchargement de {{current}} de {{total}}", + "localTransferInFlight": "{{count}} à la fois", + "localPreparingDownload": "Préparation du téléchargement...", + "localTransferCancelled": "Transfert annulé", + "localNothingToTransfer": "Rien à transférer", + "localUploadComplete": "Fichier(s) téléchargé(s) {{count}}", + "localUploadFailed": "Échec du chargement", + "localUploadPartial": "Le(s) fichier(s) {{done}} téléchargé(s), {{failed}} a échoué", + "localDownloadComplete": "Fichier(s) téléchargé(s) {{count}}", + "localDownloadFailed": "Échec du téléchargement", + "localDownloadPartial": "Téléchargement du(des) fichier(s) {{done}} , {{failed}} échoué", + "localReplaceExistingPrompt": "{{count}} éléments existent déjà dans ce dossier. Les remplacer ?", + "localReplace": "Remplacer", + "localSkipExisting": "Sauter", + "localSkippedExisting": "Élément(s) existant(s) ignoré(s) {{count}}", + "localUnsafeNamesSkipped": "Élément(s) ignoré(s) {{count}} dont le nom ne peut pas être utilisé sur cet ordinateur", + "localOpen": "Ouvrir", + "localOpenFolder": "Ouvrir le dossier", + "localUploadToRemote": "Téléverser sur le serveur", + "localUploadToRemoteMany": "Téléverser {{count}} éléments sur le serveur", + "localMoveToTrash": "Déplacer vers la corbeille", + "localMoveToTrashMany": "Déplacer les éléments {{count}} vers la Corbeille", + "localTrashConfirmSingle": "Déplacer «{{name}}» vers la Corbeille ?", + "localTrashConfirmFolder": "Déplacer le dossier «{{name}}» et tout son contenu vers la Corbeille ?", + "localTrashConfirmMany": "Déplacer {{count}} éléments vers la Corbeille ?", + "localTrashed": "Élément(s) déplacé(s) {{count}} vers la Corbeille", + "localTrashFailed": "Impossible de déplacer vers la poubelle", + "localRenameFailed": "Échec du renommage", + "localCreateFailed": "Impossible de créer l'élément", + "goToParentFolder": "Aller au dossier parent", + "resizeSidebar": "Faites glisser pour redimensionner la barre latérale (double-cliquez pour réinitialiser)", + "resizeLocalPane": "Faites glisser pour redimensionner le volet local (double-cliquez pour réinitialiser)", + "columns": "Colonnes", + "columnsHint": "Cliquez avec le bouton droit pour choisir les colonnes", + "emptyFolder": "Ce dossier est vide", + "searchFiles": "Rechercher des fichiers...", + "upload": "Téléverser", + "selectHostToStart": "Sélectionnez un hôte pour démarrer la gestion des fichiers", + "sshRequiredForFileManager": "Le gestionnaire de fichiers nécessite SSH. Cet hôte n'a pas SSH activé.", + "failedToConnect": "Échec de la connexion SSH", + "failedToLoadDirectory": "Échec du chargement du répertoire", + "noSSHConnection": "Aucune connexion SSH disponible", + "copy": "Copier", + "cut": "Couper", + "paste": "Coller", + "copyPath": "Copier le chemin", + "copyPaths": "Copier les chemins", + "delete": "Supprimer", + "properties": "Propriétés", + "refresh": "Actualiser", + "downloadFiles": "Télécharger {{count}} fichiers dans le navigateur", + "copyFiles": "Copier {{count}} éléments", + "cutFiles": "Couper {{count}} éléments", + "deleteFiles": "Supprimer {{count}} éléments", + "filesCopiedToClipboard": "{{count}} éléments copiés dans le presse-papiers", + "filesCutToClipboard": "{{count}} éléments coupés dans le presse-papiers", + "pathCopiedToClipboard": "Chemin copié dans le presse-papiers", + "pathsCopiedToClipboard": "{{count}} chemins copiés dans le presse-papiers", + "failedToCopyPath": "Échec de la copie du chemin dans le presse-papiers", + "copyFolderLink": "Copier le lien vers le dossier", + "copyCurrentFolderLink": "Copier le lien vers le dossier actuel", + "folderLinkCopied": "Lien du dossier copié dans le presse-papiers", + "failedToCopyFolderLink": "Échec de la copie du lien du dossier", + "movedItems": "{{count}} élément(s) déplacé(s)", + "failedToDeleteItem": "Échec de la suppression de l’élément", + "itemRenamedSuccessfully": "{{type}} renommé avec succès", + "failedToRenameItem": "Échec du renommage de l’élément", + "download": "Télécharger", + "openExternalEditor": "Ouvrir avec l’éditeur externe", + "chooseExternalEditor": "Choisir l’éditeur externe", + "externalEditorSelected": "Éditeur externe sélectionné", + "externalEditorDesktopOnly": "L’éditeur externe est uniquement disponible dans l’application de bureau", + "externalEditorOpened": "Ouvert dans l’éditeur externe. Les modifications enregistrées seront renvoyées vers le serveur.", + "failedToOpenExternalEditor": "Échec de l’ouverture de l’éditeur externe", + "failedToSelectExternalEditor": "Échec de la sélection de l’éditeur externe", + "permissions": "Permissions", + "size": "Taille", + "modified": "Modifié", + "path": "Chemin", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer {{name}} ?", + "permissionDenied": "Permission refusée", + "serverError": "Erreur serveur", + "fileSavedSuccessfully": "Fichier enregistré avec succès", + "failedToSaveFile": "Échec de l’enregistrement du fichier", + "confirmDeleteSingleItem": "Êtes-vous sûr de vouloir supprimer définitivement « {{name}} » ?", + "confirmDeleteMultipleItems": "Êtes-vous sûr de vouloir supprimer définitivement {{count}} éléments ?", + "confirmDeleteMultipleItemsWithFolders": "Êtes-vous sûr de vouloir supprimer définitivement {{count}} éléments ? Cela inclut les dossiers et leur contenu.", + "confirmDeleteFolder": "Êtes-vous sûr de vouloir supprimer définitivement le dossier « {{name}} » et tout son contenu ?", + "permanentDeleteWarning": "Cette action est irréversible. Le ou les éléments seront définitivement supprimés du serveur.", + "moveToTrashWarning": "Les éléments seront déplacés vers la corbeille Termix et pourront être restaurés avant l'expiration de la période de conservation.", + "itemsMovedToTrash": "{{count}} élément(s) déplacé(s) vers la Corbeille", + "trash": "Poubelle", + "trashDescription": "Les éléments supprimés sont conservés pendant {{days}} jour(s) sur cet hôte.", + "trashLoadFailed": "Échec du chargement de la corbeille", + "trashEmpty": "La poubelle est vide.", + "trashRestore": "Restaurer", + "trashRestored": "Restauré {{name}}", + "trashRestoreFailed": "Impossible de restaurer l'élément. Le chemin d'accès d'origine existe peut-être déjà.", + "trashDeletePermanently": "Supprimer définitivement", + "trashDeleteConfirm": "Supprimer définitivement {{name}}? Cette action est irréversible.", + "trashPermanentlyDeleted": "Supprimé définitivement {{name}}", + "trashDeleteFailed": "Échec de la suppression définitive de l'élément", + "trashEmptyAction": "Vider la poubelle", + "trashEmptyConfirm": "Supprimer définitivement tous les éléments de la Corbeille ? Cette action est irréversible.", + "trashEmptied": "Poubelle vidée", + "trashRetention": "Nettoyage automatique après (jours)", + "trashRetentionSaved": "Mise à jour concernant la conservation des déchets", + "trashRetentionFailed": "Échec de la mise à jour de la conservation de la corbeille", + "trashUnavailableConfirm": "Cet élément ne peut pas être déplacé vers la Corbeille, généralement parce qu'il se trouve sur un autre système de fichiers ou qu'il nécessite des autorisations élevées. Voulez-vous plutôt le supprimer définitivement ?", + "recent": "Récents", + "pinned": "Épinglés", + "folderShortcuts": "Raccourcis de dossiers", + "failedToReconnectSSH": "Échec de la reconnexion de la session SSH", + "openTerminalHere": "Ouvrir le terminal ici", + "run": "Exécuter", + "openTerminalInFolder": "Ouvrir le terminal dans ce dossier", + "openTerminalInFileLocation": "Ouvrir le terminal à l'emplacement du fichier", + "runningFile": "En cours d'exécution - {{file}}", + "onlyRunExecutableFiles": "Seuls les fichiers exécutables peuvent être exécutés", + "directories": "Dossiers", + "removedFromRecentFiles": "« {{name}} » retiré des fichiers récents", + "removeFailed": "Échec de la suppression", + "unpinnedSuccessfully": "« {{name}} » désépinglé avec succès", + "unpinFailed": "Échec du désépinglage", + "removedShortcut": "Raccourci « {{name}} » supprimé", + "removeShortcutFailed": "Échec de la suppression du raccourci", + "clearedAllRecentFiles": "Tous les fichiers récents ont été effacés", + "clearFailed": "Échec de l'effacement", + "removeFromRecentFiles": "Retirer des fichiers récents", + "clearAllRecentFiles": "Effacer tous les fichiers récents", + "unpinFile": "Désépingler le fichier", + "removeShortcut": "Supprimer le raccourci", + "pinFile": "Épingler le fichier", + "addToShortcuts": "Ajouter aux raccourcis", + "pasteFailed": "Échec du collage", + "noUndoableActions": "Aucune action annulable", + "undoCopySuccess": "Annulation de la copie : {{count}} fichier(s) copié(s) supprimé(s)", + "undoCopyFailedDelete": "Échec de l'annulation : impossible de supprimer les fichiers copiés", + "undoCopyFailedNoInfo": "Échec de l'annulation : informations sur les fichiers copiés introuvables", + "undoMoveSuccess": "Annulation du déplacement : {{count}} fichier(s) remis à leur emplacement d’origine", + "undoMoveFailedMove": "Échec de l'annulation : impossible de remettre les fichiers en place", + "undoMoveFailedNoInfo": "Échec de l'annulation : informations sur les fichiers déplacés introuvables", + "undoDeleteNotSupported": "La suppression ne peut pas être annulée : les fichiers ont été définitivement supprimés du serveur", + "undoTypeNotSupported": "Type d’annulation non pris en charge", + "undoOperationFailed": "Échec de l’annulation", + "unknownError": "Erreur inconnue", + "confirm": "Confirmer", + "find": "Rechercher...", + "replace": "Remplacer", + "downloadInstead": "Télécharger à la place", + "keyboardShortcuts": "Raccourcis clavier", + "searchAndReplace": "Rechercher et remplacer", + "editing": "Édition", + "search": "Rechercher", + "findNext": "Rechercher le suivant", + "findPrevious": "Rechercher le précédent", + "save": "Enregistrer", + "selectAll": "Tout sélectionner", + "undo": "Annuler", + "redo": "Rétablir", + "moveLineUp": "Déplacer la ligne vers le haut", + "moveLineDown": "Déplacer la ligne vers le bas", + "toggleComment": "Commenter/Décommenter", + "autoComplete": "Complétion automatique", + "imageLoadError": "Échec du chargement de l’image", + "startTyping": "Commencez à taper…", + "unknownSize": "Taille inconnue", + "fileIsEmpty": "Le fichier est vide", + "largeFileWarning": "Avertissement – Fichier volumineux", + "largeFileWarningDesc": "Ce fichier a une taille de {{size}}, ce qui peut entraîner des problèmes de performances lorsqu’il est ouvert en tant que texte.", + "fileNotFoundAndRemoved": "Le fichier « {{name}} » est introuvable et a été retiré des fichiers récents/épinglés.", + "failedToLoadFile": "Échec du chargement du fichier : {{error}}", + "serverErrorOccurred": "Une erreur serveur s’est produite. Veuillez réessayer plus tard.", + "autoSaveFailed": "Échec de l’enregistrement automatique", + "fileAutoSaved": "Fichier enregistré automatiquement", + "moveFileFailed": "Échec du déplacement de {{name}}", + "moveOperationFailed": "L’opération de déplacement a échoué", + "canOnlyCompareFiles": "Il est uniquement possible de comparer deux fichiers", + "comparingFiles": "Comparaison des fichiers : {{file1}} et {{file2}}", + "dragFailed": "L’opération de glisser-déposer a échoué", + "filePinnedSuccessfully": "Le fichier « {{name}} » a été épinglé avec succès", + "pinFileFailed": "Échec de l’épinglage du fichier", + "fileUnpinnedSuccessfully": "Le fichier « {{name}} » a été désépinglé avec succès", + "unpinFileFailed": "Impossible de désépingler le fichier", + "shortcutAddedSuccessfully": "Raccourci de dossier « {{name}} » ajouté avec succès", + "addShortcutFailed": "Impossible d'ajouter le raccourci", + "operationCompletedSuccessfully": "{{operation}} {{count}} éléments avec succès", + "operationCompleted": "{{operation}} {{count}} éléments", + "downloadFileSuccess": "Fichier {{name}} téléchargé avec succès", + "downloadFileFailed": "Échec du téléchargement", + "downloadingFile": "Téléchargement de {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Déplacer vers {{name}}", + "diffCompareWith": "Diff avec {{name}}", + "dragOutsideToDownload": "Faites glisser hors de la fenêtre pour télécharger ({{count}} fichiers)", + "newFolderDefault": "Nouveau dossier", + "newFileDefault": "Nouveau fichier.txt", + "successfullyMovedItems": "{{count}} éléments déplacés avec succès vers {{target}}", + "move": "Déplacer", + "searchInFile": "Rechercher dans le fichier (Ctrl+F)", + "showKeyboardShortcuts": "Afficher les raccourcis clavier", + "decreaseFontSize": "Diminuer la taille de police", + "increaseFontSize": "Augmenter la taille de police", + "startWritingMarkdown": "Commencez à rédiger votre contenu markdown...", + "loadingFileComparison": "Chargement de la comparaison de fichiers...", + "reload": "Recharger", + "compare": "Comparer", + "sideBySide": "Côte à côte", + "inline": "En ligne", + "fileComparison": "Comparaison de fichiers : {{file1}} vs {{file2}}", + "fileTooLarge": "Fichier trop volumineux : {{error}}", + "sshConnectionFailed": "La connexion SSH a échoué. Veuillez vérifier votre connexion à {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Impossible de charger le fichier : {{error}}", + "connecting": "Connexion en cours...", + "connectedSuccessfully": "Connecté avec succès", + "totpVerificationFailed": "Échec de la vérification TOTP", + "warpgateVerificationFailed": "Échec de l'authentification Warpgate", + "authenticationFailed": "Échec de l'authentification", + "incorrectPassphrase": "Phrase secrète incorrecte. Veuillez réessayer.", + "verificationCodePrompt": "Code de vérification :", + "changePermissions": "Modifier les permissions", + "currentPermissions": "Permissions actuelles", + "owner": "Propriétaire", + "group": "Groupe", + "others": "Autres", + "read": "Lecture", + "write": "Écriture", + "execute": "Exécution", + "permissionsChangedSuccessfully": "Permissions modifiées avec succès", + "failedToChangePermissions": "Échec de la modification des permissions", + "name": "Nom", + "sortByName": "Nom", + "sortByDate": "Date de modification", + "sortBySize": "Taille", + "ascending": "Croissant", + "descending": "Décroissant", + "root": "Racine", + "new": "Nouveau", + "sortBy": "Trier par", + "items": "Éléments", + "selected": "Sélectionné", + "editor": "Éditeur", + "octal": "Octal", + "storage": "Stockage", + "disk": "Disque", + "used": "Utilisé", + "of": "sur", + "toggleSidebar": "Afficher/Masquer la barre latérale", + "compactLayout": "Utilisez une mise en page compacte", + "comfortableLayout": "Utilisez une disposition confortable", + "cannotLoadPdf": "Impossible de charger le PDF", + "pdfLoadError": "Une erreur s'est produite lors du chargement de ce fichier PDF.", + "loadingPdf": "Chargement du PDF...", + "loadingPage": "Chargement de la page..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Transférer des fichiers entre deux hôtes SSH.", + "sourceServer": "Serveur source", + "destinationServer": "Serveur de destination", + "selectHost": "Sélectionner l'hôte", + "noHostSelected": "Aucun hôte sélectionné", + "move": "Se déplacer", + "copy": "Copie", + "upload": "Télécharger", + "itemsSelected": "{{count}} élément sélectionné", + "itemsSelected_other": "{{count}} éléments sélectionnés", + "ready": "Prêt", + "loadingHosts": "Chargement des hôtes...", + "name": "Nom", + "modified": "Modifié", + "size": "Taille", + "noFiles": "Aucun fichier", + "open": "Ouvrir", + "up": "En haut", + "refresh": "Rafraîchir", + "copyToTarget": "Copier dans le répertoire cible", + "rename": "Rebaptiser", + "delete": "Supprimer", + "editPermissions": "Autorisations de modification", + "createNewFolder": "Créer un nouveau dossier", + "deleteSelectedItems": "Supprimer les éléments sélectionnés", + "deleteConfirm": "Supprimer l'élément sélectionné {{count}} ?", + "deleteConfirm_other": "Supprimer les éléments sélectionnés {{count}} ?", + "cancel": "Annuler", + "save": "Sauvegarder", + "folderCreated": "Dossier créé", + "itemRenamed": "Article renommé", + "actionFailed": "L'action a échoué", + "itemsDeleted": "{{count}} élément supprimé", + "itemsDeleted_other": "{{count}} éléments supprimés", + "deleteFailed": "Échec de la suppression", + "permissionsUpdated": "Autorisations mises à jour", + "noRemoteSessionConnected": "Aucune session distante connectée", + "failedToListFiles": "Impossible de lister les fichiers", + "failedToRefresh": "Échec de l'actualisation", + "destinationInsideSource": "La destination ne peut pas se trouver à l'intérieur du chemin source sélectionné.", + "sameHostNoOp": "Le dossier source et le dossier de destination sont identiques." + }, + "hosts": { + "fileManager": "Gestionnaire de fichiers", + "tabFiles": "Fichiers", + "enableFileManagerMonitor": "Activer le gestionnaire de fichiers", + "enableFileManagerMonitorDesc": "Parcourir et gérer les fichiers de cet hôte via SFTP", + "scpLegacyLabel": "Mode SCP hérité", + "scpLegacyDesc": "Utiliser le transfert de fichiers hérité pour les serveurs qui ne prennent pas en charge le sous-système SFTP (par exemple, serveurs SSH embarqués ou minimalistes).", + "defaultPathLabel": "Chemin par défaut", + "fileManagerPathHint": "Le répertoire à ouvrir lors du lancement du gestionnaire de fichiers pour cet hôte.", + "fileManagerUrlCopied": "URL du gestionnaire de fichiers copiée", + "copyFileManagerUrlAction": "Copier l'URL du gestionnaire de fichiers" + }, + "settings": { + "host": { + "enable": { + "label": "Activer le gestionnaire de fichiers" + }, + "defaultPath": { + "label": "Chemin par défaut" + }, + "scpLegacy": { + "label": "Mode SCP hérité" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Fichiers" + } +} diff --git a/plugins/file-manager/locales/translated/he_IL.json b/plugins/file-manager/locales/translated/he_IL.json new file mode 100644 index 000000000..abc4faac7 --- /dev/null +++ b/plugins/file-manager/locales/translated/he_IL.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "לא נבחר מארח", + "initializingEditor": "מאתחל עורך...", + "file": "קוֹבֶץ", + "folder": "תיקייה", + "uploadFile": "העלאת קובץ", + "downloadFile": "הורדה", + "extractArchive": "ארכיון חילוץ", + "extractingArchive": "מחלץ {{name}}...", + "archiveExtractedSuccessfully": "{{name}} חולץ בהצלחה", + "extractFailed": "החילוץ נכשל", + "compressFile": "דחיסת קובץ", + "compressFiles": "דחיסת קבצים", + "compressFilesDesc": "דחיסת {{count}} פריטים לתוך ארכיון", + "archiveName": "שם הארכיון", + "enterArchiveName": "הזן שם ארכיון...", + "compressionFormat": "פורמט דחיסה", + "selectedFiles": "קבצים נבחרים", + "andMoreFiles": "ועוד {{count}}...", + "compress": "לִדחוֹס", + "compressingFiles": "דחיסת {{count}} פריטים לתוך {{name}}...", + "filesCompressedSuccessfully": "{{name}} נוצר בהצלחה", + "compressFailed": "הדחיסה נכשלה", + "edit": "לַעֲרוֹך", + "preview": "תצוגה מקדימה", + "previous": "קוֹדֵם", + "next": "הַבָּא", + "pageXOfY": "עמוד {{current}} מתוך {{total}}", + "zoomOut": "התקרבות", + "zoomIn": "לְהִתְמַקֵד", + "newFile": "קובץ חדש", + "newFolder": "תיקייה חדשה", + "rename": "שינוי שם", + "uploading": "מעלה...", + "uploadingFile": "מעלה {{name}}...", + "fileName": "שם הקובץ", + "folderName": "שם התיקייה", + "fileUploadedSuccessfully": "הקובץ \"{{name}}\" הועלה בהצלחה", + "failedToUploadFile": "העלאת הקובץ נכשלה", + "uploadingFolderFiles": "מעלה קובץ/ים {{count}}...", + "uploadedFolderFiles": "הקבצים {{count}} הועלו בהצלחה", + "uploadedFolderPartial": "העלאת קובץ/קבצים {{uploaded}} , {{failed}} נכשלה", + "fileDownloadedSuccessfully": "הקובץ \"{{name}}\" הורד בהצלחה", + "failedToDownloadFile": "הורדת הקובץ נכשלה", + "fileCreatedSuccessfully": "הקובץ \"{{name}}\" נוצר בהצלחה", + "folderCreatedSuccessfully": "תיקיית \"{{name}}\" נוצרה בהצלחה", + "failedToCreateItem": "יצירת הפריט נכשלה", + "operationFailed": "הפעולה {{operation}} נכשלה עבור {{name}}: {{error}}", + "failedToResolveSymlink": "נכשל בפענוח הסימבילינק", + "itemsDeletedSuccessfully": "{{count}} פריטים נמחקו בהצלחה", + "failedToDeleteItems": "מחיקת הפריטים נכשלה", + "sudoPasswordRequired": "נדרשת סיסמת מנהל", + "enterSudoPassword": "הזן את סיסמת הסודו כדי להמשיך בפעולה זו", + "sudoPassword": "סיסמת סודו", + "sudoOperationFailed": "פעולת הסודו נכשלה", + "sudoAuthFailed": "אימות סודו נכשל", + "dragFilesToUpload": "שחררו קבצים כאן כדי להעלות", + "dropToUploadHere": "שחרר כדי להעלות כאן", + "dropToDownloadHere": "שחררו להורדה כאן", + "localFiles": "מְקוֹמִי", + "showLocalFiles": "הצגת קבצים מקומיים (תצוגה מפוצלת)", + "hideLocalFiles": "הסתר קבצים מקומיים", + "localHome": "תיקיית הבית", + "localPathPlaceholder": "נתיב התיקייה המקומית", + "localShowHidden": "הצג קבצים מוסתרים", + "localHideHidden": "הסתר קבצים נסתרים", + "localRevealInFileManager": "חשיפה במנהל הקבצים של המערכת", + "localCannotOpenFolder": "לא ניתן לפתוח תיקייה", + "localItemCount": "פריטים {{count}}", + "localSelectedCount": "{{count}} נבחר", + "back": "בְּחֲזָרָה", + "forward": "קָדִימָה", + "up": "לְמַעלָה", + "kind": "סוּג", + "localUploadingProgress": "מעלה {{current}} מתוך {{total}}", + "localDownloadingProgress": "מוריד {{current}} מתוך {{total}}", + "localTransferInFlight": "{{count}} בבת אחת", + "localPreparingDownload": "מכין הורדה...", + "localTransferCancelled": "ההעברה בוטלה", + "localNothingToTransfer": "אין מה להעביר", + "localUploadComplete": "הועלו {{count}} קבצים", + "localUploadFailed": "ההעלאה נכשלה", + "localUploadPartial": "העלאת קובץ/קבצים {{done}} , {{failed}} נכשלה", + "localDownloadComplete": "הורד/י קובץ/ים {{count}}", + "localDownloadFailed": "ההורדה נכשלה", + "localDownloadPartial": "הורדת הקובץ/קבצים {{done}} , {{failed}} נכשלה", + "localReplaceExistingPrompt": "פריט/ים {{count}} כבר קיים/ים בתיקייה זו. האם להחליף אותם?", + "localReplace": "לְהַחלִיף", + "localSkipExisting": "לְדַלֵג", + "localSkippedExisting": "דילג על {{count}} פריטים קיימים", + "localUnsafeNamesSkipped": "דילגתי על {{count}} פריטים ששמם אינו ניתן לשימוש במחשב זה", + "localOpen": "לִפְתוֹחַ", + "localOpenFolder": "פתיחת תיקייה", + "localUploadToRemote": "העלאה לשרת", + "localUploadToRemoteMany": "העלה {{count}} פריטים לשרת", + "localMoveToTrash": "העברה לאשפה", + "localMoveToTrashMany": "העבר {{count}} פריטים לאשפה", + "localTrashConfirmSingle": "להעביר את \"{{name}}\" לאשפה?", + "localTrashConfirmFolder": "להעביר את התיקייה \"{{name}}\" ואת כל תוכנה לפח האשפה?", + "localTrashConfirmMany": "להעביר {{count}} פריטים לאשפה?", + "localTrashed": "הועבר/י {{count}} פריט/ים לאשפה", + "localTrashFailed": "לא ניתן היה להעביר לאשפה", + "localRenameFailed": "שינוי השם נכשל", + "localCreateFailed": "לא ניתן היה ליצור פריט", + "goToParentFolder": "עבור לתיקיית האב", + "resizeSidebar": "גרור כדי לשנות את גודל סרגל הצד (לחץ פעמיים כדי לאפס)", + "resizeLocalPane": "גרור כדי לשנות את גודל החלונית המקומית (לחץ פעמיים כדי לאפס)", + "columns": "עמודות", + "columnsHint": "לחיצה ימנית כדי לבחור עמודות", + "emptyFolder": "תיקייה זו ריקה", + "searchFiles": "חיפוש קבצים...", + "upload": "העלאה", + "selectHostToStart": "בחר מארח כדי להתחיל בניהול קבצים", + "sshRequiredForFileManager": "מנהל הקבצים דורש SSH. שרת זה אינו מופעל באמצעות SSH.", + "failedToConnect": "נכשלה ההתחברות ל-SSH", + "failedToLoadDirectory": "טעינת הספרייה נכשלה", + "noSSHConnection": "אין חיבור SSH זמין", + "copy": "לְהַעְתִיק", + "cut": "גְזִירָה", + "paste": "לְהַדבִּיק", + "copyPath": "העתק נתיב", + "copyPaths": "העתקת נתיבים", + "delete": "לִמְחוֹק", + "properties": "נכסים", + "refresh": "לְרַעֲנֵן", + "downloadFiles": "הורד {{count}} קבצים לדפדפן", + "copyFiles": "העתקת {{count}} פריטים", + "cutFiles": "גזור {{count}} פריטים", + "deleteFiles": "מחיקת {{count}} פריטים", + "filesCopiedToClipboard": "{{count}} פריטים הועתקו ללוח", + "filesCutToClipboard": "{{count}} פריטים נחתכו ללוח", + "pathCopiedToClipboard": "הנתיב הועתק ללוח", + "pathsCopiedToClipboard": "נתיבים {{count}} הועתקו ללוח", + "failedToCopyPath": "נכשלה העתקת הנתיב ללוח", + "copyFolderLink": "העתקת קישור לתיקייה", + "copyCurrentFolderLink": "העתקת קישור לתיקייה הנוכחית", + "folderLinkCopied": "קישור לתיקייה הועתק ללוח", + "failedToCopyFolderLink": "נכשל בהעתקת קישור לתיקייה", + "movedItems": "הועבר {{count}} פריטים", + "failedToDeleteItem": "מחיקת הפריט נכשלה", + "itemRenamedSuccessfully": "שם השינוי של {{type}} הצליח", + "failedToRenameItem": "שינוי שם הפריט נכשל", + "download": "הורדה", + "openExternalEditor": "פתוח חיצונית", + "chooseExternalEditor": "בחירת עורך חיצוני", + "externalEditorSelected": "עורך חיצוני נבחר", + "externalEditorDesktopOnly": "עורך חיצוני זמין רק באפליקציית שולחן העבודה", + "externalEditorOpened": "נפתח בעורך חיצוני. שמירה שם תעלה את השינויים בחזרה לשרת.", + "failedToOpenExternalEditor": "נכשלה פתיחת העורך החיצוני", + "failedToSelectExternalEditor": "נכשלה בחירת עורך חיצוני", + "permissions": "הרשאות", + "size": "גוֹדֶל", + "modified": "שונה", + "path": "נָתִיב", + "confirmDelete": "האם אתה בטוח שאתה רוצה למחוק {{name}}?", + "permissionDenied": "ההרשאה נדחתה", + "serverError": "שגיאת שרת", + "fileSavedSuccessfully": "הקובץ נשמר בהצלחה", + "failedToSaveFile": "שמירת הקובץ נכשלה", + "confirmDeleteSingleItem": "האם אתה בטוח שברצונך למחוק לצמיתות את \"{{name}}\"?", + "confirmDeleteMultipleItems": "האם אתה בטוח שברצונך למחוק לצמיתות {{count}} פריטים?", + "confirmDeleteMultipleItemsWithFolders": "האם אתה בטוח שברצונך למחוק לצמיתות את הפריטים {{count}} ? זה כולל תיקיות ותוכן שלהן.", + "confirmDeleteFolder": "האם אתה בטוח שברצונך למחוק לצמיתות את התיקייה \"{{name}}\" ואת כל תוכנה?", + "permanentDeleteWarning": "לא ניתן לבטל פעולה זו. הפריט/ים יימחקו לצמיתות מהשרת.", + "moveToTrashWarning": "הפריט/ים יועברו לפח האשפה של Termix וניתן יהיה לשחזר אותם לפני שתקופת השמירה תפוג.", + "itemsMovedToTrash": "פריט/ים {{count}} הועברו לאשפה", + "trash": "אַשׁפָּה", + "trashDescription": "פריטים שנמחקו נשמרים למשך {{days}} יום/ימים במארח זה.", + "trashLoadFailed": "טעינת האשפה נכשלה", + "trashEmpty": "האשפה ריקה", + "trashRestore": "לְשַׁחְזֵר", + "trashRestored": "שוחזר {{name}}", + "trashRestoreFailed": "שחזור הפריט נכשל. ייתכן שהנתיב המקורי כבר קיים.", + "trashDeletePermanently": "מחיקה לצמיתות", + "trashDeleteConfirm": "למחוק לצמיתות את {{name}}? לא ניתן לבטל פעולה זו.", + "trashPermanentlyDeleted": "נמחק לצמיתות {{name}}", + "trashDeleteFailed": "נכשלה המחיקה לצמיתות של הפריט", + "trashEmptyAction": "רוקנו את האשפה", + "trashEmptyConfirm": "למחוק לצמיתות כל פריט באשפה? לא ניתן לבטל פעולה זו.", + "trashEmptied": "ריקון האשפה", + "trashRetention": "ניקוי אוטומטי לאחר (ימים)", + "trashRetentionSaved": "שמירת האשפה עודכנה", + "trashRetentionFailed": "נכשל עדכון שמירת האשפה", + "trashUnavailableConfirm": "לא ניתן להעביר פריט זה לאשפה, בדרך כלל מכיוון שהוא נמצא במערכת קבצים אחרת או דורש הרשאות מוגברות. האם למחוק אותו לצמיתות במקום זאת?", + "recent": "אחרונים", + "pinned": "מוצמד", + "folderShortcuts": "קיצורי דרך לתיקיות", + "failedToReconnectSSH": "נכשל בחיבור מחדש של סשן SSH", + "openTerminalHere": "פתח את הטרמינל כאן", + "run": "לָרוּץ", + "openTerminalInFolder": "פתח את הטרמינל בתיקייה זו", + "openTerminalInFileLocation": "פתיחת הטרמינל במיקום הקובץ", + "runningFile": "ריצה - {{file}}", + "onlyRunExecutableFiles": "יכול להריץ רק קבצי הפעלה", + "directories": "מדריכים", + "removedFromRecentFiles": "הוסר \"{{name}}\" מהקבצים האחרונים", + "removeFailed": "ההסרה נכשלה", + "unpinnedSuccessfully": "\"{{name}}\" בוטל בהצלחה", + "unpinFailed": "ביטול ההצמדה נכשל", + "removedShortcut": "קיצור הדרך \"{{name}}\" הוסר", + "removeShortcutFailed": "הסרת קיצור הדרך נכשלה", + "clearedAllRecentFiles": "ניקה את כל הקבצים האחרונים", + "clearFailed": "ניקוי נכשל", + "removeFromRecentFiles": "הסר מהקבצים האחרונים", + "clearAllRecentFiles": "נקה את כל הקבצים האחרונים", + "unpinFile": "ביטול הצמדת קובץ", + "removeShortcut": "הסר קיצור דרך", + "pinFile": "קובץ הצמדה", + "addToShortcuts": "הוסף לקיצורי דרך", + "pasteFailed": "ההדבקה נכשלה", + "noUndoableActions": "אין פעולות שניתן לבטל", + "undoCopySuccess": "פעולת העתקה שבוטלה: מחיקת {{count}} קבצים שהועתקו", + "undoCopyFailedDelete": "ביטול נכשל: לא ניתן היה למחוק קבצים שהועתקו", + "undoCopyFailedNoInfo": "ביטול נכשל: לא ניתן היה למצוא את פרטי הקובץ שהועתק", + "undoMoveSuccess": "פעולת העברה שבוטלה: העבירו {{count}} קבצים חזרה למיקום המקורי", + "undoMoveFailedMove": "ביטול נכשל: לא ניתן היה להעביר קבצים בחזרה", + "undoMoveFailedNoInfo": "ביטול נכשל: לא ניתן היה למצוא מידע על הקובץ שהועבר", + "undoDeleteNotSupported": "לא ניתן לבטל את פעולת המחיקה: הקבצים נמחקו לצמיתות מהשרת", + "undoTypeNotSupported": "סוג פעולת ביטול לא נתמך", + "undoOperationFailed": "פעולת הביטול נכשלה", + "unknownError": "שגיאה לא ידועה", + "confirm": "לְאַשֵׁר", + "find": "לִמצוֹא...", + "replace": "לְהַחלִיף", + "downloadInstead": "הורד במקום זאת", + "keyboardShortcuts": "קיצורי מקלדת", + "searchAndReplace": "חיפוש והחלפה", + "editing": "עֲרִיכָה", + "search": "לְחַפֵּשׂ", + "findNext": "מצא את הבא", + "findPrevious": "מצא את הקודם", + "save": "לְהַצִיל", + "selectAll": "בחר הכל", + "undo": "לְבַטֵל", + "redo": "לַעֲשׂוֹת שׁוּב", + "moveLineUp": "הזזת שורה למעלה", + "moveLineDown": "הזזת שורה למטה", + "toggleComment": "החלף/הפעל תגובה", + "autoComplete": "השלמה אוטומטית", + "imageLoadError": "טעינת התמונה נכשלה", + "startTyping": "התחל להקליד...", + "unknownSize": "גודל לא ידוע", + "fileIsEmpty": "הקובץ ריק", + "largeFileWarning": "אזהרת קובץ גדול", + "largeFileWarningDesc": "קובץ זה הוא בגודל {{size}} , דבר שעלול לגרום לבעיות ביצועים בעת פתיחה כטקסט.", + "fileNotFoundAndRemoved": "הקובץ \"{{name}}\" לא נמצא והוסר מהקבצים האחרונים/המוצמדים", + "failedToLoadFile": "טעינת הקובץ נכשלה: {{error}}", + "serverErrorOccurred": "אירעה שגיאת שרת. אנא נסה שוב מאוחר יותר.", + "autoSaveFailed": "השמירה האוטומטית נכשלה", + "fileAutoSaved": "קובץ נשמר אוטומטית", + "moveFileFailed": "נכשל בהזזת {{name}}", + "moveOperationFailed": "פעולת ההעברה נכשלה", + "canOnlyCompareFiles": "ניתן להשוות רק שני קבצים", + "comparingFiles": "השוואת קבצים: {{file1}} ו- {{file2}}", + "dragFailed": "פעולת הגרירה נכשלה", + "filePinnedSuccessfully": "הקובץ \"{{name}}\" הוצמד בהצלחה", + "pinFileFailed": "נכשל בהצמדת הקובץ", + "fileUnpinnedSuccessfully": "הקובץ \"{{name}}\" נותק בהצלחה", + "unpinFileFailed": "נכשל ניתוק הקובץ", + "shortcutAddedSuccessfully": "קיצור הדרך לתיקייה \"{{name}}\" נוסף בהצלחה", + "addShortcutFailed": "הוספת קיצור דרך נכשלה", + "operationCompletedSuccessfully": "{{operation}} {{count}} פריטים הצליחו", + "operationCompleted": "{{operation}} {{count}} פריטים", + "downloadFileSuccess": "הקובץ {{name}} הורד בהצלחה", + "downloadFileFailed": "ההורדה נכשלה", + "downloadingFile": "מוריד {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "מעבר אל {{name}}", + "diffCompareWith": "השוואת הבדלים עם {{name}}", + "dragOutsideToDownload": "גרור את הקבצים אל מחוץ לחלון כדי להוריד אותם ({{count}})", + "newFolderDefault": "תיקייה חדשה", + "newFileDefault": "קובץ חדש.txt", + "successfullyMovedItems": "הועבר בהצלחה {{count}} פריטים אל {{target}}", + "move": "מַהֲלָך", + "searchInFile": "חיפוש בקובץ (Ctrl+F)", + "showKeyboardShortcuts": "הצג קיצורי מקלדת", + "decreaseFontSize": "הקטנת גודל הגופן", + "increaseFontSize": "הגדלת גודל הגופן", + "startWritingMarkdown": "התחל לכתוב את תוכן ההנחה שלך...", + "loadingFileComparison": "טוען השוואת קבצים...", + "reload": "לִטעוֹן מִחָדָשׁ", + "compare": "לְהַשְׁווֹת", + "sideBySide": "זֶה בְּצַד זֶה", + "inline": "מוטבע", + "fileComparison": "השוואת קבצים: {{file1}} לעומת {{file2}}", + "fileTooLarge": "קובץ גדול מדי: {{error}}", + "sshConnectionFailed": "חיבור SSH נכשל. אנא בדוק את החיבור שלך אל {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "טעינת הקובץ נכשלה: {{error}}", + "connecting": "מְקַשֵׁר...", + "connectedSuccessfully": "התחבר בהצלחה", + "totpVerificationFailed": "אימות TOTP נכשל", + "warpgateVerificationFailed": "אימות Warpgate נכשל", + "authenticationFailed": "האימות נכשל", + "incorrectPassphrase": "סיסמה שגויה. אנא נסה שוב.", + "verificationCodePrompt": "קוד אימות:", + "changePermissions": "שינוי הרשאות", + "currentPermissions": "הרשאות נוכחיות", + "owner": "בַּעַל", + "group": "קְבוּצָה", + "others": "אחרים", + "read": "לִקְרוֹא", + "write": "לִכתוֹב", + "execute": "לְבַצֵעַ", + "permissionsChangedSuccessfully": "ההרשאות שונו בהצלחה", + "failedToChangePermissions": "שינוי ההרשאות נכשל", + "name": "שֵׁם", + "sortByName": "שֵׁם", + "sortByDate": "תאריך שינוי", + "sortBySize": "גוֹדֶל", + "ascending": "עוֹלֶה", + "descending": "יורד", + "root": "שׁוֹרֶשׁ", + "new": "חָדָשׁ", + "sortBy": "מיין לפי", + "items": "פריטים", + "selected": "נִבחָר", + "editor": "עוֹרֵך", + "octal": "אוקטלי", + "storage": "אִחסוּן", + "disk": "דִיסק", + "used": "מְשׁוּמָשׁ", + "of": "שֶׁל", + "toggleSidebar": "הפעלה/כיבוי סרגל צד", + "compactLayout": "השתמש בפריסה קומפקטית", + "comfortableLayout": "השתמש בפריסה נוחה", + "cannotLoadPdf": "לא ניתן לטעון את ה-PDF", + "pdfLoadError": "אירעה שגיאה בטעינת קובץ PDF זה.", + "loadingPdf": "טוען PDF...", + "loadingPage": "טוען דף..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "העברת קבצים בין שני מארחי SSH.", + "sourceServer": "שרת המקור", + "destinationServer": "שרת יעד", + "selectHost": "בחר מארח", + "noHostSelected": "לא נבחר מארח", + "move": "מַהֲלָך", + "copy": "לְהַעְתִיק", + "upload": "העלאה", + "itemsSelected": "פריט {{count}} נבחר", + "itemsSelected_other": "{{count}} פריטים נבחרו", + "ready": "מוּכָן", + "loadingHosts": "טוען מארחים...", + "name": "שֵׁם", + "modified": "שונה", + "size": "גוֹדֶל", + "noFiles": "אין קבצים", + "open": "לִפְתוֹחַ", + "up": "לְמַעלָה", + "refresh": "לְרַעֲנֵן", + "copyToTarget": "העתק לספריית היעד", + "rename": "שינוי שם", + "delete": "לִמְחוֹק", + "editPermissions": "עריכת הרשאות", + "createNewFolder": "צור תיקייה חדשה", + "deleteSelectedItems": "מחק פריטים שנבחרו", + "deleteConfirm": "למחוק את הפריט {{count}} שנבחר?", + "deleteConfirm_other": "למחוק את {{count}} הפריטים שנבחרו?", + "cancel": "לְבַטֵל", + "save": "לְהַצִיל", + "folderCreated": "תיקייה נוצרה", + "itemRenamed": "שם הפריט", + "actionFailed": "הפעולה נכשלה", + "itemsDeleted": "פריט {{count}} נמחק", + "itemsDeleted_other": "{{count}} פריטים נמחקו", + "deleteFailed": "המחיקה נכשלה", + "permissionsUpdated": "ההרשאות עודכנו", + "noRemoteSessionConnected": "אין חיבור סשן מרחוק", + "failedToListFiles": "רשימת הקבצים נכשלה", + "failedToRefresh": "רענון נכשל", + "destinationInsideSource": "היעד לא יכול להיות בתוך נתיב המקור שנבחר.", + "sameHostNoOp": "המקור והיעד הם אותה תיקייה." + }, + "hosts": { + "fileManager": "מנהל הקבצים", + "tabFiles": "קבצים", + "enableFileManagerMonitor": "הפעל את מנהל הקבצים", + "enableFileManagerMonitorDesc": "עיון וניהול קבצים במארח זה דרך SFTP", + "scpLegacyLabel": "מצב SCP Legacy", + "scpLegacyDesc": "השתמש בהעברת קבצים מדור קודם עבור שרתים שאינם תומכים בתת-מערכת SFTP (למשל שרתי SSH מוטמעים או מינימליים).", + "defaultPathLabel": "נתיב ברירת מחדל", + "fileManagerPathHint": "הספרייה שתיפתח כאשר מנהל הקבצים יופעל עבור מארח זה.", + "fileManagerUrlCopied": "כתובת ה-URL של מנהל הקבצים הועתקה", + "copyFileManagerUrlAction": "העתקת כתובת URL של מנהל הקבצים" + }, + "settings": { + "host": { + "enable": { + "label": "הפעל את מנהל הקבצים" + }, + "defaultPath": { + "label": "נתיב ברירת מחדל" + }, + "scpLegacy": { + "label": "מצב SCP Legacy" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "קבצים" + } +} diff --git a/plugins/file-manager/locales/translated/hi_IN.json b/plugins/file-manager/locales/translated/hi_IN.json new file mode 100644 index 000000000..3c5fcf6f9 --- /dev/null +++ b/plugins/file-manager/locales/translated/hi_IN.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "कोई होस्ट चयनित नहीं", + "initializingEditor": "संपादक आरंभ हो रहा है...", + "file": "फ़ाइल", + "folder": "फ़ोल्डर", + "uploadFile": "फ़ाइल अपलोड करें", + "downloadFile": "डाउनलोड करें", + "extractArchive": "संग्रह निकालें", + "extractingArchive": "{{name}} निकाला जा रहा है...", + "archiveExtractedSuccessfully": "{{name}} सफलतापूर्वक निकाला गया", + "extractFailed": "निकालने में विफल", + "compressFile": "फ़ाइल संपीड़ित करें", + "compressFiles": "फ़ाइलें संपीड़ित करें", + "compressFilesDesc": "{{count}} आइटम को एक संग्रह में संपीड़ित करें", + "archiveName": "संग्रह का नाम", + "enterArchiveName": "संग्रह का नाम दर्ज करें...", + "compressionFormat": "संपीड़न प्रारूप", + "selectedFiles": "चयनित फ़ाइलें", + "andMoreFiles": "और {{count}}...", + "compress": "संपीड़ित करें", + "compressingFiles": "{{count}} आइटम को {{name}} में संपीड़ित किया जा रहा है...", + "filesCompressedSuccessfully": "{{name}} सफलतापूर्वक बनाया गया", + "compressFailed": "संपीड़न विफल", + "edit": "संपादित करें", + "preview": "पूर्वावलोकन", + "previous": "पिछला", + "next": "अगला", + "pageXOfY": "पृष्ठ {{current}} / {{total}}", + "zoomOut": "ज़ूम आउट", + "zoomIn": "ज़ूम इन", + "newFile": "नई फ़ाइल", + "newFolder": "नया फ़ोल्डर", + "rename": "नाम बदलें", + "uploading": "अपलोड हो रहा है...", + "uploadingFile": "{{name}} अपलोड हो रहा है...", + "fileName": "फ़ाइल का नाम", + "folderName": "फ़ोल्डर का नाम", + "fileUploadedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक अपलोड हो गई", + "failedToUploadFile": "फ़ाइल अपलोड करने में विफल", + "uploadingFolderFiles": "{{count}} फ़ाइलें अपलोड की जा रही हैं...", + "uploadedFolderFiles": "{{count}} फ़ाइलें सफलतापूर्वक अपलोड हो गईं", + "uploadedFolderPartial": "{{uploaded}} फ़ाइलें अपलोड की गईं, {{failed}} विफल रहीं", + "fileDownloadedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक डाउनलोड हो गई", + "failedToDownloadFile": "फ़ाइल डाउनलोड करने में विफल", + "fileCreatedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक बनाई गई", + "folderCreatedSuccessfully": "फ़ोल्डर \"{{name}}\" सफलतापूर्वक बनाया गया", + "failedToCreateItem": "आइटम बनाने में विफल", + "operationFailed": "{{name}} के लिए {{operation}} कार्रवाई विफल: {{error}}", + "failedToResolveSymlink": "symlink को हल करने में विफल", + "itemsDeletedSuccessfully": "{{count}} आइटम सफलतापूर्वक हटाए गए", + "failedToDeleteItems": "आइटम हटाने में विफल", + "sudoPasswordRequired": "प्रशासक पासवर्ड आवश्यक है", + "enterSudoPassword": "इस कार्रवाई को जारी रखने के लिए sudo पासवर्ड दर्ज करें", + "sudoPassword": "sudo पासवर्ड", + "sudoOperationFailed": "sudo संचालन विफल", + "sudoAuthFailed": "sudo प्रमाणीकरण विफल", + "dragFilesToUpload": "अपलोड करने के लिए फ़ाइलें यहाँ छोड़ें", + "dropToUploadHere": "अपलोड करने के लिए यहां ड्रॉप करें", + "dropToDownloadHere": "डाउनलोड करने के लिए यहां क्लिक करें", + "localFiles": "स्थानीय", + "showLocalFiles": "स्थानीय फ़ाइलें दिखाएँ (स्प्लिट व्यू)", + "hideLocalFiles": "स्थानीय फ़ाइलों को छुपाएँ", + "localHome": "होम फ़ोल्डर", + "localPathPlaceholder": "स्थानीय फ़ोल्डर पथ", + "localShowHidden": "छिपी हुई फ़ाइलें दिखाएँ", + "localHideHidden": "छिपी हुई फ़ाइलों को छुपाएँ", + "localRevealInFileManager": "सिस्टम फ़ाइल प्रबंधक में दिखाएँ", + "localCannotOpenFolder": "फ़ोल्डर नहीं खोला जा सकता", + "localItemCount": "{{count}} आइटम", + "localSelectedCount": "{{count}} चयनित", + "back": "पीछे", + "forward": "आगे", + "up": "ऊपर", + "kind": "दयालु", + "localUploadingProgress": "{{total}} में से {{current}} अपलोड किया जा रहा है", + "localDownloadingProgress": "{{total}} में से {{current}} डाउनलोड हो रहा है", + "localTransferInFlight": "{{count}} एक साथ", + "localPreparingDownload": "डाउनलोड की तैयारी हो रही है...", + "localTransferCancelled": "स्थानांतरण रद्द", + "localNothingToTransfer": "स्थानांतरित करने के लिए कुछ भी नहीं है", + "localUploadComplete": "{{count}} फाइल अपलोड की गई", + "localUploadFailed": "अपलोड विफल", + "localUploadPartial": "{{done}} फ़ाइलें अपलोड की गईं, {{failed}} विफल रहीं", + "localDownloadComplete": "डाउनलोड की गई {{count}} फ़ाइलें", + "localDownloadFailed": "डाउनलोड विफल", + "localDownloadPartial": "{{done}} फ़ाइलें डाउनलोड की गईं, {{failed}} डाउनलोड विफल रहा।", + "localReplaceExistingPrompt": "इस फ़ोल्डर में पहले से ही {{count}} आइटम मौजूद हैं। क्या आप उन्हें बदलना चाहेंगे?", + "localReplace": "प्रतिस्थापित करें", + "localSkipExisting": "छोडना", + "localSkippedExisting": "मौजूदा आइटमों में से {{count}} को छोड़ दिया गया", + "localUnsafeNamesSkipped": "इस कंप्यूटर पर जिन आइटमों के नाम इस्तेमाल नहीं किए जा सकते, उन्हें छोड़ दिया गया है {{count}}", + "localOpen": "खुला", + "localOpenFolder": "फोल्डर खोलें", + "localUploadToRemote": "सर्वर पर अपलोड करें", + "localUploadToRemoteMany": "सर्वर पर {{count}} आइटम अपलोड करें", + "localMoveToTrash": "ट्रैश में ले जाएं", + "localMoveToTrashMany": "{{count}} आइटम ट्रैश में ले जाएं", + "localTrashConfirmSingle": "क्या आप \"{{name}}\" को ट्रैश में ले जाना चाहते हैं?", + "localTrashConfirmFolder": "क्या आप फोल्डर \"{{name}}\" और उसकी सभी सामग्री को ट्रैश में ले जाना चाहते हैं?", + "localTrashConfirmMany": "क्या {{count}} आइटम ट्रैश में ले जाएं?", + "localTrashed": "{{count}} आइटम ट्रैश में भेज दिए गए हैं", + "localTrashFailed": "ट्रैश में नहीं ले जाया जा सका", + "localRenameFailed": "नाम बदलने में विफल रहा", + "localCreateFailed": "आइटम नहीं बनाया जा सका", + "goToParentFolder": "मूल फ़ोल्डर पर जाएँ", + "resizeSidebar": "साइडबार का आकार बदलने के लिए उसे खींचें (रीसेट करने के लिए उस पर डबल-क्लिक करें)", + "resizeLocalPane": "स्थानीय फलक का आकार बदलने के लिए उसे खींचें (आकार बदलने के लिए उस पर दो बार क्लिक करें)", + "columns": "कॉलम", + "columnsHint": "कॉलम चुनने के लिए राइट-क्लिक करें", + "emptyFolder": "यह फ़ोल्डर खाली है", + "searchFiles": "फ़ाइलें खोजें...", + "upload": "अपलोड करें", + "selectHostToStart": "फ़ाइल प्रबंधन शुरू करने के लिए एक होस्ट चुनें", + "sshRequiredForFileManager": "फ़ाइल प्रबंधक को SSH की आवश्यकता है। इस होस्ट में SSH सक्षम नहीं है।", + "failedToConnect": "SSH से कनेक्ट करने में विफल", + "failedToLoadDirectory": "निर्देशिका लोड करने में विफल", + "noSSHConnection": "कोई SSH कनेक्शन उपलब्ध नहीं है", + "copy": "कॉपी", + "cut": "काटें", + "paste": "चिपकाएँ", + "copyPath": "पथ कॉपी करें", + "copyPaths": "पथ कॉपी करें", + "delete": "हटाएँ", + "properties": "गुण", + "refresh": "ताज़ा करें", + "downloadFiles": "{{count}} फ़ाइलें ब्राउज़र पर डाउनलोड करें", + "copyFiles": "{{count}} आइटम कॉपी करें", + "cutFiles": "{{count}} आइटम काटें", + "deleteFiles": "{{count}} आइटम हटाएँ", + "filesCopiedToClipboard": "{{count}} आइटम क्लिपबोर्ड पर कॉपी किए गए", + "filesCutToClipboard": "{{count}} आइटम क्लिपबोर्ड पर काटे गए", + "pathCopiedToClipboard": "पथ क्लिपबोर्ड पर कॉपी किया गया", + "pathsCopiedToClipboard": "{{count}} पथ क्लिपबोर्ड पर कॉपी किए गए", + "failedToCopyPath": "क्लिपबोर्ड पर पथ कॉपी करने में विफल", + "copyFolderLink": "फ़ोल्डर का लिंक कॉपी करें", + "copyCurrentFolderLink": "वर्तमान फ़ोल्डर का लिंक कॉपी करें", + "folderLinkCopied": "फ़ोल्डर लिंक क्लिपबोर्ड पर कॉपी किया गया", + "failedToCopyFolderLink": "फ़ोल्डर लिंक कॉपी करने में विफल", + "movedItems": "{{count}} आइटम स्थानांतरित किए गए", + "failedToDeleteItem": "आइटम हटाने में विफल", + "itemRenamedSuccessfully": "{{type}} का नाम सफलतापूर्वक बदल दिया गया", + "failedToRenameItem": "आइटम का नाम बदलने में विफल", + "download": "डाउनलोड", + "openExternalEditor": "बाह्य संपादक खोलें", + "chooseExternalEditor": "बाह्य संपादक चुनें", + "externalEditorSelected": "बाह्य संपादक चयनित", + "externalEditorDesktopOnly": "बाह्य संपादक केवल डेस्कटॉप ऐप में उपलब्ध है", + "externalEditorOpened": "बाह्य संपादक में खोला गया। वहाँ सहेजने पर परिवर्तन सर्वर पर वापस अपलोड हो जाएँगे।", + "failedToOpenExternalEditor": "बाह्य संपादक खोलने में विफल", + "failedToSelectExternalEditor": "बाह्य संपादक चुनने में विफल", + "permissions": "अनुमतियाँ", + "size": "आकार", + "modified": "संशोधित", + "path": "पथ", + "confirmDelete": "क्या आप वाकई {{name}} हटाना चाहते हैं?", + "permissionDenied": "अनुमति अस्वीकृत", + "serverError": "सर्वर त्रुटि", + "fileSavedSuccessfully": "फ़ाइल सफलतापूर्वक सहेजी गई", + "failedToSaveFile": "फ़ाइल सहेजने में विफल", + "confirmDeleteSingleItem": "क्या आप वाकई \"{{name}}\" को स्थायी रूप से हटाना चाहते हैं?", + "confirmDeleteMultipleItems": "क्या आप वाकई {{count}} आइटम स्थायी रूप से हटाना चाहते हैं?", + "confirmDeleteMultipleItemsWithFolders": "क्या आप वाकई {{count}} आइटम स्थायी रूप से हटाना चाहते हैं? इसमें फ़ोल्डर और उनकी सामग्री शामिल है।", + "confirmDeleteFolder": "क्या आप वाकई फ़ोल्डर \"{{name}}\" और उसकी सभी सामग्री को स्थायी रूप से हटाना चाहते हैं?", + "permanentDeleteWarning": "यह क्रिया पूर्ववत नहीं की जा सकती। आइटम सर्वर से स्थायी रूप से हटा दिए जाएँगे।", + "moveToTrashWarning": "आइटम को टर्मिक्स ट्रैश में भेज दिया जाएगा और प्रतिधारण अवधि समाप्त होने से पहले इसे पुनर्स्थापित किया जा सकता है।", + "itemsMovedToTrash": "{{count}} आइटम ट्रैश में चले गए", + "trash": "कचरा", + "trashDescription": "हटाए गए आइटम इस होस्ट पर {{days}} दिनों तक रखे जाते हैं।", + "trashLoadFailed": "ट्रैश लोड करने में विफल", + "trashEmpty": "कचरा पात्र खाली है", + "trashRestore": "पुनर्स्थापित करना", + "trashRestored": "पुनर्स्थापित {{name}}", + "trashRestoreFailed": "आइटम को पुनर्स्थापित करने में विफल। मूल पथ पहले से मौजूद हो सकता है।", + "trashDeletePermanently": "स्थायी रूप से हटाएं", + "trashDeleteConfirm": "{{name}}को स्थायी रूप से हटा दें? इसे पूर्ववत नहीं किया जा सकता।", + "trashPermanentlyDeleted": "स्थायी रूप से हटा दिया गया {{name}}", + "trashDeleteFailed": "आइटम को स्थायी रूप से हटाने में विफल रहा", + "trashEmptyAction": "कचरा खाली करें", + "trashEmptyConfirm": "ट्रैश में मौजूद सभी आइटम को स्थायी रूप से हटाना चाहते हैं? इसे वापस नहीं लिया जा सकता।", + "trashEmptied": "कचरा खाली कर दिया गया", + "trashRetention": "(दिनों) के बाद स्वचालित सफाई", + "trashRetentionSaved": "कचरा संग्रहण प्रणाली को अद्यतन किया गया", + "trashRetentionFailed": "ट्रैश रिटेंशन को अपडेट करने में विफलता", + "trashUnavailableConfirm": "इस आइटम को ट्रैश में नहीं ले जाया जा सकता, आमतौर पर इसलिए क्योंकि यह किसी अन्य फ़ाइल सिस्टम पर है या इसके लिए उच्च अनुमतियों की आवश्यकता होती है। क्या इसे स्थायी रूप से हटा देना बेहतर होगा?", + "recent": "हाल ही", + "pinned": "पिन किए गए", + "folderShortcuts": "फ़ोल्डर शॉर्टकट", + "failedToReconnectSSH": "SSH सत्र पुनः कनेक्ट करने में विफल", + "openTerminalHere": "यहाँ टर्मिनल खोलें", + "run": "चलाएँ", + "openTerminalInFolder": "इस फ़ोल्डर में टर्मिनल खोलें", + "openTerminalInFileLocation": "फ़ाइल स्थान पर टर्मिनल खोलें", + "runningFile": "चल रहा है - {{file}}", + "onlyRunExecutableFiles": "केवल एक्सीक्यूटेबल फ़ाइलें ही चलाई जा सकती हैं", + "directories": "निर्देशिकाएँ", + "removedFromRecentFiles": "हाल की फ़ाइलों से \"{{name}}\" हटा दिया गया", + "removeFailed": "हटाना विफल", + "unpinnedSuccessfully": "\"{{name}}\" सफलतापूर्वक अनपिन किया गया", + "unpinFailed": "अनपिन विफल", + "removedShortcut": "शॉर्टकट \"{{name}}\" हटा दिया गया", + "removeShortcutFailed": "शॉर्टकट हटाना विफल", + "clearedAllRecentFiles": "सभी हाल की फ़ाइलें साफ़ कर दी गईं", + "clearFailed": "साफ़ करना विफल", + "removeFromRecentFiles": "हाल की फ़ाइलों से हटाएँ", + "clearAllRecentFiles": "सभी हाल की फ़ाइलें साफ़ करें", + "unpinFile": "फ़ाइल अनपिन करें", + "removeShortcut": "शॉर्टकट हटाएँ", + "pinFile": "फ़ाइल पिन करें", + "addToShortcuts": "शॉर्टकट में जोड़ें", + "pasteFailed": "चिपकाना विफल", + "noUndoableActions": "कोई पूर्ववत करने योग्य कार्रवाई नहीं", + "undoCopySuccess": "कॉपी कार्रवाई पूर्ववत की गई: {{count}} कॉपी की गई फ़ाइलें हटा दी गईं", + "undoCopyFailedDelete": "पूर्ववत विफल: कोई भी कॉपी की गई फ़ाइल नहीं हटा सका", + "undoCopyFailedNoInfo": "पूर्ववत विफल: कॉपी की गई फ़ाइल की जानकारी नहीं मिली", + "undoMoveSuccess": "स्थानांतरण कार्रवाई पूर्ववत की गई: {{count}} फ़ाइलें वापस मूल स्थान पर ले जाई गईं", + "undoMoveFailedMove": "पूर्ववत विफल: कोई भी फ़ाइल वापस नहीं ले जा सका", + "undoMoveFailedNoInfo": "पूर्ववत विफल: स्थानांतरित फ़ाइल की जानकारी नहीं मिली", + "undoDeleteNotSupported": "हटाने की कार्रवाई पूर्ववत नहीं की जा सकती: फ़ाइलें सर्वर से स्थायी रूप से हटा दी गई हैं", + "undoTypeNotSupported": "असमर्थित पूर्ववत कार्रवाई प्रकार", + "undoOperationFailed": "पूर्ववत कार्रवाई विफल", + "unknownError": "अज्ञात त्रुटि", + "confirm": "पुष्टि करें", + "find": "खोजें...", + "replace": "बदलें", + "downloadInstead": "इसके बजाय डाउनलोड करें", + "keyboardShortcuts": "कीबोर्ड शॉर्टकट्स", + "searchAndReplace": "खोजें और बदलें", + "editing": "संपादन", + "search": "खोजें", + "findNext": "अगला खोजें", + "findPrevious": "पिछला खोजें", + "save": "सहेजें", + "selectAll": "सभी चुनें", + "undo": "पूर्ववत करें", + "redo": "फिर से करें", + "moveLineUp": "पंक्ति ऊपर ले जाएँ", + "moveLineDown": "पंक्ति नीचे ले जाएँ", + "toggleComment": "टिप्पणी टॉगल करें", + "autoComplete": "स्वतः पूर्ण करें", + "imageLoadError": "छवि लोड करने में विफल", + "startTyping": "टाइप करना शुरू करें...", + "unknownSize": "अज्ञात आकार", + "fileIsEmpty": "फ़ाइल खाली है", + "largeFileWarning": "बड़ी फ़ाइल की चेतावनी", + "largeFileWarningDesc": "यह फ़ाइल {{size}} आकार की है, जिसे टेक्स्ट के रूप में खोलने पर प्रदर्शन समस्याएँ हो सकती हैं।", + "fileNotFoundAndRemoved": "फ़ाइल \"{{name}}\" नहीं मिली और हाल की/पिन की गई फ़ाइलों से हटा दी गई है", + "failedToLoadFile": "फ़ाइल लोड करने में विफल: {{error}}", + "serverErrorOccurred": "सर्वर त्रुटि हुई। कृपया बाद में पुनः प्रयास करें।", + "autoSaveFailed": "स्वतः सहेजना विफल", + "fileAutoSaved": "फ़ाइल स्वतः सहेजी गई", + "moveFileFailed": "{{name}} को स्थानांतरित करने में विफल", + "moveOperationFailed": "स्थानांतरण क्रिया विफल", + "canOnlyCompareFiles": "केवल दो फ़ाइलों की तुलना की जा सकती है", + "comparingFiles": "फ़ाइलों की तुलना: {{file1}} और {{file2}}", + "dragFailed": "खींचने की क्रिया विफल", + "filePinnedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक पिन की गई", + "pinFileFailed": "फ़ाइल पिन करने में विफल", + "fileUnpinnedSuccessfully": "फ़ाइल \"{{name}}\" अनपिन की गई सफलतापूर्वक", + "unpinFileFailed": "फ़ाइल अनपिन करने में विफल", + "shortcutAddedSuccessfully": "फ़ोल्डर शॉर्टकट \"{{name}}\" सफलतापूर्वक जोड़ा गया", + "addShortcutFailed": "शॉर्टकट जोड़ने में विफल", + "operationCompletedSuccessfully": "{{operation}} {{count}} आइटम सफलतापूर्वक", + "operationCompleted": "{{operation}} {{count}} आइटम", + "downloadFileSuccess": "फ़ाइल {{name}} सफलतापूर्वक डाउनलोड हुई", + "downloadFileFailed": "डाउनलोड विफल", + "downloadingFile": "डाउनलोड हो रहा है {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "{{name}} में ले जाएँ", + "diffCompareWith": "{{name}} के साथ डिफ तुलना करें", + "dragOutsideToDownload": "डाउनलोड करने के लिए विंडो से बाहर खींचें ({{count}} फ़ाइलें)", + "newFolderDefault": "नया फ़ोल्डर", + "newFileDefault": "नई फ़ाइल.txt", + "successfullyMovedItems": "{{count}} आइटम सफलतापूर्वक {{target}} में ले जाए गए", + "move": "स्थानांतरित करें", + "searchInFile": "फ़ाइल में खोजें (Ctrl+F)", + "showKeyboardShortcuts": "कीबोर्ड शॉर्टकट दिखाएँ", + "decreaseFontSize": "फ़ॉन्ट आकार घटाएँ", + "increaseFontSize": "फ़ॉन्ट आकार बढ़ाएँ", + "startWritingMarkdown": "अपनी Markdown सामग्री लिखना शुरू करें...", + "loadingFileComparison": "फ़ाइल तुलना लोड हो रही है...", + "reload": "पुनः लोड करें", + "compare": "तुलना करें", + "sideBySide": "साथ-साथ", + "inline": "इनलाइन", + "fileComparison": "फ़ाइल तुलना: {{file1}} बनाम {{file2}}", + "fileTooLarge": "फ़ाइल बहुत बड़ी है: {{error}}", + "sshConnectionFailed": "SSH कनेक्शन विफल। कृपया {{name}} ({{ip}}:{{port}}) से अपना कनेक्शन जाँचें।", + "loadFileFailed": "फ़ाइल लोड करने में विफल: {{error}}", + "connecting": "कनेक्ट हो रहा है...", + "connectedSuccessfully": "सफलतापूर्वक कनेक्ट हुआ", + "totpVerificationFailed": "TOTP सत्यापन विफल", + "warpgateVerificationFailed": "Warpgate सत्यापन विफल", + "authenticationFailed": "प्रमाणीकरण विफल", + "incorrectPassphrase": "गलत पासफ़्रेज़। कृपया पुनः प्रयास करें।", + "verificationCodePrompt": "सत्यापन कोड:", + "changePermissions": "अनुमतियाँ बदलें", + "currentPermissions": "वर्तमान अनुमतियाँ", + "owner": "स्वामी", + "group": "समूह", + "others": "अन्य", + "read": "पठन", + "write": "लेखन", + "execute": "निष्पादन", + "permissionsChangedSuccessfully": "अनुमतियाँ सफलतापूर्वक बदल दी गईं", + "failedToChangePermissions": "अनुमतियाँ बदलने में विफल", + "name": "नाम", + "sortByName": "नाम", + "sortByDate": "संशोधन तिथि", + "sortBySize": "आकार", + "ascending": "आरोही", + "descending": "अवरोही", + "root": "रूट", + "new": "नया", + "sortBy": "क्रमबद्ध करें", + "items": "आइटम", + "selected": "चयनित", + "editor": "संपादक", + "octal": "ऑक्टल", + "storage": "भंडारण", + "disk": "डिस्क", + "used": "प्रयुक्त", + "of": "में से", + "toggleSidebar": "साइडबार टॉगल करें", + "compactLayout": "कॉम्पैक्ट लेआउट का उपयोग करें", + "comfortableLayout": "सुविधाजनक लेआउट का उपयोग करें", + "cannotLoadPdf": "PDF लोड नहीं कर सकता", + "pdfLoadError": "इस PDF फ़ाइल को लोड करने में त्रुटि हुई।", + "loadingPdf": "PDF लोड हो रहा है...", + "loadingPage": "पृष्ठ लोड हो रहा है..." + }, + "sftpTransfer": { + "title": "एसएफटीपी", + "description": "दो SSH होस्ट के बीच फ़ाइलें स्थानांतरित करें।", + "sourceServer": "स्रोत सर्वर", + "destinationServer": "गंतव्य सर्वर", + "selectHost": "होस्ट का चयन करें", + "noHostSelected": "कोई होस्ट चयनित नहीं है", + "move": "कदम", + "copy": "प्रतिलिपि", + "upload": "अपलोड करें", + "itemsSelected": "{{count}} आइटम चयनित", + "itemsSelected_other": "{{count}} आइटम चयनित", + "ready": "तैयार", + "loadingHosts": "होस्ट लोड हो रहे हैं...", + "name": "नाम", + "modified": "संशोधित", + "size": "आकार", + "noFiles": "कोई फ़ाइल नहीं", + "open": "खुला", + "up": "ऊपर", + "refresh": "ताज़ा करना", + "copyToTarget": "लक्ष्य निर्देशिका में प्रतिलिपि करें", + "rename": "नाम बदलें", + "delete": "मिटाना", + "editPermissions": "संपादन अनुमतियाँ", + "createNewFolder": "नया फ़ोल्डर बनाएं", + "deleteSelectedItems": "चयनित आइटम हटाएं", + "deleteConfirm": "चयनित आइटम हटाएं {{count}}?", + "deleteConfirm_other": "चयनित आइटम हटाएं {{count}}?", + "cancel": "रद्द करना", + "save": "बचाना", + "folderCreated": "फ़ोल्डर बनाया गया", + "itemRenamed": "आइटम का नाम बदल दिया गया", + "actionFailed": "क्रिया: विफल रही है", + "itemsDeleted": "{{count}} आइटम हटा दिया गया", + "itemsDeleted_other": "{{count}} आइटम हटा दिए गए", + "deleteFailed": "डिलीट विफल", + "permissionsUpdated": "अनुमतियाँ अपडेट की गईं", + "noRemoteSessionConnected": "कोई रिमोट सेशन कनेक्ट नहीं हुआ", + "failedToListFiles": "फ़ाइलों को सूचीबद्ध करने में विफल", + "failedToRefresh": "रीफ़्रेश करने में विफल", + "destinationInsideSource": "गंतव्य चयनित स्रोत पथ के अंदर नहीं हो सकता।", + "sameHostNoOp": "स्रोत और गंतव्य एक ही फ़ोल्डर हैं।" + }, + "hosts": { + "fileManager": "फ़ाइल प्रबंधक", + "tabFiles": "फ़ाइलें", + "enableFileManagerMonitor": "फ़ाइल प्रबंधक सक्षम करें", + "enableFileManagerMonitorDesc": "इस होस्ट पर SFTP के ज़रिए फ़ाइलें ब्राउज़ और प्रबंधित करें", + "scpLegacyLabel": "SCP लीगेसी मोड", + "scpLegacyDesc": "उन सर्वरों के लिए लीगेसी फ़ाइल स्थानांतरण का उपयोग करें जो SFTP सबसिस्टम का समर्थन नहीं करते (जैसे एम्बेडेड या न्यूनतम SSH सर्वर)।", + "defaultPathLabel": "डिफ़ॉल्ट पथ", + "fileManagerPathHint": "जब इस होस्ट के लिए फ़ाइल प्रबंधक लॉन्च होता है तो खोलने के लिए निर्देशिका।", + "fileManagerUrlCopied": "फ़ाइल प्रबंधक URL कॉपी किया गया", + "copyFileManagerUrlAction": "फ़ाइल प्रबंधक URL कॉपी करें" + }, + "settings": { + "host": { + "enable": { + "label": "फ़ाइल प्रबंधक सक्षम करें" + }, + "defaultPath": { + "label": "डिफ़ॉल्ट पथ" + }, + "scpLegacy": { + "label": "SCP लीगेसी मोड" + } + } + }, + "nav": { + "sftp": "एसएफटीपी", + "files": "फ़ाइलें" + } +} diff --git a/plugins/file-manager/locales/translated/hu_HU.json b/plugins/file-manager/locales/translated/hu_HU.json new file mode 100644 index 000000000..b9d57cc6e --- /dev/null +++ b/plugins/file-manager/locales/translated/hu_HU.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Nincs kiválasztva gazdagép", + "initializingEditor": "Szerkesztő inicializálása...", + "file": "Fájl", + "folder": "Mappa", + "uploadFile": "Fájl feltöltése", + "downloadFile": "Letöltés", + "extractArchive": "Archívum kibontása", + "extractingArchive": "{{name}} kinyerése ...", + "archiveExtractedSuccessfully": "{{name}} sikeresen kibontva", + "extractFailed": "Kibontás sikertelen", + "compressFile": "Fájl tömörítése", + "compressFiles": "Fájlok tömörítése", + "compressFilesDesc": "Tömörítsen {{count}} elemet archívumba", + "archiveName": "Archívum neve", + "enterArchiveName": "Adja meg az archívum nevét...", + "compressionFormat": "Tömörítési formátum", + "selectedFiles": "Kiválasztott fájlok", + "andMoreFiles": "és {{count}} több...", + "compress": "Borogatás", + "compressingFiles": "{{count}} elem tömörítése {{name}}...", + "filesCompressedSuccessfully": "{{name}} sikeresen létrehozva", + "compressFailed": "Sikertelen tömörítés", + "edit": "Szerkesztés", + "preview": "Előnézet", + "previous": "Előző", + "next": "Következő", + "pageXOfY": "{{total}} oldalból a {{current}} oldal", + "zoomOut": "Kicsinyítés", + "zoomIn": "Nagyítás", + "newFile": "Új fájl", + "newFolder": "Új mappa", + "rename": "Átnevezés", + "uploading": "Feltöltés...", + "uploadingFile": "Feltöltés {{name}}...", + "fileName": "Fájlnév", + "folderName": "Mappa neve", + "fileUploadedSuccessfully": "A(z) \"{{name}}\" fájl feltöltése sikeresen megtörtént.", + "failedToUploadFile": "Nem sikerült feltölteni a fájlt", + "uploadingFolderFiles": "{{count}} fájl(ok) feltöltése...", + "uploadedFolderFiles": "{{count}} fájl(ok) feltöltve sikeresen", + "uploadedFolderPartial": "{{uploaded}} fájl(ok) feltöltése, {{failed}} sikertelen", + "fileDownloadedSuccessfully": "A(z) \"{{name}}\" fájl sikeresen letöltve", + "failedToDownloadFile": "Nem sikerült letölteni a fájlt", + "fileCreatedSuccessfully": "A(z) \"{{name}}\" fájl sikeresen létrehozva", + "folderCreatedSuccessfully": "A(z) \"{{name}}\" mappa sikeresen létrehozva", + "failedToCreateItem": "Nem sikerült létrehozni az elemet", + "operationFailed": "{{operation}} művelet sikertelen a következőhöz: {{name}}: {{error}}", + "failedToResolveSymlink": "Nem sikerült feloldani a szimbolikus linket", + "itemsDeletedSuccessfully": "{{count}} elem sikeresen törölve", + "failedToDeleteItems": "Nem sikerült törölni az elemeket", + "sudoPasswordRequired": "Rendszergazdai jelszó szükséges", + "enterSudoPassword": "Írja be a sudo jelszót a művelet folytatásához", + "sudoPassword": "Sudo jelszó", + "sudoOperationFailed": "Sudo művelet sikertelen", + "sudoAuthFailed": "Sudo hitelesítés sikertelen", + "dragFilesToUpload": "Húzd ide a fájlokat a feltöltéshez", + "dropToUploadHere": "Húzd ide a feltöltéshez", + "dropToDownloadHere": "Húzd ide a letöltéshez", + "localFiles": "Helyi", + "showLocalFiles": "Helyi fájlok megjelenítése (osztott nézet)", + "hideLocalFiles": "Helyi fájlok elrejtése", + "localHome": "Saját mappa", + "localPathPlaceholder": "Helyi mappa elérési útja", + "localShowHidden": "Rejtett fájlok megjelenítése", + "localHideHidden": "Rejtett fájlok elrejtése", + "localRevealInFileManager": "Megjelenítés a rendszerfájl-kezelőben", + "localCannotOpenFolder": "Nem lehet megnyitni a mappát", + "localItemCount": "{{count}} elem", + "localSelectedCount": "{{count}} kiválasztva", + "back": "Vissza", + "forward": "Előre", + "up": "Fel", + "kind": "Fajta", + "localUploadingProgress": "{{current}} / {{total}} feltöltése", + "localDownloadingProgress": "{{current}} letöltése a következőből: {{total}}", + "localTransferInFlight": "{{count}} egyszerre", + "localPreparingDownload": "Letöltés előkészítése...", + "localTransferCancelled": "Átutalás törölve", + "localNothingToTransfer": "Nincs mit átutalni", + "localUploadComplete": "Feltöltött {{count}} fájl(ok)", + "localUploadFailed": "Feltöltés sikertelen", + "localUploadPartial": "{{done}} fájl(ok) feltöltése, {{failed}} sikertelen", + "localDownloadComplete": "Letöltött {{count}} fájl(ok)", + "localDownloadFailed": "Letöltés sikertelen", + "localDownloadPartial": "{{done}} fájl(ok) letöltése, {{failed}} sikertelen", + "localReplaceExistingPrompt": "{{count}} elem már létezik ebben a mappában. Cserélje ki őket?", + "localReplace": "Csere", + "localSkipExisting": "Kihagyás", + "localSkippedExisting": "Kihagyott {{count}} meglévő elem(ek)", + "localUnsafeNamesSkipped": "Kihagyott {{count}} elem(ek), amelyek neve nem használható ezen a számítógépen", + "localOpen": "Nyitott", + "localOpenFolder": "Mappa megnyitása", + "localUploadToRemote": "Feltöltés a szerverre", + "localUploadToRemoteMany": "Tölts fel {{count}} elemet a szerverre", + "localMoveToTrash": "Áthelyezés a kukába", + "localMoveToTrashMany": "{{count}} elem áthelyezése a Kukába", + "localTrashConfirmSingle": "Áthelyeznéd a(z) \"{{name}}\"-t a Kukába?", + "localTrashConfirmFolder": "Áthelyezi a(z) \"{{name}}\" mappát és annak teljes tartalmát a Kukába?", + "localTrashConfirmMany": "Áthelyezi a {{count}} elemet a Kukába?", + "localTrashed": "{{count}} elem áthelyezve a Kukába", + "localTrashFailed": "Nem sikerült a Kukába helyezni", + "localRenameFailed": "Átnevezés sikertelen", + "localCreateFailed": "Nem sikerült létrehozni az elemet", + "goToParentFolder": "Ugrás a szülőmappára", + "resizeSidebar": "Húzással átméretezheted az oldalsávot (dupla kattintással visszaállíthatod)", + "resizeLocalPane": "Húzással átméretezheti a helyi panelt (dupla kattintással visszaállíthatja)", + "columns": "Oszlopok", + "columnsHint": "Kattintson jobb gombbal az oszlopok kiválasztásához", + "emptyFolder": "Ez a mappa üres", + "searchFiles": "Fájlok keresése...", + "upload": "Feltöltés", + "selectHostToStart": "Válasszon ki egy gazdagépet a fájlkezelés megkezdéséhez", + "sshRequiredForFileManager": "A fájlkezelő SSH-t igényel. Ezen a gazdagépen nincs engedélyezve az SSH.", + "failedToConnect": "Nem sikerült csatlakozni az SSH-hoz", + "failedToLoadDirectory": "Nem sikerült betölteni a könyvtárat", + "noSSHConnection": "Nincs elérhető SSH-kapcsolat", + "copy": "Másolat", + "cut": "Vágott", + "paste": "Paszta", + "copyPath": "Útvonal másolása", + "copyPaths": "Útvonalak másolása", + "delete": "Töröl", + "properties": "Tulajdonságok", + "refresh": "Frissítés", + "downloadFiles": "{{count}} fájlok letöltése böngészőbe", + "copyFiles": "{{count}} elem másolása", + "cutFiles": "{{count}} elem kivágása", + "deleteFiles": "{{count}} elem törlése", + "filesCopiedToClipboard": "{{count}} elem másolva a vágólapra", + "filesCutToClipboard": "{{count}} elem a vágólapra vágva", + "pathCopiedToClipboard": "Útvonal a vágólapra másolva", + "pathsCopiedToClipboard": "{{count}} elérési utak másolva a vágólapra", + "failedToCopyPath": "Nem sikerült a vágólapra másolni az elérési utat", + "copyFolderLink": "Link másolása mappába", + "copyCurrentFolderLink": "Link másolása az aktuális mappába", + "folderLinkCopied": "Mappa linkje a vágólapra másolva", + "failedToCopyFolderLink": "Nem sikerült a mappa linkjét másolni", + "movedItems": "Áthelyezett {{count}} elem", + "failedToDeleteItem": "Nem sikerült törölni az elemet", + "itemRenamedSuccessfully": "{{type}} átnevezése sikeresen megtörtént", + "failedToRenameItem": "Nem sikerült átnevezni az elemet", + "download": "Letöltés", + "openExternalEditor": "Külsőleg nyitott", + "chooseExternalEditor": "Válasszon külső szerkesztőt", + "externalEditorSelected": "Külső szerkesztő kiválasztva", + "externalEditorDesktopOnly": "A külső szerkesztő csak az asztali alkalmazásban érhető el.", + "externalEditorOpened": "Külső szerkesztőben megnyitva. A mentés oda feltölti a módosításokat a szerverre.", + "failedToOpenExternalEditor": "Nem sikerült megnyitni a külső szerkesztőt", + "failedToSelectExternalEditor": "Nem sikerült kiválasztani a külső szerkesztőt", + "permissions": "Engedélyek", + "size": "Méret", + "modified": "Módosított", + "path": "Útvonal", + "confirmDelete": "Biztosan törölni szeretnéd ezt: {{name}}?", + "permissionDenied": "Engedély megtagadva", + "serverError": "Szerverhiba", + "fileSavedSuccessfully": "Fájl mentése sikeresen megtörtént", + "failedToSaveFile": "Nem sikerült menteni a fájlt", + "confirmDeleteSingleItem": "Biztosan véglegesen törölni szeretnéd a(z) \"{{name}} \" fájlt?", + "confirmDeleteMultipleItems": "Biztosan véglegesen törölni szeretnéd a(z) {{count}} elemet?", + "confirmDeleteMultipleItemsWithFolders": "Biztosan véglegesen törölni szeretnéd a(z) {{count}} elemet? Ez a mappákat és azok tartalmát is tartalmazza.", + "confirmDeleteFolder": "Biztosan véglegesen törölni szeretné a(z) \"{{name}}\" mappát és annak teljes tartalmát?", + "permanentDeleteWarning": "Ez a művelet nem vonható vissza. Az elem(ek) véglegesen törlődnek a szerverről.", + "moveToTrashWarning": "Az elem(ek) a Termix Kukába kerülnek, és a megőrzési időszak lejárta előtt visszaállíthatók.", + "itemsMovedToTrash": "{{count}} elem áthelyezve a Kukába", + "trash": "Szemét", + "trashDescription": "A törölt elemek {{days}} napig maradnak meg ezen a gépen.", + "trashLoadFailed": "Nem sikerült betölteni a Kuka mappát", + "trashEmpty": "A kuka üres", + "trashRestore": "Visszaállítás", + "trashRestored": "Visszaállított {{name}}", + "trashRestoreFailed": "Nem sikerült visszaállítani az elemet. Az eredeti elérési út már létezhet.", + "trashDeletePermanently": "Végleges törlés", + "trashDeleteConfirm": "Véglegesen törölni szeretnéd a(z) {{name}}elemet? Ez a művelet nem vonható vissza.", + "trashPermanentlyDeleted": "Véglegesen törölve {{name}}", + "trashDeleteFailed": "Nem sikerült véglegesen törölni az elemet", + "trashEmptyAction": "Ürítse ki a szemetet", + "trashEmptyConfirm": "Véglegesen törli az összes elemet a Kukából? Ez a művelet nem vonható vissza.", + "trashEmptied": "Szemét kiürítve", + "trashRetention": "Automatikus tisztítás (nap) után", + "trashRetentionSaved": "A kuka megőrzése frissítve", + "trashRetentionFailed": "Nem sikerült frissíteni a Kuka megőrzési idejét", + "trashUnavailableConfirm": "Ez az elem nem helyezhető a Kukába, általában azért, mert egy másik fájlrendszeren van, vagy emelt szintű jogosultságokat igényel. Inkább véglegesen törli?", + "recent": "Legutóbbi", + "pinned": "Rögzítve", + "folderShortcuts": "Mappa parancsikonok", + "failedToReconnectSSH": "Nem sikerült újracsatlakozni az SSH-munkamenethez", + "openTerminalHere": "Nyissa meg a terminált itt", + "run": "Fut", + "openTerminalInFolder": "Nyissa meg a terminált ebben a mappában", + "openTerminalInFileLocation": "Nyissa meg a terminált a fájl helyén", + "runningFile": "Futás - {{file}}", + "onlyRunExecutableFiles": "Csak futtatható fájlokat futtathat", + "directories": "Könyvtárak", + "removedFromRecentFiles": "Eltávolította a(z) „{{name}}” elemet a legutóbbi fájlok közül", + "removeFailed": "Eltávolítás sikertelen", + "unpinnedSuccessfully": "A(z) \"{{name}}\" rögzítésének feloldása sikeresen megtörtént.", + "unpinFailed": "Feloldás sikertelen", + "removedShortcut": "Eltávolította a(z) \"{{name}} \" parancsikont", + "removeShortcutFailed": "A parancsikon eltávolítása sikertelen", + "clearedAllRecentFiles": "Az összes legutóbbi fájl törölve", + "clearFailed": "Törlés sikertelen", + "removeFromRecentFiles": "Eltávolítás a legutóbbi fájlok közül", + "clearAllRecentFiles": "Az összes legutóbbi fájl törlése", + "unpinFile": "Fájl rögzítésének feloldása", + "removeShortcut": "Parancsikon eltávolítása", + "pinFile": "Fájl rögzítése", + "addToShortcuts": "Hozzáadás a parancsikonokhoz", + "pasteFailed": "Beillesztés sikertelen", + "noUndoableActions": "Nincsenek visszavonható műveletek", + "undoCopySuccess": "Visszavont másolási művelet: Törölt {{count}} másolt fájlok", + "undoCopyFailedDelete": "Visszavonás sikertelen: Nem sikerült törölni a másolt fájlokat", + "undoCopyFailedNoInfo": "Visszavonás sikertelen: Nem találhatók másolt fájlinformációk", + "undoMoveSuccess": "Visszavont áthelyezési művelet: {{count}} fájl visszahelyezése az eredeti helyére", + "undoMoveFailedMove": "Visszavonás sikertelen: Nem sikerült fájlokat visszahelyezni", + "undoMoveFailedNoInfo": "Visszavonás sikertelen: Nem találhatók áthelyezett fájlinformációk", + "undoDeleteNotSupported": "A törlési művelet nem vonható vissza: A fájlok véglegesen törölve lettek a szerverről.", + "undoTypeNotSupported": "Nem támogatott visszavonási művelettípus", + "undoOperationFailed": "A visszavonási művelet sikertelen", + "unknownError": "Ismeretlen hiba", + "confirm": "Megerősítés", + "find": "Lelet...", + "replace": "Csere", + "downloadInstead": "Letöltés helyett", + "keyboardShortcuts": "Billentyűparancsok", + "searchAndReplace": "Keresés és csere", + "editing": "Szerkesztés", + "search": "Keresés", + "findNext": "Következő keresése", + "findPrevious": "Előző keresése", + "save": "Megtakarítás", + "selectAll": "Összes kijelölése", + "undo": "Visszavonás", + "redo": "Újra", + "moveLineUp": "Sor felfelé mozgatása", + "moveLineDown": "Sor mozgatása lejjebb", + "toggleComment": "Hozzászólás be-/kikapcsolása", + "autoComplete": "Automatikus kiegészítés", + "imageLoadError": "Nem sikerült betölteni a képet", + "startTyping": "Kezdj el gépelni...", + "unknownSize": "Ismeretlen méret", + "fileIsEmpty": "A fájl üres", + "largeFileWarning": "Nagy fájlra vonatkozó figyelmeztetés", + "largeFileWarningDesc": "A fájl mérete {{size}} , ami teljesítményproblémákat okozhat szövegként megnyitva.", + "fileNotFoundAndRemoved": "A(z) „{{name}}” fájl nem található, és eltávolításra került a legutóbbi/rögzített fájlok közül.", + "failedToLoadFile": "Nem sikerült betölteni a fájlt: {{error}}", + "serverErrorOccurred": "Szerverhiba történt. Kérjük, próbálja meg később.", + "autoSaveFailed": "Automatikus mentés sikertelen", + "fileAutoSaved": "Fájl automatikusan mentve", + "moveFileFailed": "Nem sikerült áthelyezni {{name}}", + "moveOperationFailed": "Áthelyezési művelet sikertelen", + "canOnlyCompareFiles": "Csak két fájlt lehet összehasonlítani", + "comparingFiles": "Fájlok összehasonlítása: {{file1}} és {{file2}}", + "dragFailed": "Húzási művelet sikertelen", + "filePinnedSuccessfully": "A(z) „{{name}}” fájl rögzítése sikeresen megtörtént.", + "pinFileFailed": "Nem sikerült rögzíteni a fájlt", + "fileUnpinnedSuccessfully": "A(z) „{{name}}” fájl rögzítése sikeresen feloldva", + "unpinFileFailed": "Nem sikerült feloldani a fájl rögzítését", + "shortcutAddedSuccessfully": "A(z) „{{name}}” mappa parancsikonja sikeresen hozzáadva", + "addShortcutFailed": "Nem sikerült hozzáadni a parancsikont", + "operationCompletedSuccessfully": "{{operation}} {{count}} elem sikeresen", + "operationCompleted": "{{operation}} {{count}} elem", + "downloadFileSuccess": "A {{name}} fájl sikeresen letöltve", + "downloadFileFailed": "Letöltés sikertelen", + "downloadingFile": "{{name}}… letöltése", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Áthelyezés ide: {{name}}", + "diffCompareWith": "Különbség összehasonlítása {{name}}-val", + "dragOutsideToDownload": "Húzd az ablakon kívülre a letöltéshez ({{count}} fájl)", + "newFolderDefault": "Új mappa", + "newFileDefault": "ÚjFájl.txt", + "successfullyMovedItems": "{{count}} elem sikeresen áthelyezve ide: {{target}}", + "move": "Mozog", + "searchInFile": "Keresés a fájlban (Ctrl+F)", + "showKeyboardShortcuts": "Billentyűparancsok megjelenítése", + "decreaseFontSize": "Betűméret csökkentése", + "increaseFontSize": "Betűméret növelése", + "startWritingMarkdown": "Kezdj el írni a Markdown tartalmaidat...", + "loadingFileComparison": "Fájl-összehasonlítás betöltése...", + "reload": "Újratöltés", + "compare": "Összehasonlítás", + "sideBySide": "Egymás mellett", + "inline": "Beágyazott", + "fileComparison": "Fájl-összehasonlítás: {{file1}} vs {{file2}}", + "fileTooLarge": "A fájl túl nagy: {{error}}", + "sshConnectionFailed": "SSH kapcsolat sikertelen. Kérjük, ellenőrizze a kapcsolatot a következővel: {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Nem sikerült betölteni a fájlt: {{error}}", + "connecting": "Kapcsolódás...", + "connectedSuccessfully": "Sikeres csatlakozás", + "totpVerificationFailed": "TOTP-ellenőrzés sikertelen", + "warpgateVerificationFailed": "Warpgate hitelesítés sikertelen", + "authenticationFailed": "Hitelesítés sikertelen", + "incorrectPassphrase": "Hibás jelszó. Kérjük, próbálja újra.", + "verificationCodePrompt": "Ellenőrző kód:", + "changePermissions": "Engedélyek módosítása", + "currentPermissions": "Jelenlegi engedélyek", + "owner": "Tulajdonos", + "group": "Csoport", + "others": "Mások", + "read": "Olvas", + "write": "Írás", + "execute": "Végrehajtás", + "permissionsChangedSuccessfully": "Engedélyek sikeresen módosítva", + "failedToChangePermissions": "Nem sikerült módosítani az engedélyeket", + "name": "Név", + "sortByName": "Név", + "sortByDate": "Módosítás dátuma", + "sortBySize": "Méret", + "ascending": "Növekvő", + "descending": "Csökkenő", + "root": "Gyökér", + "new": "Új", + "sortBy": "Rendezés alapja", + "items": "Tételek", + "selected": "Kiválasztott", + "editor": "Szerkesztő", + "octal": "Oktális", + "storage": "Tárolás", + "disk": "Korong", + "used": "Használt", + "of": "a", + "toggleSidebar": "Oldalsáv be- és kikapcsolása", + "compactLayout": "Kompakt elrendezés használata", + "comfortableLayout": "Használjon kényelmes elrendezést", + "cannotLoadPdf": "Nem tudom betölteni a PDF-et", + "pdfLoadError": "Hiba történt a PDF fájl betöltésekor.", + "loadingPdf": "PDF betöltése...", + "loadingPage": "Oldal betöltése..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Fájlok átvitele két SSH hoszt között.", + "sourceServer": "Forráskiszolgáló", + "destinationServer": "Célkiszolgáló", + "selectHost": "Válasszon gazdagépet", + "noHostSelected": "Nincs kiválasztva gazdagép", + "move": "Mozog", + "copy": "Másolat", + "upload": "Feltöltés", + "itemsSelected": "{{count}} elem kiválasztva", + "itemsSelected_other": "{{count}} elem kijelölve", + "ready": "Kész", + "loadingHosts": "Gazdagépek betöltése...", + "name": "Név", + "modified": "Módosított", + "size": "Méret", + "noFiles": "Nincsenek fájlok", + "open": "Nyitott", + "up": "Fel", + "refresh": "Frissítés", + "copyToTarget": "Másolás a célkönyvtárba", + "rename": "Átnevezés", + "delete": "Töröl", + "editPermissions": "Szerkesztési engedélyek", + "createNewFolder": "Új mappa létrehozása", + "deleteSelectedItems": "Kijelölt elemek törlése", + "deleteConfirm": "Törli a {{count}} kijelölt elemet?", + "deleteConfirm_other": "Törli a {{count}} kijelölt elemeket?", + "cancel": "Mégsem", + "save": "Megtakarítás", + "folderCreated": "Mappa létrehozva", + "itemRenamed": "Elem átnevezve", + "actionFailed": "Sikertelen művelet", + "itemsDeleted": "{{count}} elem törölve", + "itemsDeleted_other": "{{count}} elem törölve", + "deleteFailed": "Törlés sikertelen", + "permissionsUpdated": "Engedélyek frissítve", + "noRemoteSessionConnected": "Nincs távoli munkamenet csatlakoztatva", + "failedToListFiles": "Nem sikerült listázni a fájlokat", + "failedToRefresh": "Nem sikerült frissíteni", + "destinationInsideSource": "A cél nem lehet a kiválasztott forrásútvonalon belül.", + "sameHostNoOp": "A forrás- és célmappa ugyanaz." + }, + "hosts": { + "fileManager": "Fájlkezelő", + "tabFiles": "Fájlok", + "enableFileManagerMonitor": "Fájlkezelő engedélyezése", + "enableFileManagerMonitorDesc": "Fájlok böngészése és kezelése ezen a gazdagépen SFTP-n keresztül", + "scpLegacyLabel": "SCP örökölt mód", + "scpLegacyDesc": "Használjon hagyományos fájlátvitelt olyan szerverekhez, amelyek nem támogatják az SFTP alrendszert (pl. beágyazott vagy minimális SSH szerverek).", + "defaultPathLabel": "Alapértelmezett elérési út", + "fileManagerPathHint": "A könyvtár, amely megnyílik, amikor a fájlkezelő elindul ehhez a gazdagéphez.", + "fileManagerUrlCopied": "Fájlkezelő URL-címe másolva", + "copyFileManagerUrlAction": "Fájlkezelő URL-címének másolása" + }, + "settings": { + "host": { + "enable": { + "label": "Fájlkezelő engedélyezése" + }, + "defaultPath": { + "label": "Alapértelmezett elérési út" + }, + "scpLegacy": { + "label": "SCP örökölt mód" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Fájlok" + } +} diff --git a/plugins/file-manager/locales/translated/id_ID.json b/plugins/file-manager/locales/translated/id_ID.json new file mode 100644 index 000000000..892857ea8 --- /dev/null +++ b/plugins/file-manager/locales/translated/id_ID.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Tidak ada pembawa acara yang dipilih.", + "initializingEditor": "Menginisialisasi editor...", + "file": "Mengajukan", + "folder": "Map", + "uploadFile": "Unggah File", + "downloadFile": "Unduh", + "extractArchive": "Ekstrak Arsip", + "extractingArchive": "Mengekstrak {{name}}...", + "archiveExtractedSuccessfully": "{{name}} berhasil diekstrak", + "extractFailed": "Ekstraksi gagal", + "compressFile": "Kompres File", + "compressFiles": "Kompres File", + "compressFilesDesc": "Kompres {{count}} item ke dalam arsip", + "archiveName": "Nama Arsip", + "enterArchiveName": "Masukkan nama arsip...", + "compressionFormat": "Format Kompresi", + "selectedFiles": "File terpilih", + "andMoreFiles": "dan {{count}} lainnya...", + "compress": "Kompres", + "compressingFiles": "Mengompres {{count}} item menjadi {{name}}...", + "filesCompressedSuccessfully": "{{name}} berhasil dibuat", + "compressFailed": "Kompresi gagal", + "edit": "Edit", + "preview": "Pratinjau", + "previous": "Sebelumnya", + "next": "Berikutnya", + "pageXOfY": "Halaman {{current}} dari {{total}}", + "zoomOut": "Perkecil tampilan", + "zoomIn": "Perbesar", + "newFile": "Berkas Baru", + "newFolder": "Folder Baru", + "rename": "Ganti nama", + "uploading": "Sedang mengunggah...", + "uploadingFile": "Mengunggah {{name}}...", + "fileName": "Nama File", + "folderName": "Nama Folder", + "fileUploadedSuccessfully": "Berkas \"{{name}}\" berhasil diunggah", + "failedToUploadFile": "Gagal mengunggah file", + "uploadingFolderFiles": "Mengunggah {{count}} berkas...", + "uploadedFolderFiles": "Berhasil mengunggah {{count}} berkas", + "uploadedFolderPartial": "Pengunggahan {{uploaded}} berkas, {{failed}} gagal", + "fileDownloadedSuccessfully": "File \"{{name}}\" berhasil diunduh", + "failedToDownloadFile": "Gagal mengunduh file", + "fileCreatedSuccessfully": "File \"{{name}}\" berhasil dibuat", + "folderCreatedSuccessfully": "Folder \"{{name}}\" berhasil dibuat", + "failedToCreateItem": "Gagal membuat item", + "operationFailed": "Operasi {{operation}} gagal untuk {{name}}: {{error}}", + "failedToResolveSymlink": "Gagal menyelesaikan symlink", + "itemsDeletedSuccessfully": "{{count}} item berhasil dihapus", + "failedToDeleteItems": "Gagal menghapus item", + "sudoPasswordRequired": "Kata Sandi Administrator Diperlukan", + "enterSudoPassword": "Masukkan kata sandi sudo untuk melanjutkan operasi ini.", + "sudoPassword": "Kata sandi Sudo", + "sudoOperationFailed": "Operasi Sudo gagal", + "sudoAuthFailed": "Autentikasi Sudo gagal", + "dragFilesToUpload": "Seret file ke sini untuk mengunggah", + "dropToUploadHere": "Unggah di sini", + "dropToDownloadHere": "Klik di sini untuk mengunduh.", + "localFiles": "Lokal", + "showLocalFiles": "Tampilkan file lokal (tampilan terpisah)", + "hideLocalFiles": "Sembunyikan file lokal", + "localHome": "Folder beranda", + "localPathPlaceholder": "Jalur folder lokal", + "localShowHidden": "Tampilkan file tersembunyi", + "localHideHidden": "Sembunyikan file tersembunyi", + "localRevealInFileManager": "Ungkapkan di pengelola file sistem", + "localCannotOpenFolder": "Tidak dapat membuka folder", + "localItemCount": "{{count}} item", + "localSelectedCount": "{{count}} terpilih", + "back": "Kembali", + "forward": "Maju", + "up": "Ke atas", + "kind": "Baik", + "localUploadingProgress": "Mengunggah {{current}} dari {{total}}", + "localDownloadingProgress": "Mengunduh {{current}} dari {{total}}", + "localTransferInFlight": "{{count}} sekaligus", + "localPreparingDownload": "Mempersiapkan pengunduhan...", + "localTransferCancelled": "Transfer dibatalkan", + "localNothingToTransfer": "Tidak ada yang perlu ditransfer", + "localUploadComplete": "Berkas {{count}} telah diunggah", + "localUploadFailed": "Unggahan gagal", + "localUploadPartial": "Pengunggahan {{done}} berkas, {{failed}} gagal", + "localDownloadComplete": "Berkas {{count}} telah diunduh", + "localDownloadFailed": "Pengunduhan gagal", + "localDownloadPartial": "Pengunduhan {{done}} berkas, {{failed}} gagal", + "localReplaceExistingPrompt": "{{count}} item sudah ada di folder ini. Ganti item tersebut?", + "localReplace": "Mengganti", + "localSkipExisting": "Melewati", + "localSkippedExisting": "Melewati {{count}} item yang sudah ada", + "localUnsafeNamesSkipped": "Melewati {{count}} item karena namanya tidak dapat digunakan di komputer ini", + "localOpen": "Membuka", + "localOpenFolder": "Buka folder", + "localUploadToRemote": "Unggah ke server", + "localUploadToRemoteMany": "Unggah {{count}} item ke server", + "localMoveToTrash": "Pindahkan ke Tempat Sampah", + "localMoveToTrashMany": "Pindahkan {{count}} item ke Sampah", + "localTrashConfirmSingle": "Pindahkan \"{{name}}\" ke Sampah?", + "localTrashConfirmFolder": "Pindahkan folder \"{{name}}\" beserta seluruh isinya ke Sampah?", + "localTrashConfirmMany": "Pindahkan {{count}} item ke Sampah?", + "localTrashed": "Memindahkan {{count}} item ke Sampah", + "localTrashFailed": "Tidak bisa dipindahkan ke tempat sampah", + "localRenameFailed": "Penggantian nama gagal", + "localCreateFailed": "Tidak dapat membuat item", + "goToParentFolder": "Buka folder induk", + "resizeSidebar": "Seret untuk mengubah ukuran bilah sisi (klik dua kali untuk mengatur ulang)", + "resizeLocalPane": "Seret untuk mengubah ukuran panel lokal (klik dua kali untuk mengatur ulang)", + "columns": "Kolom", + "columnsHint": "Klik kanan untuk memilih kolom", + "emptyFolder": "Folder ini kosong", + "searchFiles": "Cari file...", + "upload": "Mengunggah", + "selectHostToStart": "Pilih host untuk memulai manajemen file.", + "sshRequiredForFileManager": "Pengelola file memerlukan SSH. Host ini tidak mengaktifkan SSH.", + "failedToConnect": "Gagal terhubung ke SSH", + "failedToLoadDirectory": "Gagal memuat direktori", + "noSSHConnection": "Tidak ada koneksi SSH yang tersedia.", + "copy": "Menyalin", + "cut": "Memotong", + "paste": "Pasta", + "copyPath": "Salin Jalur", + "copyPaths": "Salin Jalur", + "delete": "Menghapus", + "properties": "Properti", + "refresh": "Menyegarkan", + "downloadFiles": "Unduh {{count}} file ke Browser", + "copyFiles": "Salin {{count}} item", + "cutFiles": "Potong {{count}} item", + "deleteFiles": "Hapus {{count}} item", + "filesCopiedToClipboard": "{{count}} item disalin ke papan klip", + "filesCutToClipboard": "{{count}} item dipotong ke papan klip", + "pathCopiedToClipboard": "Jalur disalin ke papan klip", + "pathsCopiedToClipboard": "{{count}} jalur disalin ke papan klip", + "failedToCopyPath": "Gagal menyalin jalur ke papan klip.", + "copyFolderLink": "Salin Tautan ke Folder", + "copyCurrentFolderLink": "Salin Tautan ke Folder Saat Ini", + "folderLinkCopied": "Tautan folder disalin ke papan klip", + "failedToCopyFolderLink": "Gagal menyalin tautan folder", + "movedItems": "Memindahkan {{count}} item", + "failedToDeleteItem": "Gagal menghapus item", + "itemRenamedSuccessfully": "{{type}} berhasil diganti namanya", + "failedToRenameItem": "Gagal mengganti nama item", + "download": "Unduh", + "openExternalEditor": "Buka secara eksternal", + "chooseExternalEditor": "Pilih editor eksternal", + "externalEditorSelected": "Editor eksternal terpilih", + "externalEditorDesktopOnly": "Editor eksternal hanya tersedia di aplikasi desktop.", + "externalEditorOpened": "Dibuka di editor eksternal. Menyimpan di sana akan mengunggah perubahan kembali ke server.", + "failedToOpenExternalEditor": "Gagal membuka editor eksternal", + "failedToSelectExternalEditor": "Gagal memilih editor eksternal", + "permissions": "Izin", + "size": "Ukuran", + "modified": "Dimodifikasi", + "path": "Jalur", + "confirmDelete": "Apakah Anda yakin ingin menghapus {{name}}?", + "permissionDenied": "Izin ditolak", + "serverError": "Kesalahan Server", + "fileSavedSuccessfully": "Berkas berhasil disimpan.", + "failedToSaveFile": "Gagal menyimpan file", + "confirmDeleteSingleItem": "Apakah Anda yakin ingin menghapus \"{{name}} \" secara permanen?", + "confirmDeleteMultipleItems": "Apakah Anda yakin ingin menghapus {{count}} item secara permanen?", + "confirmDeleteMultipleItemsWithFolders": "Apakah Anda yakin ingin menghapus {{count}} item secara permanen? Ini termasuk folder dan isinya.", + "confirmDeleteFolder": "Apakah Anda yakin ingin menghapus folder \"{{name}}\" beserta seluruh isinya secara permanen?", + "permanentDeleteWarning": "Tindakan ini tidak dapat dibatalkan. Item tersebut akan dihapus secara permanen dari server.", + "moveToTrashWarning": "Item tersebut akan dipindahkan ke Tempat Sampah Termix dan dapat dipulihkan sebelum masa penyimpanan berakhir.", + "itemsMovedToTrash": "{{count}} item dipindahkan ke Sampah", + "trash": "Sampah", + "trashDescription": "Item yang dihapus akan tetap tersimpan selama {{days}} hari di host ini.", + "trashLoadFailed": "Sampah gagal dimuat", + "trashEmpty": "Tempat sampah sudah kosong", + "trashRestore": "Memulihkan", + "trashRestored": "Dipulihkan {{name}}", + "trashRestoreFailed": "Gagal memulihkan item. Jalur asli mungkin sudah ada.", + "trashDeletePermanently": "Hapus secara permanen", + "trashDeleteConfirm": "Hapus secara permanen {{name}}? Tindakan ini tidak dapat dibatalkan.", + "trashPermanentlyDeleted": "Dihapus secara permanen {{name}}", + "trashDeleteFailed": "Gagal menghapus item secara permanen.", + "trashEmptyAction": "Kosongkan Tempat Sampah", + "trashEmptyConfirm": "Menghapus semua item di Sampah secara permanen? Tindakan ini tidak dapat dibatalkan.", + "trashEmptied": "Sampah dikosongkan", + "trashRetention": "Pembersihan otomatis setelah (hari)", + "trashRetentionSaved": "Pembaruan tentang retensi sampah", + "trashRetentionFailed": "Gagal memperbarui pengaturan penyimpanan sampah.", + "trashUnavailableConfirm": "Item ini tidak dapat dipindahkan ke Sampah, biasanya karena berada di sistem file lain atau memerlukan hak akses administrator. Hapus saja secara permanen?", + "recent": "Terkini", + "pinned": "Disematkan", + "folderShortcuts": "Pintasan Folder", + "failedToReconnectSSH": "Gagal menyambungkan kembali sesi SSH", + "openTerminalHere": "Buka Terminal di Sini", + "run": "Berlari", + "openTerminalInFolder": "Buka Terminal di Folder Ini", + "openTerminalInFileLocation": "Buka Terminal di Lokasi File", + "runningFile": "Berlari - {{file}}", + "onlyRunExecutableFiles": "Hanya dapat menjalankan file yang dapat dieksekusi.", + "directories": "Direktori", + "removedFromRecentFiles": "Menghapus \"{{name}}\" dari file terbaru", + "removeFailed": "Hapus yang gagal", + "unpinnedSuccessfully": "Pin \"{{name}}\" berhasil dilepas", + "unpinFailed": "Gagal membuka pin", + "removedShortcut": "Pintasan \"{{name}} \" telah dihapus", + "removeShortcutFailed": "Penghapusan pintasan gagal.", + "clearedAllRecentFiles": "Semua file terbaru telah dihapus.", + "clearFailed": "Hapus gagal", + "removeFromRecentFiles": "Hapus dari berkas terbaru", + "clearAllRecentFiles": "Hapus semua file terbaru", + "unpinFile": "Lepaskan pin file", + "removeShortcut": "Hapus pintasan", + "pinFile": "Pin file", + "addToShortcuts": "Tambahkan ke pintasan", + "pasteFailed": "Pasta gagal", + "noUndoableActions": "Tidak ada tindakan yang tidak dapat dibatalkan.", + "undoCopySuccess": "Batalkan operasi penyalinan: Menghapus {{count}} file yang disalin", + "undoCopyFailedDelete": "Pembatalan gagal: Tidak dapat menghapus file yang disalin.", + "undoCopyFailedNoInfo": "Pembatalan gagal: Informasi file yang disalin tidak ditemukan.", + "undoMoveSuccess": "Operasi pemindahan dibatalkan: {{count}} berkas dipindahkan kembali ke lokasi semula", + "undoMoveFailedMove": "Pembatalan gagal: Tidak dapat memindahkan file apa pun kembali.", + "undoMoveFailedNoInfo": "Pembatalan gagal: Informasi file yang dipindahkan tidak ditemukan.", + "undoDeleteNotSupported": "Operasi penghapusan tidak dapat dibatalkan: File telah dihapus secara permanen dari server.", + "undoTypeNotSupported": "Jenis operasi undo yang tidak didukung", + "undoOperationFailed": "Operasi pembatalan gagal", + "unknownError": "Kesalahan tidak dikenal", + "confirm": "Mengonfirmasi", + "find": "Menemukan...", + "replace": "Mengganti", + "downloadInstead": "Unduh sebagai gantinya", + "keyboardShortcuts": "Pintasan Keyboard", + "searchAndReplace": "Cari & Ganti", + "editing": "Pengeditan", + "search": "Mencari", + "findNext": "Temukan Selanjutnya", + "findPrevious": "Temukan Sebelumnya", + "save": "Menyimpan", + "selectAll": "Pilih Semua", + "undo": "Membuka", + "redo": "Mengulangi", + "moveLineUp": "Susunan Langkah", + "moveLineDown": "Geser Garis ke Bawah", + "toggleComment": "Alihkan Komentar", + "autoComplete": "Pelengkapan Otomatis", + "imageLoadError": "Gambar gagal dimuat", + "startTyping": "Mulai mengetik...", + "unknownSize": "Ukuran tidak diketahui", + "fileIsEmpty": "Berkas kosong", + "largeFileWarning": "Peringatan Ukuran File Besar", + "largeFileWarningDesc": "File ini berukuran {{size}} , yang dapat menyebabkan masalah kinerja saat dibuka sebagai teks.", + "fileNotFoundAndRemoved": "Berkas \"{{name}}\" tidak ditemukan dan telah dihapus dari berkas terbaru/yang disematkan", + "failedToLoadFile": "Gagal memuat file: {{error}}", + "serverErrorOccurred": "Terjadi kesalahan server. Silakan coba lagi nanti.", + "autoSaveFailed": "Penyimpanan otomatis gagal", + "fileAutoSaved": "Berkas tersimpan otomatis", + "moveFileFailed": "Gagal memindahkan {{name}}", + "moveOperationFailed": "Operasi pemindahan gagal", + "canOnlyCompareFiles": "Hanya dapat membandingkan dua file.", + "comparingFiles": "Membandingkan berkas: {{file1}} dan {{file2}}", + "dragFailed": "Operasi seret gagal", + "filePinnedSuccessfully": "Berkas \"{{name}}\" berhasil disematkan", + "pinFileFailed": "Gagal menyematkan file", + "fileUnpinnedSuccessfully": "Berkas \"{{name}}\" berhasil dilepas dari pin", + "unpinFileFailed": "Gagal melepaskan pin file", + "shortcutAddedSuccessfully": "Pintasan folder \"{{name}}\" berhasil ditambahkan", + "addShortcutFailed": "Gagal menambahkan pintasan", + "operationCompletedSuccessfully": "{{operation}} {{count}} item berhasil", + "operationCompleted": "{{operation}} {{count}} item", + "downloadFileSuccess": "File {{name}} berhasil diunduh", + "downloadFileFailed": "Pengunduhan gagal", + "downloadingFile": "Mengunduh {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Pindah ke {{name}}", + "diffCompareWith": "Bandingkan perbedaan dengan {{name}}", + "dragOutsideToDownload": "Seret ke luar jendela untuk mengunduh ({{count}} file)", + "newFolderDefault": "Folder Baru", + "newFileDefault": "File Baru.txt", + "successfullyMovedItems": "Berhasil memindahkan {{count}} item ke {{target}}", + "move": "Bergerak", + "searchInFile": "Cari di dalam file (Ctrl+F)", + "showKeyboardShortcuts": "Tampilkan pintasan keyboard", + "decreaseFontSize": "Kurangi ukuran font", + "increaseFontSize": "Perbesar ukuran font", + "startWritingMarkdown": "Mulailah menulis konten markdown Anda...", + "loadingFileComparison": "Sedang memuat perbandingan file...", + "reload": "Muat ulang", + "compare": "Membandingkan", + "sideBySide": "Berdampingan", + "inline": "Sejajar", + "fileComparison": "Perbandingan File: {{file1}} vs {{file2}}", + "fileTooLarge": "Ukuran file terlalu besar: {{error}}", + "sshConnectionFailed": "Koneksi SSH gagal. Harap periksa koneksi Anda ke {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Gagal memuat file: {{error}}", + "connecting": "Menghubungkan...", + "connectedSuccessfully": "Terhubung berhasil", + "totpVerificationFailed": "Verifikasi TOTP gagal", + "warpgateVerificationFailed": "Autentikasi Warpgate gagal", + "authenticationFailed": "Autentikasi gagal", + "incorrectPassphrase": "Kata sandi salah. Silakan coba lagi.", + "verificationCodePrompt": "Kode verifikasi:", + "changePermissions": "Ubah Izin", + "currentPermissions": "Izin Saat Ini", + "owner": "Pemilik", + "group": "Kelompok", + "others": "Yang lain", + "read": "Membaca", + "write": "Menulis", + "execute": "Menjalankan", + "permissionsChangedSuccessfully": "Izin berhasil diubah.", + "failedToChangePermissions": "Gagal mengubah izin.", + "name": "Nama", + "sortByName": "Nama", + "sortByDate": "Tanggal Diubah", + "sortBySize": "Ukuran", + "ascending": "Naik", + "descending": "Menurun", + "root": "Akar", + "new": "Baru", + "sortBy": "Urutkan Berdasarkan", + "items": "Barang-barang", + "selected": "Terpilih", + "editor": "Editor", + "octal": "Oktal", + "storage": "Penyimpanan", + "disk": "Disk", + "used": "Digunakan", + "of": "dari", + "toggleSidebar": "Alihkan Sidebar", + "compactLayout": "Gunakan tata letak yang ringkas.", + "comfortableLayout": "Gunakan tata letak yang nyaman.", + "cannotLoadPdf": "Tidak dapat memuat PDF", + "pdfLoadError": "Terjadi kesalahan saat memuat file PDF ini.", + "loadingPdf": "Sedang memuat PDF...", + "loadingPage": "Sedang memuat halaman..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Mentransfer file antara dua host SSH.", + "sourceServer": "Server Sumber", + "destinationServer": "Server Tujuan", + "selectHost": "Pilih tuan rumah", + "noHostSelected": "Tidak ada pembawa acara yang dipilih.", + "move": "Bergerak", + "copy": "Menyalin", + "upload": "Mengunggah", + "itemsSelected": "{{count}} item dipilih", + "itemsSelected_other": "{{count}} item terpilih", + "ready": "Siap", + "loadingHosts": "Memuat host...", + "name": "Nama", + "modified": "Dimodifikasi", + "size": "Ukuran", + "noFiles": "Tidak ada berkas", + "open": "Membuka", + "up": "Ke atas", + "refresh": "Menyegarkan", + "copyToTarget": "Salin ke Direktori Target", + "rename": "Ganti nama", + "delete": "Menghapus", + "editPermissions": "Edit Izin", + "createNewFolder": "Buat Folder Baru", + "deleteSelectedItems": "Hapus Item yang Dipilih", + "deleteConfirm": "Hapus {{count}} item yang dipilih?", + "deleteConfirm_other": "Hapus {{count}} item yang dipilih?", + "cancel": "Membatalkan", + "save": "Menyimpan", + "folderCreated": "Folder telah dibuat.", + "itemRenamed": "Item diganti namanya", + "actionFailed": "Tindakan gagal", + "itemsDeleted": "{{count}} item dihapus", + "itemsDeleted_other": "{{count}} item dihapus", + "deleteFailed": "Penghapusan gagal", + "permissionsUpdated": "Izin telah diperbarui.", + "noRemoteSessionConnected": "Tidak ada sesi jarak jauh yang terhubung.", + "failedToListFiles": "Gagal menampilkan daftar file.", + "failedToRefresh": "Gagal memperbarui", + "destinationInsideSource": "Tujuan tidak boleh berada di dalam jalur sumber yang dipilih.", + "sameHostNoOp": "Sumber dan tujuan berada di folder yang sama." + }, + "hosts": { + "fileManager": "Pengelola File", + "tabFiles": "Berkas", + "enableFileManagerMonitor": "Aktifkan Pengelola File", + "enableFileManagerMonitorDesc": "Jelajahi dan kelola file di host ini melalui SFTP.", + "scpLegacyLabel": "Mode Warisan SCP", + "scpLegacyDesc": "Gunakan transfer file lama untuk server yang tidak mendukung subsistem SFTP (misalnya server SSH terintegrasi atau minimal).", + "defaultPathLabel": "Jalur Default", + "fileManagerPathHint": "Direktori yang akan dibuka saat pengelola file diluncurkan untuk host ini.", + "fileManagerUrlCopied": "URL Pengelola File disalin", + "copyFileManagerUrlAction": "Salin URL Pengelola File" + }, + "settings": { + "host": { + "enable": { + "label": "Aktifkan Pengelola File" + }, + "defaultPath": { + "label": "Jalur Default" + }, + "scpLegacy": { + "label": "Mode Warisan SCP" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Berkas" + } +} diff --git a/plugins/file-manager/locales/translated/it_IT.json b/plugins/file-manager/locales/translated/it_IT.json new file mode 100644 index 000000000..9aa53904e --- /dev/null +++ b/plugins/file-manager/locales/translated/it_IT.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Nessun host selezionato", + "initializingEditor": "Inizializzazione editor...", + "file": "File", + "folder": "Cartella", + "uploadFile": "Carica file", + "downloadFile": "Scarica", + "extractArchive": "Estrai archivio", + "extractingArchive": "Estrazione di {{name}}...", + "archiveExtractedSuccessfully": "{{name}} estratto con successo", + "extractFailed": "Estrazione non riuscita", + "compressFile": "Comprimi file", + "compressFiles": "Comprimi file", + "compressFilesDesc": "Comprimi {{count}} elementi in un archivio", + "archiveName": "Nome archivio", + "enterArchiveName": "Inserisci nome archivio...", + "compressionFormat": "Formato compressione", + "selectedFiles": "File selezionati", + "andMoreFiles": "e altri {{count}}...", + "compress": "Comprimi", + "compressingFiles": "Comprimendo {{count}} elementi in {{name}}...", + "filesCompressedSuccessfully": "{{name}} creato con successo", + "compressFailed": "Compressione non riuscita", + "edit": "Modifica", + "preview": "Anteprima", + "previous": "Precedente", + "next": "Successivo", + "pageXOfY": "Pagina {{current}} di {{total}}", + "zoomOut": "Riduci zoom", + "zoomIn": "Aumenta zoom", + "newFile": "Nuovo file", + "newFolder": "Nuova cartella", + "rename": "Rinomina", + "uploading": "Caricamento in corso...", + "uploadingFile": "Caricamento di {{name}}...", + "fileName": "Nome file", + "folderName": "Nome cartella", + "fileUploadedSuccessfully": "File \"{{name}}\" caricato con successo", + "failedToUploadFile": "Caricamento file non riuscito", + "uploadingFolderFiles": "Caricamento del/dei file {{count}} in corso...", + "uploadedFolderFiles": "File caricati con successo {{count}}", + "uploadedFolderPartial": "Caricamento del/i file {{uploaded}} non riuscito/i {{failed}}", + "fileDownloadedSuccessfully": "File \"{{name}}\" scaricato con successo", + "failedToDownloadFile": "Scaricamento file non riuscito", + "fileCreatedSuccessfully": "File \"{{name}}\" creato con successo", + "folderCreatedSuccessfully": "Cartella \"{{name}}\" creata con successo", + "failedToCreateItem": "Creazione elemento non riuscita", + "operationFailed": "Operazione {{operation}} non riuscita per {{name}}: {{error}}", + "failedToResolveSymlink": "Risoluzione del collegamento simbolico non riuscita", + "itemsDeletedSuccessfully": "{{count}} elementi eliminati con successo", + "failedToDeleteItems": "Eliminazione elementi non riuscita", + "sudoPasswordRequired": "Password amministratore richiesta", + "enterSudoPassword": "Inserisci la password sudo per continuare l'operazione", + "sudoPassword": "Password sudo", + "sudoOperationFailed": "Operazione sudo non riuscita", + "sudoAuthFailed": "Autenticazione sudo non riuscita", + "dragFilesToUpload": "Trascina qui i file per caricarli", + "dropToUploadHere": "Trascina qui per caricare", + "dropToDownloadHere": "Scarica qui", + "localFiles": "Locale", + "showLocalFiles": "Mostra i file locali (vista divisa)", + "hideLocalFiles": "Nascondi i file locali", + "localHome": "Cartella Home", + "localPathPlaceholder": "Percorso della cartella locale", + "localShowHidden": "Mostra i file nascosti", + "localHideHidden": "Nascondi i file nascosti", + "localRevealInFileManager": "Mostra nel gestore file di sistema", + "localCannotOpenFolder": "Impossibile aprire la cartella", + "localItemCount": "{{count}} elementi", + "localSelectedCount": "{{count}} selezionato", + "back": "Indietro", + "forward": "Inoltrare", + "up": "Su", + "kind": "Tipo", + "localUploadingProgress": "Caricamento di {{current}} di {{total}}", + "localDownloadingProgress": "Download di {{current}} di {{total}}", + "localTransferInFlight": "{{count}} subito", + "localPreparingDownload": "Download in corso...", + "localTransferCancelled": "Trasferimento annullato", + "localNothingToTransfer": "Nulla da trasferire", + "localUploadComplete": "Caricato/i file {{count}}", + "localUploadFailed": "Caricamento non riuscito", + "localUploadPartial": "Caricamento del/i file {{done}} non riuscito/i {{failed}}", + "localDownloadComplete": "Scaricato il/i file {{count}}", + "localDownloadFailed": "Download non riuscito", + "localDownloadPartial": "Scaricamento del/dei file {{done}} , {{failed}} non riuscito", + "localReplaceExistingPrompt": "{{count}} L'elemento/gli elementi esistono già in questa cartella. Sostituirli?", + "localReplace": "Sostituire", + "localSkipExisting": "Saltare", + "localSkippedExisting": "Saltato {{count}} elemento/i esistente/i", + "localUnsafeNamesSkipped": "Saltati {{count}} elementi i cui nomi non possono essere utilizzati su questo computer", + "localOpen": "Aprire", + "localOpenFolder": "Apri la cartella", + "localUploadToRemote": "Carica sul server", + "localUploadToRemoteMany": "Carica {{count}} elementi sul server", + "localMoveToTrash": "Sposta nel Cestino", + "localMoveToTrashMany": "Sposta {{count}} elementi nel Cestino", + "localTrashConfirmSingle": "Spostare \"{{name}}\" nel Cestino?", + "localTrashConfirmFolder": "Spostare la cartella \"{{name}}\" e tutto il suo contenuto nel Cestino?", + "localTrashConfirmMany": "Spostare {{count}} elementi nel Cestino?", + "localTrashed": "Spostati {{count}} elementi nel Cestino", + "localTrashFailed": "Impossibile spostare nel Cestino", + "localRenameFailed": "Rinominare non riuscito", + "localCreateFailed": "Impossibile creare l'elemento", + "goToParentFolder": "Vai alla cartella principale", + "resizeSidebar": "Trascina per ridimensionare la barra laterale (doppio clic per ripristinare le dimensioni originali)", + "resizeLocalPane": "Trascina per ridimensionare il riquadro locale (doppio clic per ripristinare le dimensioni originali)", + "columns": "Colonne", + "columnsHint": "Fare clic con il pulsante destro del mouse per scegliere le colonne", + "emptyFolder": "Questa cartella è vuota", + "searchFiles": "Cerca file...", + "upload": "Carica", + "selectHostToStart": "Seleziona un host per avviare la gestione file", + "sshRequiredForFileManager": "Il gestore file richiede SSH. Questo host non ha SSH abilitato.", + "failedToConnect": "Connessione a SSH non riuscita", + "failedToLoadDirectory": "Caricamento directory non riuscito", + "noSSHConnection": "Nessuna connessione SSH disponibile", + "copy": "Copia", + "cut": "Taglia", + "paste": "Incolla", + "copyPath": "Copia percorso", + "copyPaths": "Copia percorsi", + "delete": "Elimina", + "properties": "Proprietà", + "refresh": "Aggiorna", + "downloadFiles": "Scarica {{count}} file nel browser", + "copyFiles": "Copia {{count}} elementi", + "cutFiles": "Taglia {{count}} elementi", + "deleteFiles": "Elimina {{count}} elementi", + "filesCopiedToClipboard": "{{count}} elementi copiati negli appunti", + "filesCutToClipboard": "{{count}} elementi tagliati negli appunti", + "pathCopiedToClipboard": "Percorso copiato negli appunti", + "pathsCopiedToClipboard": "{{count}} percorsi copiati negli appunti", + "failedToCopyPath": "Impossibile copiare il percorso negli appunti", + "copyFolderLink": "Copia collegamento alla cartella", + "copyCurrentFolderLink": "Copia collegamento alla cartella corrente", + "folderLinkCopied": "Collegamento alla cartella copiato negli appunti", + "failedToCopyFolderLink": "Impossibile copiare il collegamento alla cartella", + "movedItems": "Spostati {{count}} elementi", + "failedToDeleteItem": "Impossibile eliminare l'elemento", + "itemRenamedSuccessfully": "{{type}} rinominato con successo", + "failedToRenameItem": "Impossibile rinominare l'elemento", + "download": "Scarica", + "openExternalEditor": "Apri esternamente", + "chooseExternalEditor": "Scegli editor esterno", + "externalEditorSelected": "Editor esterno selezionato", + "externalEditorDesktopOnly": "L'editor esterno è disponibile solo nell'app desktop", + "externalEditorOpened": "Aperto nell'editor esterno. Salvando lì, le modifiche verranno caricate nuovamente sul server.", + "failedToOpenExternalEditor": "Impossibile aprire l'editor esterno", + "failedToSelectExternalEditor": "Impossibile selezionare l'editor esterno", + "permissions": "Permessi", + "size": "Dimensione", + "modified": "Modificato", + "path": "Percorso", + "confirmDelete": "Sei sicuro di voler eliminare {{name}}?", + "permissionDenied": "Permesso negato", + "serverError": "Errore del server", + "fileSavedSuccessfully": "File salvato con successo", + "failedToSaveFile": "Impossibile salvare il file", + "confirmDeleteSingleItem": "Sei sicuro di voler eliminare definitivamente \"{{name}}\"?", + "confirmDeleteMultipleItems": "Sei sicuro di voler eliminare definitivamente {{count}} elementi?", + "confirmDeleteMultipleItemsWithFolders": "Sei sicuro di voler eliminare definitivamente {{count}} elementi? Sono incluse cartelle e il loro contenuto.", + "confirmDeleteFolder": "Sei sicuro di voler eliminare definitivamente la cartella \"{{name}}\" e tutto il suo contenuto?", + "permanentDeleteWarning": "Questa azione non può essere annullata. Gli elementi verranno eliminati definitivamente dal server.", + "moveToTrashWarning": "L'articolo o gli articoli verranno spostati nel Cestino Termix e potranno essere ripristinati prima della scadenza del periodo di conservazione.", + "itemsMovedToTrash": "{{count}} elemento/i spostato/i nel Cestino", + "trash": "Spazzatura", + "trashDescription": "Gli elementi eliminati vengono conservati per {{days}} giorni su questo host.", + "trashLoadFailed": "Impossibile caricare il Cestino", + "trashEmpty": "Il cestino è vuoto", + "trashRestore": "Ripristinare", + "trashRestored": "Ripristinato {{name}}", + "trashRestoreFailed": "Impossibile ripristinare l'elemento. Il percorso originale potrebbe già esistere.", + "trashDeletePermanently": "Eliminare definitivamente", + "trashDeleteConfirm": "Eliminare definitivamente {{name}}? Questa operazione non può essere annullata.", + "trashPermanentlyDeleted": "Eliminato definitivamente {{name}}", + "trashDeleteFailed": "Impossibile eliminare definitivamente l'elemento", + "trashEmptyAction": "Svuotare il cestino", + "trashEmptyConfirm": "Eliminare definitivamente tutti gli elementi nel Cestino? Questa operazione non può essere annullata.", + "trashEmptied": "Cestino svuotato", + "trashRetention": "Pulizia automatica dopo (giorni)", + "trashRetentionSaved": "Gestione dei rifiuti aggiornata", + "trashRetentionFailed": "Impossibile aggiornare la conservazione dei rifiuti", + "trashUnavailableConfirm": "Questo elemento non può essere spostato nel Cestino, solitamente perché si trova su un altro file system o richiede privilegi elevati. Vuoi eliminarlo definitivamente?", + "recent": "Recenti", + "pinned": "Fissati", + "folderShortcuts": "Collegamenti alle cartelle", + "failedToReconnectSSH": "Impossibile riconnettersi alla sessione SSH", + "openTerminalHere": "Apri terminale qui", + "run": "Esegui", + "openTerminalInFolder": "Apri terminale in questa cartella", + "openTerminalInFileLocation": "Apri terminale nella posizione del file", + "runningFile": "In esecuzione - {{file}}", + "onlyRunExecutableFiles": "È possibile eseguire solo file eseguibili", + "directories": "Directory", + "removedFromRecentFiles": "Rimosso \"{{name}}\" dai file recenti", + "removeFailed": "Rimozione non riuscita", + "unpinnedSuccessfully": "Rimosso \"{{name}}\" dai preferiti con successo", + "unpinFailed": "Rimozione dai preferiti non riuscita", + "removedShortcut": "Rimosso collegamento \"{{name}}\"", + "removeShortcutFailed": "Rimozione collegamento non riuscita", + "clearedAllRecentFiles": "Cancellati tutti i file recenti", + "clearFailed": "Cancellazione non riuscita", + "removeFromRecentFiles": "Rimuovi dai file recenti", + "clearAllRecentFiles": "Cancella tutti i file recenti", + "unpinFile": "Rimuovi dai preferiti", + "removeShortcut": "Rimuovi collegamento", + "pinFile": "Aggiungi ai preferiti", + "addToShortcuts": "Aggiungi ai collegamenti", + "pasteFailed": "Incolla non riuscito", + "noUndoableActions": "Nessuna azione annullabile", + "undoCopySuccess": "Operazione di copia annullata: eliminati {{count}} file copiati", + "undoCopyFailedDelete": "Annullamento non riuscito: impossibile eliminare i file copiati", + "undoCopyFailedNoInfo": "Annullamento non riuscito: impossibile trovare informazioni sui file copiati", + "undoMoveSuccess": "Operazione di spostamento annullata: riportati {{count}} file nella posizione originale", + "undoMoveFailedMove": "Annullamento non riuscito: impossibile riportare i file indietro", + "undoMoveFailedNoInfo": "Annullamento non riuscito: impossibile trovare informazioni sui file spostati", + "undoDeleteNotSupported": "L'operazione di eliminazione non può essere annullata: i file sono stati eliminati permanentemente dal server", + "undoTypeNotSupported": "Tipo di operazione di annullamento non supportato", + "undoOperationFailed": "Operazione di annullamento non riuscita", + "unknownError": "Errore sconosciuto", + "confirm": "Conferma", + "find": "Trova...", + "replace": "Sostituisci", + "downloadInstead": "Scarica invece", + "keyboardShortcuts": "Scorciatoie da tastiera", + "searchAndReplace": "Cerca e sostituisci", + "editing": "Modifica", + "search": "Cerca", + "findNext": "Trova successivo", + "findPrevious": "Trova precedente", + "save": "Salva", + "selectAll": "Seleziona tutto", + "undo": "Annulla", + "redo": "Ripristina", + "moveLineUp": "Sposta riga su", + "moveLineDown": "Sposta riga giù", + "toggleComment": "Commenta/Decommenta", + "autoComplete": "Completamento automatico", + "imageLoadError": "Caricamento immagine non riuscito", + "startTyping": "Inizia a digitare...", + "unknownSize": "Dimensione sconosciuta", + "fileIsEmpty": "Il file è vuoto", + "largeFileWarning": "Avviso file di grandi dimensioni", + "largeFileWarningDesc": "Questo file ha una dimensione di {{size}}, che potrebbe causare problemi di prestazioni se aperto come testo.", + "fileNotFoundAndRemoved": "File \"{{name}}\" non trovato ed è stato rimosso dai file recenti/fissati", + "failedToLoadFile": "Caricamento del file non riuscito: {{error}}", + "serverErrorOccurred": "Si è verificato un errore del server. Riprova più tardi.", + "autoSaveFailed": "Salvataggio automatico non riuscito", + "fileAutoSaved": "File salvato automaticamente", + "moveFileFailed": "Spostamento di {{name}} non riuscito", + "moveOperationFailed": "Operazione di spostamento non riuscita", + "canOnlyCompareFiles": "È possibile confrontare solo due file", + "comparingFiles": "Confronto dei file: {{file1}} e {{file2}}", + "dragFailed": "Operazione di trascinamento non riuscita", + "filePinnedSuccessfully": "File \"{{name}}\" fissato con successo", + "pinFileFailed": "Fissaggio del file non riuscito", + "fileUnpinnedSuccessfully": "File \"{{name}}\" rimosso dai fissati con successo", + "unpinFileFailed": "Impossibile rimuovere il file fissato", + "shortcutAddedSuccessfully": "Collegamento alla cartella \"{{name}}\" aggiunto con successo", + "addShortcutFailed": "Impossibile aggiungere il collegamento", + "operationCompletedSuccessfully": "{{operation}} {{count}} elementi con successo", + "operationCompleted": "{{operation}} {{count}} elementi", + "downloadFileSuccess": "File {{name}} scaricato con successo", + "downloadFileFailed": "Download non riuscito", + "downloadingFile": "Download in corso {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Sposta in {{name}}", + "diffCompareWith": "Diff con {{name}}", + "dragOutsideToDownload": "Trascina fuori dalla finestra per scaricare ({{count}} file)", + "newFolderDefault": "NuovaCartella", + "newFileDefault": "NuovoFile.txt", + "successfullyMovedItems": "{{count}} elementi spostati con successo in {{target}}", + "move": "Sposta", + "searchInFile": "Cerca nel file (Ctrl+F)", + "showKeyboardShortcuts": "Mostra scorciatoie da tastiera", + "decreaseFontSize": "Riduci dimensione carattere", + "increaseFontSize": "Aumenta dimensione carattere", + "startWritingMarkdown": "Inizia a scrivere il tuo contenuto markdown...", + "loadingFileComparison": "Caricamento confronto file in corso...", + "reload": "Ricarica", + "compare": "Confronta", + "sideBySide": "Fianco a fianco", + "inline": "In linea", + "fileComparison": "Confronto file: {{file1}} vs {{file2}}", + "fileTooLarge": "File troppo grande: {{error}}", + "sshConnectionFailed": "Connessione SSH non riuscita. Controlla la connessione a {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Caricamento del file non riuscito: {{error}}", + "connecting": "Connessione in corso...", + "connectedSuccessfully": "Connesso con successo", + "totpVerificationFailed": "Verifica TOTP non riuscita", + "warpgateVerificationFailed": "Autenticazione Warpgate non riuscita", + "authenticationFailed": "Autenticazione non riuscita", + "incorrectPassphrase": "Frase di accesso non corretta. Riprova.", + "verificationCodePrompt": "Codice di verifica:", + "changePermissions": "Modifica permessi", + "currentPermissions": "Permessi attuali", + "owner": "Proprietario", + "group": "Gruppo", + "others": "Altri", + "read": "Lettura", + "write": "Scrittura", + "execute": "Esecuzione", + "permissionsChangedSuccessfully": "Permessi modificati con successo", + "failedToChangePermissions": "Modifica dei permessi non riuscita", + "name": "Nome", + "sortByName": "Nome", + "sortByDate": "Data di modifica", + "sortBySize": "Dimensione", + "ascending": "Crescente", + "descending": "Decrescente", + "root": "Radice", + "new": "Nuovo", + "sortBy": "Ordina per", + "items": "Elementi", + "selected": "Selezionati", + "editor": "Editor", + "octal": "Ottale", + "storage": "Storage", + "disk": "Disco", + "used": "Usato", + "of": "di", + "toggleSidebar": "Mostra/Nascondi barra laterale", + "compactLayout": "Utilizzare un layout compatto", + "comfortableLayout": "Utilizzare una disposizione comoda", + "cannotLoadPdf": "Impossibile caricare il PDF", + "pdfLoadError": "Si è verificato un errore durante il caricamento del file PDF.", + "loadingPdf": "Caricamento PDF in corso...", + "loadingPage": "Caricamento pagina in corso..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Trasferire file tra due host SSH.", + "sourceServer": "Server sorgente", + "destinationServer": "Server di destinazione", + "selectHost": "Seleziona l'host", + "noHostSelected": "Nessun host selezionato", + "move": "Mossa", + "copy": "Copia", + "upload": "Caricamento", + "itemsSelected": "{{count}} elemento selezionato", + "itemsSelected_other": "{{count}} elementi selezionati", + "ready": "Pronto", + "loadingHosts": "Caricamento host in corso...", + "name": "Nome", + "modified": "Modificato", + "size": "Misurare", + "noFiles": "Nessun file", + "open": "Aprire", + "up": "Su", + "refresh": "Rinfrescato", + "copyToTarget": "Copia nella directory di destinazione", + "rename": "Rinominare", + "delete": "Eliminare", + "editPermissions": "Modifica autorizzazioni", + "createNewFolder": "Crea una nuova cartella", + "deleteSelectedItems": "Elimina gli elementi selezionati", + "deleteConfirm": "Eliminare l'elemento selezionato {{count}}?", + "deleteConfirm_other": "Eliminare gli elementi selezionati {{count}}?", + "cancel": "Cancellare", + "save": "Salva", + "folderCreated": "Cartella creata", + "itemRenamed": "Elemento rinominato", + "actionFailed": "L'azione è fallita", + "itemsDeleted": "{{count}} elemento eliminato", + "itemsDeleted_other": "{{count}} elementi eliminati", + "deleteFailed": "Eliminazione non riuscita", + "permissionsUpdated": "Autorizzazioni aggiornate", + "noRemoteSessionConnected": "Nessuna sessione remota connessa", + "failedToListFiles": "Impossibile elencare i file", + "failedToRefresh": "Aggiornamento non riuscito", + "destinationInsideSource": "La destinazione non può trovarsi all'interno del percorso di origine selezionato.", + "sameHostNoOp": "La cartella di origine e quella di destinazione sono la stessa." + }, + "hosts": { + "fileManager": "File Manager", + "tabFiles": "File", + "enableFileManagerMonitor": "Abilita File Manager", + "enableFileManagerMonitorDesc": "Sfoglia e gestisci i file su questo host tramite SFTP", + "scpLegacyLabel": "Modalità SCP legacy", + "scpLegacyDesc": "Usa il trasferimento file legacy per server che non supportano il sottosistema SFTP (ad es. server SSH embedded o minimali).", + "defaultPathLabel": "Percorso predefinito", + "fileManagerPathHint": "La directory da aprire all'avvio del file manager per questo host.", + "fileManagerUrlCopied": "URL file manager copiato", + "copyFileManagerUrlAction": "Copia URL File Manager" + }, + "settings": { + "host": { + "enable": { + "label": "Abilita File Manager" + }, + "defaultPath": { + "label": "Percorso predefinito" + }, + "scpLegacy": { + "label": "Modalità SCP legacy" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "File" + } +} diff --git a/plugins/file-manager/locales/translated/ja_JP.json b/plugins/file-manager/locales/translated/ja_JP.json new file mode 100644 index 000000000..310c66e36 --- /dev/null +++ b/plugins/file-manager/locales/translated/ja_JP.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "ホストが選択されていません", + "initializingEditor": "エディターを初期化中...", + "file": "ファイル", + "folder": "フォルダー", + "uploadFile": "ファイルをアップロード", + "downloadFile": "ダウンロード", + "extractArchive": "アーカイブを展開", + "extractingArchive": "{{name}} を展開中...", + "archiveExtractedSuccessfully": "{{name}} の展開が完了しました", + "extractFailed": "展開に失敗しました", + "compressFile": "ファイルを圧縮", + "compressFiles": "ファイルを圧縮", + "compressFilesDesc": "{{count}} 個の項目をアーカイブに圧縮", + "archiveName": "アーカイブ名", + "enterArchiveName": "アーカイブ名を入力...", + "compressionFormat": "圧縮形式", + "selectedFiles": "選択したファイル", + "andMoreFiles": "他 {{count}} 件...", + "compress": "圧縮", + "compressingFiles": "{{count}} 個の項目を {{name}} に圧縮中...", + "filesCompressedSuccessfully": "{{name}} が正常に作成されました", + "compressFailed": "圧縮に失敗しました", + "edit": "編集", + "preview": "プレビュー", + "previous": "前へ", + "next": "次へ", + "pageXOfY": "{{current}} / {{total}} ページ", + "zoomOut": "縮小", + "zoomIn": "拡大", + "newFile": "新規ファイル", + "newFolder": "新規フォルダ", + "rename": "名前を変更", + "uploading": "アップロード中...", + "uploadingFile": "{{name}} をアップロード中...", + "fileName": "ファイル名", + "folderName": "フォルダ名", + "fileUploadedSuccessfully": "ファイル \"{{name}}\" のアップロードが完了しました", + "failedToUploadFile": "ファイルのアップロードに失敗しました", + "uploadingFolderFiles": "{{count}} 個のファイルをアップロードしています...", + "uploadedFolderFiles": "{{count}} 個のファイルが正常にアップロードされました", + "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", + "fileDownloadedSuccessfully": "ファイル \"{{name}}\" のダウンロードが完了しました", + "failedToDownloadFile": "ファイルのダウンロードに失敗しました", + "fileCreatedSuccessfully": "ファイル \"{{name}}\" が正常に作成されました", + "folderCreatedSuccessfully": "フォルダ \"{{name}}\" が正常に作成されました", + "failedToCreateItem": "アイテムの作成に失敗しました", + "operationFailed": "{{name}} の {{operation}} 操作が失敗しました: {{error}}", + "failedToResolveSymlink": "シンボリックリンクの解決に失敗しました", + "itemsDeletedSuccessfully": "{{count}} 個のアイテムが正常に削除されました", + "failedToDeleteItems": "アイテムの削除に失敗しました", + "sudoPasswordRequired": "管理者パスワードが必要です", + "enterSudoPassword": "操作を続行するには sudo パスワードを入力してください", + "sudoPassword": "Sudo パスワード", + "sudoOperationFailed": "Sudo 操作に失敗しました", + "sudoAuthFailed": "Sudo 認証に失敗しました", + "dragFilesToUpload": "ここにファイルをドロップしてアップロード", + "dropToUploadHere": "ここにアップロードしてください", + "dropToDownloadHere": "ここからダウンロードしてください", + "localFiles": "地元", + "showLocalFiles": "ローカルファイルを表示する(分割表示)", + "hideLocalFiles": "ローカルファイルを非表示にする", + "localHome": "ホームフォルダ", + "localPathPlaceholder": "ローカルフォルダパス", + "localShowHidden": "隠しファイルを表示する", + "localHideHidden": "隠しファイルを隠す", + "localRevealInFileManager": "システムファイルマネージャで表示", + "localCannotOpenFolder": "フォルダーを開けません", + "localItemCount": "{{count}} アイテム", + "localSelectedCount": "{{count}} 選択済み", + "back": "戻る", + "forward": "フォワード", + "up": "上", + "kind": "親切", + "localUploadingProgress": "{{current}} / {{total}} をアップロード中", + "localDownloadingProgress": "{{current}} をダウンロード中 {{total}}", + "localTransferInFlight": "{{count}} すぐに", + "localPreparingDownload": "ダウンロード準備中...", + "localTransferCancelled": "転送がキャンセルされました", + "localNothingToTransfer": "譲渡するものはありません", + "localUploadComplete": "{{count}} 個のファイルをアップロードしました", + "localUploadFailed": "アップロードに失敗しました", + "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", + "localDownloadComplete": "{{count}} 個のファイルをダウンロードしました", + "localDownloadFailed": "ダウンロードに失敗しました", + "localDownloadPartial": "{{done}} 個のファイルをダウンロードしましたが、 {{failed}} 失敗しました。", + "localReplaceExistingPrompt": "このフォルダには既に{{count}} 個のアイテムが存在します。置き換えますか?", + "localReplace": "交換する", + "localSkipExisting": "スキップ", + "localSkippedExisting": "既存のアイテムをスキップしました {{count}}", + "localUnsafeNamesSkipped": "このコンピューターでは名前を使用できない {{count}} 個のアイテムをスキップしました", + "localOpen": "開ける", + "localOpenFolder": "フォルダーを開く", + "localUploadToRemote": "サーバーにアップロード", + "localUploadToRemoteMany": "{{count}} 個のアイテムをサーバーにアップロードします", + "localMoveToTrash": "ゴミ箱に移動", + "localMoveToTrashMany": "{{count}} 個のアイテムをゴミ箱に移動します", + "localTrashConfirmSingle": "\"{{name}}\" をゴミ箱に移動しますか?", + "localTrashConfirmFolder": "フォルダ「{{name}}」とその中のすべての内容をゴミ箱に移動しますか?", + "localTrashConfirmMany": "{{count}} 個のアイテムをゴミ箱に移動しますか?", + "localTrashed": "{{count}} 個のアイテムをゴミ箱に移動しました", + "localTrashFailed": "ゴミ箱に移動できませんでした", + "localRenameFailed": "名前変更に失敗しました", + "localCreateFailed": "アイテムを作成できませんでした", + "goToParentFolder": "親フォルダへ移動", + "resizeSidebar": "サイドバーのサイズを変更するにはドラッグしてください(ダブルクリックでリセットできます)。", + "resizeLocalPane": "ローカルペインのサイズを変更するにはドラッグしてください(ダブルクリックでリセットできます)。", + "columns": "コラム", + "columnsHint": "右クリックして列を選択", + "emptyFolder": "このフォルダーは空です", + "searchFiles": "ファイルを検索...", + "upload": "アップロード", + "selectHostToStart": "ファイル管理を開始するホストを選択してください", + "sshRequiredForFileManager": "ファイルマネージャーには SSH が必要です。このホストでは SSH が有効になっていません。", + "failedToConnect": "SSH への接続に失敗しました", + "failedToLoadDirectory": "ディレクトリの読み込みに失敗しました", + "noSSHConnection": "利用可能な SSH 接続がありません", + "copy": "コピー", + "cut": "切り取り", + "paste": "貼り付け", + "copyPath": "パスをコピー", + "copyPaths": "パスをコピー", + "delete": "削除", + "properties": "プロパティ", + "refresh": "更新", + "downloadFiles": "{{count}} 個のファイルをブラウザにダウンロード", + "copyFiles": "{{count}} 個のアイテムをコピー", + "cutFiles": "{{count}} 個のアイテムを切り取り", + "deleteFiles": "{{count}} 個のアイテムを削除", + "filesCopiedToClipboard": "{{count}} 個のアイテムがクリップボードにコピーされました", + "filesCutToClipboard": "{{count}} 個のアイテムがクリップボードに切り取られました", + "pathCopiedToClipboard": "パスがクリップボードにコピーされました", + "pathsCopiedToClipboard": "{{count}} 個のパスがクリップボードにコピーされました", + "failedToCopyPath": "パスをクリップボードにコピーできませんでした", + "copyFolderLink": "フォルダへのリンクをコピー", + "copyCurrentFolderLink": "現在のフォルダへのリンクをコピー", + "folderLinkCopied": "フォルダリンクをクリップボードにコピーしました", + "failedToCopyFolderLink": "フォルダリンクをコピーできませんでした", + "movedItems": "{{count}} 個のアイテムを移動しました", + "failedToDeleteItem": "アイテムを削除できませんでした", + "itemRenamedSuccessfully": "{{type}} の名前を変更しました", + "failedToRenameItem": "アイテムの名前を変更できませんでした", + "download": "ダウンロード", + "openExternalEditor": "外部エディタで開く", + "chooseExternalEditor": "外部エディタを選択", + "externalEditorSelected": "外部エディタを選択しました", + "externalEditorDesktopOnly": "外部エディタはデスクトップアプリでのみ利用可能です", + "externalEditorOpened": "外部エディタで開きました。保存すると変更がサーバーにアップロードされます。", + "failedToOpenExternalEditor": "外部エディタを開けませんでした", + "failedToSelectExternalEditor": "外部エディタを選択できませんでした", + "permissions": "アクセス権限", + "size": "サイズ", + "modified": "更新日時", + "path": "パス", + "confirmDelete": "本当に {{name}} を削除しますか?", + "permissionDenied": "アクセスが拒否されました", + "serverError": "サーバーエラー", + "fileSavedSuccessfully": "ファイルを保存しました", + "failedToSaveFile": "ファイルを保存できませんでした", + "confirmDeleteSingleItem": "\"{{name}}\" を完全に削除してもよろしいですか?", + "confirmDeleteMultipleItems": "{{count}} 個のアイテムを完全に削除してもよろしいですか?", + "confirmDeleteMultipleItemsWithFolders": "{{count}} 個のアイテムを完全に削除してもよろしいですか?フォルダとその内容も含まれます。", + "confirmDeleteFolder": "フォルダ \"{{name}}\" とその内容をすべて完全に削除してもよろしいですか?", + "permanentDeleteWarning": "この操作は元に戻せません。アイテムはサーバーから完全に削除されます。", + "moveToTrashWarning": "該当品はTermixのゴミ箱に移動され、保管期間が終了する前に復元することができます。", + "itemsMovedToTrash": "{{count}} 個のアイテムがゴミ箱に移動されました", + "trash": "ごみ", + "trashDescription": "削除されたアイテムは、このホスト上で {{days}} 日間保持されます。", + "trashLoadFailed": "ゴミ箱の読み込みに失敗しました", + "trashEmpty": "ゴミ箱は空です", + "trashRestore": "復元する", + "trashRestored": "復元済み {{name}}", + "trashRestoreFailed": "アイテムの復元に失敗しました。元のパスが既に存在している可能性があります。", + "trashDeletePermanently": "完全に削除する", + "trashDeleteConfirm": "{{name}}を完全に削除しますか?これは元に戻せません。", + "trashPermanentlyDeleted": "完全に削除されました {{name}}", + "trashDeleteFailed": "アイテムの完全削除に失敗しました", + "trashEmptyAction": "空のゴミ箱", + "trashEmptyConfirm": "ゴミ箱内のすべてのアイテムを完全に削除しますか?この操作は元に戻せません。", + "trashEmptied": "ゴミを空にする", + "trashRetention": "(日数)後に自動クリーンアップ", + "trashRetentionSaved": "ゴミ保持に関する最新情報", + "trashRetentionFailed": "ゴミ箱の保持期間の更新に失敗しました", + "trashUnavailableConfirm": "このアイテムはゴミ箱に移動できません。通常、別のファイルシステム上にあるか、管理者権限が必要なためです。代わりに完全に削除しますか?", + "recent": "最近使用した項目", + "pinned": "ピン留め", + "folderShortcuts": "フォルダショートカット", + "failedToReconnectSSH": "SSH セッションに再接続できませんでした", + "openTerminalHere": "ここでターミナルを開く", + "run": "実行", + "openTerminalInFolder": "このフォルダでターミナルを開く", + "openTerminalInFileLocation": "ファイルの場所でターミナルを開く", + "runningFile": "実行中 - {{file}}", + "onlyRunExecutableFiles": "実行可能ファイルのみ実行できます", + "directories": "ディレクトリ", + "removedFromRecentFiles": "最近使用したファイルから \"{{name}}\" を削除しました", + "removeFailed": "削除に失敗しました", + "unpinnedSuccessfully": "\"{{name}}\" のピン留めを解除しました", + "unpinFailed": "ピン留め解除に失敗しました", + "removedShortcut": "ショートカット \"{{name}}\" を削除しました", + "removeShortcutFailed": "ショートカットの削除に失敗しました", + "clearedAllRecentFiles": "最近使用したファイルをすべて消去しました", + "clearFailed": "消去に失敗しました", + "removeFromRecentFiles": "最近使用したファイルから削除", + "clearAllRecentFiles": "最近使用したファイルをすべて消去", + "unpinFile": "ファイルのピン留めを解除", + "removeShortcut": "ショートカットを削除", + "pinFile": "ファイルをピン留め", + "addToShortcuts": "ショートカットに追加", + "pasteFailed": "貼り付けに失敗しました", + "noUndoableActions": "元に戻せる操作はありません", + "undoCopySuccess": "コピー操作を元に戻しました: {{count}} 個のコピーファイルを削除しました", + "undoCopyFailedDelete": "元に戻せませんでした: コピーファイルを削除できませんでした", + "undoCopyFailedNoInfo": "元に戻せませんでした: コピーファイル情報が見つかりませんでした", + "undoMoveSuccess": "移動操作を元に戻しました: {{count}} 個のファイルを元の場所に戻しました", + "undoMoveFailedMove": "元に戻せませんでした: ファイルを元の場所に戻せませんでした", + "undoMoveFailedNoInfo": "元に戻せませんでした: 移動ファイル情報が見つかりませんでした", + "undoDeleteNotSupported": "削除操作は元に戻せません: ファイルはサーバーから完全に削除されました", + "undoTypeNotSupported": "元に戻せない操作タイプです", + "undoOperationFailed": "元に戻す操作に失敗しました", + "unknownError": "不明なエラー", + "confirm": "確認", + "find": "検索...", + "replace": "置換", + "downloadInstead": "代わりにダウンロード", + "keyboardShortcuts": "キーボードショートカット", + "searchAndReplace": "検索と置換", + "editing": "編集", + "search": "検索", + "findNext": "次を検索", + "findPrevious": "前を検索", + "save": "保存", + "selectAll": "すべて選択", + "undo": "元に戻す", + "redo": "やり直し", + "moveLineUp": "行を上に移動", + "moveLineDown": "行を下に移動", + "toggleComment": "コメント行の切り替え", + "autoComplete": "自動補完", + "imageLoadError": "画像の読み込みに失敗しました", + "startTyping": "入力を開始...", + "unknownSize": "サイズ不明", + "fileIsEmpty": "ファイルは空です", + "largeFileWarning": "大きなファイルの警告", + "largeFileWarningDesc": "このファイルのサイズは{{size}}で、テキストとして開くとパフォーマンスの問題が発生する可能性があります。", + "fileNotFoundAndRemoved": "ファイル \"{{name}}\" が見つからないため、最近使ったファイル/ピン留めから削除されました。", + "failedToLoadFile": "ファイルの読み込みに失敗しました: {{error}}", + "serverErrorOccurred": "サーバーエラーが発生しました。後でもう一度お試しください。", + "autoSaveFailed": "自動保存に失敗しました", + "fileAutoSaved": "ファイルを自動保存しました", + "moveFileFailed": "{{name}} の移動に失敗しました", + "moveOperationFailed": "移動操作に失敗しました", + "canOnlyCompareFiles": "2つのファイルのみ比較できます", + "comparingFiles": "ファイルを比較中: {{file1}} と {{file2}}", + "dragFailed": "ドラッグ操作に失敗しました", + "filePinnedSuccessfully": "ファイル \"{{name}}\" をピン留めしました", + "pinFileFailed": "ファイルのピン留めに失敗しました", + "fileUnpinnedSuccessfully": "ファイル \"{{name}}\" のピン留めを解除しました", + "unpinFileFailed": "ファイルのピン留め解除に失敗しました", + "shortcutAddedSuccessfully": "フォルダショートカット \"{{name}}\" を追加しました", + "addShortcutFailed": "ショートカットの追加に失敗しました", + "operationCompletedSuccessfully": "{{count}} アイテムを正常に{{operation}}しました", + "operationCompleted": "{{count}} アイテムを{{operation}}しました", + "downloadFileSuccess": "ファイル {{name}} のダウンロードが完了しました", + "downloadFileFailed": "ダウンロードに失敗しました", + "downloadingFile": "ダウンロード中 {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "{{name}} に移動", + "diffCompareWith": "{{name}} と差分比較", + "dragOutsideToDownload": "ウィンドウ外にドラッグしてダウンロード ({{count}} ファイル)", + "newFolderDefault": "新規フォルダ", + "newFileDefault": "新規ファイル.txt", + "successfullyMovedItems": "{{count}} アイテムを {{target}} に移動しました", + "move": "移動", + "searchInFile": "ファイル内検索 (Ctrl+F)", + "showKeyboardShortcuts": "キーボードショートカットを表示", + "decreaseFontSize": "フォントサイズを縮小", + "increaseFontSize": "フォントサイズを拡大", + "startWritingMarkdown": "マークダウンコンテンツの作成を開始...", + "loadingFileComparison": "ファイル比較を読み込み中...", + "reload": "再読み込み", + "compare": "比較", + "sideBySide": "横並び表示", + "inline": "インライン表示", + "fileComparison": "ファイル比較: {{file1}} 対 {{file2}}", + "fileTooLarge": "ファイルが大きすぎます: {{error}}", + "sshConnectionFailed": "SSH接続に失敗しました。 {{name}} ({{ip}}:{{port}}) への接続を確認してください", + "loadFileFailed": "ファイルの読み込みに失敗しました: {{error}}", + "connecting": "接続中...", + "connectedSuccessfully": "接続しました", + "totpVerificationFailed": "TOTP検証に失敗しました", + "warpgateVerificationFailed": "Warpgate認証に失敗しました", + "authenticationFailed": "認証に失敗しました", + "incorrectPassphrase": "パスフレーズが間違っています。もう一度お試しください。", + "verificationCodePrompt": "認証コード:", + "changePermissions": "パーミッション変更", + "currentPermissions": "現在のパーミッション", + "owner": "所有者", + "group": "グループ", + "others": "その他", + "read": "読み取り", + "write": "書き込み", + "execute": "実行", + "permissionsChangedSuccessfully": "パーミッションが正常に変更されました", + "failedToChangePermissions": "パーミッションの変更に失敗しました", + "name": "名前", + "sortByName": "名前", + "sortByDate": "更新日時", + "sortBySize": "サイズ", + "ascending": "昇順", + "descending": "降順", + "root": "ルート", + "new": "新規", + "sortBy": "並べ替え", + "items": "アイテム", + "selected": "選択中", + "editor": "エディター", + "octal": "8進数", + "storage": "ストレージ", + "disk": "ディスク", + "used": "使用量", + "of": "の", + "toggleSidebar": "サイドバー切り替え", + "compactLayout": "コンパクトなレイアウトを使用する", + "comfortableLayout": "快適なレイアウトを使用する", + "cannotLoadPdf": "PDFを読み込めません", + "pdfLoadError": "このPDFファイルの読み込み中にエラーが発生しました。", + "loadingPdf": "PDFを読み込み中...", + "loadingPage": "ページを読み込み中..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "2つのSSHホスト間でファイルを転送します。", + "sourceServer": "ソースサーバー", + "destinationServer": "宛先サーバー", + "selectHost": "ホストを選択", + "noHostSelected": "ホストが選択されていません", + "move": "動く", + "copy": "コピー", + "upload": "アップロード", + "itemsSelected": "{{count}} アイテムが選択されました", + "itemsSelected_other": "{{count}} 個のアイテムが選択されました", + "ready": "準備ができて", + "loadingHosts": "ホストを読み込んでいます...", + "name": "名前", + "modified": "修正済み", + "size": "サイズ", + "noFiles": "ファイルなし", + "open": "開ける", + "up": "上", + "refresh": "更新", + "copyToTarget": "ターゲットディレクトリにコピー", + "rename": "名前を変更する", + "delete": "消去", + "editPermissions": "権限の編集", + "createNewFolder": "新しいフォルダーを作成する", + "deleteSelectedItems": "選択したアイテムを削除", + "deleteConfirm": "選択した項目 {{count}} を削除しますか?", + "deleteConfirm_other": "選択した項目 {{count}} を削除しますか?", + "cancel": "キャンセル", + "save": "保存", + "folderCreated": "フォルダが作成されました", + "itemRenamed": "アイテム名が変更されました", + "actionFailed": "アクションが失敗しました", + "itemsDeleted": "{{count}} アイテムが削除されました", + "itemsDeleted_other": "{{count}} 個のアイテムが削除されました", + "deleteFailed": "削除に失敗しました", + "permissionsUpdated": "権限が更新されました", + "noRemoteSessionConnected": "リモートセッションが接続されていません", + "failedToListFiles": "ファイルの一覧表示に失敗しました", + "failedToRefresh": "更新に失敗しました", + "destinationInsideSource": "宛先は、選択したソースパス内にあってはなりません。", + "sameHostNoOp": "ソースと宛先は同じフォルダです。" + }, + "hosts": { + "fileManager": "ファイルマネージャー", + "tabFiles": "ファイル", + "enableFileManagerMonitor": "ファイルマネージャーを有効にする", + "enableFileManagerMonitorDesc": "このホストのファイルを SFTP で閲覧・管理します", + "scpLegacyLabel": "SCP レガシーモード", + "scpLegacyDesc": "SFTP サブシステムをサポートしないサーバー(組み込みや最小構成の SSH サーバーなど)に対してレガシーファイル転送を使用します", + "defaultPathLabel": "デフォルトパス", + "fileManagerPathHint": "このホストのファイルマネージャー起動時に開くディレクトリ", + "fileManagerUrlCopied": "ファイルマネージャーURLをコピーしました", + "copyFileManagerUrlAction": "ファイルマネージャー URL をコピー" + }, + "settings": { + "host": { + "enable": { + "label": "ファイルマネージャーを有効にする" + }, + "defaultPath": { + "label": "デフォルトパス" + }, + "scpLegacy": { + "label": "SCP レガシーモード" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "ファイル" + } +} diff --git a/plugins/file-manager/locales/translated/ko_KR.json b/plugins/file-manager/locales/translated/ko_KR.json new file mode 100644 index 000000000..3be2ccd62 --- /dev/null +++ b/plugins/file-manager/locales/translated/ko_KR.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "선택된 호스트 없음", + "initializingEditor": "편집기 초기화 중...", + "file": "파일", + "folder": "폴더", + "uploadFile": "파일 업로드", + "downloadFile": "다운로드", + "extractArchive": "아카이브 추출", + "extractingArchive": "{{name}} 압축 해제 중...", + "archiveExtractedSuccessfully": "{{name}} 압축 해제 완료", + "extractFailed": "압축 해제 실패", + "compressFile": "파일 압축", + "compressFiles": "파일 압축", + "compressFilesDesc": "{{count}}개 항목을 아카이브로 압축", + "archiveName": "아카이브 이름", + "enterArchiveName": "아카이브 이름 입력...", + "compressionFormat": "압축 형식", + "selectedFiles": "선택한 파일", + "andMoreFiles": "그리고 {{count}}개 더...", + "compress": "압축", + "compressingFiles": "{{count}}개 항목을 {{name}}(으)로 압축 중...", + "filesCompressedSuccessfully": "{{name}} 생성 완료", + "compressFailed": "압축 실패", + "edit": "편집", + "preview": "미리보기", + "previous": "이전", + "next": "다음", + "pageXOfY": "{{total}}페이지 중 {{current}}페이지", + "zoomOut": "축소", + "zoomIn": "확대", + "newFile": "새 파일", + "newFolder": "새 폴더", + "rename": "이름 변경", + "uploading": "업로드 중...", + "uploadingFile": "{{name}} 업로드 중...", + "fileName": "파일 이름", + "folderName": "폴더 이름", + "fileUploadedSuccessfully": "파일 \"{{name}}\" 업로드 완료", + "failedToUploadFile": "파일 업로드 실패", + "uploadingFolderFiles": "{{count}} 파일을 업로드하는 중...", + "uploadedFolderFiles": "{{count}} 파일을 성공적으로 업로드했습니다.", + "uploadedFolderPartial": "업로드된 파일 {{uploaded}} 개 중 {{failed}} 개에 실패했습니다.", + "fileDownloadedSuccessfully": "파일 \"{{name}}\" 다운로드 완료", + "failedToDownloadFile": "파일 다운로드 실패", + "fileCreatedSuccessfully": "파일 \"{{name}}\" 생성 완료", + "folderCreatedSuccessfully": "폴더 \"{{name}}\" 생성 완료", + "failedToCreateItem": "항목 생성 실패", + "operationFailed": "{{name}}의 {{operation}} 작업 실패: {{error}}", + "failedToResolveSymlink": "심볼릭 링크 해석 실패", + "itemsDeletedSuccessfully": "{{count}}개 항목이 성공적으로 삭제되었습니다", + "failedToDeleteItems": "항목 삭제 실패", + "sudoPasswordRequired": "관리자 비밀번호가 필요합니다", + "enterSudoPassword": "계속하려면 sudo 암호를 입력하세요", + "sudoPassword": "Sudo 암호", + "sudoOperationFailed": "Sudo 작업 실패", + "sudoAuthFailed": "Sudo 인증 실패", + "dragFilesToUpload": "업로드할 파일을 여기에 놓으세요", + "dropToUploadHere": "여기에 드롭하여 업로드하세요", + "dropToDownloadHere": "여기를 클릭하여 다운로드하세요", + "localFiles": "현지의", + "showLocalFiles": "로컬 파일 표시(분할 화면)", + "hideLocalFiles": "로컬 파일 숨기기", + "localHome": "홈 폴더", + "localPathPlaceholder": "로컬 폴더 경로", + "localShowHidden": "숨겨진 파일 표시", + "localHideHidden": "숨겨진 파일 숨기기", + "localRevealInFileManager": "시스템 파일 관리자에서 보기", + "localCannotOpenFolder": "폴더를 열 수 없습니다", + "localItemCount": "{{count}} 항목", + "localSelectedCount": "{{count}} 선택됨", + "back": "뒤쪽에", + "forward": "앞으로", + "up": "위로", + "kind": "친절한", + "localUploadingProgress": "{{current}} 의 {{total}}를 업로드 중입니다.", + "localDownloadingProgress": "{{current}} 의 {{total}} 다운로드 중", + "localTransferInFlight": "{{count}} 즉시", + "localPreparingDownload": "다운로드 준비 중...", + "localTransferCancelled": "이적 취소됨", + "localNothingToTransfer": "이체할 내용이 없습니다", + "localUploadComplete": "{{count}} 개의 파일을 업로드했습니다.", + "localUploadFailed": "업로드 실패", + "localUploadPartial": "업로드된 파일 {{done}} 개, {{failed}} 개에 실패했습니다.", + "localDownloadComplete": "{{count}} 파일을 다운로드했습니다.", + "localDownloadFailed": "다운로드 실패", + "localDownloadPartial": "{{done}} 파일 다운로드 실패, {{failed}}", + "localReplaceExistingPrompt": "이 폴더에 {{count}} 항목이 이미 있습니다. 바꾸시겠습니까?", + "localReplace": "바꾸다", + "localSkipExisting": "건너뛰다", + "localSkippedExisting": "기존 항목 {{count}} 개를 건너뛰었습니다.", + "localUnsafeNamesSkipped": "이 컴퓨터에서 사용할 수 없는 이름을 가진 {{count}} 항목을 건너뛰었습니다.", + "localOpen": "열려 있는", + "localOpenFolder": "폴더 열기", + "localUploadToRemote": "서버에 업로드", + "localUploadToRemoteMany": "{{count}} 아이템을 서버에 업로드하세요", + "localMoveToTrash": "쓰레기통으로 이동", + "localMoveToTrashMany": "{{count}} 항목을 휴지통으로 이동", + "localTrashConfirmSingle": "\"{{name}}\"를 휴지통으로 옮기시겠습니까?", + "localTrashConfirmFolder": "폴더 \"{{name}}\"와 그 안의 모든 내용을 휴지통으로 옮기시겠습니까?", + "localTrashConfirmMany": "{{count}} 항목을 휴지통으로 옮기시겠습니까?", + "localTrashed": "{{count}} 항목을 휴지통으로 이동했습니다.", + "localTrashFailed": "쓰레기통으로 옮길 수 없었습니다", + "localRenameFailed": "이름 변경 실패", + "localCreateFailed": "항목을 생성할 수 없습니다.", + "goToParentFolder": "상위 폴더로 이동", + "resizeSidebar": "드래그하여 사이드바 크기를 조절하세요 (더블 클릭하면 원래 크기로 돌아갑니다).", + "resizeLocalPane": "드래그하여 로컬 창의 크기를 조정하세요(두 번 클릭하면 원래 크기로 돌아갑니다).", + "columns": "컬럼", + "columnsHint": "마우스 오른쪽 버튼을 클릭하여 열을 선택하세요.", + "emptyFolder": "이 폴더는 비어 있습니다", + "searchFiles": "파일 검색...", + "upload": "업로드", + "selectHostToStart": "파일 관리를 시작할 호스트를 선택하세요", + "sshRequiredForFileManager": "파일 관리자에는 SSH가 필요합니다. 이 호스트에는 SSH가 활성화되어 있지 않습니다.", + "failedToConnect": "SSH 연결 실패", + "failedToLoadDirectory": "디렉터리 로드 실패", + "noSSHConnection": "SSH 연결을 사용할 수 없음", + "copy": "복사", + "cut": "잘라내기", + "paste": "붙여넣기", + "copyPath": "경로 복사", + "copyPaths": "경로 복사", + "delete": "삭제", + "properties": "속성", + "refresh": "새로 고침", + "downloadFiles": "브라우저로 {{count}}개 파일 다운로드", + "copyFiles": "{{count}}개 항목 복사", + "cutFiles": "{{count}}개 항목 잘라내기", + "deleteFiles": "{{count}}개 항목 삭제", + "filesCopiedToClipboard": "{{count}}개 항목이 클립보드에 복사되었습니다", + "filesCutToClipboard": "{{count}}개 항목을 클립보드로 잘라냈습니다", + "pathCopiedToClipboard": "경로가 클립보드에 복사되었습니다", + "pathsCopiedToClipboard": "{{count}}개 경로가 클립보드에 복사되었습니다", + "failedToCopyPath": "클립보드에 경로를 복사하지 못했습니다", + "copyFolderLink": "폴더 링크 복사", + "copyCurrentFolderLink": "현재 폴더 링크 복사", + "folderLinkCopied": "폴더 링크가 클립보드에 복사되었습니다", + "failedToCopyFolderLink": "폴더 링크를 복사하지 못했습니다", + "movedItems": "{{count}}개 항목을 이동했습니다", + "failedToDeleteItem": "항목을 삭제하지 못했습니다", + "itemRenamedSuccessfully": "{{type}} 이름이 변경되었습니다", + "failedToRenameItem": "항목 이름을 변경하지 못했습니다", + "download": "다운로드", + "openExternalEditor": "외부 편집기로 열기", + "chooseExternalEditor": "외부 편집기 선택", + "externalEditorSelected": "외부 편집기가 선택되었습니다", + "externalEditorDesktopOnly": "외부 편집기는 데스크톱 앱에서만 사용할 수 있습니다", + "externalEditorOpened": "외부 편집기에서 열렸습니다. 저장하면 변경 사항이 서버에 업로드됩니다.", + "failedToOpenExternalEditor": "외부 편집기를 열지 못했습니다", + "failedToSelectExternalEditor": "외부 편집기를 선택하지 못했습니다", + "permissions": "권한", + "size": "크기", + "modified": "수정됨", + "path": "경로", + "confirmDelete": "{{name}}을(를) 삭제하시겠습니까?", + "permissionDenied": "권한이 거부되었습니다", + "serverError": "서버 오류", + "fileSavedSuccessfully": "파일이 저장되었습니다", + "failedToSaveFile": "파일을 저장하지 못했습니다", + "confirmDeleteSingleItem": "\"{{name}}\"을(를) 영구적으로 삭제하시겠습니까?", + "confirmDeleteMultipleItems": "{{count}}개 항목을 영구적으로 삭제하시겠습니까?", + "confirmDeleteMultipleItemsWithFolders": "{{count}}개 항목을 영구적으로 삭제하시겠습니까? 폴더 및 해당 내용이 포함됩니다.", + "confirmDeleteFolder": "\"{{name}}\" 폴더와 모든 내용을 영구적으로 삭제하시겠습니까?", + "permanentDeleteWarning": "이 작업은 되돌릴 수 없습니다. 항목이 서버에서 영구적으로 삭제됩니다.", + "moveToTrashWarning": "해당 항목은 Termix 휴지통으로 이동되며, 보존 기간이 만료되기 전에 복원할 수 있습니다.", + "itemsMovedToTrash": "{{count}} 항목이 휴지통으로 이동되었습니다.", + "trash": "쓰레기", + "trashDescription": "삭제된 항목은 이 호스트에 {{days}} 일 동안 보관됩니다.", + "trashLoadFailed": "휴지통을 불러오는 데 실패했습니다.", + "trashEmpty": "쓰레기통이 비어있습니다", + "trashRestore": "복원하다", + "trashRestored": "복원됨 {{name}}", + "trashRestoreFailed": "항목 복원에 실패했습니다. 원래 경로가 이미 존재할 수 있습니다.", + "trashDeletePermanently": "영구 삭제", + "trashDeleteConfirm": "{{name}}를 영구적으로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "trashPermanentlyDeleted": "영구 삭제됨 {{name}}", + "trashDeleteFailed": "항목을 영구적으로 삭제하는 데 실패했습니다.", + "trashEmptyAction": "쓰레기통을 비우세요", + "trashEmptyConfirm": "휴지통에 있는 모든 항목을 영구적으로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "trashEmptied": "쓰레기통이 비워졌습니다", + "trashRetention": "자동 정리 시간 (일)", + "trashRetentionSaved": "휴지통 보존 시간 업데이트됨", + "trashRetentionFailed": "휴지통 보존 기간 업데이트에 실패했습니다.", + "trashUnavailableConfirm": "이 항목은 다른 파일 시스템에 있거나 관리자 권한이 필요하기 때문에 휴지통으로 이동할 수 없습니다. 영구적으로 삭제하시겠습니까?", + "recent": "최근 항목", + "pinned": "고정됨", + "folderShortcuts": "폴더 바로 가기", + "failedToReconnectSSH": "SSH 세션 재연결에 실패했습니다", + "openTerminalHere": "여기서 터미널 열기", + "run": "실행", + "openTerminalInFolder": "이 폴더에서 터미널 열기", + "openTerminalInFileLocation": "파일 위치에서 터미널 열기", + "runningFile": "실행 중 - {{file}}", + "onlyRunExecutableFiles": "실행 파일만 실행할 수 있습니다", + "directories": "디렉터리", + "removedFromRecentFiles": "최근 파일에서 \"{{name}}\" 제거됨", + "removeFailed": "제거 실패", + "unpinnedSuccessfully": "\"{{name}}\" 고정 해제 완료", + "unpinFailed": "고정 해제 실패", + "removedShortcut": "바로 가기 \"{{name}}\" 제거됨", + "removeShortcutFailed": "바로 가기 제거 실패", + "clearedAllRecentFiles": "모든 최근 파일 지움", + "clearFailed": "지우기 실패", + "removeFromRecentFiles": "최근 파일에서 제거", + "clearAllRecentFiles": "모든 최근 파일 지우기", + "unpinFile": "파일 고정 해제", + "removeShortcut": "바로 가기 제거", + "pinFile": "파일 고정", + "addToShortcuts": "바로 가기에 추가", + "pasteFailed": "붙여넣기 실패", + "noUndoableActions": "실행 취소할 동작 없음", + "undoCopySuccess": "복사 작업 실행 취소: 복사된 파일 {{count}}개 삭제됨", + "undoCopyFailedDelete": "실행 취소 실패: 복사된 파일을 삭제할 수 없음", + "undoCopyFailedNoInfo": "실행 취소 실패: 복사된 파일 정보를 찾을 수 없음", + "undoMoveSuccess": "이동 작업 실행 취소: {{count}}개 파일을 원래 위치로 다시 이동했습니다", + "undoMoveFailedMove": "실행 취소 실패: 파일을 되돌릴 수 없음", + "undoMoveFailedNoInfo": "실행 취소 실패: 이동된 파일 정보를 찾을 수 없음", + "undoDeleteNotSupported": "삭제 작업은 실행 취소할 수 없습니다: 서버에서 파일이 영구 삭제됨", + "undoTypeNotSupported": "지원되지 않는 실행 취소 작업 유형", + "undoOperationFailed": "실행 취소 작업 실패", + "unknownError": "알 수 없는 오류", + "confirm": "확인", + "find": "찾기...", + "replace": "바꾸기", + "downloadInstead": "대신 다운로드", + "keyboardShortcuts": "키보드 단축키", + "searchAndReplace": "검색 및 바꾸기", + "editing": "편집", + "search": "검색", + "findNext": "다음 찾기", + "findPrevious": "이전 찾기", + "save": "저장", + "selectAll": "모두 선택", + "undo": "실행 취소", + "redo": "다시 실행", + "moveLineUp": "줄 위로 이동", + "moveLineDown": "줄 아래로 이동", + "toggleComment": "주석 토글", + "autoComplete": "자동 완성", + "imageLoadError": "이미지를 불러오지 못했습니다", + "startTyping": "입력하세요...", + "unknownSize": "알 수 없는 크기", + "fileIsEmpty": "파일이 비어 있습니다", + "largeFileWarning": "대용량 파일 경고", + "largeFileWarningDesc": "이 파일의 크기는 {{size}}이며, 텍스트로 열 경우 성능 문제가 발생할 수 있습니다.", + "fileNotFoundAndRemoved": "\"{{name}}\" 파일을 찾을 수 없어 최근/고정 파일에서 제거되었습니다.", + "failedToLoadFile": "파일을 불러오지 못했습니다: {{error}}", + "serverErrorOccurred": "서버 오류가 발생했습니다. 나중에 다시 시도하세요.", + "autoSaveFailed": "자동 저장 실패", + "fileAutoSaved": "파일 자동 저장됨", + "moveFileFailed": "{{name}} 이동 실패", + "moveOperationFailed": "이동 작업 실패", + "canOnlyCompareFiles": "파일 두 개만 비교할 수 있습니다.", + "comparingFiles": "파일 비교 중: {{file1}} 및 {{file2}}", + "dragFailed": "드래그 작업 실패", + "filePinnedSuccessfully": "\"{{name}}\" 파일이 고정되었습니다.", + "pinFileFailed": "파일 고정 실패", + "fileUnpinnedSuccessfully": "\"{{name}}\" 파일이 고정 해제되었습니다.", + "unpinFileFailed": "파일 고정 해제 실패", + "shortcutAddedSuccessfully": "폴더 바로가기 \"{{name}}\"이 추가되었습니다.", + "addShortcutFailed": "바로가기 추가 실패", + "operationCompletedSuccessfully": "{{count}}개 항목 {{operation}} 성공", + "operationCompleted": "{{count}}개 항목 {{operation}}", + "downloadFileSuccess": "파일 {{name}} 다운로드 성공", + "downloadFileFailed": "다운로드 실패", + "downloadingFile": "{{name}}… 다운로드 중", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "{{name}}로 이동", + "diffCompareWith": "{{name}}와/과 Diff 비교", + "dragOutsideToDownload": "창 밖으로 드래그하여 다운로드 ({{count}}개 파일)", + "newFolderDefault": "새 폴더", + "newFileDefault": "새 파일.txt", + "successfullyMovedItems": "{{count}}개 항목을 {{target}}에 성공적으로 이동했습니다.", + "move": "이동", + "searchInFile": "파일에서 검색 (Ctrl+F)", + "showKeyboardShortcuts": "키보드 단축키 보기", + "decreaseFontSize": "글꼴 크기 줄이기", + "increaseFontSize": "글꼴 크기 늘리기", + "startWritingMarkdown": "마크다운 내용 작성을 시작하세요...", + "loadingFileComparison": "파일 비교 로딩 중...", + "reload": "새로 고침", + "compare": "비교", + "sideBySide": "나란히 보기", + "inline": "인라인", + "fileComparison": "파일 비교: {{file1}} vs {{file2}}", + "fileTooLarge": "파일이 너무 큽니다: {{error}}", + "sshConnectionFailed": "SSH 연결 실패. {{name}}({{ip}}:{{port}})에 대한 연결을 확인하세요.", + "loadFileFailed": "파일 불러오기 실패: {{error}}", + "connecting": "연결 중...", + "connectedSuccessfully": "연결 성공", + "totpVerificationFailed": "TOTP 인증 실패", + "warpgateVerificationFailed": "Warpgate 인증 실패", + "authenticationFailed": "인증 실패", + "incorrectPassphrase": "잘못된 암호입니다. 다시 시도하세요.", + "verificationCodePrompt": "인증 코드:", + "changePermissions": "권한 변경", + "currentPermissions": "현재 권한", + "owner": "소유자", + "group": "그룹", + "others": "기타", + "read": "읽기", + "write": "쓰기", + "execute": "실행", + "permissionsChangedSuccessfully": "권한이 성공적으로 변경되었습니다", + "failedToChangePermissions": "권한 변경에 실패했습니다", + "name": "이름", + "sortByName": "이름", + "sortByDate": "수정일", + "sortBySize": "크기", + "ascending": "오름차순", + "descending": "내림차순", + "root": "루트", + "new": "새로 만들기", + "sortBy": "정렬 기준", + "items": "항목", + "selected": "선택됨", + "editor": "편집기", + "octal": "8진수", + "storage": "저장소", + "disk": "디스크", + "used": "사용", + "of": "중", + "toggleSidebar": "사이드바 표시/숨기기", + "compactLayout": "간결한 레이아웃을 사용하세요", + "comfortableLayout": "편안한 레이아웃을 사용하세요", + "cannotLoadPdf": "PDF를 로드할 수 없습니다", + "pdfLoadError": "이 PDF 파일을 로드하는 중 오류가 발생했습니다.", + "loadingPdf": "PDF 로드 중...", + "loadingPage": "페이지 로드 중..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "두 SSH 호스트 간에 파일을 전송합니다.", + "sourceServer": "소스 서버", + "destinationServer": "대상 서버", + "selectHost": "호스트를 선택하세요", + "noHostSelected": "호스트가 선택되지 않았습니다.", + "move": "이동하다", + "copy": "복사", + "upload": "업로드", + "itemsSelected": "{{count}} 항목이 선택되었습니다", + "itemsSelected_other": "{{count}} 항목이 선택되었습니다", + "ready": "준비가 된", + "loadingHosts": "호스트를 불러오는 중...", + "name": "이름", + "modified": "수정됨", + "size": "크기", + "noFiles": "파일 없음", + "open": "열려 있는", + "up": "위로", + "refresh": "새로 고치다", + "copyToTarget": "대상 디렉터리로 복사", + "rename": "이름 변경", + "delete": "삭제", + "editPermissions": "편집 권한", + "createNewFolder": "새 폴더 만들기", + "deleteSelectedItems": "선택한 항목 삭제", + "deleteConfirm": "선택한 항목 {{count}} 을 삭제하시겠습니까?", + "deleteConfirm_other": "선택한 항목 {{count}} 을 삭제하시겠습니까?", + "cancel": "취소", + "save": "구하다", + "folderCreated": "폴더가 생성되었습니다", + "itemRenamed": "항목 이름이 변경됨", + "actionFailed": "작업이 실패했습니다", + "itemsDeleted": "{{count}} 항목이 삭제되었습니다", + "itemsDeleted_other": "{{count}} 항목이 삭제되었습니다", + "deleteFailed": "삭제 실패", + "permissionsUpdated": "권한이 업데이트되었습니다", + "noRemoteSessionConnected": "원격 세션이 연결되지 않았습니다.", + "failedToListFiles": "파일 목록을 불러오는 데 실패했습니다.", + "failedToRefresh": "새로 고침에 실패했습니다", + "destinationInsideSource": "대상 경로는 선택한 소스 경로 내에 있을 수 없습니다.", + "sameHostNoOp": "원본 폴더와 대상 폴더가 동일합니다." + }, + "hosts": { + "fileManager": "파일 관리자", + "tabFiles": "파일", + "enableFileManagerMonitor": "파일 관리자 활성화", + "enableFileManagerMonitorDesc": "SFTP를 통해 이 호스트의 파일 탐색 및 관리", + "scpLegacyLabel": "SCP 레거시 모드", + "scpLegacyDesc": "SFTP 하위 시스템을 지원하지 않는 서버(예: 임베디드 또는 최소 SSH 서버)에 레거시 파일 전송을 사용합니다.", + "defaultPathLabel": "기본 경로", + "fileManagerPathHint": "이 호스트의 파일 관리자 실행 시 열리는 디렉터리입니다.", + "fileManagerUrlCopied": "파일 관리자 URL 복사됨", + "copyFileManagerUrlAction": "파일 관리자 URL 복사" + }, + "settings": { + "host": { + "enable": { + "label": "파일 관리자 활성화" + }, + "defaultPath": { + "label": "기본 경로" + }, + "scpLegacy": { + "label": "SCP 레거시 모드" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "파일" + } +} diff --git a/plugins/file-manager/locales/translated/nl_NL.json b/plugins/file-manager/locales/translated/nl_NL.json new file mode 100644 index 000000000..a27ef3f7a --- /dev/null +++ b/plugins/file-manager/locales/translated/nl_NL.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Geen host geselecteerd", + "initializingEditor": "Editor wordt geïnitialiseerd...", + "file": "Bestand", + "folder": "Map", + "uploadFile": "Bestand uploaden", + "downloadFile": "Download", + "extractArchive": "Archief uitpakken", + "extractingArchive": "Extractie van {{name}}...", + "archiveExtractedSuccessfully": "{{name}} succesvol geëxtraheerd", + "extractFailed": "Extractie mislukt", + "compressFile": "Bestand comprimeren", + "compressFiles": "Bestanden comprimeren", + "compressFilesDesc": "Comprimeer {{count}} items tot een archief", + "archiveName": "Archiefnaam", + "enterArchiveName": "Voer de archiefnaam in...", + "compressionFormat": "Compressieformaat", + "selectedFiles": "Geselecteerde bestanden", + "andMoreFiles": "en {{count}} meer...", + "compress": "Comprimeren", + "compressingFiles": "Het comprimeren van {{count}} items naar {{name}}...", + "filesCompressedSuccessfully": "{{name}} succesvol aangemaakt", + "compressFailed": "Compressie mislukt", + "edit": "Bewerking", + "preview": "Voorbeeld", + "previous": "Vorig", + "next": "Volgende", + "pageXOfY": "Pagina {{current}} van {{total}}", + "zoomOut": "Uitzoomen", + "zoomIn": "Inzoomen", + "newFile": "Nieuw bestand", + "newFolder": "Nieuwe map", + "rename": "Naam wijzigen", + "uploading": "Bezig met uploaden...", + "uploadingFile": "Bezig met uploaden {{name}}...", + "fileName": "Bestandsnaam", + "folderName": "Mapnaam", + "fileUploadedSuccessfully": "Bestand \"{{name}}\" succesvol geüpload", + "failedToUploadFile": "Het uploaden van het bestand is mislukt.", + "uploadingFolderFiles": "Bestand(en) {{count}} uploaden...", + "uploadedFolderFiles": "Bestand(en) {{count}} succesvol geüpload", + "uploadedFolderPartial": "Geüploade {{uploaded}} bestand(en), {{failed}} mislukt", + "fileDownloadedSuccessfully": "Bestand \"{{name}}\" succesvol gedownload", + "failedToDownloadFile": "Het downloaden van het bestand is mislukt.", + "fileCreatedSuccessfully": "Bestand \"{{name}}\" is succesvol aangemaakt.", + "folderCreatedSuccessfully": "Map \"{{name}}\" is succesvol aangemaakt", + "failedToCreateItem": "Het aanmaken van het item is mislukt.", + "operationFailed": "{{operation}} bewerking mislukt voor {{name}}: {{error}}", + "failedToResolveSymlink": "Het is niet gelukt om de symbolische link op te lossen.", + "itemsDeletedSuccessfully": "{{count}} items succesvol verwijderd", + "failedToDeleteItems": "Het verwijderen van items is mislukt.", + "sudoPasswordRequired": "Beheerderswachtwoord vereist", + "enterSudoPassword": "Voer het sudo-wachtwoord in om deze bewerking voort te zetten.", + "sudoPassword": "Sudo-wachtwoord", + "sudoOperationFailed": "De sudo-bewerking is mislukt.", + "sudoAuthFailed": "Sudo-authenticatie mislukt", + "dragFilesToUpload": "Sleep bestanden hierheen om ze te uploaden", + "dropToUploadHere": "Sleep hierheen om te uploaden", + "dropToDownloadHere": "Sleep hierheen om te downloaden", + "localFiles": "Lokaal", + "showLocalFiles": "Lokale bestanden weergeven (gesplitste weergave)", + "hideLocalFiles": "Lokale bestanden verbergen", + "localHome": "Startpagina-map", + "localPathPlaceholder": "Lokaal mappad", + "localShowHidden": "Verborgen bestanden weergeven", + "localHideHidden": "Verborgen bestanden verbergen", + "localRevealInFileManager": "Weergeven in de systeembestandsbeheerder", + "localCannotOpenFolder": "Kan de map niet openen", + "localItemCount": "{{count}} items", + "localSelectedCount": "{{count}} geselecteerd", + "back": "Rug", + "forward": "Vooruit", + "up": "Omhoog", + "kind": "Vriendelijk", + "localUploadingProgress": "Uploaden van {{current}} van {{total}}", + "localDownloadingProgress": "{{current}} van {{total}} downloaden", + "localTransferInFlight": "{{count}} tegelijk", + "localPreparingDownload": "Download wordt voorbereid...", + "localTransferCancelled": "Overdracht geannuleerd", + "localNothingToTransfer": "Niets over te dragen", + "localUploadComplete": "Geüploade {{count}} bestand(en)", + "localUploadFailed": "Uploaden mislukt", + "localUploadPartial": "Geüploade {{done}} bestand(en), {{failed}} mislukt", + "localDownloadComplete": "Gedownloade {{count}} bestand(en)", + "localDownloadFailed": "Download mislukt", + "localDownloadPartial": "Gedownloade {{done}} bestand(en), {{failed}} mislukt", + "localReplaceExistingPrompt": "{{count}} item(s) bestaan al in deze map. Wilt u ze vervangen?", + "localReplace": "Vervangen", + "localSkipExisting": "Overslaan", + "localSkippedExisting": "Bestaande item(s) overgeslagen {{count}}", + "localUnsafeNamesSkipped": "Er zijn {{count}} items overgeslagen waarvan de namen niet op deze computer gebruikt kunnen worden.", + "localOpen": "Open", + "localOpenFolder": "Map openen", + "localUploadToRemote": "Uploaden naar server", + "localUploadToRemoteMany": "Upload {{count}} items naar de server", + "localMoveToTrash": "Verplaats het naar de prullenbak", + "localMoveToTrashMany": "Verplaats {{count}} items naar de prullenbak", + "localTrashConfirmSingle": "\"{{name}}\" naar de prullenbak verplaatsen?", + "localTrashConfirmFolder": "Verplaats de map \"{{name}}\" en alle inhoud ervan naar de prullenbak?", + "localTrashConfirmMany": "Verplaats {{count}} items naar de prullenbak?", + "localTrashed": "Verplaatst {{count}} item(s) naar de prullenbak", + "localTrashFailed": "Kon niet naar de prullenbak verplaatst worden.", + "localRenameFailed": "Hernoemen mislukt", + "localCreateFailed": "Kon item niet aanmaken", + "goToParentFolder": "Ga naar de bovenliggende map", + "resizeSidebar": "Sleep om de zijbalk te vergroten of verkleinen (dubbelklik om terug te zetten).", + "resizeLocalPane": "Sleep om het lokale venster te vergroten of verkleinen (dubbelklikken om terug te zetten).", + "columns": "Kolommen", + "columnsHint": "Klik met de rechtermuisknop om kolommen te kiezen.", + "emptyFolder": "Deze map is leeg.", + "searchFiles": "Bestanden doorzoeken...", + "upload": "Uploaden", + "selectHostToStart": "Selecteer een host om het bestandsbeheer te starten.", + "sshRequiredForFileManager": "Bestandsbeheer vereist SSH. Op deze host is SSH niet ingeschakeld.", + "failedToConnect": "Verbinding met SSH mislukt", + "failedToLoadDirectory": "Map laden mislukt", + "noSSHConnection": "Geen SSH-verbinding beschikbaar", + "copy": "Kopiëren", + "cut": "Snee", + "paste": "Plakken", + "copyPath": "Kopieer pad", + "copyPaths": "Kopieerpaden", + "delete": "Verwijderen", + "properties": "Eigenschappen", + "refresh": "Vernieuwen", + "downloadFiles": "Download {{count}} bestanden naar de browser", + "copyFiles": "Kopieer {{count}} items", + "cutFiles": "Knip {{count}} items", + "deleteFiles": "Verwijder {{count}} items", + "filesCopiedToClipboard": "{{count}} items gekopieerd naar het klembord", + "filesCutToClipboard": "{{count}} items naar het klembord geknipt", + "pathCopiedToClipboard": "Pad gekopieerd naar klembord", + "pathsCopiedToClipboard": "{{count}} paden gekopieerd naar het klembord", + "failedToCopyPath": "Het kopiëren van het pad naar het klembord is mislukt.", + "copyFolderLink": "Kopieer de link naar de map", + "copyCurrentFolderLink": "Kopieer de link naar de huidige map", + "folderLinkCopied": "Maplink gekopieerd naar het klembord.", + "failedToCopyFolderLink": "Het kopiëren van de maplink is mislukt.", + "movedItems": "Verplaatste {{count}} items", + "failedToDeleteItem": "Het verwijderen van het item is mislukt.", + "itemRenamedSuccessfully": "{{type}} succesvol hernoemd", + "failedToRenameItem": "Het hernoemen van het item is mislukt.", + "download": "Download", + "openExternalEditor": "Extern openen", + "chooseExternalEditor": "Externe editor kiezen", + "externalEditorSelected": "Externe redacteur geselecteerd", + "externalEditorDesktopOnly": "De externe editor is alleen beschikbaar in de desktop-app.", + "externalEditorOpened": "Geopend in een externe editor. Door daar op te slaan, worden de wijzigingen naar de server geüpload.", + "failedToOpenExternalEditor": "Het openen van de externe editor is mislukt.", + "failedToSelectExternalEditor": "Externe editor selecteren mislukt", + "permissions": "Toestemmingen", + "size": "Maat", + "modified": "Gewijzigd", + "path": "Pad", + "confirmDelete": "Weet je zeker dat je {{name}} wilt verwijderen?", + "permissionDenied": "Toegang geweigerd", + "serverError": "Serverfout", + "fileSavedSuccessfully": "Bestand succesvol opgeslagen", + "failedToSaveFile": "Het opslaan van het bestand is mislukt.", + "confirmDeleteSingleItem": "Weet je zeker dat je \"{{name}} \" permanent wilt verwijderen?", + "confirmDeleteMultipleItems": "Weet je zeker dat je {{count}} items permanent wilt verwijderen?", + "confirmDeleteMultipleItemsWithFolders": "Weet je zeker dat je {{count}} items permanent wilt verwijderen? Dit omvat mappen en hun inhoud.", + "confirmDeleteFolder": "Weet je zeker dat je de map \"{{name}}\" en alle inhoud ervan permanent wilt verwijderen?", + "permanentDeleteWarning": "Deze actie kan niet ongedaan worden gemaakt. Het/de item(s) wordt/worden permanent van de server verwijderd.", + "moveToTrashWarning": "Het/de item(s) wordt/worden verplaatst naar de Termix-prullenbak en kan/kunnen worden hersteld voordat de bewaartermijn is verlopen.", + "itemsMovedToTrash": "{{count}} item(s) verplaatst naar de prullenbak", + "trash": "Afval", + "trashDescription": "Verwijderde items worden {{days}} dag(en) op deze host bewaard.", + "trashLoadFailed": "Het laden van de prullenbak is mislukt.", + "trashEmpty": "De prullenbak is leeg.", + "trashRestore": "Herstellen", + "trashRestored": "Hersteld {{name}}", + "trashRestoreFailed": "Het herstellen van het item is mislukt. Het oorspronkelijke pad bestaat mogelijk al.", + "trashDeletePermanently": "Permanent verwijderen", + "trashDeleteConfirm": "Permanent verwijderen {{name}}? Dit kan niet ongedaan gemaakt worden.", + "trashPermanentlyDeleted": "Permanent verwijderd {{name}}", + "trashDeleteFailed": "Het is niet gelukt om het item permanent te verwijderen.", + "trashEmptyAction": "Leeg de prullenbak", + "trashEmptyConfirm": "Alle items in de prullenbak permanent verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "trashEmptied": "Vuilnisbak geleegd", + "trashRetention": "Automatische opruiming na (dagen)", + "trashRetentionSaved": "Afvalretentie bijgewerkt", + "trashRetentionFailed": "Het bijwerken van de prullenbakretentie is mislukt.", + "trashUnavailableConfirm": "Dit item kan niet naar de prullenbak worden verplaatst, meestal omdat het zich op een ander bestandssysteem bevindt of verhoogde machtigingen vereist. Wilt u het in plaats daarvan permanent verwijderen?", + "recent": "Recent", + "pinned": "Vastgepind", + "folderShortcuts": "Mapsnelkoppelingen", + "failedToReconnectSSH": "Het opnieuw tot stand brengen van de SSH-sessie is mislukt.", + "openTerminalHere": "Terminal hier openen", + "run": "Loop", + "openTerminalInFolder": "Open Terminal in deze map", + "openTerminalInFileLocation": "Open Terminal op de bestandslocatie.", + "runningFile": "Hardlopen - {{file}}", + "onlyRunExecutableFiles": "Kan alleen uitvoerbare bestanden uitvoeren", + "directories": "Gidsen", + "removedFromRecentFiles": "\"{{name}}\" is verwijderd uit recente bestanden", + "removeFailed": "Verwijderen mislukt", + "unpinnedSuccessfully": "\"{{name}}\" succesvol losgemaakt", + "unpinFailed": "Ontgrendelen mislukt", + "removedShortcut": "Sneltoets \"{{name}}\" verwijderd", + "removeShortcutFailed": "Snelkoppeling verwijderen mislukt", + "clearedAllRecentFiles": "Alle recente bestanden verwijderd.", + "clearFailed": "Wissen mislukt", + "removeFromRecentFiles": "Verwijderen uit recente bestanden", + "clearAllRecentFiles": "Wis alle recente bestanden", + "unpinFile": "Maak het bestand los", + "removeShortcut": "Snelkoppeling verwijderen", + "pinFile": "Speldbestand", + "addToShortcuts": "Toevoegen aan snelkoppelingen", + "pasteFailed": "Plakken mislukt", + "noUndoableActions": "Geen onomkeerbare acties", + "undoCopySuccess": "Kopieerbewerking ongedaan gemaakt: {{count}} gekopieerde bestanden verwijderd", + "undoCopyFailedDelete": "Ongedaan maken mislukt: Er konden geen gekopieerde bestanden worden verwijderd.", + "undoCopyFailedNoInfo": "Ongedaan maken mislukt: Gekopieerde bestandsinformatie niet gevonden.", + "undoMoveSuccess": "Verplaatsingsbewerking ongedaan gemaakt: {{count}} bestanden teruggeplaatst naar de oorspronkelijke locatie", + "undoMoveFailedMove": "Ongedaan maken mislukt: Er konden geen bestanden teruggezet worden.", + "undoMoveFailedNoInfo": "Ongedaan maken mislukt: Verplaatste bestandsinformatie niet gevonden.", + "undoDeleteNotSupported": "De verwijderingsbewerking kan niet ongedaan worden gemaakt: de bestanden zijn permanent van de server verwijderd.", + "undoTypeNotSupported": "Niet-ondersteund type ongedaan maken-bewerking", + "undoOperationFailed": "De bewerking 'Ongedaan maken' is mislukt.", + "unknownError": "Onbekende fout", + "confirm": "Bevestigen", + "find": "Vinden...", + "replace": "Vervangen", + "downloadInstead": "Download in plaats daarvan", + "keyboardShortcuts": "Toetsenbord sneltoetsen", + "searchAndReplace": "Zoeken en vervangen", + "editing": "Bewerken", + "search": "Zoekopdracht", + "findNext": "Volgende zoeken", + "findPrevious": "Vorige zoeken", + "save": "Redden", + "selectAll": "Alles selecteren", + "undo": "Ongedaan maken", + "redo": "Opnieuw doen", + "moveLineUp": "Verplaats je in de rij", + "moveLineDown": "Lijn omlaag verplaatsen", + "toggleComment": "Reactie weergeven/verbergen", + "autoComplete": "Automatisch aanvullen", + "imageLoadError": "Afbeelding laden mislukt", + "startTyping": "Begin met typen...", + "unknownSize": "Onbekende grootte", + "fileIsEmpty": "Het bestand is leeg.", + "largeFileWarning": "Waarschuwing voor grote bestanden", + "largeFileWarningDesc": "Dit bestand is {{size}} groot, wat prestatieproblemen kan veroorzaken wanneer het als tekstbestand wordt geopend.", + "fileNotFoundAndRemoved": "Bestand \"{{name}}\" is niet gevonden en is verwijderd uit recente/vastgepinde bestanden.", + "failedToLoadFile": "Bestand laden mislukt: {{error}}", + "serverErrorOccurred": "Er is een serverfout opgetreden. Probeer het later opnieuw.", + "autoSaveFailed": "Automatisch opslaan is mislukt", + "fileAutoSaved": "Bestand automatisch opgeslagen", + "moveFileFailed": "Verplaatsen mislukt {{name}}", + "moveOperationFailed": "Verplaatsingsbewerking mislukt", + "canOnlyCompareFiles": "Er kunnen slechts twee bestanden vergeleken worden.", + "comparingFiles": "Bestanden vergelijken: {{file1}} en {{file2}}", + "dragFailed": "Sleepbewerking mislukt", + "filePinnedSuccessfully": "Bestand \"{{name}}\" succesvol vastgezet", + "pinFileFailed": "Het vastzetten van het bestand is mislukt.", + "fileUnpinnedSuccessfully": "Bestand \"{{name}}\" is succesvol losgemaakt.", + "unpinFileFailed": "Het losmaken van het bestand is mislukt.", + "shortcutAddedSuccessfully": "Mapsnelkoppeling \"{{name}}\" succesvol toegevoegd", + "addShortcutFailed": "Snelkoppeling toevoegen mislukt", + "operationCompletedSuccessfully": "{{operation}} {{count}} items succesvol", + "operationCompleted": "{{operation}} {{count}} items", + "downloadFileSuccess": "Bestand {{name}} succesvol gedownload", + "downloadFileFailed": "Download mislukt", + "downloadingFile": "Downloaden {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Ga naar {{name}}", + "diffCompareWith": "Vergelijk het verschil met {{name}}", + "dragOutsideToDownload": "Sleep buiten het venster om bestanden te downloaden ({{count}})", + "newFolderDefault": "Nieuwe map", + "newFileDefault": "NieuwBestand.txt", + "successfullyMovedItems": "Succesvol {{count}} items verplaatst naar {{target}}", + "move": "Beweging", + "searchInFile": "Zoeken in bestand (Ctrl+F)", + "showKeyboardShortcuts": "Toon sneltoetsen", + "decreaseFontSize": "Verklein de lettergrootte", + "increaseFontSize": "Lettergrootte vergroten", + "startWritingMarkdown": "Begin met het schrijven van je markdown-content...", + "loadingFileComparison": "Bestandsvergelijking laden...", + "reload": "Herladen", + "compare": "Vergelijken", + "sideBySide": "Naast elkaar", + "inline": "Inline", + "fileComparison": "Bestandsvergelijking: {{file1}} versus {{file2}}", + "fileTooLarge": "Bestand te groot: {{error}}", + "sshConnectionFailed": "SSH-verbinding mislukt. Controleer uw verbinding met {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Bestand laden mislukt: {{error}}", + "connecting": "Verbinden...", + "connectedSuccessfully": "Verbinding succesvol tot stand gebracht.", + "totpVerificationFailed": "TOTP-verificatie mislukt", + "warpgateVerificationFailed": "Warpgate-authenticatie mislukt", + "authenticationFailed": "Authenticatie mislukt", + "incorrectPassphrase": "Onjuiste wachtzin. Probeer het opnieuw.", + "verificationCodePrompt": "Verificatiecode:", + "changePermissions": "Machtigingen wijzigen", + "currentPermissions": "Huidige vergunningen", + "owner": "Eigenaar", + "group": "Groep", + "others": "Anderen", + "read": "Lezen", + "write": "Schrijven", + "execute": "Uitvoeren", + "permissionsChangedSuccessfully": "Machtigingen succesvol gewijzigd", + "failedToChangePermissions": "Het wijzigen van de machtigingen is mislukt.", + "name": "Naam", + "sortByName": "Naam", + "sortByDate": "Datum gewijzigd", + "sortBySize": "Maat", + "ascending": "Stijgend", + "descending": "Dalend", + "root": "Wortel", + "new": "Nieuw", + "sortBy": "Sorteren op", + "items": "Artikelen", + "selected": "Gekozen", + "editor": "Editor", + "octal": "Octaal", + "storage": "Opslag", + "disk": "Schijf", + "used": "Gebruikt", + "of": "van", + "toggleSidebar": "Zijbalk in-/uitschakelen", + "compactLayout": "Gebruik een compacte lay-out.", + "comfortableLayout": "Gebruik een comfortabele lay-out.", + "cannotLoadPdf": "Kan PDF niet laden", + "pdfLoadError": "Er is een fout opgetreden bij het laden van dit PDF-bestand.", + "loadingPdf": "PDF laden...", + "loadingPage": "Pagina laden..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Bestanden overdragen tussen twee SSH-hosts.", + "sourceServer": "Bronserver", + "destinationServer": "Doelserver", + "selectHost": "Selecteer host", + "noHostSelected": "Geen host geselecteerd", + "move": "Beweging", + "copy": "Kopiëren", + "upload": "Uploaden", + "itemsSelected": "{{count}} item geselecteerd", + "itemsSelected_other": "{{count}} items geselecteerd", + "ready": "Klaar", + "loadingHosts": "Hosts worden geladen...", + "name": "Naam", + "modified": "Gewijzigd", + "size": "Maat", + "noFiles": "Geen bestanden", + "open": "Open", + "up": "Omhoog", + "refresh": "Vernieuwen", + "copyToTarget": "Kopiëren naar doelmap", + "rename": "Naam wijzigen", + "delete": "Verwijderen", + "editPermissions": "Machtigingen bewerken", + "createNewFolder": "Nieuwe map maken", + "deleteSelectedItems": "Geselecteerde items verwijderen", + "deleteConfirm": "Verwijder het geselecteerde item {{count}}?", + "deleteConfirm_other": "Verwijder {{count}} geselecteerde items?", + "cancel": "Annuleren", + "save": "Redden", + "folderCreated": "Map aangemaakt", + "itemRenamed": "Item hernoemd", + "actionFailed": "Actie mislukt", + "itemsDeleted": "{{count}} item verwijderd", + "itemsDeleted_other": "{{count}} items verwijderd", + "deleteFailed": "Verwijderen mislukt", + "permissionsUpdated": "Machtigingen bijgewerkt", + "noRemoteSessionConnected": "Geen externe sessie verbonden", + "failedToListFiles": "Het lukte niet om de bestanden weer te geven.", + "failedToRefresh": "Vernieuwen mislukt", + "destinationInsideSource": "De bestemming mag zich niet binnen het geselecteerde bronpad bevinden.", + "sameHostNoOp": "De bron- en bestemmingsmap zijn dezelfde map." + }, + "hosts": { + "fileManager": "Bestandsbeheerder", + "tabFiles": "Bestanden", + "enableFileManagerMonitor": "Bestandsbeheer inschakelen", + "enableFileManagerMonitorDesc": "Blader door en beheer bestanden op deze host via SFTP.", + "scpLegacyLabel": "SCP Legacy-modus", + "scpLegacyDesc": "Gebruik de traditionele bestandsoverdrachtsmethode voor servers die het SFTP-subsysteem niet ondersteunen (bijvoorbeeld ingebouwde of minimale SSH-servers).", + "defaultPathLabel": "Standaardpad", + "fileManagerPathHint": "De map die geopend moet worden wanneer de bestandsbeheerder voor deze host wordt gestart.", + "fileManagerUrlCopied": "URL van bestandsbeheerder gekopieerd", + "copyFileManagerUrlAction": "Kopieer de URL van de bestandsbeheerder" + }, + "settings": { + "host": { + "enable": { + "label": "Bestandsbeheer inschakelen" + }, + "defaultPath": { + "label": "Standaardpad" + }, + "scpLegacy": { + "label": "SCP Legacy-modus" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Bestanden" + } +} diff --git a/plugins/file-manager/locales/translated/no_NO.json b/plugins/file-manager/locales/translated/no_NO.json new file mode 100644 index 000000000..bfd609f0e --- /dev/null +++ b/plugins/file-manager/locales/translated/no_NO.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Ingen vert valgt", + "initializingEditor": "Initialiserer redigeringsprogrammet...", + "file": "Fil", + "folder": "Mappe", + "uploadFile": "Last opp fil", + "downloadFile": "Last ned", + "extractArchive": "Utdragsarkiv", + "extractingArchive": "Uttrekker {{name}}...", + "archiveExtractedSuccessfully": "{{name}} ble pakket ut", + "extractFailed": "Utpakking mislyktes", + "compressFile": "Komprimer fil", + "compressFiles": "Komprimer filer", + "compressFilesDesc": "Komprimer {{count}} elementer til et arkiv", + "archiveName": "Arkivnavn", + "enterArchiveName": "Skriv inn arkivnavn...", + "compressionFormat": "Kompresjonsformat", + "selectedFiles": "Valgte filer", + "andMoreFiles": "og {{count}} mer...", + "compress": "Komprimere", + "compressingFiles": "Komprimerer {{count}} elementer til {{name}}...", + "filesCompressedSuccessfully": "{{name}} ble opprettet", + "compressFailed": "Komprimeringen mislyktes", + "edit": "Redigere", + "preview": "Forhåndsvisning", + "previous": "Tidligere", + "next": "Neste", + "pageXOfY": "Side {{current}} av {{total}}", + "zoomOut": "Zoom ut", + "zoomIn": "Zoom inn", + "newFile": "Ny fil", + "newFolder": "Ny mappe", + "rename": "Gi nytt navn", + "uploading": "Laster opp...", + "uploadingFile": "Laster opp {{name}}...", + "fileName": "Filnavn", + "folderName": "Mappenavn", + "fileUploadedSuccessfully": "Filen «{{name}}» ble lastet opp", + "failedToUploadFile": "Kunne ikke laste opp filen", + "uploadingFolderFiles": "Laster opp {{count}} fil(er)...", + "uploadedFolderFiles": "Lastet opp {{count}} fil(er)", + "uploadedFolderPartial": "Opplasting av {{uploaded}} fil(er), {{failed}} mislyktes", + "fileDownloadedSuccessfully": "Filen «{{name}}» er lastet ned", + "failedToDownloadFile": "Kunne ikke laste ned filen", + "fileCreatedSuccessfully": "Filen «{{name}}» er opprettet", + "folderCreatedSuccessfully": "Mappen «{{name}}» er opprettet", + "failedToCreateItem": "Kunne ikke opprette elementet", + "operationFailed": "{{operation}} operasjonen mislyktes for {{name}}: {{error}}", + "failedToResolveSymlink": "Klarte ikke å løse symbolsk lenke", + "itemsDeletedSuccessfully": "{{count}} elementer er slettet", + "failedToDeleteItems": "Kunne ikke slette elementer", + "sudoPasswordRequired": "Administratorpassord kreves", + "enterSudoPassword": "Skriv inn sudo-passordet for å fortsette denne operasjonen", + "sudoPassword": "Sudo-passord", + "sudoOperationFailed": "Sudo-operasjonen mislyktes", + "sudoAuthFailed": "Sudo-autentisering mislyktes", + "dragFilesToUpload": "Slipp filer her for å laste dem opp", + "dropToUploadHere": "Slipp for å laste opp her", + "dropToDownloadHere": "Slipp for å laste ned her", + "localFiles": "Lokalt", + "showLocalFiles": "Vis lokale filer (delt visning)", + "hideLocalFiles": "Skjul lokale filer", + "localHome": "Hjemmappe", + "localPathPlaceholder": "Lokal mappebane", + "localShowHidden": "Vis skjulte filer", + "localHideHidden": "Skjul skjulte filer", + "localRevealInFileManager": "Avslør i systemfilbehandleren", + "localCannotOpenFolder": "Kan ikke åpne mappen", + "localItemCount": "{{count}} elementer", + "localSelectedCount": "{{count}} valgt", + "back": "Tilbake", + "forward": "Framover", + "up": "Opp", + "kind": "Type", + "localUploadingProgress": "Laster opp {{current}} av {{total}}", + "localDownloadingProgress": "Laster ned {{current}} av {{total}}", + "localTransferInFlight": "{{count}} med en gang", + "localPreparingDownload": "Forbereder nedlasting ...", + "localTransferCancelled": "Overføringen er avbrutt", + "localNothingToTransfer": "Ingenting å overføre", + "localUploadComplete": "Opplastede {{count}} fil(er)", + "localUploadFailed": "Opplastingen mislyktes", + "localUploadPartial": "Opplasting av {{done}} fil(er), {{failed}} mislyktes", + "localDownloadComplete": "Lastet ned {{count}} fil(er)", + "localDownloadFailed": "Nedlastingen mislyktes", + "localDownloadPartial": "Nedlasting av {{done}} fil(er), {{failed}} mislyktes", + "localReplaceExistingPrompt": "{{count}} element(er) finnes allerede i denne mappen. Vil du erstatte dem?", + "localReplace": "Erstatt", + "localSkipExisting": "Hopp over", + "localSkippedExisting": "Hoppet over {{count}} eksisterende element(er)", + "localUnsafeNamesSkipped": "Hoppet over {{count}} elementer hvis navn ikke kan brukes på denne datamaskinen", + "localOpen": "Åpne", + "localOpenFolder": "Åpne mappen", + "localUploadToRemote": "Last opp til server", + "localUploadToRemoteMany": "Last opp {{count}} elementer til serveren", + "localMoveToTrash": "Flytt til papirkurven", + "localMoveToTrashMany": "Flytt {{count}} elementer til papirkurven", + "localTrashConfirmSingle": "Flytte «{{name}}» til papirkurven?", + "localTrashConfirmFolder": "Flytte mappen «{{name}}» og alt innholdet til papirkurven?", + "localTrashConfirmMany": "Flytte {{count}} elementer til papirkurven?", + "localTrashed": "Flyttet {{count}} element(er) til papirkurven", + "localTrashFailed": "Kunne ikke flytte til papirkurven", + "localRenameFailed": "Mislyktes i å gi nytt navn", + "localCreateFailed": "Kunne ikke opprette elementet", + "goToParentFolder": "Gå til overordnet mappe", + "resizeSidebar": "Dra for å endre størrelsen på sidefeltet (dobbeltklikk for å tilbakestille)", + "resizeLocalPane": "Dra for å endre størrelsen på den lokale ruten (dobbeltklikk for å tilbakestille)", + "columns": "Kolonner", + "columnsHint": "Høyreklikk for å velge kolonner", + "emptyFolder": "Denne mappen er tom", + "searchFiles": "Søk i filer...", + "upload": "Last opp", + "selectHostToStart": "Velg en vert for å starte filbehandling", + "sshRequiredForFileManager": "Filbehandleren krever SSH. Denne verten har ikke SSH aktivert.", + "failedToConnect": "Kunne ikke koble til SSH", + "failedToLoadDirectory": "Kunne ikke laste inn katalogen", + "noSSHConnection": "Ingen SSH-tilkobling tilgjengelig", + "copy": "Kopiere", + "cut": "Kutt", + "paste": "Lim inn", + "copyPath": "Kopier bane", + "copyPaths": "Kopier stier", + "delete": "Slett", + "properties": "Eiendommer", + "refresh": "Forfriske", + "downloadFiles": "Last ned {{count}} filer til nettleseren", + "copyFiles": "Kopier {{count}} elementer", + "cutFiles": "Klipp ut {{count}} elementer", + "deleteFiles": "Slett {{count}} elementer", + "filesCopiedToClipboard": "{{count}} elementer kopiert til utklippstavlen", + "filesCutToClipboard": "{{count}} elementer klippet til utklippstavlen", + "pathCopiedToClipboard": "Sti kopiert til utklippstavlen", + "pathsCopiedToClipboard": "{{count}} stier kopiert til utklippstavlen", + "failedToCopyPath": "Kunne ikke kopiere banen til utklippstavlen", + "copyFolderLink": "Kopier lenke til mappe", + "copyCurrentFolderLink": "Kopier lenke til gjeldende mappe", + "folderLinkCopied": "Mappelenke kopiert til utklippstavlen", + "failedToCopyFolderLink": "Kunne ikke kopiere mappelenken", + "movedItems": "Flyttet {{count}} elementer", + "failedToDeleteItem": "Kunne ikke slette elementet", + "itemRenamedSuccessfully": "{{type}} har fått nytt navn", + "failedToRenameItem": "Kunne ikke gi nytt navn til elementet", + "download": "Last ned", + "openExternalEditor": "Åpne eksternt", + "chooseExternalEditor": "Velg ekstern editor", + "externalEditorSelected": "Ekstern redigerer valgt", + "externalEditorDesktopOnly": "Ekstern editor er bare tilgjengelig i skrivebordsappen", + "externalEditorOpened": "Åpnet i ekstern editor. Hvis du lagrer der, lastes endringene opp tilbake til serveren.", + "failedToOpenExternalEditor": "Kunne ikke åpne ekstern editor", + "failedToSelectExternalEditor": "Kunne ikke velge ekstern redigeringsprogram", + "permissions": "Tillatelser", + "size": "Størrelse", + "modified": "Modifisert", + "path": "Sti", + "confirmDelete": "Er du sikker på at du vil slette {{name}}?", + "permissionDenied": "Tillatelse nektet", + "serverError": "Serverfeil", + "fileSavedSuccessfully": "Filen er lagret", + "failedToSaveFile": "Kunne ikke lagre filen", + "confirmDeleteSingleItem": "Er du sikker på at du vil slette «{{name}}» permanent?", + "confirmDeleteMultipleItems": "Er du sikker på at du vil slette {{count}} elementer permanent?", + "confirmDeleteMultipleItemsWithFolders": "Er du sikker på at du vil slette {{count}} elementer permanent? Dette inkluderer mapper og innholdet i dem.", + "confirmDeleteFolder": "Er du sikker på at du vil slette mappen «{{name}}» og alt innholdet i den permanent?", + "permanentDeleteWarning": "Denne handlingen kan ikke angres. Elementet/elementene vil bli permanent slettet fra serveren.", + "moveToTrashWarning": "Elementet/elementene vil bli flyttet til Termix-papirkurven og kan gjenopprettes før oppbevaringsperioden utløper.", + "itemsMovedToTrash": "{{count}} element(er) flyttet til papirkurven", + "trash": "Søppel", + "trashDescription": "Slettede elementer lagres i {{days}} dag(er) på denne verten.", + "trashLoadFailed": "Kunne ikke laste inn papirkurven", + "trashEmpty": "Papirkurven er tom", + "trashRestore": "Restaurere", + "trashRestored": "Gjenopprettet {{name}}", + "trashRestoreFailed": "Kunne ikke gjenopprette elementet. Den opprinnelige banen finnes kanskje allerede.", + "trashDeletePermanently": "Slett permanent", + "trashDeleteConfirm": "Vil du slette {{name}}permanent? Dette kan ikke angres.", + "trashPermanentlyDeleted": "Slettet permanent {{name}}", + "trashDeleteFailed": "Kunne ikke slette elementet permanent", + "trashEmptyAction": "Tøm søppelbøtten", + "trashEmptyConfirm": "Vil du slette alle elementene i papirkurven permanent? Dette kan ikke angres.", + "trashEmptied": "Søppelet tømt", + "trashRetention": "Automatisk opprydding etter (dager)", + "trashRetentionSaved": "Oppbevaring av søppel er oppdatert", + "trashRetentionFailed": "Kunne ikke oppdatere papirkurven", + "trashUnavailableConfirm": "Dette elementet kan ikke flyttes til papirkurven, vanligvis fordi det er på et annet filsystem eller krever utvidede tillatelser. Slette det permanent i stedet?", + "recent": "Nylig", + "pinned": "Festet", + "folderShortcuts": "Mappesnarveier", + "failedToReconnectSSH": "Kunne ikke koble til SSH-økten på nytt", + "openTerminalHere": "Åpne terminalen her", + "run": "Løp", + "openTerminalInFolder": "Åpne terminalen i denne mappen", + "openTerminalInFileLocation": "Åpne terminalen på filplassering", + "runningFile": "Løping - {{file}}", + "onlyRunExecutableFiles": "Kan bare kjøre kjørbare filer", + "directories": "Kataloger", + "removedFromRecentFiles": "Fjernet «{{name}}» fra nylige filer", + "removeFailed": "Fjerning mislyktes", + "unpinnedSuccessfully": "«{{name}}» er løsnet", + "unpinFailed": "Løsne mislyktes", + "removedShortcut": "Fjernet snarveien «{{name}}»", + "removeShortcutFailed": "Fjerning av snarvei mislyktes", + "clearedAllRecentFiles": "Slettet alle nylige filer", + "clearFailed": "Fjerning mislyktes", + "removeFromRecentFiles": "Fjern fra nylige filer", + "clearAllRecentFiles": "Fjern alle nylige filer", + "unpinFile": "Løsne filen", + "removeShortcut": "Fjern snarvei", + "pinFile": "Fest filen", + "addToShortcuts": "Legg til i snarveier", + "pasteFailed": "Lime inn mislyktes", + "noUndoableActions": "Ingen angrebare handlinger", + "undoCopySuccess": "Angret kopiering: Slettet {{count}} kopierte filer", + "undoCopyFailedDelete": "Angre mislyktes: Kunne ikke slette noen kopierte filer", + "undoCopyFailedNoInfo": "Angre mislyktes: Finner ikke informasjon om den kopierte filen", + "undoMoveSuccess": "Angret flytteoperasjon: Flyttet {{count}} filer tilbake til opprinnelig plassering", + "undoMoveFailedMove": "Angre mislyktes: Kunne ikke flytte noen filer tilbake", + "undoMoveFailedNoInfo": "Angre mislyktes: Finner ikke informasjon om den flyttede filen", + "undoDeleteNotSupported": "Sletteoperasjonen kan ikke angres: Filene er slettet permanent fra serveren", + "undoTypeNotSupported": "Ustøttet angreoperasjonstype", + "undoOperationFailed": "Angreoperasjonen mislyktes", + "unknownError": "Ukjent feil", + "confirm": "Bekrefte", + "find": "Finne...", + "replace": "Erstatt", + "downloadInstead": "Last ned i stedet", + "keyboardShortcuts": "Tastatursnarveier", + "searchAndReplace": "Søk og erstatt", + "editing": "Redigering", + "search": "Søk", + "findNext": "Finn neste", + "findPrevious": "Finn forrige", + "save": "Spare", + "selectAll": "Velg alle", + "undo": "Angre", + "redo": "Gjør om", + "moveLineUp": "Flytt linje opp", + "moveLineDown": "Flytt linje ned", + "toggleComment": "Veksle kommentar", + "autoComplete": "Autofullfør", + "imageLoadError": "Kunne ikke laste inn bildet", + "startTyping": "Begynn å skrive...", + "unknownSize": "Ukjent størrelse", + "fileIsEmpty": "Filen er tom", + "largeFileWarning": "Advarsel om stor fil", + "largeFileWarningDesc": "Denne filen er {{size}} stor, noe som kan forårsake ytelsesproblemer når den åpnes som tekst.", + "fileNotFoundAndRemoved": "Filen «{{name}}» ble ikke funnet og er fjernet fra nylige/festede filer", + "failedToLoadFile": "Kunne ikke laste inn filen: {{error}}", + "serverErrorOccurred": "Det oppsto en serverfeil. Prøv igjen senere.", + "autoSaveFailed": "Automatisk lagring mislyktes", + "fileAutoSaved": "Fil lagret automatisk", + "moveFileFailed": "Kunne ikke flytte {{name}}", + "moveOperationFailed": "Flytteoperasjonen mislyktes", + "canOnlyCompareFiles": "Kan bare sammenligne to filer", + "comparingFiles": "Sammenligning av filer: {{file1}} og {{file2}}", + "dragFailed": "Draoperasjonen mislyktes", + "filePinnedSuccessfully": "Filen «{{name}}» er låst", + "pinFileFailed": "Kunne ikke feste filen", + "fileUnpinnedSuccessfully": "Filen «{{name}}» ble løsnet", + "unpinFileFailed": "Kunne ikke løsne filen", + "shortcutAddedSuccessfully": "Mappsnarveien «{{name}}» er lagt til", + "addShortcutFailed": "Kunne ikke legge til snarvei", + "operationCompletedSuccessfully": "{{operation}} {{count}} elementer ble vellykket", + "operationCompleted": "{{operation}} {{count}} elementer", + "downloadFileSuccess": "Fil {{name}} er lastet ned", + "downloadFileFailed": "Nedlastingen mislyktes", + "downloadingFile": "Laster ned {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Flytt til {{name}}", + "diffCompareWith": "Differansesammenligning med {{name}}", + "dragOutsideToDownload": "Dra utenfor vinduet for å laste ned ({{count}} filer)", + "newFolderDefault": "Ny mappe", + "newFileDefault": "Nyfil.txt", + "successfullyMovedItems": "Flyttet {{count}} elementer til {{target}}", + "move": "Flytte", + "searchInFile": "Søk i filen (Ctrl+F)", + "showKeyboardShortcuts": "Vis hurtigtaster", + "decreaseFontSize": "Reduser skriftstørrelsen", + "increaseFontSize": "Øk skriftstørrelsen", + "startWritingMarkdown": "Begynn å skrive innholdet ditt med nedsatt pris...", + "loadingFileComparison": "Laster inn filsammenligning ...", + "reload": "Last inn på nytt", + "compare": "Sammenligne", + "sideBySide": "Side om side", + "inline": "Innebygd", + "fileComparison": "Filsammenligning: {{file1}} vs. {{file2}}", + "fileTooLarge": "Filen er for stor: {{error}}", + "sshConnectionFailed": "SSH-tilkoblingen mislyktes. Sjekk tilkoblingen din til {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Kunne ikke laste inn filen: {{error}}", + "connecting": "Kobler til...", + "connectedSuccessfully": "Tilkoblet", + "totpVerificationFailed": "TOTP-verifisering mislyktes", + "warpgateVerificationFailed": "Warpgate-autentisering mislyktes", + "authenticationFailed": "Autentisering mislyktes", + "incorrectPassphrase": "Feil passordfrase. Prøv på nytt.", + "verificationCodePrompt": "Verifiseringskode:", + "changePermissions": "Endre tillatelser", + "currentPermissions": "Gjeldende tillatelser", + "owner": "Eier", + "group": "Gruppe", + "others": "Andre", + "read": "Lese", + "write": "Skrive", + "execute": "Henrette", + "permissionsChangedSuccessfully": "Tillatelsene er endret", + "failedToChangePermissions": "Kunne ikke endre tillatelser", + "name": "Navn", + "sortByName": "Navn", + "sortByDate": "Dato endret", + "sortBySize": "Størrelse", + "ascending": "Stigende", + "descending": "Synkende", + "root": "Rot", + "new": "Ny", + "sortBy": "Sorter etter", + "items": "Elementer", + "selected": "Valgt", + "editor": "Redaktør", + "octal": "Oktal", + "storage": "Lagring", + "disk": "Disk", + "used": "Brukt", + "of": "av", + "toggleSidebar": "Slå sidefeltet av/på", + "compactLayout": "Bruk kompakt oppsett", + "comfortableLayout": "Bruk komfortabel layout", + "cannotLoadPdf": "Kan ikke laste inn PDF-filen", + "pdfLoadError": "Det oppsto en feil under lasting av denne PDF-filen.", + "loadingPdf": "Laster inn PDF-filen...", + "loadingPage": "Laster inn side..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Overfør filer mellom to SSH-verter.", + "sourceServer": "Kildeserver", + "destinationServer": "Destinasjonsserver", + "selectHost": "Velg vert", + "noHostSelected": "Ingen vert valgt", + "move": "Flytte", + "copy": "Kopiere", + "upload": "Last opp", + "itemsSelected": "{{count}} element valgt", + "itemsSelected_other": "{{count}} elementer valgt", + "ready": "Ferdig", + "loadingHosts": "Laster inn verter ...", + "name": "Navn", + "modified": "Modifisert", + "size": "Størrelse", + "noFiles": "Ingen filer", + "open": "Åpne", + "up": "Opp", + "refresh": "Forfriske", + "copyToTarget": "Kopier til målkatalogen", + "rename": "Gi nytt navn", + "delete": "Slett", + "editPermissions": "Rediger tillatelser", + "createNewFolder": "Opprett ny mappe", + "deleteSelectedItems": "Slett valgte elementer", + "deleteConfirm": "Slette {{count}} valgt element?", + "deleteConfirm_other": "Slette {{count}} valgte elementer?", + "cancel": "Kansellere", + "save": "Spare", + "folderCreated": "Mappe opprettet", + "itemRenamed": "Elementet har fått nytt navn", + "actionFailed": "Handlingen mislyktes", + "itemsDeleted": "{{count}} element slettet", + "itemsDeleted_other": "{{count}} elementer slettet", + "deleteFailed": "Slettingen mislyktes", + "permissionsUpdated": "Tillatelser oppdatert", + "noRemoteSessionConnected": "Ingen ekstern økt tilkoblet", + "failedToListFiles": "Kunne ikke liste opp filer", + "failedToRefresh": "Kunne ikke laste inn på nytt", + "destinationInsideSource": "Destinasjonen kan ikke være innenfor den valgte kildebanen.", + "sameHostNoOp": "Kilde og destinasjon er i samme mappe." + }, + "hosts": { + "fileManager": "Filbehandler", + "tabFiles": "Filer", + "enableFileManagerMonitor": "Aktiver filbehandler", + "enableFileManagerMonitorDesc": "Bla gjennom og administrer filer på denne verten via SFTP", + "scpLegacyLabel": "SCP Legacy-modus", + "scpLegacyDesc": "Bruk eldre filoverføring for servere som ikke støtter SFTP-undersystemet (f.eks. innebygde eller minimale SSH-servere).", + "defaultPathLabel": "Standardsti", + "fileManagerPathHint": "Katalogen som skal åpnes når filbehandleren starter for denne verten.", + "fileManagerUrlCopied": "URL-adressen til filbehandleren er kopiert", + "copyFileManagerUrlAction": "Kopier URL-adressen til filbehandleren" + }, + "settings": { + "host": { + "enable": { + "label": "Aktiver filbehandler" + }, + "defaultPath": { + "label": "Standardsti" + }, + "scpLegacy": { + "label": "SCP Legacy-modus" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Filer" + } +} diff --git a/plugins/file-manager/locales/translated/pl_PL.json b/plugins/file-manager/locales/translated/pl_PL.json new file mode 100644 index 000000000..56d07f4a7 --- /dev/null +++ b/plugins/file-manager/locales/translated/pl_PL.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Nie wybrano hosta", + "initializingEditor": "Inicjalizacja edytora...", + "file": "Plik", + "folder": "Folder", + "uploadFile": "Prześlij plik", + "downloadFile": "Pobierz", + "extractArchive": "Wyodrębnij archiwum", + "extractingArchive": "Wyodrębnianie {{name}}...", + "archiveExtractedSuccessfully": "{{name}} wyodrębniono pomyślnie", + "extractFailed": "Wyodrębnienie nie powiodło się", + "compressFile": "Kompresuj plik", + "compressFiles": "Kompresuj pliki", + "compressFilesDesc": "Kompresuj {{count}} elementów do archiwum", + "archiveName": "Nazwa archiwum", + "enterArchiveName": "Wprowadź nazwę archiwum...", + "compressionFormat": "Format kompresji", + "selectedFiles": "Wybrane pliki", + "andMoreFiles": "i jeszcze {{count}}...", + "compress": "Kompresuj", + "compressingFiles": "Kompresowanie {{count}} elementów do {{name}}...", + "filesCompressedSuccessfully": "{{name}} utworzono pomyślnie", + "compressFailed": "Kompresja nie powiodła się", + "edit": "Edytuj", + "preview": "Podgląd", + "previous": "Poprzedni", + "next": "Następny", + "pageXOfY": "Strona {{current}} z {{total}}", + "zoomOut": "Oddal", + "zoomIn": "Powiększ", + "newFile": "Nowy plik", + "newFolder": "Nowy folder", + "rename": "Zmień nazwę", + "uploading": "Przesyłanie...", + "uploadingFile": "Przesyłanie {{name}}...", + "fileName": "Nazwa pliku", + "folderName": "Nazwa folderu", + "fileUploadedSuccessfully": "Plik „{{name}}” został pomyślnie przesłany", + "failedToUploadFile": "Nie udało się przesłać pliku", + "uploadingFolderFiles": "Przesyłanie {{count}} plików...", + "uploadedFolderFiles": "Pomyślnie przesłano {{count}} plików", + "uploadedFolderPartial": "Przesłano {{uploaded}} plików, {{failed}} nie powiodło się", + "fileDownloadedSuccessfully": "Plik „{{name}}” został pomyślnie pobrany", + "failedToDownloadFile": "Nie udało się pobrać pliku", + "fileCreatedSuccessfully": "Plik „{{name}}” został pomyślnie utworzony", + "folderCreatedSuccessfully": "Folder „{{name}}” został pomyślnie utworzony", + "failedToCreateItem": "Nie udało się utworzyć elementu", + "operationFailed": "Operacja {{operation}} nie powiodła się dla {{name}}: {{error}}", + "failedToResolveSymlink": "Nie udało się rozwiązać dowiązania symbolicznego", + "itemsDeletedSuccessfully": "Pomyślnie usunięto {{count}} elementów", + "failedToDeleteItems": "Nie udało się usunąć elementów", + "sudoPasswordRequired": "Wymagane hasło administratora", + "enterSudoPassword": "Wprowadź hasło sudo, aby kontynuować tę operację", + "sudoPassword": "Hasło sudo", + "sudoOperationFailed": "Operacja sudo nie powiodła się", + "sudoAuthFailed": "Uwierzytelnianie sudo nie powiodło się", + "dragFilesToUpload": "Upuść pliki tutaj, aby je przesłać", + "dropToUploadHere": "Upuść, aby przesłać tutaj", + "dropToDownloadHere": "Upuść, aby pobrać tutaj", + "localFiles": "Lokalny", + "showLocalFiles": "Pokaż pliki lokalne (widok podzielony)", + "hideLocalFiles": "Ukryj pliki lokalne", + "localHome": "Folder domowy", + "localPathPlaceholder": "Ścieżka do folderu lokalnego", + "localShowHidden": "Pokaż ukryte pliki", + "localHideHidden": "Ukryj ukryte pliki", + "localRevealInFileManager": "Pokaż w menedżerze plików systemowych", + "localCannotOpenFolder": "Nie można otworzyć folderu", + "localItemCount": "{{count}} elementów", + "localSelectedCount": "{{count}} wybrano", + "back": "Z powrotem", + "forward": "Do przodu", + "up": "W górę", + "kind": "Uprzejmy", + "localUploadingProgress": "Przesyłanie {{current}} z {{total}}", + "localDownloadingProgress": "Pobieranie {{current}} z {{total}}", + "localTransferInFlight": "{{count}} natychmiast", + "localPreparingDownload": "Przygotowywanie do pobrania...", + "localTransferCancelled": "Transfer anulowany", + "localNothingToTransfer": "Nic do przeniesienia", + "localUploadComplete": "Przesłano {{count}} plików", + "localUploadFailed": "Przesyłanie nie powiodło się", + "localUploadPartial": "Przesłano {{done}} plików, {{failed}} nie powiodło się", + "localDownloadComplete": "Pobrano {{count}} plików", + "localDownloadFailed": "Pobieranie nie powiodło się", + "localDownloadPartial": "Pobrano {{done}} plików, {{failed}} nie powiodło się", + "localReplaceExistingPrompt": "W tym folderze istnieje już {{count}} elementów. Czy je zastąpić?", + "localReplace": "Zastępować", + "localSkipExisting": "Pominąć", + "localSkippedExisting": "Pominięto {{count}} istniejących elementów", + "localUnsafeNamesSkipped": "Pominięto {{count}} elementów, których nazw nie można używać na tym komputerze", + "localOpen": "Otwarte", + "localOpenFolder": "Otwórz folder", + "localUploadToRemote": "Prześlij na serwer", + "localUploadToRemoteMany": "Prześlij {{count}} elementów na serwer", + "localMoveToTrash": "Przenieś do kosza", + "localMoveToTrashMany": "Przenieś {{count}} elementów do Kosza", + "localTrashConfirmSingle": "Przenieść „{{name}}” do Kosza?", + "localTrashConfirmFolder": "Czy przenieść folder „{{name}}” i całą jego zawartość do Kosza?", + "localTrashConfirmMany": "Przenieść {{count}} elementów do Kosza?", + "localTrashed": "Przeniesiono {{count}} elementów do Kosza", + "localTrashFailed": "Nie można przenieść do Kosza", + "localRenameFailed": "Zmiana nazwy nie powiodła się", + "localCreateFailed": "Nie można utworzyć elementu", + "goToParentFolder": "Przejdź do folderu nadrzędnego", + "resizeSidebar": "Przeciągnij, aby zmienić rozmiar paska bocznego (kliknij dwukrotnie, aby zresetować)", + "resizeLocalPane": "Przeciągnij, aby zmienić rozmiar panelu lokalnego (kliknij dwukrotnie, aby zresetować)", + "columns": "Kolumny", + "columnsHint": "Kliknij prawym przyciskiem myszy, aby wybrać kolumny", + "emptyFolder": "Ten folder jest pusty", + "searchFiles": "Wyszukaj pliki...", + "upload": "Prześlij", + "selectHostToStart": "Wybierz hosta, aby rozpocząć zarządzanie plikami", + "sshRequiredForFileManager": "Menedżer plików wymaga SSH. Ten host nie ma włączonego SSH.", + "failedToConnect": "Nie udało się połączyć z SSH", + "failedToLoadDirectory": "Nie udało się załadować katalogu", + "noSSHConnection": "Brak dostępnego połączenia SSH", + "copy": "Kopiuj", + "cut": "Wytnij", + "paste": "Wklej", + "copyPath": "Kopiuj ścieżkę", + "copyPaths": "Kopiuj ścieżki", + "delete": "Usuń", + "properties": "Właściwości", + "refresh": "Odśwież", + "downloadFiles": "Pobierz {{count}} plików do przeglądarki", + "copyFiles": "Skopiuj {{count}} elementów", + "cutFiles": "Wytnij {{count}} elementów", + "deleteFiles": "Usuń {{count}} elementów", + "filesCopiedToClipboard": "{{count}} elementów skopiowano do schowka", + "filesCutToClipboard": "{{count}} elementów wycięto do schowka", + "pathCopiedToClipboard": "Ścieżka skopiowana do schowka", + "pathsCopiedToClipboard": "{{count}} ścieżek skopiowano do schowka", + "failedToCopyPath": "Nie udało się skopiować ścieżki do schowka", + "copyFolderLink": "Kopiuj link do folderu", + "copyCurrentFolderLink": "Kopiuj link do bieżącego folderu", + "folderLinkCopied": "Link do folderu skopiowany do schowka", + "failedToCopyFolderLink": "Nie udało się skopiować linku do folderu", + "movedItems": "Przeniesiono {{count}} elementów", + "failedToDeleteItem": "Nie udało się usunąć elementu", + "itemRenamedSuccessfully": "Zmieniono nazwę: {{type}}", + "failedToRenameItem": "Nie udało się zmienić nazwy elementu", + "download": "Pobierz", + "openExternalEditor": "Otwórz zewnętrznie", + "chooseExternalEditor": "Wybierz edytor zewnętrzny", + "externalEditorSelected": "Wybrano edytor zewnętrzny", + "externalEditorDesktopOnly": "Edytor zewnętrzny jest dostępny tylko w aplikacji desktopowej", + "externalEditorOpened": "Otwarto w edytorze zewnętrznym. Zapisanie tam spowoduje przesłanie zmian z powrotem na serwer.", + "failedToOpenExternalEditor": "Nie udało się otworzyć edytora zewnętrznego", + "failedToSelectExternalEditor": "Nie udało się wybrać edytora zewnętrznego", + "permissions": "Uprawnienia", + "size": "Rozmiar", + "modified": "Zmodyfikowano", + "path": "Ścieżka", + "confirmDelete": "Czy na pewno chcesz usunąć {{name}}?", + "permissionDenied": "Brak uprawnień", + "serverError": "Błąd serwera", + "fileSavedSuccessfully": "Plik zapisany pomyślnie", + "failedToSaveFile": "Nie udało się zapisać pliku", + "confirmDeleteSingleItem": "Czy na pewno chcesz trwale usunąć „{{name}}”?", + "confirmDeleteMultipleItems": "Czy na pewno chcesz trwale usunąć {{count}} elementów?", + "confirmDeleteMultipleItemsWithFolders": "Czy na pewno chcesz trwale usunąć {{count}} elementów? Dotyczy to również folderów i ich zawartości.", + "confirmDeleteFolder": "Czy na pewno chcesz trwale usunąć folder „{{name}}” i całą jego zawartość?", + "permanentDeleteWarning": "Tej czynności nie można cofnąć. Elementy zostaną trwale usunięte z serwera.", + "moveToTrashWarning": "Elementy zostaną przeniesione do Kosza Termix i będzie je można przywrócić przed upływem okresu przechowywania.", + "itemsMovedToTrash": "{{count}} elementów przeniesiono do Kosza", + "trash": "Kosz", + "trashDescription": "Usunięte elementy są przechowywane na tym hoście przez {{days}} dni.", + "trashLoadFailed": "Nie udało się załadować Kosza", + "trashEmpty": "Kosz jest pusty", + "trashRestore": "Przywróć", + "trashRestored": "Przywrócono {{name}}", + "trashRestoreFailed": "Nie udało się przywrócić elementu. Oryginalna ścieżka może już istnieć.", + "trashDeletePermanently": "Usuń trwale", + "trashDeleteConfirm": "Czy trwale usunąć {{name}}? Tego nie można cofnąć.", + "trashPermanentlyDeleted": "Trwale usunięto {{name}}", + "trashDeleteFailed": "Nie udało się trwale usunąć elementu", + "trashEmptyAction": "Opróżnij kosz", + "trashEmptyConfirm": "Trwale usunąć wszystkie elementy z Kosza? Tego nie da się cofnąć.", + "trashEmptied": "Kosz opróżniony", + "trashRetention": "Automatyczne czyszczenie po (dniach)", + "trashRetentionSaved": "Zaktualizowano czas przechowywania w Koszu", + "trashRetentionFailed": "Nie udało się zaktualizować czasu przechowywania w Koszu", + "trashUnavailableConfirm": "Tego elementu nie można przenieść do Kosza, zazwyczaj dlatego, że znajduje się w innym systemie plików lub wymaga podwyższonych uprawnień. Czy zamiast tego usunąć go trwale?", + "recent": "Ostatnie", + "pinned": "Przypięte", + "folderShortcuts": "Skróty folderów", + "failedToReconnectSSH": "Nie udało się ponownie połączyć z sesją SSH", + "openTerminalHere": "Otwórz terminal tutaj", + "run": "Uruchom", + "openTerminalInFolder": "Otwórz terminal w tym folderze", + "openTerminalInFileLocation": "Otwórz terminal w lokalizacji pliku", + "runningFile": "Wykonywanie - {{file}}", + "onlyRunExecutableFiles": "Można uruchamiać tylko pliki wykonywalne", + "directories": "Katalogi", + "removedFromRecentFiles": "Usunięto „{{name}}” z ostatnich plików", + "removeFailed": "Nie udało się usunąć", + "unpinnedSuccessfully": "Pomyślnie odpięto „{{name}}”", + "unpinFailed": "Odpięcie nie powiodło się", + "removedShortcut": "Usunięto skrót „{{name}}”", + "removeShortcutFailed": "Nie udało się usunąć skrótu", + "clearedAllRecentFiles": "Wyczyszczono wszystkie ostatnie pliki", + "clearFailed": "Nie udało się wyczyścić", + "removeFromRecentFiles": "Usuń z ostatnich plików", + "clearAllRecentFiles": "Wyczyść wszystkie ostatnie pliki", + "unpinFile": "Odepnij plik", + "removeShortcut": "Usuń skrót", + "pinFile": "Przypnij plik", + "addToShortcuts": "Dodaj do skrótów", + "pasteFailed": "Wklejenie nie powiodło się", + "noUndoableActions": "Brak działań do cofnięcia", + "undoCopySuccess": "Cofnięto kopiowanie: usunięto {{count}} skopiowanych plików", + "undoCopyFailedDelete": "Nie udało się cofnąć: Nie udało się usunąć żadnych skopiowanych plików", + "undoCopyFailedNoInfo": "Nie udało się cofnąć: Nie można znaleźć informacji o skopiowanym pliku", + "undoMoveSuccess": "Cofnięto przenoszenie: przeniesiono {{count}} plików z powrotem do pierwotnej lokalizacji", + "undoMoveFailedMove": "Nie udało się cofnąć: Nie można przenieść żadnych plików z powrotem", + "undoMoveFailedNoInfo": "Nie udało się cofnąć: Nie można znaleźć informacji o przeniesionym pliku", + "undoDeleteNotSupported": "Operacji usunięcia nie można cofnąć: pliki zostały trwale usunięte z serwera", + "undoTypeNotSupported": "Nieobsługiwany typ operacji cofania", + "undoOperationFailed": "Operacja cofania nie powiodła się", + "unknownError": "Nieznany błąd", + "confirm": "Potwierdź", + "find": "Znajdź...", + "replace": "Zamień", + "downloadInstead": "Pobierz zamiast tego", + "keyboardShortcuts": "Skróty klawiaturowe", + "searchAndReplace": "Wyszukaj i zamień", + "editing": "Edycja", + "search": "Szukaj", + "findNext": "Znajdź następny", + "findPrevious": "Znajdź poprzednie", + "save": "Zapisz", + "selectAll": "Zaznacz wszystko", + "undo": "Cofnij", + "redo": "Ponów", + "moveLineUp": "Przesuń linię w górę", + "moveLineDown": "Przesuń linię w dół", + "toggleComment": "Przełącz komentarz", + "autoComplete": "Automatyczne uzupełnianie", + "imageLoadError": "Nie udało się załadować obrazu", + "startTyping": "Zacznij pisać...", + "unknownSize": "Nieznany rozmiar", + "fileIsEmpty": "Plik jest pusty", + "largeFileWarning": "Ostrzeżenie o dużym pliku", + "largeFileWarningDesc": "Ten plik ma rozmiar {{size}}, co może powodować problemy z wydajnością przy otwarciu jako tekst.", + "fileNotFoundAndRemoved": "Plik „{{name}}” nie został znaleziony i został usunięty z ostatnich/przypiętych plików", + "failedToLoadFile": "Nie udało się załadować pliku: {{error}}", + "serverErrorOccurred": "Wystąpił błąd serwera. Spróbuj ponownie później.", + "autoSaveFailed": "Automatyczne zapisywanie nie powiodło się", + "fileAutoSaved": "Plik zapisany automatycznie", + "moveFileFailed": "Nie udało się przenieść {{name}}", + "moveOperationFailed": "Operacja przenoszenia nie powiodła się", + "canOnlyCompareFiles": "Można porównać tylko dwa pliki", + "comparingFiles": "Porównywanie plików: {{file1}} i {{file2}}", + "dragFailed": "Operacja przeciągania nie powiodła się", + "filePinnedSuccessfully": "Plik „{{name}}” został pomyślnie przypięty", + "pinFileFailed": "Nie udało się przypiąć pliku", + "fileUnpinnedSuccessfully": "Plik „{{name}}” został pomyślnie odpięty", + "unpinFileFailed": "Nie udało się odpiąć pliku", + "shortcutAddedSuccessfully": "Skrót folderu „{{name}}” został pomyślnie dodany", + "addShortcutFailed": "Nie udało się dodać skrótu", + "operationCompletedSuccessfully": "{{operation}} {{count}} elementów pomyślnie", + "operationCompleted": "{{operation}} {{count}} elementów", + "downloadFileSuccess": "Plik {{name}} został pomyślnie pobrany", + "downloadFileFailed": "Pobieranie nie powiodło się", + "downloadingFile": "Pobieranie {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Przenieś do {{name}}", + "diffCompareWith": "Porównaj z {{name}}", + "dragOutsideToDownload": "Przeciągnij poza okno, aby pobrać ({{count}} plików)", + "newFolderDefault": "NowyFolder", + "newFileDefault": "NowyPlik.txt", + "successfullyMovedItems": "Pomyślnie przeniesiono {{count}} elementów do {{target}}", + "move": "Przenieś", + "searchInFile": "Wyszukaj w pliku (Ctrl+F)", + "showKeyboardShortcuts": "Pokaż skróty klawiaturowe", + "decreaseFontSize": "Zmniejsz rozmiar czcionki", + "increaseFontSize": "Zwiększ rozmiar czcionki", + "startWritingMarkdown": "Zacznij pisać treść w Markdown...", + "loadingFileComparison": "Ładowanie porównania plików...", + "reload": "Przeładuj", + "compare": "Porównaj", + "sideBySide": "Obok siebie", + "inline": "W linii", + "fileComparison": "Porównanie plików: {{file1}} vs {{file2}}", + "fileTooLarge": "Plik jest za duży: {{error}}", + "sshConnectionFailed": "Połączenie SSH nie powiodło się. Sprawdź połączenie z {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Nie udało się załadować pliku: {{error}}", + "connecting": "Łączenie...", + "connectedSuccessfully": "Połączono pomyślnie", + "totpVerificationFailed": "Weryfikacja TOTP nie powiodła się", + "warpgateVerificationFailed": "Uwierzytelnianie Warpgate nie powiodło się", + "authenticationFailed": "Uwierzytelnianie nie powiodło się", + "incorrectPassphrase": "Nieprawidłowe hasło klucza. Spróbuj ponownie.", + "verificationCodePrompt": "Kod weryfikacyjny:", + "changePermissions": "Zmień uprawnienia", + "currentPermissions": "Aktualne uprawnienia", + "owner": "Właściciel", + "group": "Grupa", + "others": "Inni", + "read": "Odczyt", + "write": "Zapis", + "execute": "Wykonywanie", + "permissionsChangedSuccessfully": "Uprawnienia zostały pomyślnie zmienione", + "failedToChangePermissions": "Nie udało się zmienić uprawnień", + "name": "Nazwa", + "sortByName": "Nazwa", + "sortByDate": "Data modyfikacji", + "sortBySize": "Rozmiar", + "ascending": "Rosnąco", + "descending": "Malejąco", + "root": "Katalog główny", + "new": "Nowy", + "sortBy": "Sortuj według", + "items": "Elementy", + "selected": "Zaznaczone", + "editor": "Edytor", + "octal": "Ósemkowy", + "storage": "Magazyn", + "disk": "Dysk", + "used": "Użyte", + "of": "z", + "toggleSidebar": "Przełącz pasek boczny", + "compactLayout": "Użyj kompaktowego układu", + "comfortableLayout": "Użyj wygodnego układu", + "cannotLoadPdf": "Nie można załadować pliku PDF", + "pdfLoadError": "Wystąpił błąd podczas ładowania pliku PDF.", + "loadingPdf": "Ładowanie pliku PDF...", + "loadingPage": "Ładowanie strony..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Przesyłanie plików pomiędzy dwoma hostami SSH.", + "sourceServer": "Serwer źródłowy", + "destinationServer": "Serwer docelowy", + "selectHost": "Wybierz hosta", + "noHostSelected": "Nie wybrano hosta", + "move": "Przenosić", + "copy": "Kopia", + "upload": "Wgrywać", + "itemsSelected": "{{count}} wybrano element", + "itemsSelected_other": "Wybrano {{count}} elementów", + "ready": "Gotowy", + "loadingHosts": "Ładowanie hostów...", + "name": "Nazwa", + "modified": "Zmodyfikowany", + "size": "Rozmiar", + "noFiles": "Brak plików", + "open": "Otwarte", + "up": "W górę", + "refresh": "Odświeżać", + "copyToTarget": "Kopiuj do katalogu docelowego", + "rename": "Przemianować", + "delete": "Usuwać", + "editPermissions": "Edytuj uprawnienia", + "createNewFolder": "Utwórz nowy folder", + "deleteSelectedItems": "Usuń wybrane elementy", + "deleteConfirm": "Usunąć {{count}} wybrany element?", + "deleteConfirm_other": "Usunąć {{count}} wybranych elementów?", + "cancel": "Anulować", + "save": "Ratować", + "folderCreated": "Utworzono folder", + "itemRenamed": "Zmieniono nazwę elementu", + "actionFailed": "Akcja nie powiodła się", + "itemsDeleted": "{{count}} element usunięty", + "itemsDeleted_other": "Usunięto {{count}} elementów", + "deleteFailed": "Usunięcie nie powiodło się", + "permissionsUpdated": "Zaktualizowano uprawnienia", + "noRemoteSessionConnected": "Brak połączonych sesji zdalnych", + "failedToListFiles": "Nie udało się wyświetlić listy plików", + "failedToRefresh": "Nie udało się odświeżyć", + "destinationInsideSource": "Miejsce docelowe nie może znajdować się wewnątrz wybranej ścieżki źródłowej.", + "sameHostNoOp": "Źródło i cel znajdują się w tym samym folderze." + }, + "hosts": { + "fileManager": "Menedżer plików", + "tabFiles": "Pliki", + "enableFileManagerMonitor": "Włącz menedżer plików", + "enableFileManagerMonitorDesc": "Przeglądaj i zarządzaj plikami na tym hoście przez SFTP", + "scpLegacyLabel": "Tryb SCP Legacy", + "scpLegacyDesc": "Użyj starszej metody transferu plików dla serwerów, które nie obsługują podsystemu SFTP (np. wbudowanych lub minimalnych serwerów SSH).", + "defaultPathLabel": "Domyślna ścieżka", + "fileManagerPathHint": "Katalog, który zostanie otwarty po uruchomieniu menedżera plików dla tego hosta.", + "fileManagerUrlCopied": "Skopiowano adres URL menedżera plików", + "copyFileManagerUrlAction": "Kopiuj adres URL menedżera plików" + }, + "settings": { + "host": { + "enable": { + "label": "Włącz menedżer plików" + }, + "defaultPath": { + "label": "Domyślna ścieżka" + }, + "scpLegacy": { + "label": "Tryb SCP Legacy" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Pliki" + } +} diff --git a/plugins/file-manager/locales/translated/pt_BR.json b/plugins/file-manager/locales/translated/pt_BR.json new file mode 100644 index 000000000..f5cf7a188 --- /dev/null +++ b/plugins/file-manager/locales/translated/pt_BR.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Nenhum host selecionado", + "initializingEditor": "Inicializando o editor...", + "file": "Arquivo", + "folder": "Pasta", + "uploadFile": "Carregar arquivo", + "downloadFile": "Download", + "extractArchive": "Extrair arquivo", + "extractingArchive": "Extraindo {{name}}...", + "archiveExtractedSuccessfully": "{{name}} extraído com sucesso", + "extractFailed": "A extração falhou", + "compressFile": "Compactar arquivo", + "compressFiles": "Comprimir arquivos", + "compressFilesDesc": "Comprima {{count}} itens em um arquivo", + "archiveName": "Nome do arquivo", + "enterArchiveName": "Digite o nome do arquivo...", + "compressionFormat": "Formato de compressão", + "selectedFiles": "Arquivos selecionados", + "andMoreFiles": "e {{count}} mais...", + "compress": "Compressa", + "compressingFiles": "Comprimir {{count}} itens em {{name}}...", + "filesCompressedSuccessfully": "{{name}} criado com sucesso", + "compressFailed": "A compressão falhou", + "edit": "Editar", + "preview": "Pré-visualização", + "previous": "Anterior", + "next": "Próximo", + "pageXOfY": "Página {{current}} de {{total}}", + "zoomOut": "Diminuir o zoom", + "zoomIn": "Ampliar", + "newFile": "Novo arquivo", + "newFolder": "Nova pasta", + "rename": "Renomear", + "uploading": "Carregando...", + "uploadingFile": "Carregando {{name}}...", + "fileName": "Nome do arquivo", + "folderName": "Nome da pasta", + "fileUploadedSuccessfully": "Arquivo \"{{name}}\" enviado com sucesso", + "failedToUploadFile": "Falha ao carregar o arquivo", + "uploadingFolderFiles": "Enviando {{count}} arquivo(s)...", + "uploadedFolderFiles": "Arquivo(s) carregado(s) com sucesso {{count}}", + "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", + "fileDownloadedSuccessfully": "Arquivo \"{{name}}\" baixado com sucesso", + "failedToDownloadFile": "Falha ao baixar o arquivo", + "fileCreatedSuccessfully": "Arquivo \"{{name}}\" criado com sucesso", + "folderCreatedSuccessfully": "Pasta \"{{name}}\" criada com sucesso", + "failedToCreateItem": "Falha ao criar o item", + "operationFailed": "A operação {{operation}} falhou para {{name}}: {{error}}", + "failedToResolveSymlink": "Falha ao resolver o link simbólico", + "itemsDeletedSuccessfully": "{{count}} itens excluídos com sucesso", + "failedToDeleteItems": "Falha ao excluir itens", + "sudoPasswordRequired": "Senha de administrador necessária", + "enterSudoPassword": "Digite a senha de sudo para continuar esta operação.", + "sudoPassword": "Senha de sudo", + "sudoOperationFailed": "A operação sudo falhou", + "sudoAuthFailed": "Falha na autenticação sudo", + "dragFilesToUpload": "Arraste e solte os arquivos aqui para fazer o upload.", + "dropToUploadHere": "Arraste e faça o upload aqui.", + "dropToDownloadHere": "Faça o download aqui.", + "localFiles": "Local", + "showLocalFiles": "Exibir arquivos locais (visualização dividida)", + "hideLocalFiles": "Ocultar arquivos locais", + "localHome": "Pasta inicial", + "localPathPlaceholder": "Caminho da pasta local", + "localShowHidden": "Mostrar arquivos ocultos", + "localHideHidden": "Ocultar arquivos ocultos", + "localRevealInFileManager": "Exibir no gerenciador de arquivos do sistema", + "localCannotOpenFolder": "Não é possível abrir a pasta", + "localItemCount": "{{count}} itens", + "localSelectedCount": "{{count}} selecionado", + "back": "Voltar", + "forward": "Avançar", + "up": "Acima", + "kind": "Tipo", + "localUploadingProgress": "Enviando {{current}} de {{total}}", + "localDownloadingProgress": "Baixando {{current}} de {{total}}", + "localTransferInFlight": "{{count}} imediatamente", + "localPreparingDownload": "Preparando o download...", + "localTransferCancelled": "Transferência cancelada", + "localNothingToTransfer": "Nada a transferir", + "localUploadComplete": "Uploaded {{count}} file(s)", + "localUploadFailed": "Falha no upload", + "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", + "localDownloadComplete": "Arquivo(s) baixado(s) {{count}}", + "localDownloadFailed": "Falha no download", + "localDownloadPartial": "Downloaded {{done}} file(s), {{failed}} failed", + "localReplaceExistingPrompt": "{{count}} Já existem itens nesta pasta. Deseja substituí-los?", + "localReplace": "Substituir", + "localSkipExisting": "Pular", + "localSkippedExisting": "Ignorou {{count}} item(ns) existente(s)", + "localUnsafeNamesSkipped": "Ignorado {{count}} item(ns) cujos nomes não podem ser usados neste computador", + "localOpen": "Abrir", + "localOpenFolder": "Abra a pasta", + "localUploadToRemote": "Enviar para o servidor", + "localUploadToRemoteMany": "Enviar {{count}} itens para o servidor", + "localMoveToTrash": "Mover para a Lixeira", + "localMoveToTrashMany": "Mova {{count}} itens para a Lixeira", + "localTrashConfirmSingle": "Mover \"{{name}}\" para a Lixeira?", + "localTrashConfirmFolder": "Mover a pasta \"{{name}}\" e todo o seu conteúdo para a Lixeira?", + "localTrashConfirmMany": "Mover {{count}} itens para a Lixeira?", + "localTrashed": "Movidos {{count}} itens para a Lixeira", + "localTrashFailed": "Não foi possível mover para a Lixeira", + "localRenameFailed": "A renomeação falhou", + "localCreateFailed": "Não foi possível criar o item.", + "goToParentFolder": "Acesse a pasta principal", + "resizeSidebar": "Arraste para redimensionar a barra lateral (clique duas vezes para redefinir).", + "resizeLocalPane": "Arraste para redimensionar o painel local (clique duas vezes para redefinir).", + "columns": "Colunas", + "columnsHint": "Clique com o botão direito do mouse para escolher as colunas.", + "emptyFolder": "Esta pasta está vazia", + "searchFiles": "Pesquisar arquivos...", + "upload": "Carregar", + "selectHostToStart": "Selecione um host para iniciar o gerenciamento de arquivos.", + "sshRequiredForFileManager": "O gerenciador de arquivos requer SSH. Este host não tem o SSH habilitado.", + "failedToConnect": "Falha ao conectar-se ao SSH", + "failedToLoadDirectory": "Falha ao carregar o diretório", + "noSSHConnection": "Nenhuma conexão SSH disponível", + "copy": "Cópia", + "cut": "Corte", + "paste": "Colar", + "copyPath": "Copiar caminho", + "copyPaths": "Copiar caminhos", + "delete": "Excluir", + "properties": "Propriedades", + "refresh": "Atualizar", + "downloadFiles": "Baixar {{count}} arquivos para o navegador", + "copyFiles": "Copiar {{count}} itens", + "cutFiles": "Recortar {{count}} itens", + "deleteFiles": "Excluir {{count}} itens", + "filesCopiedToClipboard": "{{count}} itens copiados para a área de transferência", + "filesCutToClipboard": "{{count}} itens copiados para a área de transferência", + "pathCopiedToClipboard": "Caminho copiado para a área de transferência", + "pathsCopiedToClipboard": "{{count}} caminhos copiados para a área de transferência", + "failedToCopyPath": "Falha ao copiar o caminho para a área de transferência", + "copyFolderLink": "Copiar link para a pasta", + "copyCurrentFolderLink": "Copiar link para a pasta atual", + "folderLinkCopied": "Link da pasta copiado para a área de transferência", + "failedToCopyFolderLink": "Falha ao copiar o link da pasta", + "movedItems": "Itens movidos {{count}}", + "failedToDeleteItem": "Falha ao excluir o item", + "itemRenamedSuccessfully": "{{type}} renomeado com sucesso", + "failedToRenameItem": "Falha ao renomear o item", + "download": "Download", + "openExternalEditor": "Abrir externamente", + "chooseExternalEditor": "Escolha um editor externo.", + "externalEditorSelected": "Editor externo selecionado", + "externalEditorDesktopOnly": "O editor externo está disponível apenas no aplicativo para desktop.", + "externalEditorOpened": "Aberto em um editor externo. Salvar aqui enviará as alterações de volta para o servidor.", + "failedToOpenExternalEditor": "Não foi possível abrir o editor externo.", + "failedToSelectExternalEditor": "Não foi possível selecionar o editor externo.", + "permissions": "Permissões", + "size": "Tamanho", + "modified": "Modificado", + "path": "Caminho", + "confirmDelete": "Tem certeza de que deseja excluir {{name}}?", + "permissionDenied": "Permissão negada", + "serverError": "Erro do servidor", + "fileSavedSuccessfully": "Arquivo salvo com sucesso", + "failedToSaveFile": "Falha ao salvar o arquivo", + "confirmDeleteSingleItem": "Tem certeza de que deseja excluir permanentemente \"{{name}}\"?", + "confirmDeleteMultipleItems": "Tem certeza de que deseja excluir permanentemente {{count}} itens?", + "confirmDeleteMultipleItemsWithFolders": "Tem certeza de que deseja excluir permanentemente {{count}} itens? Isso inclui pastas e seus conteúdos.", + "confirmDeleteFolder": "Tem certeza de que deseja excluir permanentemente a pasta \"{{name}}\" e todo o seu conteúdo?", + "permanentDeleteWarning": "Esta ação não pode ser desfeita. O(s) item(ns) será(ão) excluído(s) permanentemente do servidor.", + "moveToTrashWarning": "O(s) item(ns) será(ão) movido(s) para a Lixeira Termix e poderá(ão) ser restaurado(s) antes do término do período de retenção.", + "itemsMovedToTrash": "{{count}} item(ns) movido(s) para a Lixeira", + "trash": "Lixo", + "trashDescription": "Os itens excluídos são retidos por {{days}} dia(s) neste host.", + "trashLoadFailed": "Falha ao carregar a Lixeira", + "trashEmpty": "A lixeira está vazia.", + "trashRestore": "Restaurar", + "trashRestored": "Restaurado {{name}}", + "trashRestoreFailed": "Falha ao restaurar o item. O caminho original pode já existir.", + "trashDeletePermanently": "Excluir permanentemente", + "trashDeleteConfirm": "Excluir permanentemente {{name}}? Esta ação não pode ser desfeita.", + "trashPermanentlyDeleted": "Excluído permanentemente {{name}}", + "trashDeleteFailed": "Falha ao excluir o item permanentemente", + "trashEmptyAction": "Esvaziar o lixo", + "trashEmptyConfirm": "Excluir permanentemente todos os itens da Lixeira? Esta ação é irreversível.", + "trashEmptied": "Lixo esvaziado", + "trashRetention": "Limpeza automática após (dias)", + "trashRetentionSaved": "Retenção de lixo atualizada", + "trashRetentionFailed": "Falha na atualização da retenção de lixo", + "trashUnavailableConfirm": "Este item não pode ser movido para a Lixeira, geralmente porque está em outro sistema de arquivos ou requer permissões elevadas. Deseja excluí-lo permanentemente?", + "recent": "Recente", + "pinned": "Fixado", + "folderShortcuts": "Atalhos de pastas", + "failedToReconnectSSH": "Falha ao reconectar a sessão SSH", + "openTerminalHere": "Abra o terminal aqui", + "run": "Correr", + "openTerminalInFolder": "Abra o terminal nesta pasta.", + "openTerminalInFileLocation": "Abra o terminal no local do arquivo.", + "runningFile": "Correndo - {{file}}", + "onlyRunExecutableFiles": "Só é possível executar arquivos executáveis.", + "directories": "Diretórios", + "removedFromRecentFiles": "Removemos \"{{name}}\" dos arquivos recentes", + "removeFailed": "Remover falha", + "unpinnedSuccessfully": "Desfixado \"{{name}}\" com sucesso", + "unpinFailed": "Desafixar falhou", + "removedShortcut": "Atalho removido \"{{name}}\"", + "removeShortcutFailed": "A remoção do atalho falhou", + "clearedAllRecentFiles": "Apaguei todos os arquivos recentes.", + "clearFailed": "Limpar falhou", + "removeFromRecentFiles": "Remover dos arquivos recentes", + "clearAllRecentFiles": "Limpar todos os arquivos recentes", + "unpinFile": "Desafixar arquivo", + "removeShortcut": "Remover atalho", + "pinFile": "Arquivo PIN", + "addToShortcuts": "Adicionar aos atalhos", + "pasteFailed": "A colagem falhou", + "noUndoableActions": "Ações não podem ser desfeitas", + "undoCopySuccess": "Operação de cópia desfeita: Arquivos copiados excluídos {{count}}", + "undoCopyFailedDelete": "Falha ao desfazer: Não foi possível excluir nenhum arquivo copiado.", + "undoCopyFailedNoInfo": "A ação de desfazer falhou: Não foi possível encontrar as informações do arquivo copiado.", + "undoMoveSuccess": "Operação de movimentação desfeita: {{count}} arquivos foram movidos de volta para o local original.", + "undoMoveFailedMove": "Falha ao desfazer: Não foi possível mover nenhum arquivo de volta.", + "undoMoveFailedNoInfo": "A ação de desfazer falhou: Não foi possível encontrar as informações do arquivo movido.", + "undoDeleteNotSupported": "A operação de exclusão não pode ser desfeita: os arquivos foram excluídos permanentemente do servidor.", + "undoTypeNotSupported": "Tipo de operação de desfazer não suportado", + "undoOperationFailed": "A operação de desfazer falhou", + "unknownError": "Erro desconhecido", + "confirm": "Confirmar", + "find": "Encontrar...", + "replace": "Substituir", + "downloadInstead": "Baixe em vez de baixar.", + "keyboardShortcuts": "Atalhos de teclado", + "searchAndReplace": "Localizar e substituir", + "editing": "Edição", + "search": "Procurar", + "findNext": "Encontre a seguir", + "findPrevious": "Encontrar Anterior", + "save": "Salvar", + "selectAll": "Selecionar tudo", + "undo": "Desfazer", + "redo": "Refazer", + "moveLineUp": "Mova a fila para cima", + "moveLineDown": "Deslizar a linha para baixo", + "toggleComment": "Alternar comentário", + "autoComplete": "Preenchimento automático", + "imageLoadError": "Falha ao carregar a imagem", + "startTyping": "Comece a digitar...", + "unknownSize": "Tamanho desconhecido", + "fileIsEmpty": "O arquivo está vazio", + "largeFileWarning": "Aviso de Arquivo Grande", + "largeFileWarningDesc": "Este arquivo tem {{size}} de tamanho, o que pode causar problemas de desempenho ao ser aberto como texto.", + "fileNotFoundAndRemoved": "O arquivo \"{{name}}\" não foi encontrado e foi removido dos arquivos recentes/fixados", + "failedToLoadFile": "Falha ao carregar o arquivo: {{error}}", + "serverErrorOccurred": "Ocorreu um erro no servidor. Tente novamente mais tarde.", + "autoSaveFailed": "O salvamento automático falhou", + "fileAutoSaved": "Arquivo salvo automaticamente", + "moveFileFailed": "Falha ao mover {{name}}", + "moveOperationFailed": "A operação de movimentação falhou", + "canOnlyCompareFiles": "Só é possível comparar dois arquivos.", + "comparingFiles": "Comparando arquivos: {{file1}} e {{file2}}", + "dragFailed": "A operação de arrastar falhou", + "filePinnedSuccessfully": "Arquivo \"{{name}}\" fixado com sucesso", + "pinFileFailed": "Falha ao fixar o arquivo", + "fileUnpinnedSuccessfully": "Arquivo \"{{name}}\" desafixado com sucesso", + "unpinFileFailed": "Falha ao desafixar o arquivo", + "shortcutAddedSuccessfully": "Atalho de pasta \"{{name}}\" adicionado com sucesso", + "addShortcutFailed": "Falha ao adicionar atalho", + "operationCompletedSuccessfully": "{{operation}} {{count}} itens com sucesso", + "operationCompleted": "{{operation}} {{count}} itens", + "downloadFileSuccess": "Arquivo {{name}} baixado com sucesso", + "downloadFileFailed": "Falha no download", + "downloadingFile": "Baixando {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Mover para {{name}}", + "diffCompareWith": "Diff compare com {{name}}", + "dragOutsideToDownload": "Arraste para fora da janela para baixar ({{count}} arquivos)", + "newFolderDefault": "Nova pasta", + "newFileDefault": "NovoArquivo.txt", + "successfullyMovedItems": "Movidos com sucesso {{count}} itens para {{target}}", + "move": "Mover", + "searchInFile": "Pesquisar no arquivo (Ctrl+F)", + "showKeyboardShortcuts": "Mostrar atalhos de teclado", + "decreaseFontSize": "Diminuir o tamanho da fonte", + "increaseFontSize": "Aumentar o tamanho da fonte", + "startWritingMarkdown": "Comece a escrever seu conteúdo em Markdown...", + "loadingFileComparison": "Carregando comparação de arquivos...", + "reload": "Recarregar", + "compare": "Comparar", + "sideBySide": "Lado a lado", + "inline": "Em linha", + "fileComparison": "Comparação de arquivos: {{file1}} vs {{file2}}", + "fileTooLarge": "Arquivo muito grande: {{error}}", + "sshConnectionFailed": "A conexão SSH falhou. Verifique sua conexão com {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Falha ao carregar o arquivo: {{error}}", + "connecting": "Conectando...", + "connectedSuccessfully": "Conexão estabelecida com sucesso.", + "totpVerificationFailed": "A verificação TOTP falhou.", + "warpgateVerificationFailed": "A autenticação do portal de guerra falhou.", + "authenticationFailed": "A autenticação falhou.", + "incorrectPassphrase": "Senha incorreta. Tente novamente.", + "verificationCodePrompt": "Código de verificação:", + "changePermissions": "Alterar permissões", + "currentPermissions": "Permissões atuais", + "owner": "Proprietário", + "group": "Grupo", + "others": "Outros", + "read": "Ler", + "write": "Escrever", + "execute": "Executar", + "permissionsChangedSuccessfully": "Permissões alteradas com sucesso", + "failedToChangePermissions": "Falha ao alterar as permissões", + "name": "Nome", + "sortByName": "Nome", + "sortByDate": "Data de modificação", + "sortBySize": "Tamanho", + "ascending": "Ascendente", + "descending": "Descendente", + "root": "Raiz", + "new": "Novo", + "sortBy": "Ordenar por", + "items": "Unid", + "selected": "Selecionado", + "editor": "Editor", + "octal": "Octal", + "storage": "Armazenar", + "disk": "Disco", + "used": "Usado", + "of": "de", + "toggleSidebar": "Alternar barra lateral", + "compactLayout": "Utilize um layout compacto.", + "comfortableLayout": "Use um layout confortável", + "cannotLoadPdf": "Não foi possível carregar o PDF.", + "pdfLoadError": "Ocorreu um erro ao carregar este arquivo PDF.", + "loadingPdf": "Carregando PDF...", + "loadingPage": "Carregando página..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Transferir arquivos entre dois hosts SSH.", + "sourceServer": "Servidor de origem", + "destinationServer": "Servidor de destino", + "selectHost": "Selecione o host", + "noHostSelected": "Nenhum host selecionado", + "move": "Mover", + "copy": "Cópia", + "upload": "Carregar", + "itemsSelected": "{{count}} item selecionado", + "itemsSelected_other": "{{count}} itens selecionados", + "ready": "Preparar", + "loadingHosts": "Carregando hosts...", + "name": "Nome", + "modified": "Modificado", + "size": "Tamanho", + "noFiles": "Nenhum arquivo", + "open": "Abrir", + "up": "Acima", + "refresh": "Atualizar", + "copyToTarget": "Copiar para o diretório de destino", + "rename": "Renomear", + "delete": "Excluir", + "editPermissions": "Editar permissões", + "createNewFolder": "Criar nova pasta", + "deleteSelectedItems": "Excluir itens selecionados", + "deleteConfirm": "Delete {{count}} selected item?", + "deleteConfirm_other": "Excluir {{count}} itens selecionados?", + "cancel": "Cancelar", + "save": "Salvar", + "folderCreated": "Pasta criada", + "itemRenamed": "Item renomeado", + "actionFailed": "A ação falhou", + "itemsDeleted": "{{count}} item excluído", + "itemsDeleted_other": "{{count}} itens excluídos", + "deleteFailed": "Falha ao excluir", + "permissionsUpdated": "Permissões atualizadas", + "noRemoteSessionConnected": "Nenhuma sessão remota conectada.", + "failedToListFiles": "Falha ao listar os arquivos", + "failedToRefresh": "Falha ao atualizar", + "destinationInsideSource": "O destino não pode estar dentro do caminho de origem selecionado.", + "sameHostNoOp": "A origem e o destino são a mesma pasta." + }, + "hosts": { + "fileManager": "Gerenciador de arquivos", + "tabFiles": "Arquivos", + "enableFileManagerMonitor": "Ativar o Gerenciador de Arquivos", + "enableFileManagerMonitorDesc": "Navegue e gerencie arquivos neste host via SFTP.", + "scpLegacyLabel": "Modo Legado SCP", + "scpLegacyDesc": "Utilize a transferência de arquivos legada para servidores que não suportam o subsistema SFTP (por exemplo, servidores SSH embutidos ou mínimos).", + "defaultPathLabel": "Caminho padrão", + "fileManagerPathHint": "O diretório a ser aberto quando o gerenciador de arquivos for iniciado para este host.", + "fileManagerUrlCopied": "URL do Gerenciador de Arquivos copiada", + "copyFileManagerUrlAction": "Copiar URL do Gerenciador de Arquivos" + }, + "settings": { + "host": { + "enable": { + "label": "Ativar o Gerenciador de Arquivos" + }, + "defaultPath": { + "label": "Caminho padrão" + }, + "scpLegacy": { + "label": "Modo Legado SCP" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Arquivos" + } +} diff --git a/plugins/file-manager/locales/translated/pt_PT.json b/plugins/file-manager/locales/translated/pt_PT.json new file mode 100644 index 000000000..6eca084e0 --- /dev/null +++ b/plugins/file-manager/locales/translated/pt_PT.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Nenhum host selecionado", + "initializingEditor": "A inicializar editor...", + "file": "Ficheiro", + "folder": "Pasta", + "uploadFile": "Carregar Ficheiro", + "downloadFile": "Descarregar", + "extractArchive": "Extrair Arquivo", + "extractingArchive": "A extrair {{name}}...", + "archiveExtractedSuccessfully": "{{name}} extraído com sucesso", + "extractFailed": "Extração falhou", + "compressFile": "Comprimir Ficheiro", + "compressFiles": "Comprimir Ficheiros", + "compressFilesDesc": "Comprimir {{count}} itens num arquivo", + "archiveName": "Nome do arquivo", + "enterArchiveName": "Introduza o nome do arquivo...", + "compressionFormat": "Formato de compressão", + "selectedFiles": "Ficheiros selecionados", + "andMoreFiles": "e mais {{count}}...", + "compress": "Comprimir", + "compressingFiles": "A comprimir {{count}} itens em {{name}}...", + "filesCompressedSuccessfully": "{{name}} criado com sucesso", + "compressFailed": "Compressão falhou", + "edit": "Editar", + "preview": "Pré-visualizar", + "previous": "Anterior", + "next": "Seguinte", + "pageXOfY": "Página {{current}} de {{total}}", + "zoomOut": "Reduzir", + "zoomIn": "Ampliar", + "newFile": "Novo Ficheiro", + "newFolder": "Nova Pasta", + "rename": "Renomear", + "uploading": "A enviar...", + "uploadingFile": "A enviar {{name}}...", + "fileName": "Nome do ficheiro", + "folderName": "Nome da pasta", + "fileUploadedSuccessfully": "Ficheiro \"{{name}}\" enviado com sucesso", + "failedToUploadFile": "Falha ao enviar ficheiro", + "uploadingFolderFiles": "A enviar {{count}} ficheiro(s)...", + "uploadedFolderFiles": "Ficheiro(s) carregado(s) com sucesso {{count}}", + "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", + "fileDownloadedSuccessfully": "Ficheiro \"{{name}}\" descarregado com sucesso", + "failedToDownloadFile": "Falha ao descarregar ficheiro", + "fileCreatedSuccessfully": "Ficheiro \"{{name}}\" criado com sucesso", + "folderCreatedSuccessfully": "Pasta \"{{name}}\" criada com sucesso", + "failedToCreateItem": "Falha ao criar item", + "operationFailed": "Operação {{operation}} falhou para {{name}}: {{error}}", + "failedToResolveSymlink": "Falha ao resolver ligação simbólica", + "itemsDeletedSuccessfully": "{{count}} itens eliminados com êxito", + "failedToDeleteItems": "Falha ao eliminar itens", + "sudoPasswordRequired": "Palavra-passe de administrador necessária", + "enterSudoPassword": "Introduza a palavra-passe sudo para continuar esta operação", + "sudoPassword": "Palavra-passe sudo", + "sudoOperationFailed": "Operação sudo falhou", + "sudoAuthFailed": "Autenticação sudo falhou", + "dragFilesToUpload": "Arraste ficheiros aqui para carregar", + "dropToUploadHere": "Arraste e faça o upload aqui.", + "dropToDownloadHere": "Faça o download aqui.", + "localFiles": "Local", + "showLocalFiles": "Visualizar ficheiros locais (visualização dividida)", + "hideLocalFiles": "Ocultar ficheiros locais", + "localHome": "Pasta inicial", + "localPathPlaceholder": "Caminho da pasta local", + "localShowHidden": "Mostrar ficheiros ocultos", + "localHideHidden": "Ocultar ficheiros ocultos", + "localRevealInFileManager": "Exibir no gestor de ficheiros do sistema", + "localCannotOpenFolder": "Não é possível abrir a pasta", + "localItemCount": "{{count}} artigos", + "localSelectedCount": "{{count}} selecionado", + "back": "Costas", + "forward": "Encaminhar", + "up": "Para cima", + "kind": "Amabilidade", + "localUploadingProgress": "A enviar {{current}} de {{total}}", + "localDownloadingProgress": "A descarregar {{current}} de {{total}}", + "localTransferInFlight": "{{count}} imediatamente", + "localPreparingDownload": "A preparar o download...", + "localTransferCancelled": "Transferência cancelada", + "localNothingToTransfer": "Nada a transferir", + "localUploadComplete": "Uploaded {{count}} file(s)", + "localUploadFailed": "Falha no upload", + "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", + "localDownloadComplete": "Ficheiro(s) descarregado(s) {{count}}", + "localDownloadFailed": "Falha no download", + "localDownloadPartial": "Downloaded {{done}} file(s), {{failed}} failed", + "localReplaceExistingPrompt": "{{count}} Já existem itens nesta pasta. Deseja substituí-los?", + "localReplace": "Substituir", + "localSkipExisting": "Ignorar", + "localSkippedExisting": "Ignorou {{count}} item(s) existente(s)", + "localUnsafeNamesSkipped": "Ignorado {{count}} item(s) cujos nomes não podem ser utilizados neste computador", + "localOpen": "Aberto", + "localOpenFolder": "Abra a pasta", + "localUploadToRemote": "Enviar para o servidor", + "localUploadToRemoteMany": "Enviar {{count}} itens para o servidor", + "localMoveToTrash": "Mover para a Reciclagem", + "localMoveToTrashMany": "Mover {{count}} itens para a Reciclagem", + "localTrashConfirmSingle": "Deslocar \"{{name}}\" para a Reciclagem?", + "localTrashConfirmFolder": "Mover a pasta \"{{name}}\" e todo o seu conteúdo para a Reciclagem?", + "localTrashConfirmMany": "Mover {{count}} itens para a Reciclagem?", + "localTrashed": "Movidos {{count}} itens para a Reciclagem", + "localTrashFailed": "Não foi possível mover para a Reciclagem", + "localRenameFailed": "A renomeação falhou", + "localCreateFailed": "Não foi possível criar o item.", + "goToParentFolder": "Aceda à pasta principal", + "resizeSidebar": "Arraste para redimensionar a barra lateral (clique duas vezes para repor).", + "resizeLocalPane": "Arraste para redimensionar o painel local (clique duas vezes para repor).", + "columns": "Colunas", + "columnsHint": "Clique com o botão direito do rato para escolher as colunas.", + "emptyFolder": "Esta pasta está vazia", + "searchFiles": "Procurar ficheiros...", + "upload": "Carregar", + "selectHostToStart": "Selecione uma máquina para iniciar a gestão de ficheiros", + "sshRequiredForFileManager": "O gestor de ficheiros requer SSH. Esta máquina não tem SSH ativado.", + "failedToConnect": "Falha ao ligar ao SSH", + "failedToLoadDirectory": "Falha ao carregar diretório", + "noSSHConnection": "Nenhuma ligação SSH disponível", + "copy": "Copiar", + "cut": "Cortar", + "paste": "Colar", + "copyPath": "Copiar caminho", + "copyPaths": "Copiar caminhos", + "delete": "Eliminar", + "properties": "Propriedades", + "refresh": "Atualizar", + "downloadFiles": "Descarregar {{count}} ficheiros para o navegador", + "copyFiles": "Copiar {{count}} itens", + "cutFiles": "Cortar {{count}} itens", + "deleteFiles": "Eliminar {{count}} itens", + "filesCopiedToClipboard": "{{count}} itens copiados para a área de transferência", + "filesCutToClipboard": "{{count}} itens cortados para a área de transferência", + "pathCopiedToClipboard": "Caminho copiado para a área de transferência", + "pathsCopiedToClipboard": "{{count}} caminhos copiados para a área de transferência", + "failedToCopyPath": "Falha ao copiar o caminho para a área de transferência", + "copyFolderLink": "Copiar ligação para a pasta", + "copyCurrentFolderLink": "Copiar ligação para a pasta atual", + "folderLinkCopied": "Ligação da pasta copiada para a área de transferência", + "failedToCopyFolderLink": "Falha ao copiar a ligação da pasta", + "movedItems": "{{count}} itens movidos", + "failedToDeleteItem": "Falha ao eliminar o item", + "itemRenamedSuccessfully": "Renomeação de {{type}} bem-sucedida.", + "failedToRenameItem": "Falha ao renomear o item", + "download": "Transferir", + "openExternalEditor": "Abrir externamente", + "chooseExternalEditor": "Escolher editor externo", + "externalEditorSelected": "Editor externo selecionado", + "externalEditorDesktopOnly": "O editor externo está disponível apenas na aplicação de ambiente de trabalho", + "externalEditorOpened": "Aberto no editor externo. Ao guardar, as alterações serão enviadas de volta para o servidor.", + "failedToOpenExternalEditor": "Falha ao abrir o editor externo", + "failedToSelectExternalEditor": "Falha ao selecionar o editor externo", + "permissions": "Permissões", + "size": "Tamanho", + "modified": "Modificado", + "path": "Caminho", + "confirmDelete": "Tem a certeza de que pretende eliminar {{name}}?", + "permissionDenied": "Permissão negada", + "serverError": "Erro do servidor", + "fileSavedSuccessfully": "Ficheiro guardado com sucesso", + "failedToSaveFile": "Falha ao guardar o ficheiro", + "confirmDeleteSingleItem": "Tem a certeza de que pretende eliminar permanentemente \"{{name}}\"?", + "confirmDeleteMultipleItems": "Tem a certeza de que pretende eliminar permanentemente {{count}} itens?", + "confirmDeleteMultipleItemsWithFolders": "Tem a certeza de que pretende eliminar permanentemente {{count}} itens? Isto inclui pastas e o seu conteúdo.", + "confirmDeleteFolder": "Tem a certeza de que pretende eliminar permanentemente a pasta \"{{name}}\" e todo o seu conteúdo?", + "permanentDeleteWarning": "Esta ação não pode ser anulada. Os itens serão permanentemente eliminados do servidor.", + "moveToTrashWarning": "O(s) artigo(s) será(ão) movido(s) para a Reciclagem Termix e poderá(ão) ser restaurado(s) antes do fim do período de retenção.", + "itemsMovedToTrash": "{{count}} artigo(s) movido(s) para a Reciclagem", + "trash": "Lixo", + "trashDescription": "Os itens eliminados são retidos durante {{days}} dia(s) neste host.", + "trashLoadFailed": "Falha ao carregar a Reciclagem", + "trashEmpty": "A papeleira está vazia.", + "trashRestore": "Restaurar", + "trashRestored": "Restaurado {{name}}", + "trashRestoreFailed": "Falha ao restaurar o item. O caminho original pode já existir.", + "trashDeletePermanently": "Apagar permanentemente", + "trashDeleteConfirm": "Apagar permanentemente {{name}}? Esta ação não pode ser anulada.", + "trashPermanentlyDeleted": "Eliminado permanentemente {{name}}", + "trashDeleteFailed": "Falha ao eliminar o item permanentemente", + "trashEmptyAction": "Esvaziar o lixo", + "trashEmptyConfirm": "Apagar permanentemente todos os itens da Reciclagem? Esta ação é irreversível.", + "trashEmptied": "Lixo esvaziado", + "trashRetention": "Limpeza automática após (dias)", + "trashRetentionSaved": "Retenção de lixo atualizada", + "trashRetentionFailed": "Falha na atualização da retenção de lixo", + "trashUnavailableConfirm": "Este item não pode ser movido para a Reciclagem, geralmente porque está noutro sistema de ficheiros ou requer permissões elevadas. Deseja eliminá-lo permanentemente?", + "recent": "Recentes", + "pinned": "Fixados", + "folderShortcuts": "Atalhos de pastas", + "failedToReconnectSSH": "Falha ao restabelecer a sessão SSH", + "openTerminalHere": "Abrir Terminal Aqui", + "run": "Executar", + "openTerminalInFolder": "Abrir Terminal Nesta Pasta", + "openTerminalInFileLocation": "Abrir Terminal na Localização do Ficheiro", + "runningFile": "Em execução - {{file}}", + "onlyRunExecutableFiles": "Só pode executar ficheiros executáveis", + "directories": "Pastas", + "removedFromRecentFiles": "Removido \"{{name}}\" dos ficheiros recentes", + "removeFailed": "Falha ao remover", + "unpinnedSuccessfully": "\"{{name}}\" desafixado com sucesso", + "unpinFailed": "Falha ao desafixar", + "removedShortcut": "Atalho \"{{name}}\" removido", + "removeShortcutFailed": "Falha ao remover atalho", + "clearedAllRecentFiles": "Limpos todos os ficheiros recentes", + "clearFailed": "Falha ao limpar", + "removeFromRecentFiles": "Remover dos ficheiros recentes", + "clearAllRecentFiles": "Limpar todos os ficheiros recentes", + "unpinFile": "Desafixar ficheiro", + "removeShortcut": "Remover atalho", + "pinFile": "Afixar ficheiro", + "addToShortcuts": "Adicionar aos atalhos", + "pasteFailed": "Falha ao colar", + "noUndoableActions": "Sem ações para desfazer", + "undoCopySuccess": "Desfeita a operação de cópia: Eliminados {{count}} ficheiros copiados", + "undoCopyFailedDelete": "Falha ao desfazer: Não foi possível eliminar os ficheiros copiados", + "undoCopyFailedNoInfo": "Falha ao desfazer: Não foi possível encontrar a informação dos ficheiros copiados", + "undoMoveSuccess": "Desfeita a operação de mover: {{count}} ficheiros movidos de volta para o local original", + "undoMoveFailedMove": "Falha ao desfazer: Não foi possível mover nenhum ficheiro de volta", + "undoMoveFailedNoInfo": "Falha ao desfazer: Não foi possível encontrar a informação dos ficheiros movidos", + "undoDeleteNotSupported": "A operação de eliminar não pode ser desfeita: Os ficheiros foram eliminados permanentemente do servidor", + "undoTypeNotSupported": "Tipo de operação de desfazer não suportado", + "undoOperationFailed": "Falha ao desfazer a operação", + "unknownError": "Erro desconhecido", + "confirm": "Confirmar", + "find": "Procurar...", + "replace": "Substituir", + "downloadInstead": "Transferir em alternativa", + "keyboardShortcuts": "Atalhos de teclado", + "searchAndReplace": "Pesquisar e substituir", + "editing": "Edição", + "search": "Pesquisar", + "findNext": "Localizar seguinte", + "findPrevious": "Localizar anterior", + "save": "Guardar", + "selectAll": "Selecionar tudo", + "undo": "Desfazer", + "redo": "Refazer", + "moveLineUp": "Mover linha para cima", + "moveLineDown": "Mover linha para baixo", + "toggleComment": "Alternar comentário", + "autoComplete": "Autocompletar", + "imageLoadError": "Erro ao carregar a imagem", + "startTyping": "Comece a escrever...", + "unknownSize": "Tamanho desconhecido", + "fileIsEmpty": "O ficheiro está vazio", + "largeFileWarning": "Aviso de ficheiro grande", + "largeFileWarningDesc": "Este ficheiro tem {{size}} de tamanho, o que poderá causar problemas de desempenho ao ser aberto como texto.", + "fileNotFoundAndRemoved": "O ficheiro \"{{name}}\" não foi encontrado e foi removido dos ficheiros recentes/fixados", + "failedToLoadFile": "Erro ao carregar o ficheiro: {{error}}", + "serverErrorOccurred": "Ocorreu um erro no servidor. Por favor, tente novamente mais tarde.", + "autoSaveFailed": "Falha na gravação automática", + "fileAutoSaved": "Ficheiro gravado automaticamente", + "moveFileFailed": "Falha ao mover {{name}}", + "moveOperationFailed": "Falha na operação de movimentação", + "canOnlyCompareFiles": "Só é possível comparar dois ficheiros", + "comparingFiles": "A comparar ficheiros: {{file1}} e {{file2}}", + "dragFailed": "Falha ao arrastar", + "filePinnedSuccessfully": "Ficheiro \"{{name}}\" fixado com sucesso", + "pinFileFailed": "Erro ao fixar o ficheiro", + "fileUnpinnedSuccessfully": "Ficheiro \"{{name}}\" desafixado com sucesso", + "unpinFileFailed": "Falha ao desafixar ficheiro", + "shortcutAddedSuccessfully": "Atalho para a pasta \"{{name}}\" adicionado com sucesso", + "addShortcutFailed": "Falha ao adicionar atalho", + "operationCompletedSuccessfully": "{{operation}} {{count}} itens com sucesso", + "operationCompleted": "{{operation}} {{count}} itens", + "downloadFileSuccess": "Ficheiro {{name}} descarregado com sucesso", + "downloadFileFailed": "Falha ao descarregar", + "downloadingFile": "A descarregar {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Mover para {{name}}", + "diffCompareWith": "Comparar diferenças com {{name}}", + "dragOutsideToDownload": "Arraste para fora da janela para descarregar ({{count}} ficheiros)", + "newFolderDefault": "NovaPasta", + "newFileDefault": "NovoFicheiro.txt", + "successfullyMovedItems": "{{count}} itens movidos com sucesso para {{target}}", + "move": "Mover", + "searchInFile": "Procurar no ficheiro (Ctrl+F)", + "showKeyboardShortcuts": "Mostrar atalhos de teclado", + "decreaseFontSize": "Diminuir tamanho da letra", + "increaseFontSize": "Aumentar tamanho da letra", + "startWritingMarkdown": "Comece a escrever o seu conteúdo em markdown...", + "loadingFileComparison": "A carregar comparação de ficheiros...", + "reload": "Recarregar", + "compare": "Comparar", + "sideBySide": "Lado a lado", + "inline": "Em linha", + "fileComparison": "Comparação de ficheiros: {{file1}} vs {{file2}}", + "fileTooLarge": "Ficheiro demasiado grande: {{error}}", + "sshConnectionFailed": "Falha na ligação SSH. Verifique a sua ligação a {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Falha ao carregar ficheiro: {{error}}", + "connecting": "A ligar...", + "connectedSuccessfully": "Ligado com sucesso", + "totpVerificationFailed": "Falha na verificação TOTP", + "warpgateVerificationFailed": "Falha na autenticação Warpgate", + "authenticationFailed": "Falha na autenticação", + "incorrectPassphrase": "Frase-passe incorreta. Tente novamente.", + "verificationCodePrompt": "Código de verificação:", + "changePermissions": "Alterar Permissões", + "currentPermissions": "Permissões Atuais", + "owner": "Proprietário", + "group": "Grupo", + "others": "Outros", + "read": "Leitura", + "write": "Escrita", + "execute": "Execução", + "permissionsChangedSuccessfully": "Permissões alteradas com sucesso", + "failedToChangePermissions": "Falha ao alterar permissões", + "name": "Nome", + "sortByName": "Nome", + "sortByDate": "Data de modificação", + "sortBySize": "Tamanho", + "ascending": "Ascendente", + "descending": "Descendente", + "root": "Raiz", + "new": "Novo", + "sortBy": "Ordenar por", + "items": "Itens", + "selected": "Selecionados", + "editor": "Editor", + "octal": "Octal", + "storage": "Armazenamento", + "disk": "Disco", + "used": "Utilizado", + "of": "de", + "toggleSidebar": "Alternar Barra Lateral", + "compactLayout": "Utilize um layout compacto.", + "comfortableLayout": "Utilize um layout confortável", + "cannotLoadPdf": "Não é possível carregar o PDF", + "pdfLoadError": "Ocorreu um erro ao carregar este ficheiro PDF.", + "loadingPdf": "A carregar PDF...", + "loadingPage": "A carregar página..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Transferir ficheiros entre dois hosts SSH.", + "sourceServer": "Servidor de origem", + "destinationServer": "Servidor de destino", + "selectHost": "Selecione o host", + "noHostSelected": "Nenhum host selecionado", + "move": "Mover", + "copy": "Cópia", + "upload": "Carregar", + "itemsSelected": "{{count}} item selecionado", + "itemsSelected_other": "{{count}} itens selecionados", + "ready": "Pronto", + "loadingHosts": "Carregando hosts...", + "name": "Nome", + "modified": "Modificado", + "size": "Tamanho", + "noFiles": "Nenhum ficheiro", + "open": "Aberto", + "up": "Para cima", + "refresh": "Atualizar", + "copyToTarget": "Copiar para o diretório de destino", + "rename": "Renomear", + "delete": "Eliminar", + "editPermissions": "Editar permissões", + "createNewFolder": "Criar nova pasta", + "deleteSelectedItems": "Apagar itens selecionados", + "deleteConfirm": "Delete {{count}} selected item?", + "deleteConfirm_other": "Apagar {{count}} itens selecionados?", + "cancel": "Cancelar", + "save": "Guardar", + "folderCreated": "Pasta criada", + "itemRenamed": "Artigo renomeado", + "actionFailed": "A ação falhou", + "itemsDeleted": "{{count}} item eliminado", + "itemsDeleted_other": "{{count}} itens eliminados", + "deleteFailed": "Falha ao eliminar", + "permissionsUpdated": "Permissões atualizadas", + "noRemoteSessionConnected": "Nenhuma sessão remota ligada.", + "failedToListFiles": "Falha ao listar os ficheiros", + "failedToRefresh": "Falha ao atualizar", + "destinationInsideSource": "O destino não pode estar dentro do caminho de origem selecionado.", + "sameHostNoOp": "A origem e o destino são a mesma pasta." + }, + "hosts": { + "fileManager": "Gestor de Ficheiros", + "tabFiles": "Ficheiros", + "enableFileManagerMonitor": "Ativar Gestor de Ficheiros", + "enableFileManagerMonitorDesc": "Explorar e gerir ficheiros neste host via SFTP", + "scpLegacyLabel": "Modo SCP Legado", + "scpLegacyDesc": "Utilizar transferência de ficheiros legada para servidores que não suportam o subsistema SFTP (ex.: servidores SSH embebidos ou mínimos)", + "defaultPathLabel": "Caminho Predefinido", + "fileManagerPathHint": "O diretório a abrir quando o gestor de ficheiros for iniciado para este host", + "fileManagerUrlCopied": "URL do gestor de ficheiros copiada", + "copyFileManagerUrlAction": "Copiar URL do Gestor de Ficheiros" + }, + "settings": { + "host": { + "enable": { + "label": "Ativar Gestor de Ficheiros" + }, + "defaultPath": { + "label": "Caminho Predefinido" + }, + "scpLegacy": { + "label": "Modo SCP Legado" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Ficheiros" + } +} diff --git a/plugins/file-manager/locales/translated/ro_RO.json b/plugins/file-manager/locales/translated/ro_RO.json new file mode 100644 index 000000000..f09ce35a5 --- /dev/null +++ b/plugins/file-manager/locales/translated/ro_RO.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Nicio gazdă selectată", + "initializingEditor": "Se inițializează editorul...", + "file": "Fişier", + "folder": "Pliant", + "uploadFile": "Încărcați fișierul", + "downloadFile": "Descărcare", + "extractArchive": "Extrageți arhiva", + "extractingArchive": "Extragerea {{name}}...", + "archiveExtractedSuccessfully": "{{name}} extras cu succes", + "extractFailed": "Extragerea a eșuat", + "compressFile": "Comprimați fișierul", + "compressFiles": "Comprimați fișierele", + "compressFilesDesc": "Comprimați elementele {{count}} într-o arhivă", + "archiveName": "Numele arhivei", + "enterArchiveName": "Introduceți numele arhivei...", + "compressionFormat": "Format de compresie", + "selectedFiles": "Fișiere selectate", + "andMoreFiles": "și {{count}} mai multe...", + "compress": "Comprima", + "compressingFiles": "Comprimarea elementelor {{count}} în {{name}}...", + "filesCompressedSuccessfully": "{{name}} creat cu succes", + "compressFailed": "Compresia a eșuat", + "edit": "Edita", + "preview": "Previzualizare", + "previous": "Anterior", + "next": "Următorul", + "pageXOfY": "Pagina {{current}} din {{total}}", + "zoomOut": "Micșorare", + "zoomIn": "Mărire", + "newFile": "Fișier nou", + "newFolder": "Dosar nou", + "rename": "Redenumire", + "uploading": "Se încarcă...", + "uploadingFile": "Se încarcă {{name}}...", + "fileName": "Nume de fișier", + "folderName": "Nume folder", + "fileUploadedSuccessfully": "Fișierul „{{name}}” a fost încărcat cu succes", + "failedToUploadFile": "Nu s-a putut încărca fișierul", + "uploadingFolderFiles": "Se încarcă {{count}} fișier(e)...", + "uploadedFolderFiles": "Fișier(e) {{count}} încărcat(e) cu succes", + "uploadedFolderPartial": "Încărcarea fișierelor {{uploaded}} , {{failed}} a eșuat.", + "fileDownloadedSuccessfully": "Fișierul „{{name}}” a fost descărcat cu succes", + "failedToDownloadFile": "Nu s-a putut descărca fișierul", + "fileCreatedSuccessfully": "Fișierul „{{name}}” a fost creat cu succes", + "folderCreatedSuccessfully": "Dosarul „{{name}}” a fost creat cu succes", + "failedToCreateItem": "Nu s-a putut crea elementul", + "operationFailed": "Operația {{operation}} a eșuat pentru {{name}}: {{error}}", + "failedToResolveSymlink": "Nu s-a putut rezolva legătura simbolică", + "itemsDeletedSuccessfully": "{{count}} elemente șterse cu succes", + "failedToDeleteItems": "Nu s-au putut șterge elementele", + "sudoPasswordRequired": "Parolă de administrator necesară", + "enterSudoPassword": "Introduceți parola sudo pentru a continua această operațiune", + "sudoPassword": "Parolă Sudo", + "sudoOperationFailed": "Operațiunea Sudo a eșuat", + "sudoAuthFailed": "Autentificarea Sudo a eșuat", + "dragFilesToUpload": "Plasați fișierele aici pentru încărcare", + "dropToUploadHere": "Plasați aici pentru a încărca", + "dropToDownloadHere": "Plasați pentru descărcare aici", + "localFiles": "Local", + "showLocalFiles": "Afișați fișierele locale (vizualizare divizată)", + "hideLocalFiles": "Ascunde fișierele locale", + "localHome": "Dosar principal", + "localPathPlaceholder": "Calea folderului local", + "localShowHidden": "Afișează fișierele ascunse", + "localHideHidden": "Ascunde fișierele ascunse", + "localRevealInFileManager": "Afișează în managerul de fișiere al sistemului", + "localCannotOpenFolder": "Nu se poate deschide folderul", + "localItemCount": "{{count}} articole", + "localSelectedCount": "{{count}} selectat", + "back": "Spate", + "forward": "Redirecţiona", + "up": "Sus", + "kind": "Fel", + "localUploadingProgress": "Se încarcă {{current}} din {{total}}", + "localDownloadingProgress": "Se descarcă {{current}} din {{total}}", + "localTransferInFlight": "{{count}} deodată", + "localPreparingDownload": "Se pregătește descărcarea...", + "localTransferCancelled": "Transfer anulat", + "localNothingToTransfer": "Nimic de transferat", + "localUploadComplete": "Fișiere încărcate {{count}}", + "localUploadFailed": "Încărcarea a eșuat", + "localUploadPartial": "Încărcarea fișierelor {{done}} , {{failed}} a eșuat.", + "localDownloadComplete": "Fișiere descărcate {{count}}", + "localDownloadFailed": "Descărcarea a eșuat", + "localDownloadPartial": "Descărcarea fișierelor {{done}} , {{failed}} a eșuat.", + "localReplaceExistingPrompt": "{{count}} element(e) există deja în acest folder. Le înlocuiți?", + "localReplace": "Înlocui", + "localSkipExisting": "Sari peste", + "localSkippedExisting": "{{count}} element(e) existent(e) omis(e)", + "localUnsafeNamesSkipped": "{{count}} element(e) ale cărui nume nu pot fi utilizate pe acest computer au fost omise", + "localOpen": "Deschide", + "localOpenFolder": "Deschideți folderul", + "localUploadToRemote": "Încărcare pe server", + "localUploadToRemoteMany": "Încărcați {{count}} elemente pe server", + "localMoveToTrash": "Mută în coșul de gunoi", + "localMoveToTrashMany": "Mută articolele {{count}} în Coșul de gunoi", + "localTrashConfirmSingle": "Mutați „{{name}}” în coșul de gunoi?", + "localTrashConfirmFolder": "Mutați folderul „{{name}}” și tot conținutul său în Coșul de gunoi?", + "localTrashConfirmMany": "Mutați {{count}} elemente în coșul de gunoi?", + "localTrashed": "{{count}} element(e) mutat(e) în coșul de gunoi", + "localTrashFailed": "Nu s-a putut muta în coșul de gunoi", + "localRenameFailed": "Redenumirea a eșuat", + "localCreateFailed": "Nu s-a putut crea elementul", + "goToParentFolder": "Accesați folderul părinte", + "resizeSidebar": "Trageți pentru a redimensiona bara laterală (faceți dublu clic pentru a reseta)", + "resizeLocalPane": "Trageți pentru a redimensiona panoul local (faceți dublu clic pentru a reseta)", + "columns": "Coloane", + "columnsHint": "Faceți clic dreapta pentru a alege coloane", + "emptyFolder": "Acest folder este gol", + "searchFiles": "Căutați fișiere...", + "upload": "Încărcare", + "selectHostToStart": "Selectați o gazdă pentru a începe gestionarea fișierelor", + "sshRequiredForFileManager": "Managerul de fișiere necesită SSH. Această gazdă nu are SSH activat.", + "failedToConnect": "Conectarea la SSH a eșuat", + "failedToLoadDirectory": "Directorul nu a putut fi încărcat", + "noSSHConnection": "Nu este disponibilă nicio conexiune SSH", + "copy": "Copie", + "cut": "Tăiere", + "paste": "Pastă", + "copyPath": "Copiere cale", + "copyPaths": "Copiați căile", + "delete": "Şterge", + "properties": "Proprietăți", + "refresh": "Reîmprospăta", + "downloadFiles": "Descărcați fișierele {{count}} în browser", + "copyFiles": "Copiază elementele {{count}}", + "cutFiles": "Decupează elementele {{count}}", + "deleteFiles": "Ștergeți elementele {{count}}", + "filesCopiedToClipboard": "{{count}} elemente copiate în clipboard", + "filesCutToClipboard": "{{count}} elemente tăiate în clipboard", + "pathCopiedToClipboard": "Calea a fost copiată în clipboard", + "pathsCopiedToClipboard": "Căile {{count}} au fost copiate în clipboard", + "failedToCopyPath": "Nu s-a putut copia calea în clipboard", + "copyFolderLink": "Copiați linkul în folder", + "copyCurrentFolderLink": "Copiați linkul în folderul curent", + "folderLinkCopied": "Legătura la dosar a fost copiată în clipboard", + "failedToCopyFolderLink": "Nu s-a putut copia linkul dosarului", + "movedItems": "Articole mutate {{count}}", + "failedToDeleteItem": "Nu s-a putut șterge elementul", + "itemRenamedSuccessfully": "{{type}} redenumit cu succes", + "failedToRenameItem": "Nu s-a putut redenumi elementul", + "download": "Descărcare", + "openExternalEditor": "Deschis la exterior", + "chooseExternalEditor": "Alegeți un editor extern", + "externalEditorSelected": "Editor extern selectat", + "externalEditorDesktopOnly": "Editorul extern este disponibil doar în aplicația pentru desktop", + "externalEditorOpened": "Deschis într-un editor extern. Salvarea acolo va încărca modificările înapoi pe server.", + "failedToOpenExternalEditor": "Nu s-a putut deschide editorul extern", + "failedToSelectExternalEditor": "Nu s-a putut selecta editorul extern", + "permissions": "Permisiuni", + "size": "Dimensiune", + "modified": "Modificat", + "path": "Cale", + "confirmDelete": "Sigur vrei să ștergi {{name}}?", + "permissionDenied": "Acces refuzat", + "serverError": "Eroare server", + "fileSavedSuccessfully": "Fișierul a fost salvat cu succes", + "failedToSaveFile": "Nu s-a putut salva fișierul", + "confirmDeleteSingleItem": "Sigur vrei să ștergi definitiv „{{name}}”?", + "confirmDeleteMultipleItems": "Sigur vrei să ștergi definitiv elementele {{count}}?", + "confirmDeleteMultipleItemsWithFolders": "Sigur vrei să ștergi definitiv elementele {{count}} ? Aceasta include folderele și conținutul acestora.", + "confirmDeleteFolder": "Sigur doriți să ștergeți definitiv folderul „{{name}}” și tot conținutul său?", + "permanentDeleteWarning": "Această acțiune nu poate fi anulată. Elementul (elementele) va fi (vor) șters(e) definitiv de pe server.", + "moveToTrashWarning": "Elementul (elementele) va (vor) fi mutat(e) în Coșul de gunoi Termix și va (pot) fi restaurat(e) înainte de expirarea perioadei de păstrare.", + "itemsMovedToTrash": "{{count}} element(e) mutat(e) în Coșul de gunoi", + "trash": "Gunoi", + "trashDescription": "Elementele șterse sunt păstrate timp de {{days}} zi(le) pe această gazdă.", + "trashLoadFailed": "Nu s-a putut încărca Coșul de gunoi", + "trashEmpty": "Coșul de gunoi este gol", + "trashRestore": "Restabili", + "trashRestored": "Restaurat {{name}}", + "trashRestoreFailed": "Elementul nu a putut fi restaurat. Calea originală ar putea exista deja.", + "trashDeletePermanently": "Ștergeți definitiv", + "trashDeleteConfirm": "Ștergeți definitiv {{name}}? Această acțiune nu poate fi anulată.", + "trashPermanentlyDeleted": "Șters definitiv {{name}}", + "trashDeleteFailed": "Nu s-a putut șterge definitiv elementul", + "trashEmptyAction": "Goliți coșul de gunoi", + "trashEmptyConfirm": "Ștergeți definitiv toate elementele din Coșul de gunoi? Această acțiune nu poate fi anulată.", + "trashEmptied": "Coș de gunoi golit", + "trashRetention": "Curățare automată după (zile)", + "trashRetentionSaved": "Reținerea coșului de gunoi actualizată", + "trashRetentionFailed": "Nu s-a putut actualiza opțiunea „Păstrarea în coș”", + "trashUnavailableConfirm": "Acest element nu poate fi mutat în Coșul de gunoi, de obicei pentru că se află pe un alt sistem de fișiere sau necesită permisiuni ridicate. Îl ștergeți definitiv în schimb?", + "recent": "Recent", + "pinned": "Fixat", + "folderShortcuts": "Comenzi rapide pentru foldere", + "failedToReconnectSSH": "Reconectarea sesiunii SSH a eșuat.", + "openTerminalHere": "Deschideți terminalul aici", + "run": "Aleargă", + "openTerminalInFolder": "Deschideți terminalul în acest folder", + "openTerminalInFileLocation": "Deschideți terminalul la locația fișierului", + "runningFile": "Alergare - {{file}}", + "onlyRunExecutableFiles": "Poate rula doar fișiere executabile", + "directories": "Directoare", + "removedFromRecentFiles": "„{{name}}” a fost eliminat din fișierele recente", + "removeFailed": "Eliminarea a eșuat", + "unpinnedSuccessfully": "„{{name}}” a fost anulată cu succes", + "unpinFailed": "Anularea fixării a eșuat", + "removedShortcut": "S-a eliminat comanda rapidă „{{name}}”", + "removeShortcutFailed": "Eliminarea comenzii rapide a eșuat", + "clearedAllRecentFiles": "Am șters toate fișierele recente", + "clearFailed": "Ștergerea a eșuat", + "removeFromRecentFiles": "Eliminați din fișierele recente", + "clearAllRecentFiles": "Ștergeți toate fișierele recente", + "unpinFile": "Anulează fixarea fișierului", + "removeShortcut": "Eliminați comanda rapidă", + "pinFile": "Fișier fixat", + "addToShortcuts": "Adăugați la comenzi rapide", + "pasteFailed": "Lipirea a eșuat", + "noUndoableActions": "Nicio acțiune care nu poate fi anulată", + "undoCopySuccess": "Operațiune de copiere anulată: Fișierele copiate {{count}} au fost șterse", + "undoCopyFailedDelete": "Anulare eșuată: Nu s-au putut șterge fișierele copiate", + "undoCopyFailedNoInfo": "Anulare eșuată: Nu s-au găsit informațiile despre fișierul copiat", + "undoMoveSuccess": "Operațiune de mutare anulată: Fișierele {{count}} au fost mutate înapoi în locația originală.", + "undoMoveFailedMove": "Anulare eșuată: Nu s-au putut muta fișierele înapoi", + "undoMoveFailedNoInfo": "Anulare eșuată: Nu s-au găsit informații despre fișierul mutat", + "undoDeleteNotSupported": "Operațiunea de ștergere nu poate fi anulată: Fișierele au fost șterse definitiv de pe server.", + "undoTypeNotSupported": "Tip de operațiune de anulare neacceptat", + "undoOperationFailed": "Operațiunea de anulare a eșuat", + "unknownError": "Eroare necunoscută", + "confirm": "Confirma", + "find": "Găsi...", + "replace": "Înlocui", + "downloadInstead": "Descărcați în schimb", + "keyboardShortcuts": "Comenzi rapide de la tastatură", + "searchAndReplace": "Căutare și înlocuire", + "editing": "Editare", + "search": "Căutare", + "findNext": "Găsește următorul", + "findPrevious": "Găsește precedentul", + "save": "Salva", + "selectAll": "Selectați tot", + "undo": "Anula", + "redo": "Refaceți", + "moveLineUp": "Mută alinierea în sus", + "moveLineDown": "Mută linia în jos", + "toggleComment": "Comutare comentariu", + "autoComplete": "Completare automată", + "imageLoadError": "Nu s-a putut încărca imaginea", + "startTyping": "Începeți să tastați...", + "unknownSize": "Dimensiune necunoscută", + "fileIsEmpty": "Fișierul este gol", + "largeFileWarning": "Avertisment fișier mare", + "largeFileWarningDesc": "Acest fișier are dimensiunea {{size}} , ceea ce poate cauza probleme de performanță atunci când este deschis ca text.", + "fileNotFoundAndRemoved": "Fișierul „{{name}}” nu a fost găsit și a fost eliminat din fișierele recente/fixate.", + "failedToLoadFile": "Nu s-a putut încărca fișierul: {{error}}", + "serverErrorOccurred": "A apărut o eroare de server. Vă rugăm să încercați din nou mai târziu.", + "autoSaveFailed": "Salvarea automată a eșuat", + "fileAutoSaved": "Fișier salvat automat", + "moveFileFailed": "Nu s-a putut muta {{name}}", + "moveOperationFailed": "Operațiunea de mutare a eșuat", + "canOnlyCompareFiles": "Se pot compara doar două fișiere", + "comparingFiles": "Compararea fișierelor: {{file1}} și {{file2}}", + "dragFailed": "Operația de tragere a eșuat", + "filePinnedSuccessfully": "Fișierul „{{name}}” a fost fixat cu succes", + "pinFileFailed": "Fișierul nu a fost fixat", + "fileUnpinnedSuccessfully": "Fișierul „{{name}}” a fost anulat cu succes.", + "unpinFileFailed": "Nu s-a putut anula fixarea fișierului", + "shortcutAddedSuccessfully": "Comanda rapidă pentru folderul „{{name}}” a fost adăugată cu succes", + "addShortcutFailed": "Nu s-a putut adăuga comanda rapidă", + "operationCompletedSuccessfully": "{{operation}} {{count}} articole au fost selectate cu succes", + "operationCompleted": "{{operation}} {{count}} articole", + "downloadFileSuccess": "Fișierul {{name}} a fost descărcat cu succes", + "downloadFileFailed": "Descărcarea a eșuat", + "downloadingFile": "Se descarcă {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Mută la {{name}}", + "diffCompareWith": "Comparație diferențială cu {{name}}", + "dragOutsideToDownload": "Trageți în afara ferestrei pentru a descărca (fișiere{{count}})", + "newFolderDefault": "Dosar nou", + "newFileDefault": "FișierNou.txt", + "successfullyMovedItems": "{{count}} articole au fost mutate cu succes în {{target}}", + "move": "Mişcare", + "searchInFile": "Căutare în fișier (Ctrl+F)", + "showKeyboardShortcuts": "Afișați comenzile rapide de la tastatură", + "decreaseFontSize": "Micșorează dimensiunea fontului", + "increaseFontSize": "Măriți dimensiunea fontului", + "startWritingMarkdown": "Începe să scrii conținutul pentru markdown...", + "loadingFileComparison": "Se încarcă comparația fișierelor...", + "reload": "Reîncărcare", + "compare": "Comparaţie", + "sideBySide": "Alăturat", + "inline": "În linie", + "fileComparison": "Comparație fișiere: {{file1}} vs {{file2}}", + "fileTooLarge": "Fișier prea mare: {{error}}", + "sshConnectionFailed": "Conexiunea SSH a eșuat. Vă rugăm să verificați conexiunea la {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Nu s-a putut încărca fișierul: {{error}}", + "connecting": "Se conectează...", + "connectedSuccessfully": "Conectare reușită", + "totpVerificationFailed": "Verificarea TOTP a eșuat", + "warpgateVerificationFailed": "Autentificarea Warpgate a eșuat", + "authenticationFailed": "Autentificarea a eșuat", + "incorrectPassphrase": "Parolă incorectă. Vă rugăm să încercați din nou.", + "verificationCodePrompt": "Cod de verificare:", + "changePermissions": "Modificați permisiunile", + "currentPermissions": "Permisiuni actuale", + "owner": "Proprietar", + "group": "Grup", + "others": "Alţii", + "read": "Citire", + "write": "Scrie", + "execute": "Executa", + "permissionsChangedSuccessfully": "Permisiunile au fost modificate cu succes", + "failedToChangePermissions": "Nu s-au putut modifica permisiunile", + "name": "Nume", + "sortByName": "Nume", + "sortByDate": "Data modificării", + "sortBySize": "Dimensiune", + "ascending": "Ascendent", + "descending": "Descendent", + "root": "Rădăcină", + "new": "Nou", + "sortBy": "Filtrează după", + "items": "Articole", + "selected": "Selectat", + "editor": "Editor", + "octal": "Octal", + "storage": "Depozitare", + "disk": "Disc", + "used": "Folosit", + "of": "de", + "toggleSidebar": "Comutare bară laterală", + "compactLayout": "Folosește un aspect compact", + "comfortableLayout": "Folosește un aspect confortabil", + "cannotLoadPdf": "Nu se poate încărca PDF-ul", + "pdfLoadError": "A apărut o eroare la încărcarea acestui fișier PDF.", + "loadingPdf": "Se încarcă PDF-ul...", + "loadingPage": "Se încarcă pagina..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Transferă fișiere între două gazde SSH.", + "sourceServer": "Server sursă", + "destinationServer": "Server de destinație", + "selectHost": "Selectați gazda", + "noHostSelected": "Nicio gazdă selectată", + "move": "Mişcare", + "copy": "Copie", + "upload": "Încărcare", + "itemsSelected": "{{count}} element selectat", + "itemsSelected_other": "{{count}} elemente selectate", + "ready": "Gata", + "loadingHosts": "Se încarcă gazdele...", + "name": "Nume", + "modified": "Modificat", + "size": "Dimensiune", + "noFiles": "Niciun fișier", + "open": "Deschide", + "up": "Sus", + "refresh": "Reîmprospăta", + "copyToTarget": "Copiați în directorul țintă", + "rename": "Redenumire", + "delete": "Şterge", + "editPermissions": "Editați permisiunile", + "createNewFolder": "Creați un folder nou", + "deleteSelectedItems": "Ștergeți elementele selectate", + "deleteConfirm": "Ștergeți elementul selectat {{count}}?", + "deleteConfirm_other": "Ștergeți {{count}} elementele selectate?", + "cancel": "Anula", + "save": "Salva", + "folderCreated": "Dosar creat", + "itemRenamed": "Element redenumit", + "actionFailed": "Acțiunea a eșuat", + "itemsDeleted": "{{count}} element șters", + "itemsDeleted_other": "{{count}} elemente șterse", + "deleteFailed": "Ștergerea a eșuat", + "permissionsUpdated": "Permisiunile au fost actualizate", + "noRemoteSessionConnected": "Nicio sesiune la distanță conectată", + "failedToListFiles": "Nu s-au putut lista fișierele", + "failedToRefresh": "Nu s-a putut actualiza", + "destinationInsideSource": "Destinația nu poate fi în interiorul căii sursă selectate.", + "sameHostNoOp": "Sursa și destinația sunt în același folder." + }, + "hosts": { + "fileManager": "Manager de fișiere", + "tabFiles": "Fișiere", + "enableFileManagerMonitor": "Activează Managerul de fișiere", + "enableFileManagerMonitorDesc": "Răsfoiți și gestionați fișierele de pe această gazdă prin SFTP", + "scpLegacyLabel": "Modul SCP Legacy", + "scpLegacyDesc": "Folosește transferul de fișiere vechi pentru serverele care nu acceptă subsistemul SFTP (de exemplu, servere SSH încorporate sau minimale).", + "defaultPathLabel": "Calea implicită", + "fileManagerPathHint": "Directorul care va fi deschis la lansarea managerului de fișiere pentru această gazdă.", + "fileManagerUrlCopied": "Adresa URL a Managerului de fișiere a fost copiată", + "copyFileManagerUrlAction": "Copiați adresa URL a managerului de fișiere" + }, + "settings": { + "host": { + "enable": { + "label": "Activează Managerul de fișiere" + }, + "defaultPath": { + "label": "Calea implicită" + }, + "scpLegacy": { + "label": "Modul SCP Legacy" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Fișiere" + } +} diff --git a/plugins/file-manager/locales/translated/ru_RU.json b/plugins/file-manager/locales/translated/ru_RU.json new file mode 100644 index 000000000..8e959f731 --- /dev/null +++ b/plugins/file-manager/locales/translated/ru_RU.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Хост не выбран", + "initializingEditor": "Инициализация редактора...", + "file": "Файл", + "folder": "Папка", + "uploadFile": "Загрузить файл", + "downloadFile": "Скачать", + "extractArchive": "Извлечь архив", + "extractingArchive": "Распаковка {{name}}...", + "archiveExtractedSuccessfully": "{{name}} успешно распакован", + "extractFailed": "Ошибка распаковки", + "compressFile": "Сжать файл", + "compressFiles": "Сжать файлы", + "compressFilesDesc": "Сжать {{count}} элементов в архив", + "archiveName": "Имя архива", + "enterArchiveName": "Введите имя архива...", + "compressionFormat": "Формат сжатия", + "selectedFiles": "Выбранные файлы", + "andMoreFiles": "и ещё {{count}}...", + "compress": "Сжать", + "compressingFiles": "Сжатие {{count}} элементов в {{name}}...", + "filesCompressedSuccessfully": "{{name}} успешно создан", + "compressFailed": "Ошибка сжатия", + "edit": "Редактировать", + "preview": "Предпросмотр", + "previous": "Назад", + "next": "Далее", + "pageXOfY": "Страница {{current}} из {{total}}", + "zoomOut": "Уменьшить", + "zoomIn": "Увеличить", + "newFile": "Новый файл", + "newFolder": "Новая папка", + "rename": "Переименовать", + "uploading": "Загрузка...", + "uploadingFile": "Загрузка {{name}}...", + "fileName": "Имя файла", + "folderName": "Имя папки", + "fileUploadedSuccessfully": "Файл «{{name}}» успешно загружен", + "failedToUploadFile": "Не удалось загрузить файл", + "uploadingFolderFiles": "Загрузка {{count}} файла(ов)...", + "uploadedFolderFiles": "Файл(ы) {{count}} успешно загружен(ы).", + "uploadedFolderPartial": "Загрузка {{uploaded}} файла(ов), {{failed}} не удалась.", + "fileDownloadedSuccessfully": "Файл «{{name}}» успешно скачан", + "failedToDownloadFile": "Не удалось скачать файл", + "fileCreatedSuccessfully": "Файл «{{name}}» успешно создан", + "folderCreatedSuccessfully": "Папка «{{name}}» успешно создана", + "failedToCreateItem": "Не удалось создать элемент", + "operationFailed": "Операция {{operation}} не удалась для {{name}}: {{error}}", + "failedToResolveSymlink": "Не удалось разрешить символическую ссылку", + "itemsDeletedSuccessfully": "{{count}} элементов успешно удалено", + "failedToDeleteItems": "Не удалось удалить элементы", + "sudoPasswordRequired": "Требуется пароль администратора", + "enterSudoPassword": "Введите пароль sudo для продолжения операции", + "sudoPassword": "Пароль sudo", + "sudoOperationFailed": "Операция sudo не удалась", + "sudoAuthFailed": "Аутентификация sudo не удалась", + "dragFilesToUpload": "Перетащите файлы сюда для загрузки", + "dropToUploadHere": "Перетащите файл для загрузки сюда.", + "dropToDownloadHere": "Скачать можно здесь (перетащите ссылку)", + "localFiles": "Местный", + "showLocalFiles": "Отображение локальных файлов (в режиме разделенного экрана)", + "hideLocalFiles": "Скрыть локальные файлы", + "localHome": "Домашняя папка", + "localPathPlaceholder": "Путь к локальной папке", + "localShowHidden": "Показать скрытые файлы", + "localHideHidden": "Скрыть скрытые файлы", + "localRevealInFileManager": "Показать в системном файловом менеджере", + "localCannotOpenFolder": "Не удаётся открыть папку", + "localItemCount": "{{count}} предметов", + "localSelectedCount": "{{count}} выбрано", + "back": "Назад", + "forward": "Вперед", + "up": "Вверх", + "kind": "Добрый", + "localUploadingProgress": "Загрузка {{current}} из {{total}}", + "localDownloadingProgress": "Загрузка {{current}} из {{total}}", + "localTransferInFlight": "{{count}} сразу", + "localPreparingDownload": "Подготовка к загрузке...", + "localTransferCancelled": "Трансфер отменен", + "localNothingToTransfer": "Нет ничего для перевода", + "localUploadComplete": "Загружено {{count}} файлов", + "localUploadFailed": "Загрузка не удалась.", + "localUploadPartial": "Загрузка {{done}} файла(ов), {{failed}} не удалась.", + "localDownloadComplete": "Загружено {{count}} файлов", + "localDownloadFailed": "Загрузка не удалась.", + "localDownloadPartial": "Загрузка {{done}} файла(ов), {{failed}} не удалась.", + "localReplaceExistingPrompt": "{{count}} Элемент(ы) уже существуют в этой папке. Заменить их?", + "localReplace": "Заменять", + "localSkipExisting": "Пропускать", + "localSkippedExisting": "Пропущено {{count}} существующих элементов", + "localUnsafeNamesSkipped": "Пропущено {{count}} элементов, названия которых нельзя использовать на этом компьютере", + "localOpen": "Открыть", + "localOpenFolder": "Открыть папку", + "localUploadToRemote": "Загрузка на сервер", + "localUploadToRemoteMany": "Загрузите {{count}} предметов на сервер", + "localMoveToTrash": "Переместить в мусорное ведро", + "localMoveToTrashMany": "Переместите {{count}} элементов в корзину", + "localTrashConfirmSingle": "Переместить \"{{name}}\" в корзину?", + "localTrashConfirmFolder": "Переместить папку \"{{name}}\" и всё её содержимое в корзину?", + "localTrashConfirmMany": "Переместить {{count}} элементов в корзину?", + "localTrashed": "Перемещено {{count}} предметов в корзину", + "localTrashFailed": "Не удалось переместить в мусорный бак.", + "localRenameFailed": "Переименование не удалось.", + "localCreateFailed": "Не удалось создать элемент.", + "goToParentFolder": "Перейдите в родительскую папку", + "resizeSidebar": "Перетащите ползунок, чтобы изменить размер боковой панели (двойной щелчок для сброса).", + "resizeLocalPane": "Перетащите курсор, чтобы изменить размер локальной панели (двойной щелчок для сброса).", + "columns": "Колонки", + "columnsHint": "Щелкните правой кнопкой мыши, чтобы выбрать столбцы.", + "emptyFolder": "Эта папка пуста", + "searchFiles": "Поиск файлов...", + "upload": "Загрузить", + "selectHostToStart": "Выберите хост для начала управления файлами", + "sshRequiredForFileManager": "Файловому менеджеру требуется SSH. У этого хоста SSH не включен.", + "failedToConnect": "Не удалось подключиться к SSH", + "failedToLoadDirectory": "Не удалось загрузить каталог", + "noSSHConnection": "Нет доступного SSH-подключения", + "copy": "Копировать", + "cut": "Вырезать", + "paste": "Вставить", + "copyPath": "Копировать путь", + "copyPaths": "Копировать пути", + "delete": "Удалить", + "properties": "Свойства", + "refresh": "Обновить", + "downloadFiles": "Скачать {{count}} файлов в браузер", + "copyFiles": "Копировать {{count}} элементов", + "cutFiles": "Вырезать {{count}} элементов", + "deleteFiles": "Удалить {{count}} элементов", + "filesCopiedToClipboard": "{{count}} элементов скопировано в буфер обмена", + "filesCutToClipboard": "{{count}} элементов вырезано в буфер обмена", + "pathCopiedToClipboard": "Путь скопирован в буфер обмена", + "pathsCopiedToClipboard": "{{count}} путей скопировано в буфер обмена", + "failedToCopyPath": "Не удалось скопировать путь в буфер обмена", + "copyFolderLink": "Скопировать ссылку на папку", + "copyCurrentFolderLink": "Скопировать ссылку на текущую папку", + "folderLinkCopied": "Ссылка на папку скопирована в буфер обмена", + "failedToCopyFolderLink": "Не удалось скопировать ссылку на папку", + "movedItems": "Перемещено {{count}} элементов", + "failedToDeleteItem": "Не удалось удалить элемент", + "itemRenamedSuccessfully": "{{type}} успешно переименован(а)", + "failedToRenameItem": "Не удалось переименовать элемент", + "download": "Скачать", + "openExternalEditor": "Открыть во внешнем редакторе", + "chooseExternalEditor": "Выбрать внешний редактор", + "externalEditorSelected": "Внешний редактор выбран", + "externalEditorDesktopOnly": "Внешний редактор доступен только в настольном приложении", + "externalEditorOpened": "Открыт во внешнем редакторе. Сохранение в нём загрузит изменения обратно на сервер.", + "failedToOpenExternalEditor": "Не удалось открыть внешний редактор", + "failedToSelectExternalEditor": "Не удалось выбрать внешний редактор", + "permissions": "Права доступа", + "size": "Размер", + "modified": "Изменён", + "path": "Путь", + "confirmDelete": "Вы уверены, что хотите удалить {{name}}?", + "permissionDenied": "Доступ запрещён", + "serverError": "Ошибка сервера", + "fileSavedSuccessfully": "Файл успешно сохранён", + "failedToSaveFile": "Не удалось сохранить файл", + "confirmDeleteSingleItem": "Вы уверены, что хотите безвозвратно удалить \"{{name}}\"?", + "confirmDeleteMultipleItems": "Вы уверены, что хотите безвозвратно удалить {{count}} элементов?", + "confirmDeleteMultipleItemsWithFolders": "Вы уверены, что хотите безвозвратно удалить {{count}} элементов? Включая папки и их содержимое.", + "confirmDeleteFolder": "Вы уверены, что хотите безвозвратно удалить папку \"{{name}}\" и всё её содержимое?", + "permanentDeleteWarning": "Это действие необратимо. Элемент(ы) будут безвозвратно удалены с сервера.", + "moveToTrashWarning": "Изделие(а) будет перемещено в мусорный контейнер Termix и может быть возвращено в рабочее состояние до истечения срока хранения.", + "itemsMovedToTrash": "{{count}} элемент(ы) перемещены в корзину", + "trash": "Мусор", + "trashDescription": "Удаленные элементы сохраняются на этом хосте в течение {{days}} дней.", + "trashLoadFailed": "Не удалось загрузить мусор.", + "trashEmpty": "Мусорное ведро пусто.", + "trashRestore": "Восстановить", + "trashRestored": "Восстановлено {{name}}", + "trashRestoreFailed": "Не удалось восстановить элемент. Возможно, исходный путь уже существует.", + "trashDeletePermanently": "Удалить навсегда", + "trashDeleteConfirm": "Удалить {{name}}навсегда? Это действие необратимо.", + "trashPermanentlyDeleted": "Удалено навсегда {{name}}", + "trashDeleteFailed": "Не удалось удалить элемент навсегда.", + "trashEmptyAction": "Опустошите мусорный бак", + "trashEmptyConfirm": "Удалить все элементы из корзины навсегда? Это действие необратимо.", + "trashEmptied": "Мусор вывезен", + "trashRetention": "Автоматическая очистка через (дней)", + "trashRetentionSaved": "Обновлена информация о хранении мусора.", + "trashRetentionFailed": "Не удалось обновить срок хранения корзины.", + "trashUnavailableConfirm": "Этот элемент нельзя переместить в корзину, как правило, потому что он находится в другой файловой системе или требует повышенных прав доступа. Может, лучше удалить его навсегда?", + "recent": "Недавние", + "pinned": "Закреплённые", + "folderShortcuts": "Ярлыки папок", + "failedToReconnectSSH": "Не удалось переподключить SSH-сессию", + "openTerminalHere": "Открыть терминал здесь", + "run": "Запустить", + "openTerminalInFolder": "Открыть терминал в этой папке", + "openTerminalInFileLocation": "Открыть терминал в расположении файла", + "runningFile": "Выполняется - {{file}}", + "onlyRunExecutableFiles": "Можно запускать только исполняемые файлы", + "directories": "Каталоги", + "removedFromRecentFiles": "Удалено \"{{name}}\" из недавних файлов", + "removeFailed": "Не удалось удалить", + "unpinnedSuccessfully": "Успешно откреплено \"{{name}}\"", + "unpinFailed": "Не удалось открепить", + "removedShortcut": "Ярлык \"{{name}}\" удалён", + "removeShortcutFailed": "Не удалось удалить ярлык", + "clearedAllRecentFiles": "Очищен список недавних файлов", + "clearFailed": "Очистка не удалась", + "removeFromRecentFiles": "Удалить из списка недавних файлов", + "clearAllRecentFiles": "Очистить список недавних файлов", + "unpinFile": "Открепить файл", + "removeShortcut": "Удалить ярлык", + "pinFile": "Закрепить файл", + "addToShortcuts": "Добавить в ярлыки", + "pasteFailed": "Вставка не удалась", + "noUndoableActions": "Нет действий для отмены", + "undoCopySuccess": "Отмена копирования: удалено {{count}} скопированных файлов", + "undoCopyFailedDelete": "Отмена не удалась: не удалось удалить скопированные файлы", + "undoCopyFailedNoInfo": "Отмена не удалась: не найдена информация о скопированных файлах", + "undoMoveSuccess": "Отмена перемещения: {{count}} файлов возвращено в исходное расположение", + "undoMoveFailedMove": "Отмена не удалась: не удалось вернуть файлы", + "undoMoveFailedNoInfo": "Отмена не удалась: не найдена информация о перемещённых файлах", + "undoDeleteNotSupported": "Операцию удаления нельзя отменить: файлы навсегда удалены с сервера", + "undoTypeNotSupported": "Неподдерживаемый тип операции отмены", + "undoOperationFailed": "Операция отмены не удалась", + "unknownError": "Неизвестная ошибка", + "confirm": "Подтвердить", + "find": "Поиск...", + "replace": "Заменить", + "downloadInstead": "Скачать вместо", + "keyboardShortcuts": "Горячие клавиши", + "searchAndReplace": "Поиск и замена", + "editing": "Редактирование", + "search": "Поиск", + "findNext": "Найти далее", + "findPrevious": "Найти предыдущий", + "save": "Сохранить", + "selectAll": "Выделить всё", + "undo": "Отменить", + "redo": "Повторить", + "moveLineUp": "Переместить строку вверх", + "moveLineDown": "Переместить строку вниз", + "toggleComment": "Переключить комментарий", + "autoComplete": "Автодополнение", + "imageLoadError": "Не удалось загрузить изображение", + "startTyping": "Начните вводить...", + "unknownSize": "Неизвестный размер", + "fileIsEmpty": "Файл пуст", + "largeFileWarning": "Предупреждение о большом файле", + "largeFileWarningDesc": "Размер этого файла {{size}}, что может вызвать проблемы с производительностью при открытии в текстовом виде.", + "fileNotFoundAndRemoved": "Файл \"{{name}}\" не найден и был удалён из недавних/закреплённых файлов", + "failedToLoadFile": "Не удалось загрузить файл: {{error}}", + "serverErrorOccurred": "Произошла ошибка сервера. Попробуйте позже.", + "autoSaveFailed": "Автосохранение не удалось", + "fileAutoSaved": "Файл автосохранён", + "moveFileFailed": "Не удалось переместить {{name}}", + "moveOperationFailed": "Операция перемещения не удалась", + "canOnlyCompareFiles": "Можно сравнивать только два файла", + "comparingFiles": "Сравнение файлов: {{file1}} и {{file2}}", + "dragFailed": "Операция перетаскивания не удалась", + "filePinnedSuccessfully": "Файл \"{{name}}\" успешно закреплён", + "pinFileFailed": "Не удалось закрепить файл", + "fileUnpinnedSuccessfully": "Файл \"{{name}}\" успешно откреплён", + "unpinFileFailed": "Не удалось открепить файл", + "shortcutAddedSuccessfully": "Ярлык папки \"{{name}}\" успешно добавлен", + "addShortcutFailed": "Не удалось добавить ярлык", + "operationCompletedSuccessfully": "{{operation}} {{count}} элементов успешно", + "operationCompleted": "{{operation}} {{count}} элементов", + "downloadFileSuccess": "Файл {{name}} успешно загружен", + "downloadFileFailed": "Не удалось загрузить", + "downloadingFile": "Загрузка {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Переместить в {{name}}", + "diffCompareWith": "Сравнить с {{name}}", + "dragOutsideToDownload": "Перетащите за пределы окна для загрузки ({{count}} файлов)", + "newFolderDefault": "Новая папка", + "newFileDefault": "Новый файл.txt", + "successfullyMovedItems": "Успешно перемещено {{count}} элементов в {{target}}", + "move": "Переместить", + "searchInFile": "Поиск в файле (Ctrl+F)", + "showKeyboardShortcuts": "Показать сочетания клавиш", + "decreaseFontSize": "Уменьшить размер шрифта", + "increaseFontSize": "Увеличить размер шрифта", + "startWritingMarkdown": "Начните писать Markdown-контент...", + "loadingFileComparison": "Загрузка сравнения файлов...", + "reload": "Перезагрузить", + "compare": "Сравнить", + "sideBySide": "Бок о бок", + "inline": "Встроенный", + "fileComparison": "Сравнение файлов: {{file1}} и {{file2}}", + "fileTooLarge": "Файл слишком большой: {{error}}", + "sshConnectionFailed": "Ошибка SSH-подключения. Проверьте подключение к {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Не удалось загрузить файл: {{error}}", + "connecting": "Подключение...", + "connectedSuccessfully": "Подключено успешно", + "totpVerificationFailed": "Ошибка проверки TOTP", + "warpgateVerificationFailed": "Ошибка аутентификации Warpgate", + "authenticationFailed": "Ошибка аутентификации", + "incorrectPassphrase": "Неверная парольная фраза. Попробуйте еще раз.", + "verificationCodePrompt": "Код подтверждения:", + "changePermissions": "Изменить права доступа", + "currentPermissions": "Текущие права доступа", + "owner": "Владелец", + "group": "Группа", + "others": "Остальные", + "read": "Чтение", + "write": "Запись", + "execute": "Выполнение", + "permissionsChangedSuccessfully": "Права доступа успешно изменены", + "failedToChangePermissions": "Не удалось изменить права доступа", + "name": "Имя", + "sortByName": "По имени", + "sortByDate": "По дате изменения", + "sortBySize": "По размеру", + "ascending": "По возрастанию", + "descending": "По убыванию", + "root": "Корень", + "new": "Создать", + "sortBy": "Сортировка", + "items": "Элементов", + "selected": "Выбрано", + "editor": "Редактор", + "octal": "Восьмеричный", + "storage": "Хранилище", + "disk": "Диск", + "used": "Занято", + "of": "из", + "toggleSidebar": "Переключить боковую панель", + "compactLayout": "Используйте компактную компоновку.", + "comfortableLayout": "Используйте удобную планировку", + "cannotLoadPdf": "Не удаётся загрузить PDF", + "pdfLoadError": "Ошибка при загрузке этого PDF-файла.", + "loadingPdf": "Загрузка PDF...", + "loadingPage": "Загрузка страницы..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Передача файлов между двумя SSH-хостами.", + "sourceServer": "Исходный сервер", + "destinationServer": "Сервер назначения", + "selectHost": "Выберите хост", + "noHostSelected": "Хост не выбран", + "move": "Двигаться", + "copy": "Копия", + "upload": "Загрузить", + "itemsSelected": "{{count}} выбранный элемент", + "itemsSelected_other": "{{count}} выбранные элементы", + "ready": "Готовый", + "loadingHosts": "Загрузка хостов...", + "name": "Имя", + "modified": "Модифицированный", + "size": "Размер", + "noFiles": "Нет файлов", + "open": "Открыть", + "up": "Вверх", + "refresh": "Обновить", + "copyToTarget": "Скопировать в целевую директорию", + "rename": "Переименовать", + "delete": "Удалить", + "editPermissions": "Изменить права доступа", + "createNewFolder": "Создать новую папку", + "deleteSelectedItems": "Удалить выбранные элементы", + "deleteConfirm": "Удалить {{count}} выбранный элемент?", + "deleteConfirm_other": "Удалить {{count}} выбранные элементы?", + "cancel": "Отмена", + "save": "Сохранять", + "folderCreated": "Создана папка", + "itemRenamed": "Элемент переименован", + "actionFailed": "Действие не удалось.", + "itemsDeleted": "{{count}} элемент удален", + "itemsDeleted_other": "{{count}} удаленные элементы", + "deleteFailed": "Удаление не удалось.", + "permissionsUpdated": "Права доступа обновлены.", + "noRemoteSessionConnected": "Удалённая сессия не подключена", + "failedToListFiles": "Не удалось отобразить список файлов.", + "failedToRefresh": "Не удалось обновить", + "destinationInsideSource": "Место назначения не может находиться внутри выбранного пути источника.", + "sameHostNoOp": "Исходная и целевая папки — одна и та же." + }, + "hosts": { + "fileManager": "Файловый менеджер", + "tabFiles": "Файлы", + "enableFileManagerMonitor": "Включить файловый менеджер", + "enableFileManagerMonitorDesc": "Просмотр и управление файлами на этом хосте через SFTP", + "scpLegacyLabel": "Устаревший режим SCP", + "scpLegacyDesc": "Использовать устаревший протокол передачи файлов для серверов, не поддерживающих подсистему SFTP (например, встроенные или минимальные SSH-серверы).", + "defaultPathLabel": "Путь по умолчанию", + "fileManagerPathHint": "Каталог, который открывается при запуске файлового менеджера для этого хоста.", + "fileManagerUrlCopied": "URL файлового менеджера скопирован", + "copyFileManagerUrlAction": "Копировать URL файлового менеджера" + }, + "settings": { + "host": { + "enable": { + "label": "Включить файловый менеджер" + }, + "defaultPath": { + "label": "Путь по умолчанию" + }, + "scpLegacy": { + "label": "Устаревший режим SCP" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Файлы" + } +} diff --git a/plugins/file-manager/locales/translated/sr_SP.json b/plugins/file-manager/locales/translated/sr_SP.json new file mode 100644 index 000000000..15c708588 --- /dev/null +++ b/plugins/file-manager/locales/translated/sr_SP.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Није изабран домаћин", + "initializingEditor": "Иницијализација уређивача...", + "file": "Датотека", + "folder": "Фасцикла", + "uploadFile": "Отпреми датотеку", + "downloadFile": "Преузми", + "extractArchive": "Издвоји архиву", + "extractingArchive": "Издвајање {{name}}...", + "archiveExtractedSuccessfully": "{{name}} је успешно извучено", + "extractFailed": "Издвајање није успело", + "compressFile": "Компресуј датотеку", + "compressFiles": "Компресуј датотеке", + "compressFilesDesc": "Компресуј {{count}} ставки у архиву", + "archiveName": "Назив архиве", + "enterArchiveName": "Унесите име архиве...", + "compressionFormat": "Формат компресије", + "selectedFiles": "Изабране датотеке", + "andMoreFiles": "и још {{count}}...", + "compress": "Компресуј", + "compressingFiles": "Компресија {{count}} ставки у {{name}}...", + "filesCompressedSuccessfully": "{{name}} је успешно креирано", + "compressFailed": "Компресија није успела", + "edit": "Измени", + "preview": "Преглед", + "previous": "Претходно", + "next": "Следеће", + "pageXOfY": "Страница {{current}} од {{total}}", + "zoomOut": "Умањи", + "zoomIn": "Увећај", + "newFile": "Нова датотека", + "newFolder": "Нова фасцикла", + "rename": "Преименуј", + "uploading": "Отпремање...", + "uploadingFile": "Отпремање {{name}}...", + "fileName": "Име датотеке", + "folderName": "Назив фасцикле", + "fileUploadedSuccessfully": "Датотека „{{name}}“ је успешно отпремљена", + "failedToUploadFile": "Отпремање датотеке није успело", + "uploadingFolderFiles": "Отпремање {{count}} датотека...", + "uploadedFolderFiles": "Успешно отпремљено {{count}} датотека", + "uploadedFolderPartial": "Отпремљено {{uploaded}} датотека, {{failed}} није успело", + "fileDownloadedSuccessfully": "Датотека „{{name}}“ је успешно преузета", + "failedToDownloadFile": "Преузимање датотеке није успело", + "fileCreatedSuccessfully": "Датотека „{{name}}“ је успешно креирана", + "folderCreatedSuccessfully": "Фасцикла „{{name}}“ је успешно креирана", + "failedToCreateItem": "Није успело креирање ставке", + "operationFailed": "Операција {{operation}} није успела за {{name}}: {{error}}", + "failedToResolveSymlink": "Није успело решавање симболичке везе", + "itemsDeletedSuccessfully": "{{count}} ставке су успешно обрисане", + "failedToDeleteItems": "Брисање ставки није успело", + "sudoPasswordRequired": "Потребна је администраторска лозинка", + "enterSudoPassword": "Унесите лозинку за судо да бисте наставили ову операцију", + "sudoPassword": "Sudo лозинка", + "sudoOperationFailed": "Sudo операција није успела", + "sudoAuthFailed": "Судо аутентификација није успела", + "dragFilesToUpload": "Отпустите датотеке овде да бисте их отпремили", + "dropToUploadHere": "Отпустите да бисте отпремили овде", + "dropToDownloadHere": "Отпустите да бисте преузели овде", + "localFiles": "Локално", + "showLocalFiles": "Прикажи локалне датотеке (подељени приказ)", + "hideLocalFiles": "Сакриј локалне датотеке", + "localHome": "Почетна фасцикла", + "localPathPlaceholder": "Путања локалне фасцикле", + "localShowHidden": "Прикажи скривене датотеке", + "localHideHidden": "Сакриј скривене датотеке", + "localRevealInFileManager": "Откриј у системском менаџеру датотека", + "localCannotOpenFolder": "Не могу да отворим фасциклу", + "localItemCount": "{{count}} ставке", + "localSelectedCount": "{{count}} изабрано", + "back": "Назад", + "forward": "Напред", + "up": "Горе", + "kind": "Врста", + "localUploadingProgress": "Отпремање {{current}} од {{total}}", + "localDownloadingProgress": "Преузимање {{current}} од {{total}}", + "localTransferInFlight": "{{count}} одједном", + "localPreparingDownload": "Припрема преузимања...", + "localTransferCancelled": "Трансфер отказан", + "localNothingToTransfer": "Ништа за пренос", + "localUploadComplete": "Отпремљено {{count}} датотека", + "localUploadFailed": "Отпремање није успело", + "localUploadPartial": "Отпремљено {{done}} датотека, {{failed}} није успело", + "localDownloadComplete": "Преузето {{count}} датотека", + "localDownloadFailed": "Преузимање није успело", + "localDownloadPartial": "Преузимање {{done}} датотека, {{failed}} није успело", + "localReplaceExistingPrompt": "{{count}} став(е) већ постоје у овој фасцикли. Заменити их?", + "localReplace": "Замени", + "localSkipExisting": "Прескочи", + "localSkippedExisting": "Прескочено {{count}} постојећих ставки", + "localUnsafeNamesSkipped": "Прескочено је {{count}} ставки чија имена се не могу користити на овом рачунару", + "localOpen": "Отворено", + "localOpenFolder": "Отвори фасциклу", + "localUploadToRemote": "Отпреми на сервер", + "localUploadToRemoteMany": "Отпреми {{count}} ставки на сервер", + "localMoveToTrash": "Премести у отпад", + "localMoveToTrashMany": "Премести {{count}} ставки у Отпад", + "localTrashConfirmSingle": "Преместити „{{name}}“ у Отпад?", + "localTrashConfirmFolder": "Преместити фасциклу „{{name}}“ и сав њен садржај у корпу за отпад?", + "localTrashConfirmMany": "Преместити {{count}} ставки у Отпад?", + "localTrashed": "Премештено {{count}} ставки у Отпад", + "localTrashFailed": "Није могуће преместити у Отпад", + "localRenameFailed": "Преименовање није успело", + "localCreateFailed": "Није могуће креирати ставку", + "goToParentFolder": "Иди у надређени фолдер", + "resizeSidebar": "Превуците да бисте променили величину бочне траке (двапут кликните да бисте ресетовали)", + "resizeLocalPane": "Превуците да бисте променили величину локалног окна (двапут кликните да бисте ресетовали)", + "columns": "Колоне", + "columnsHint": "Кликните десним тастером миша да бисте изабрали колоне", + "emptyFolder": "Ова фасцикла је празна", + "searchFiles": "Претражи датотеке...", + "upload": "Отпреми", + "selectHostToStart": "Изаберите хост да бисте започели управљање датотекама", + "sshRequiredForFileManager": "Менаџер датотека захтева SSH. Овај хост нема омогућен SSH.", + "failedToConnect": "Повезивање са SSH-ом није успело", + "failedToLoadDirectory": "Учитавање директоријума није успело", + "noSSHConnection": "SSH веза није доступна", + "copy": "Копија", + "cut": "Исеци", + "paste": "Залепи", + "copyPath": "Копирај путању", + "copyPaths": "Копирај путање", + "delete": "Обриши", + "properties": "Некретнине", + "refresh": "Освежи", + "downloadFiles": "Преузмите {{count}} датотека у прегледач", + "copyFiles": "Копирај {{count}} ставке", + "cutFiles": "Исеци {{count}} ставки", + "deleteFiles": "Обриши {{count}} ставки", + "filesCopiedToClipboard": "{{count}} ставке копиране у међуспремник", + "filesCutToClipboard": "{{count}} ставке исечене у међуспремник", + "pathCopiedToClipboard": "Путања је копирана у међуспремник", + "pathsCopiedToClipboard": "{{count}} путање копиране у међуспремник", + "failedToCopyPath": "Копирање путање у међуспремник није успело", + "copyFolderLink": "Копирај везу у фасциклу", + "copyCurrentFolderLink": "Копирај везу у тренутну фасциклу", + "folderLinkCopied": "Линк фасцикле копиран у међуспремник", + "failedToCopyFolderLink": "Копирање линка фасцикле није успело", + "movedItems": "Премештено {{count}} ставки", + "failedToDeleteItem": "Брисање ставке није успело", + "itemRenamedSuccessfully": "{{type}} је успешно преименован", + "failedToRenameItem": "Преименовање ставке није успело", + "download": "Преузми", + "openExternalEditor": "Отвори споља", + "chooseExternalEditor": "Изаберите спољни уређивач", + "externalEditorSelected": "Изабран је спољни уредник", + "externalEditorDesktopOnly": "Спољни уређивач је доступан само у десктоп апликацији", + "externalEditorOpened": "Отворено у екстерном уређивачу. Чувањем тамо ће се измене отпремити назад на сервер.", + "failedToOpenExternalEditor": "Отварање екстерног уређивача није успело", + "failedToSelectExternalEditor": "Није успело бирање спољног уређивача", + "permissions": "Дозволе", + "size": "Величина", + "modified": "Измењено", + "path": "Путања", + "confirmDelete": "Да ли сте сигурни да желите да обришете {{name}}?", + "permissionDenied": "Дозвола одбијена", + "serverError": "Грешка сервера", + "fileSavedSuccessfully": "Датотека је успешно сачувана", + "failedToSaveFile": "Чување датотеке није успело", + "confirmDeleteSingleItem": "Да ли сте сигурни да желите трајно да избришете „{{name}}“?", + "confirmDeleteMultipleItems": "Да ли сте сигурни да желите трајно да избришете {{count}} ставке?", + "confirmDeleteMultipleItemsWithFolders": "Да ли сте сигурни да желите трајно да избришете {{count}} ставке? Ово укључује фасцикле и њихов садржај.", + "confirmDeleteFolder": "Да ли сте сигурни да желите трајно да избришете фасциклу „{{name}}“ и сав њен садржај?", + "permanentDeleteWarning": "Ова радња се не може поништити. Ставка(е) ће бити трајно избрисане са сервера.", + "moveToTrashWarning": "Ставка(е) ће бити премештена(е) у смеће Термикса и може(ју) се вратити пре истека периода чувања.", + "itemsMovedToTrash": "{{count}} ставке премештено у Отпад", + "trash": "Смеће", + "trashDescription": "Обрисане ставке се чувају {{days}} дана на овом хосту.", + "trashLoadFailed": "Учитавање Отпада није успело", + "trashEmpty": "Отпад је празан", + "trashRestore": "Врати", + "trashRestored": "Враћено {{name}}", + "trashRestoreFailed": "Враћање ставке није успело. Оригинална путања можда већ постоји.", + "trashDeletePermanently": "Трајно избриши", + "trashDeleteConfirm": "Трајно избрисати {{name}}? Ово се не може поништити.", + "trashPermanentlyDeleted": "Трајно обрисано {{name}}", + "trashDeleteFailed": "Трајно брисање ставке није успело", + "trashEmptyAction": "Испразни отпад", + "trashEmptyConfirm": "Трајно избрисати сваку ставку у Отпаду? Ово се не може поништити.", + "trashEmptied": "Отпад је испражњен", + "trashRetention": "Аутоматско чишћење после (дана)", + "trashRetentionSaved": "Задржавање отпада је ажурирано", + "trashRetentionFailed": "Ажурирање задржавања у отпаду није успело", + "trashUnavailableConfirm": "Ова ставка не може бити премештена у Отпад, обично зато што се налази на другом систему датотека или захтева повишена права. Желите ли да је трајно избришете?", + "recent": "Недавно", + "pinned": "Закачено", + "folderShortcuts": "Пречице за фасцикле", + "failedToReconnectSSH": "Поновно повезивање SSH сесије није успело", + "openTerminalHere": "Отвори терминал овде", + "run": "Трчи", + "openTerminalInFolder": "Отвори терминал у овој фасцикли", + "openTerminalInFileLocation": "Отвори терминал на локацији датотеке", + "runningFile": "Трчање - {{file}}", + "onlyRunExecutableFiles": "Може покренути само извршне датотеке", + "directories": "Директоријуми", + "removedFromRecentFiles": "Уклоњено је „{{name}}“ из недавних датотека", + "removeFailed": "Уклањање није успело", + "unpinnedSuccessfully": "Откачено „{{name}}“ успешно", + "unpinFailed": "Откачивање није успело", + "removedShortcut": "Уклоњена пречица „{{name}}“", + "removeShortcutFailed": "Уклањање пречице није успело", + "clearedAllRecentFiles": "Обрисани су сви недавно објављени фајлови", + "clearFailed": "Брисање није успело", + "removeFromRecentFiles": "Уклони из недавних датотека", + "clearAllRecentFiles": "Обриши све недавно објављене датотеке", + "unpinFile": "Откачи датотеку", + "removeShortcut": "Уклони пречицу", + "pinFile": "Закачи датотеку", + "addToShortcuts": "Додај у пречице", + "pasteFailed": "Лепљење није успело", + "noUndoableActions": "Нема радњи које се могу поништити", + "undoCopySuccess": "Поништена операција копирања: Обрисано {{count}} копираних датотека", + "undoCopyFailedDelete": "Поништавање није успело: Није могуће обрисати ниједну копирану датотеку", + "undoCopyFailedNoInfo": "Поништавање није успело: Нису пронађене информације о копираној датотеци", + "undoMoveSuccess": "Поништена операција премештања: Премештено {{count}} датотека назад на оригиналну локацију", + "undoMoveFailedMove": "Поништавање није успело: Није могуће вратити датотеке", + "undoMoveFailedNoInfo": "Поништавање није успело: Нису пронађене информације о премештеној датотеци", + "undoDeleteNotSupported": "Брисање се не може поништити: Датотеке су трајно обрисане са сервера", + "undoTypeNotSupported": "Неподржани тип операције поништавања", + "undoOperationFailed": "Поништавање није успело", + "unknownError": "Непозната грешка", + "confirm": "Потврди", + "find": "Пронађи...", + "replace": "Замени", + "downloadInstead": "Преузми уместо тога", + "keyboardShortcuts": "Пречице на тастатури", + "searchAndReplace": "Претражи и замени", + "editing": "Уређивање", + "search": "Претрага", + "findNext": "Пронађи следеће", + "findPrevious": "Пронађи претходно", + "save": "Сачувај", + "selectAll": "Изабери све", + "undo": "Поништи", + "redo": "Понови", + "moveLineUp": "Помери ред нагоре", + "moveLineDown": "Помери ред надоле", + "toggleComment": "Укључи/искључи коментар", + "autoComplete": "Аутоматско довршавање", + "imageLoadError": "Учитавање слике није успело", + "startTyping": "Почни да куцаш...", + "unknownSize": "Непозната величина", + "fileIsEmpty": "Датотека је празна", + "largeFileWarning": "Упозорење о великој датотеци", + "largeFileWarningDesc": "Ова датотека је величине {{size}} , што може изазвати проблеме са перформансама када се отвори као текст.", + "fileNotFoundAndRemoved": "Датотека „{{name}}“ није пронађена и уклоњена је из недавно коришћених/закачених датотека", + "failedToLoadFile": "Учитавање датотеке није успело: {{error}}", + "serverErrorOccurred": "Дошло је до грешке сервера. Молимо покушајте поново касније.", + "autoSaveFailed": "Аутоматско чување није успело", + "fileAutoSaved": "Датотека је аутоматски сачувана", + "moveFileFailed": "Премештање није успело {{name}}", + "moveOperationFailed": "Операција премештања није успела", + "canOnlyCompareFiles": "Могуће је упоредити само две датотеке", + "comparingFiles": "Поређење датотека: {{file1}} и {{file2}}", + "dragFailed": "Превлачење није успело", + "filePinnedSuccessfully": "Датотека „{{name}}“ је успешно закачена", + "pinFileFailed": "Качење датотеке није успело", + "fileUnpinnedSuccessfully": "Датотека „{{name}}“ је успешно откачена", + "unpinFileFailed": "Откачивање датотеке није успело", + "shortcutAddedSuccessfully": "Пречица за фасциклу „{{name}}“ је успешно додата", + "addShortcutFailed": "Додавање пречице није успело", + "operationCompletedSuccessfully": "{{operation}} {{count}} ставке успешно", + "operationCompleted": "{{operation}} {{count}} ставке", + "downloadFileSuccess": "Датотека {{name}} је успешно преузета", + "downloadFileFailed": "Преузимање није успело", + "downloadingFile": "Преузимање {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Премести на {{name}}", + "diffCompareWith": "Разлика у поређењу са {{name}}", + "dragOutsideToDownload": "Превуците спољашњост прозора да бисте преузели ({{count}} датотека)", + "newFolderDefault": "Нови фолдер", + "newFileDefault": "НоваДатотека.txt", + "successfullyMovedItems": "Успешно премештено {{count}} ставки у {{target}}", + "move": "Помери", + "searchInFile": "Претражи у датотеци (Ctrl+F)", + "showKeyboardShortcuts": "Прикажи пречице на тастатури", + "decreaseFontSize": "Смањи величину фонта", + "increaseFontSize": "Повећај величину фонта", + "startWritingMarkdown": "Почните да пишете свој садржај у markdown формату...", + "loadingFileComparison": "Учитавање поређења датотека...", + "reload": "Поново учитај", + "compare": "Упореди", + "sideBySide": "Један поред другог", + "inline": "Уграђено", + "fileComparison": "Поређење датотека: {{file1}} наспрам {{file2}}", + "fileTooLarge": "Датотека је превелика: {{error}}", + "sshConnectionFailed": "SSH веза није успела. Молимо вас да проверите везу са {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Учитавање датотеке није успело: {{error}}", + "connecting": "Повезивање...", + "connectedSuccessfully": "Успешно повезано", + "totpVerificationFailed": "Верификација TOTP-а није успела", + "warpgateVerificationFailed": "Аутентификација Варпгејта није успела", + "authenticationFailed": "Аутентификација није успела", + "incorrectPassphrase": "Нетачна лозинка. Молимо покушајте поново.", + "verificationCodePrompt": "Верификациони код:", + "changePermissions": "Промени дозволе", + "currentPermissions": "Тренутне дозволе", + "owner": "Власник", + "group": "Група", + "others": "Други", + "read": "Читај", + "write": "Пиши", + "execute": "Изврши", + "permissionsChangedSuccessfully": "Дозволе су успешно промењене", + "failedToChangePermissions": "Промена дозвола није успела", + "name": "Име", + "sortByName": "Име", + "sortByDate": "Датум измене", + "sortBySize": "Величина", + "ascending": "Растуће", + "descending": "Опадајући", + "root": "Корен", + "new": "Ново", + "sortBy": "Сортирај по", + "items": "Ставке", + "selected": "Изабрано", + "editor": "Уредник", + "octal": "Октално", + "storage": "Складиштење", + "disk": "Диск", + "used": "Коришћено", + "of": "од", + "toggleSidebar": "Укључи/искључи бочну траку", + "compactLayout": "Користите компактни распоред", + "comfortableLayout": "Користите удобан распоред", + "cannotLoadPdf": "Није могуће учитати PDF", + "pdfLoadError": "Дошло је до грешке при учитавању ове PDF датотеке.", + "loadingPdf": "Учитавање PDF-а...", + "loadingPage": "Учитавање странице..." + }, + "sftpTransfer": { + "title": "СФТП", + "description": "Пренос датотека између два SSH хоста.", + "sourceServer": "Изворни сервер", + "destinationServer": "Одредишни сервер", + "selectHost": "Изаберите хоста", + "noHostSelected": "Није изабран домаћин", + "move": "Помери", + "copy": "Копија", + "upload": "Отпреми", + "itemsSelected": "{{count}} ставка је изабрана", + "itemsSelected_other": "Изабрано је {{count}} ставки", + "ready": "Спреман", + "loadingHosts": "Учитавање хостова...", + "name": "Име", + "modified": "Измењено", + "size": "Величина", + "noFiles": "Нема датотека", + "open": "Отворено", + "up": "Горе", + "refresh": "Освежи", + "copyToTarget": "Копирај у циљни директоријум", + "rename": "Преименуј", + "delete": "Обриши", + "editPermissions": "Измени дозволе", + "createNewFolder": "Направи нову фасциклу", + "deleteSelectedItems": "Обриши изабране ставке", + "deleteConfirm": "Обришите {{count}} изабрану ставку?", + "deleteConfirm_other": "Обришите {{count}} изабраних ставки?", + "cancel": "Откажи", + "save": "Сачувај", + "folderCreated": "Фасцикла је направљена", + "itemRenamed": "Ставка је преименована", + "actionFailed": "Акција није успела", + "itemsDeleted": "{{count}} ставка је обрисана", + "itemsDeleted_other": "{{count}} ставки су обрисане", + "deleteFailed": "Брисање није успело", + "permissionsUpdated": "Дозволе су ажуриране", + "noRemoteSessionConnected": "Није повезана удаљена сесија", + "failedToListFiles": "Навођење датотека није успело", + "failedToRefresh": "Освежавање није успело", + "destinationInsideSource": "Одредиште не може бити унутар изабране изворне путање.", + "sameHostNoOp": "Извор и одредиште су иста фасцикла." + }, + "hosts": { + "fileManager": "Менаџер датотека", + "tabFiles": "Датотеке", + "enableFileManagerMonitor": "Омогући менаџер датотека", + "enableFileManagerMonitorDesc": "Прегледајте и управљајте датотекама на овом хосту преко SFTP-а", + "scpLegacyLabel": "SCP Legacy Mode", + "scpLegacyDesc": "Користите старији пренос датотека за сервере који не подржавају SFTP подсистем (нпр. уграђени или минимални SSH сервери).", + "defaultPathLabel": "Подразумевана путања", + "fileManagerPathHint": "Директоријум који треба отворити када се покрене менаџер датотека за овај хост.", + "fileManagerUrlCopied": "URL менаџера датотека је копиран", + "copyFileManagerUrlAction": "Копирај URL менаџера датотека" + }, + "settings": { + "host": { + "enable": { + "label": "Омогући менаџер датотека" + }, + "defaultPath": { + "label": "Подразумевана путања" + }, + "scpLegacy": { + "label": "SCP Legacy Mode" + } + } + }, + "nav": { + "sftp": "СФТП", + "files": "Датотеке" + } +} diff --git a/plugins/file-manager/locales/translated/sv_SE.json b/plugins/file-manager/locales/translated/sv_SE.json new file mode 100644 index 000000000..ededda1fa --- /dev/null +++ b/plugins/file-manager/locales/translated/sv_SE.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Ingen värd vald", + "initializingEditor": "Initierar redigeraren...", + "file": "Fil", + "folder": "Mapp", + "uploadFile": "Ladda upp fil", + "downloadFile": "Ladda ner", + "extractArchive": "Utdragsarkiv", + "extractingArchive": "Extraherar {{name}}...", + "archiveExtractedSuccessfully": "{{name}} extraherades", + "extractFailed": "Extraheringen misslyckades", + "compressFile": "Komprimera fil", + "compressFiles": "Komprimera filer", + "compressFilesDesc": "Komprimera {{count}} objekt till ett arkiv", + "archiveName": "Arkivnamn", + "enterArchiveName": "Ange arkivnamn...", + "compressionFormat": "Komprimeringsformat", + "selectedFiles": "Valda filer", + "andMoreFiles": "och {{count}} mer...", + "compress": "Komprimera", + "compressingFiles": "Komprimerar {{count}} objekt till {{name}}...", + "filesCompressedSuccessfully": "{{name}} skapades", + "compressFailed": "Komprimeringen misslyckades", + "edit": "Redigera", + "preview": "Förhandsvisning", + "previous": "Tidigare", + "next": "Nästa", + "pageXOfY": "Sida {{current}} av {{total}}", + "zoomOut": "Zooma ut", + "zoomIn": "Zooma in", + "newFile": "Ny fil", + "newFolder": "Ny mapp", + "rename": "Döpa om", + "uploading": "Laddar upp...", + "uploadingFile": "Laddar upp {{name}}...", + "fileName": "Filnamn", + "folderName": "Mappnamn", + "fileUploadedSuccessfully": "Filen \"{{name}}\" har laddats upp", + "failedToUploadFile": "Misslyckades med att ladda upp filen", + "uploadingFolderFiles": "Laddar upp {{count}} fil(er)...", + "uploadedFolderFiles": "Uppladdade {{count}} fil(er) framgångsrikt", + "uploadedFolderPartial": "Uppladdade {{uploaded}} fil(er), {{failed}} misslyckades", + "fileDownloadedSuccessfully": "Filen \"{{name}}\" har laddats ner", + "failedToDownloadFile": "Misslyckades med att ladda ner filen", + "fileCreatedSuccessfully": "Filen \"{{name}}\" skapades", + "folderCreatedSuccessfully": "Mappen \"{{name}}\" skapades", + "failedToCreateItem": "Misslyckades med att skapa objektet", + "operationFailed": "{{operation}} -åtgärden misslyckades för {{name}}: {{error}}", + "failedToResolveSymlink": "Misslyckades med att lösa symbolisk länk", + "itemsDeletedSuccessfully": "{{count}} objekt har raderats", + "failedToDeleteItems": "Misslyckades med att ta bort objekt", + "sudoPasswordRequired": "Administratörslösenord krävs", + "enterSudoPassword": "Ange sudo-lösenordet för att fortsätta den här operationen", + "sudoPassword": "Sudo-lösenord", + "sudoOperationFailed": "Sudo-operationen misslyckades", + "sudoAuthFailed": "Sudo-autentisering misslyckades", + "dragFilesToUpload": "Släpp filer här för att ladda upp", + "dropToUploadHere": "Släpp för att ladda upp här", + "dropToDownloadHere": "Släpp för att ladda ner här", + "localFiles": "Lokal", + "showLocalFiles": "Visa lokala filer (delad vy)", + "hideLocalFiles": "Dölj lokala filer", + "localHome": "Hemmappen", + "localPathPlaceholder": "Sökväg till lokal mapp", + "localShowHidden": "Visa dolda filer", + "localHideHidden": "Dölj dolda filer", + "localRevealInFileManager": "Visa i systemets filhanterare", + "localCannotOpenFolder": "Kan inte öppna mappen", + "localItemCount": "{{count}} objekt", + "localSelectedCount": "{{count}} vald", + "back": "Tillbaka", + "forward": "Fram", + "up": "Upp", + "kind": "Slag", + "localUploadingProgress": "Laddar upp {{current}} av {{total}}", + "localDownloadingProgress": "Laddar ner {{current}} av {{total}}", + "localTransferInFlight": "{{count}} på en gång", + "localPreparingDownload": "Förbereder nedladdning...", + "localTransferCancelled": "Överföringen avbruten", + "localNothingToTransfer": "Inget att överföra", + "localUploadComplete": "Uppladdade {{count}} fil(er)", + "localUploadFailed": "Uppladdningen misslyckades", + "localUploadPartial": "Uppladdade {{done}} fil(er), {{failed}} misslyckades", + "localDownloadComplete": "Nedladdade {{count}} fil(er)", + "localDownloadFailed": "Nedladdningen misslyckades", + "localDownloadPartial": "Nedladdning av {{done}} fil(er), {{failed}} misslyckades", + "localReplaceExistingPrompt": "{{count}} objekt finns redan i den här mappen. Vill du ersätta dem?", + "localReplace": "Ersätta", + "localSkipExisting": "Hoppa", + "localSkippedExisting": "Hoppade över {{count}} befintliga objekt", + "localUnsafeNamesSkipped": "Hoppade över {{count}} objekt vars namn inte kan användas på den här datorn", + "localOpen": "Öppna", + "localOpenFolder": "Öppna mappen", + "localUploadToRemote": "Ladda upp till servern", + "localUploadToRemoteMany": "Ladda upp {{count}} objekt till servern", + "localMoveToTrash": "Flytta till papperskorgen", + "localMoveToTrashMany": "Flytta {{count}} objekt till papperskorgen", + "localTrashConfirmSingle": "Flytta \"{{name}}\" till papperskorgen?", + "localTrashConfirmFolder": "Flytta mappen \"{{name}}\" och allt dess innehåll till papperskorgen?", + "localTrashConfirmMany": "Flytta {{count}} objekt till papperskorgen?", + "localTrashed": "Flyttade {{count}} objekt till papperskorgen", + "localTrashFailed": "Kunde inte flytta till papperskorgen", + "localRenameFailed": "Byte av namn misslyckades", + "localCreateFailed": "Kunde inte skapa objektet", + "goToParentFolder": "Gå till överordnad mapp", + "resizeSidebar": "Dra för att ändra storlek på sidofältet (dubbelklicka för att återställa)", + "resizeLocalPane": "Dra för att ändra storlek på den lokala rutan (dubbelklicka för att återställa)", + "columns": "Kolumner", + "columnsHint": "Högerklicka för att välja kolumner", + "emptyFolder": "Den här mappen är tom", + "searchFiles": "Sök filer...", + "upload": "Ladda upp", + "selectHostToStart": "Välj en värd för att starta filhanteringen", + "sshRequiredForFileManager": "Filhanteraren kräver SSH. Den här värden har inte SSH aktiverat.", + "failedToConnect": "Misslyckades med att ansluta till SSH", + "failedToLoadDirectory": "Misslyckades med att ladda katalogen", + "noSSHConnection": "Ingen SSH-anslutning tillgänglig", + "copy": "Kopiera", + "cut": "Skära", + "paste": "Klistra", + "copyPath": "Kopiera sökväg", + "copyPaths": "Kopiera sökvägar", + "delete": "Radera", + "properties": "Fastigheter", + "refresh": "Uppdatera", + "downloadFiles": "Ladda ner {{count}} filer till webbläsaren", + "copyFiles": "Kopiera {{count}} objekt", + "cutFiles": "Klipp ut {{count}} objekt", + "deleteFiles": "Ta bort {{count}} objekt", + "filesCopiedToClipboard": "{{count}} objekt kopierade till urklipp", + "filesCutToClipboard": "{{count}} objekt klippta till urklipp", + "pathCopiedToClipboard": "Sökväg kopierad till urklipp", + "pathsCopiedToClipboard": "{{count}} sökvägar kopierade till urklipp", + "failedToCopyPath": "Misslyckades med att kopiera sökvägen till urklipp", + "copyFolderLink": "Kopiera länk till mapp", + "copyCurrentFolderLink": "Kopiera länk till aktuell mapp", + "folderLinkCopied": "Mapplänk kopierad till urklipp", + "failedToCopyFolderLink": "Misslyckades med att kopiera mapplänken", + "movedItems": "Flyttade {{count}} objekt", + "failedToDeleteItem": "Misslyckades med att ta bort objektet", + "itemRenamedSuccessfully": "{{type}} har bytt namn", + "failedToRenameItem": "Misslyckades med att byta namn på objektet", + "download": "Ladda ner", + "openExternalEditor": "Öppna externt", + "chooseExternalEditor": "Välj extern redigerare", + "externalEditorSelected": "Extern redigerare vald", + "externalEditorDesktopOnly": "Extern redigerare är endast tillgänglig i skrivbordsappen", + "externalEditorOpened": "Öppnad i extern redigerare. Om du sparar där laddas ändringarna upp tillbaka till servern.", + "failedToOpenExternalEditor": "Misslyckades med att öppna den externa redigeraren", + "failedToSelectExternalEditor": "Misslyckades med att välja extern redigerare", + "permissions": "Behörigheter", + "size": "Storlek", + "modified": "Ändrad", + "path": "Väg", + "confirmDelete": "Är du säker på att du vill ta bort {{name}}?", + "permissionDenied": "Tillstånd nekad", + "serverError": "Serverfel", + "fileSavedSuccessfully": "Filen har sparats", + "failedToSaveFile": "Misslyckades med att spara filen", + "confirmDeleteSingleItem": "Är du säker på att du vill ta bort \"{{name}} \" permanent?", + "confirmDeleteMultipleItems": "Är du säker på att du vill ta bort {{count}} objekt permanent?", + "confirmDeleteMultipleItemsWithFolders": "Är du säker på att du vill ta bort {{count}} objekt permanent? Detta inkluderar mappar och deras innehåll.", + "confirmDeleteFolder": "Är du säker på att du vill permanent ta bort mappen \"{{name}}\" och allt dess innehåll?", + "permanentDeleteWarning": "Den här åtgärden kan inte ångras. Objektet/objekten kommer att raderas permanent från servern.", + "moveToTrashWarning": "Objektet/objekten kommer att flyttas till Termix-papperskorgen och kan återställas innan lagringsperioden löper ut.", + "itemsMovedToTrash": "{{count}} objekt flyttade till papperskorgen", + "trash": "Skräp", + "trashDescription": "Borttagna objekt sparas i {{days}} dag(ar) på den här värden.", + "trashLoadFailed": "Misslyckades med att ladda papperskorgen", + "trashEmpty": "Papperskorgen är tom", + "trashRestore": "Återställa", + "trashRestored": "Återställd {{name}}", + "trashRestoreFailed": "Det gick inte att återställa objektet. Den ursprungliga sökvägen kan redan finnas.", + "trashDeletePermanently": "Ta bort permanent", + "trashDeleteConfirm": "Vill du permanent radera {{name}}? Detta kan inte ångras.", + "trashPermanentlyDeleted": "Permanent borttagen {{name}}", + "trashDeleteFailed": "Misslyckades med att ta bort objektet permanent", + "trashEmptyAction": "Töm papperskorgen", + "trashEmptyConfirm": "Vill du permanent radera alla objekt i papperskorgen? Det här kan inte ångras.", + "trashEmptied": "Papperskorgen tömd", + "trashRetention": "Automatisk rensning efter (dagar)", + "trashRetentionSaved": "Papperskorgen har lagrats uppdaterad", + "trashRetentionFailed": "Misslyckades med att uppdatera papperskorgen", + "trashUnavailableConfirm": "Det här objektet kan inte flyttas till papperskorgen, vanligtvis för att det finns på ett annat filsystem eller kräver utökade behörigheter. Vill du ta bort det permanent istället?", + "recent": "Ny", + "pinned": "Fäst", + "folderShortcuts": "Mappgenvägar", + "failedToReconnectSSH": "Misslyckades med att återansluta SSH-sessionen", + "openTerminalHere": "Öppna terminalen här", + "run": "Sikt", + "openTerminalInFolder": "Öppna terminalen i den här mappen", + "openTerminalInFileLocation": "Öppna terminalen på filplatsen", + "runningFile": "Löpning - {{file}}", + "onlyRunExecutableFiles": "Kan bara köra körbara filer", + "directories": "Kataloger", + "removedFromRecentFiles": "Tog bort \"{{name}}\" från senaste filer", + "removeFailed": "Borttagning misslyckades", + "unpinnedSuccessfully": "\"{{name}}\" har lossats", + "unpinFailed": "Misslyckades med att lossa", + "removedShortcut": "Tog bort genvägen \"{{name}}\"", + "removeShortcutFailed": "Misslyckades med att ta bort genvägen", + "clearedAllRecentFiles": "Rensade alla senaste filer", + "clearFailed": "Rensa misslyckades", + "removeFromRecentFiles": "Ta bort från senaste filer", + "clearAllRecentFiles": "Rensa alla senaste filer", + "unpinFile": "Lossa filen", + "removeShortcut": "Ta bort genväg", + "pinFile": "Fäst fil", + "addToShortcuts": "Lägg till i genvägar", + "pasteFailed": "Klistra in misslyckades", + "noUndoableActions": "Inga ångerbara åtgärder", + "undoCopySuccess": "Ångrad kopiering: Raderade {{count}} kopierade filer", + "undoCopyFailedDelete": "Ångra misslyckades: Kunde inte ta bort några kopierade filer", + "undoCopyFailedNoInfo": "Ångra misslyckades: Kunde inte hitta informationen om den kopierade filen", + "undoMoveSuccess": "Ångrade flyttåtgärd: Flyttade {{count}} filer tillbaka till ursprunglig plats", + "undoMoveFailedMove": "Ångra misslyckades: Kunde inte flytta tillbaka några filer", + "undoMoveFailedNoInfo": "Ångra misslyckades: Kunde inte hitta informationen om den flyttade filen", + "undoDeleteNotSupported": "Raderingen kan inte ångras: Filerna har raderats permanent från servern", + "undoTypeNotSupported": "Ångra-åtgärdstypen stöds inte", + "undoOperationFailed": "Ångraåtgärden misslyckades", + "unknownError": "Okänt fel", + "confirm": "Bekräfta", + "find": "Hitta...", + "replace": "Ersätta", + "downloadInstead": "Ladda ner istället", + "keyboardShortcuts": "Kortkommandon", + "searchAndReplace": "Sök och ersätt", + "editing": "Redigering", + "search": "Söka", + "findNext": "Hitta nästa", + "findPrevious": "Hitta föregående", + "save": "Spara", + "selectAll": "Markera alla", + "undo": "Ångra", + "redo": "Göra om", + "moveLineUp": "Flytta upp raden", + "moveLineDown": "Flytta raden nedåt", + "toggleComment": "Växla kommentar", + "autoComplete": "Autoslutför", + "imageLoadError": "Misslyckades med att ladda bilden", + "startTyping": "Börja skriva...", + "unknownSize": "Okänd storlek", + "fileIsEmpty": "Filen är tom", + "largeFileWarning": "Varning för stora filer", + "largeFileWarningDesc": "Den här filen är {{size}} stor, vilket kan orsaka prestandaproblem när den öppnas som text.", + "fileNotFoundAndRemoved": "Filen \"{{name}}\" hittades inte och har tagits bort från senaste/fästa filer", + "failedToLoadFile": "Misslyckades med att ladda filen: {{error}}", + "serverErrorOccurred": "Serverfel uppstod. Försök igen senare.", + "autoSaveFailed": "Automatisk sparning misslyckades", + "fileAutoSaved": "Filen sparades automatiskt", + "moveFileFailed": "Misslyckades med att flytta {{name}}", + "moveOperationFailed": "Flyttoperationen misslyckades", + "canOnlyCompareFiles": "Kan bara jämföra två filer", + "comparingFiles": "Jämför filer: {{file1}} och {{file2}}", + "dragFailed": "Dragningsåtgärden misslyckades", + "filePinnedSuccessfully": "Filen \"{{name}}\" har fästs", + "pinFileFailed": "Misslyckades med att fästa filen", + "fileUnpinnedSuccessfully": "Filen \"{{name}}\" har lossats", + "unpinFileFailed": "Misslyckades med att lossa filen", + "shortcutAddedSuccessfully": "Mappgenvägen \"{{name}}\" har lagts till", + "addShortcutFailed": "Misslyckades med att lägga till genväg", + "operationCompletedSuccessfully": "{{operation}} {{count}} objekt lyckades", + "operationCompleted": "{{operation}} {{count}} objekt", + "downloadFileSuccess": "Filen {{name}} har laddats ner", + "downloadFileFailed": "Nedladdningen misslyckades", + "downloadingFile": "Laddar ner {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Flytta till {{name}}", + "diffCompareWith": "Differensjämförelse med {{name}}", + "dragOutsideToDownload": "Dra utanför fönstret för att ladda ner ({{count}} filer)", + "newFolderDefault": "Nymapp", + "newFileDefault": "Nyfil.txt", + "successfullyMovedItems": "Flyttade {{count}} objekt till {{target}}", + "move": "Flytta", + "searchInFile": "Sök i filen (Ctrl+F)", + "showKeyboardShortcuts": "Visa kortkommandon", + "decreaseFontSize": "Minska teckenstorleken", + "increaseFontSize": "Öka teckenstorleken", + "startWritingMarkdown": "Börja skriva ditt nedskrivningsinnehåll...", + "loadingFileComparison": "Laddar filjämförelse...", + "reload": "Ladda om", + "compare": "Jämföra", + "sideBySide": "Sida vid sida", + "inline": "Inline", + "fileComparison": "Filjämförelse: {{file1}} vs {{file2}}", + "fileTooLarge": "Filen är för stor: {{error}}", + "sshConnectionFailed": "SSH-anslutningen misslyckades. Kontrollera din anslutning till {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Misslyckades med att ladda filen: {{error}}", + "connecting": "Ansluter...", + "connectedSuccessfully": "Ansluten", + "totpVerificationFailed": "TOTP-verifiering misslyckades", + "warpgateVerificationFailed": "Warpgate-autentisering misslyckades", + "authenticationFailed": "Autentiseringen misslyckades", + "incorrectPassphrase": "Felaktigt lösenord. Försök igen.", + "verificationCodePrompt": "Verifieringskod:", + "changePermissions": "Ändra behörigheter", + "currentPermissions": "Nuvarande behörigheter", + "owner": "Ägare", + "group": "Grupp", + "others": "Andra", + "read": "Läsa", + "write": "Skriva", + "execute": "Utföra", + "permissionsChangedSuccessfully": "Behörigheter har ändrats", + "failedToChangePermissions": "Misslyckades med att ändra behörigheter", + "name": "Namn", + "sortByName": "Namn", + "sortByDate": "Datum ändrat", + "sortBySize": "Storlek", + "ascending": "Stigande", + "descending": "Fallande", + "root": "Rot", + "new": "Ny", + "sortBy": "Sortera efter", + "items": "Föremål", + "selected": "Vald", + "editor": "Redaktör", + "octal": "Oktal", + "storage": "Lagring", + "disk": "Disk", + "used": "Begagnad", + "of": "av", + "toggleSidebar": "Växla sidofält", + "compactLayout": "Använd kompakt layout", + "comfortableLayout": "Använd bekväm layout", + "cannotLoadPdf": "Kan inte ladda PDF-filen", + "pdfLoadError": "Det uppstod ett fel när PDF-filen laddades.", + "loadingPdf": "Laddar PDF...", + "loadingPage": "Laddar sida..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Överför filer mellan två SSH-värdar.", + "sourceServer": "Källserver", + "destinationServer": "Destinationsserver", + "selectHost": "Välj värd", + "noHostSelected": "Ingen värd vald", + "move": "Flytta", + "copy": "Kopiera", + "upload": "Ladda upp", + "itemsSelected": "{{count}} objekt valt", + "itemsSelected_other": "{{count}} objekt valda", + "ready": "Redo", + "loadingHosts": "Laddar värdar...", + "name": "Namn", + "modified": "Ändrad", + "size": "Storlek", + "noFiles": "Inga filer", + "open": "Öppna", + "up": "Upp", + "refresh": "Uppdatera", + "copyToTarget": "Kopiera till målkatalog", + "rename": "Döpa om", + "delete": "Radera", + "editPermissions": "Redigera behörigheter", + "createNewFolder": "Skapa ny mapp", + "deleteSelectedItems": "Ta bort valda objekt", + "deleteConfirm": "Ta bort {{count}} markerat objekt?", + "deleteConfirm_other": "Ta bort {{count}} markerade objekt?", + "cancel": "Avboka", + "save": "Spara", + "folderCreated": "Mapp skapad", + "itemRenamed": "Artikeln har bytt namn", + "actionFailed": "Åtgärden misslyckades", + "itemsDeleted": "{{count}} objekt borttaget", + "itemsDeleted_other": "{{count}} objekt raderade", + "deleteFailed": "Raderingen misslyckades", + "permissionsUpdated": "Behörigheter uppdaterade", + "noRemoteSessionConnected": "Ingen fjärrsession ansluten", + "failedToListFiles": "Misslyckades med att lista filer", + "failedToRefresh": "Misslyckades med att uppdatera", + "destinationInsideSource": "Destinationen får inte vara inom den valda källsökvägen.", + "sameHostNoOp": "Källa och destination är samma mapp." + }, + "hosts": { + "fileManager": "Filhanteraren", + "tabFiles": "Filer", + "enableFileManagerMonitor": "Aktivera filhanteraren", + "enableFileManagerMonitorDesc": "Bläddra bland och hantera filer på den här värden via SFTP", + "scpLegacyLabel": "SCP Legacy-läge", + "scpLegacyDesc": "Använd äldre filöverföring för servrar som inte stöder SFTP-undersystemet (t.ex. inbäddade eller minimala SSH-servrar).", + "defaultPathLabel": "Standardsökväg", + "fileManagerPathHint": "Katalogen som ska öppnas när filhanteraren startas för den här värden.", + "fileManagerUrlCopied": "Filhanterarens URL kopierad", + "copyFileManagerUrlAction": "Kopiera filhanterarens URL" + }, + "settings": { + "host": { + "enable": { + "label": "Aktivera filhanteraren" + }, + "defaultPath": { + "label": "Standardsökväg" + }, + "scpLegacy": { + "label": "SCP Legacy-läge" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Filer" + } +} diff --git a/plugins/file-manager/locales/translated/th_TH.json b/plugins/file-manager/locales/translated/th_TH.json new file mode 100644 index 000000000..49e71e2ae --- /dev/null +++ b/plugins/file-manager/locales/translated/th_TH.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "ไม่ได้เลือกโฮสต์", + "initializingEditor": "กำลังเริ่มต้นโปรแกรมแก้ไข...", + "file": "ไฟล์", + "folder": "โฟลเดอร์", + "uploadFile": "อัปโหลดไฟล์", + "downloadFile": "ดาวน์โหลด", + "extractArchive": "แตกไฟล์เก็บถาวร", + "extractingArchive": "กำลังดึงข้อมูล {{name}}...", + "archiveExtractedSuccessfully": "{{name}} ดึงข้อมูลสำเร็จแล้ว", + "extractFailed": "การดึงข้อมูลล้มเหลว", + "compressFile": "บีบอัดไฟล์", + "compressFiles": "บีบอัดไฟล์", + "compressFilesDesc": "บีบอัดรายการ {{count}} รายการลงในไฟล์เก็บถาวร", + "archiveName": "ชื่อไฟล์เก็บถาวร", + "enterArchiveName": "ป้อนชื่อไฟล์เก็บถาวร...", + "compressionFormat": "รูปแบบการบีบอัด", + "selectedFiles": "ไฟล์ที่เลือก", + "andMoreFiles": "และ {{count}} เพิ่มเติม...", + "compress": "บีบอัด", + "compressingFiles": "บีบอัดรายการ {{count}} รายการลงใน {{name}} รายการ ...", + "filesCompressedSuccessfully": "{{name}} สร้างสำเร็จแล้ว", + "compressFailed": "การบีบอัดล้มเหลว", + "edit": "แก้ไข", + "preview": "ตัวอย่าง", + "previous": "ก่อนหน้า", + "next": "ต่อไป", + "pageXOfY": "หน้า {{current}} จาก {{total}}", + "zoomOut": "ซูมออก", + "zoomIn": "ซูมเข้า", + "newFile": "ไฟล์ใหม่", + "newFolder": "โฟลเดอร์ใหม่", + "rename": "เปลี่ยนชื่อ", + "uploading": "กำลังอัปโหลด...", + "uploadingFile": "กำลังอัปโหลด {{name}}...", + "fileName": "ชื่อไฟล์", + "folderName": "ชื่อโฟลเดอร์", + "fileUploadedSuccessfully": "ไฟล์ \"{{name}}\" อัปโหลดสำเร็จแล้ว", + "failedToUploadFile": "ไม่สามารถอัปโหลดไฟล์ได้", + "uploadingFolderFiles": "กำลังอัปโหลดไฟล์ {{count}} ไฟล์...", + "uploadedFolderFiles": "อัปโหลดไฟล์ {{count}} ไฟล์สำเร็จแล้ว", + "uploadedFolderPartial": "อัปโหลดไฟล์ {{uploaded}} ไฟล์, {{failed}} ล้มเหลว", + "fileDownloadedSuccessfully": "ดาวน์โหลดไฟล์ \"{{name}}\" สำเร็จแล้ว", + "failedToDownloadFile": "ไม่สามารถดาวน์โหลดไฟล์ได้", + "fileCreatedSuccessfully": "สร้างไฟล์ \"{{name}}\" สำเร็จแล้ว", + "folderCreatedSuccessfully": "สร้างโฟลเดอร์ \"{{name}}\" สำเร็จแล้ว", + "failedToCreateItem": "ไม่สามารถสร้างรายการได้", + "operationFailed": "การดำเนินการ {{operation}} ล้มเหลวสำหรับ {{name}}: {{error}}", + "failedToResolveSymlink": "ไม่สามารถแก้ไขลิงก์สัญลักษณ์ได้", + "itemsDeletedSuccessfully": "รายการ {{count}} ถูกลบสำเร็จแล้ว", + "failedToDeleteItems": "ไม่สามารถลบรายการได้", + "sudoPasswordRequired": "ต้องใช้รหัสผ่านผู้ดูแลระบบ", + "enterSudoPassword": "ป้อนรหัสผ่าน sudo เพื่อดำเนินการต่อ", + "sudoPassword": "รหัสผ่าน Sudo", + "sudoOperationFailed": "การดำเนินการ Sudo ล้มเหลว", + "sudoAuthFailed": "การตรวจสอบสิทธิ์ Sudo ล้มเหลว", + "dragFilesToUpload": "ลากไฟล์มาวางที่นี่เพื่ออัปโหลด", + "dropToUploadHere": "ลากเส้นเพื่ออัปโหลดที่นี่", + "dropToDownloadHere": "คลิกที่นี่เพื่อดาวน์โหลด", + "localFiles": "ท้องถิ่น", + "showLocalFiles": "แสดงไฟล์ในเครื่อง (มุมมองแบบแบ่งหน้าจอ)", + "hideLocalFiles": "ซ่อนไฟล์ในเครื่อง", + "localHome": "โฟลเดอร์บ้าน", + "localPathPlaceholder": "เส้นทางโฟลเดอร์ในเครื่อง", + "localShowHidden": "แสดงไฟล์ที่ซ่อนอยู่", + "localHideHidden": "ซ่อนไฟล์ที่ซ่อนไว้", + "localRevealInFileManager": "เปิดเผยในตัวจัดการไฟล์ระบบ", + "localCannotOpenFolder": "ไม่สามารถเปิดโฟลเดอร์ได้", + "localItemCount": "{{count}} รายการ", + "localSelectedCount": "{{count}} เลือกแล้ว", + "back": "กลับ", + "forward": "ซึ่งไปข้างหน้า", + "up": "ขึ้น", + "kind": "ใจดี", + "localUploadingProgress": "กำลังอัปโหลด {{current}} จาก {{total}}", + "localDownloadingProgress": "กำลังดาวน์โหลด {{current}} จาก {{total}}", + "localTransferInFlight": "{{count}} ทันที", + "localPreparingDownload": "กำลังเตรียมดาวน์โหลด...", + "localTransferCancelled": "การโอนเงินถูกยกเลิก", + "localNothingToTransfer": "ไม่มีอะไรต้องโอน", + "localUploadComplete": "อัปโหลดไฟล์ {{count}} ไฟล์แล้ว", + "localUploadFailed": "การอัปโหลดล้มเหลว", + "localUploadPartial": "อัปโหลดไฟล์ {{done}} ไฟล์, {{failed}} ล้มเหลว", + "localDownloadComplete": "ดาวน์โหลดไฟล์ {{count}} ไฟล์แล้ว", + "localDownloadFailed": "การดาวน์โหลดล้มเหลว", + "localDownloadPartial": "ดาวน์โหลดไฟล์ {{done}} ไฟล์, {{failed}} ไฟล์ล้มเหลว", + "localReplaceExistingPrompt": "{{count}} รายการมีอยู่แล้วในโฟลเดอร์นี้ ต้องการแทนที่หรือไม่?", + "localReplace": "แทนที่", + "localSkipExisting": "ข้าม", + "localSkippedExisting": "ข้ามรายการที่มีอยู่ {{count}} รายการ", + "localUnsafeNamesSkipped": "ข้ามรายการ {{count}} รายการ เนื่องจากไม่สามารถใช้ชื่อรายการดังกล่าวในคอมพิวเตอร์เครื่องนี้ได้", + "localOpen": "เปิด", + "localOpenFolder": "เปิดโฟลเดอร์", + "localUploadToRemote": "อัปโหลดไปยังเซิร์ฟเวอร์", + "localUploadToRemoteMany": "อัปโหลด {{count}} รายการไปยังเซิร์ฟเวอร์", + "localMoveToTrash": "ย้ายไปที่ถังขยะ", + "localMoveToTrashMany": "ย้ายรายการ {{count}} รายการไปยังถังขยะ", + "localTrashConfirmSingle": "ย้าย \"{{name}}\" ไปที่ถังขยะ?", + "localTrashConfirmFolder": "ย้ายโฟลเดอร์ \"{{name}}\" และเนื้อหาทั้งหมดไปที่ถังขยะ?", + "localTrashConfirmMany": "ย้ายรายการ {{count}} รายการไปยังถังขยะหรือไม่?", + "localTrashed": "ย้ายรายการ {{count}} รายการไปยังถังขยะ", + "localTrashFailed": "ไม่สามารถย้ายไปถังขยะได้", + "localRenameFailed": "การเปลี่ยนชื่อล้มเหลว", + "localCreateFailed": "ไม่สามารถสร้างรายการได้", + "goToParentFolder": "ไปที่โฟลเดอร์หลัก", + "resizeSidebar": "ลากเพื่อปรับขนาดแถบด้านข้าง (ดับเบิ้ลคลิกเพื่อรีเซ็ต)", + "resizeLocalPane": "ลากเพื่อปรับขนาดบานหน้าต่างเฉพาะที่ (ดับเบิ้ลคลิกเพื่อรีเซ็ต)", + "columns": "คอลัมน์", + "columnsHint": "คลิกขวาเพื่อเลือกคอลัมน์", + "emptyFolder": "โฟลเดอร์นี้ว่างเปล่า", + "searchFiles": "ค้นหาไฟล์...", + "upload": "อัปโหลด", + "selectHostToStart": "เลือกโฮสต์เพื่อเริ่มการจัดการไฟล์", + "sshRequiredForFileManager": "โปรแกรมจัดการไฟล์ต้องการการเชื่อมต่อ SSH แต่เครื่องนี้ไม่ได้เปิดใช้งาน SSH", + "failedToConnect": "ไม่สามารถเชื่อมต่อ SSH ได้", + "failedToLoadDirectory": "ไม่สามารถโหลดไดเร็กทอรีได้", + "noSSHConnection": "ไม่มีการเชื่อมต่อ SSH ให้ใช้งาน", + "copy": "สำเนา", + "cut": "ตัด", + "paste": "แปะ", + "copyPath": "คัดลอกเส้นทาง", + "copyPaths": "คัดลอกเส้นทาง", + "delete": "ลบ", + "properties": "คุณสมบัติ", + "refresh": "รีเฟรช", + "downloadFiles": "ดาวน์โหลดไฟล์ {{count}} ไปยังเบราว์เซอร์", + "copyFiles": "คัดลอกรายการ {{count}} รายการ", + "cutFiles": "ตัด {{count}} รายการ", + "deleteFiles": "ลบรายการ {{count}} รายการ", + "filesCopiedToClipboard": "{{count}} รายการที่คัดลอกไปยังคลิปบอร์ด", + "filesCutToClipboard": "{{count}} รายการที่ถูกตัดไปยังคลิปบอร์ด", + "pathCopiedToClipboard": "คัดลอกเส้นทางไปยังคลิปบอร์ดแล้ว", + "pathsCopiedToClipboard": "{{count}} คัดลอกเส้นทางไปยังคลิปบอร์ดแล้ว", + "failedToCopyPath": "ไม่สามารถคัดลอกเส้นทางไปยังคลิปบอร์ดได้", + "copyFolderLink": "คัดลink ไปยังโฟลเดอร์", + "copyCurrentFolderLink": "คัดลink ไปยังโฟลเดอร์ปัจจุบัน", + "folderLinkCopied": "คัดลอกลิงก์โฟลเดอร์ไปยังคลิปบอร์ดแล้ว", + "failedToCopyFolderLink": "ไม่สามารถคัดลอกลิงก์โฟลเดอร์ได้", + "movedItems": "ย้ายรายการ {{count}} รายการ", + "failedToDeleteItem": "ไม่สามารถลบรายการได้", + "itemRenamedSuccessfully": "{{type}} เปลี่ยนชื่อสำเร็จแล้ว", + "failedToRenameItem": "ไม่สามารถเปลี่ยนชื่อรายการได้", + "download": "ดาวน์โหลด", + "openExternalEditor": "เปิดจากภายนอก", + "chooseExternalEditor": "เลือกตัวแก้ไขภายนอก", + "externalEditorSelected": "เลือกบรรณาธิการภายนอก", + "externalEditorDesktopOnly": "โปรแกรมแก้ไขภายนอกใช้งานได้เฉพาะในแอปพลิเคชันบนเดสก์ท็อปเท่านั้น", + "externalEditorOpened": "เปิดในโปรแกรมแก้ไขภายนอก การบันทึกในโปรแกรมแก้ไขนั้นจะอัปโหลดการเปลี่ยนแปลงกลับไปยังเซิร์ฟเวอร์", + "failedToOpenExternalEditor": "ไม่สามารถเปิดโปรแกรมแก้ไขภายนอกได้", + "failedToSelectExternalEditor": "ไม่สามารถเลือกตัวแก้ไขภายนอกได้", + "permissions": "สิทธิ์การเข้าถึง", + "size": "ขนาด", + "modified": "แก้ไขแล้ว", + "path": "เส้นทาง", + "confirmDelete": "คุณแน่ใจหรือไม่ว่าต้องการลบ {{name}}?", + "permissionDenied": "ไม่ได้รับอนุญาต", + "serverError": "ข้อผิดพลาดของเซิร์ฟเวอร์", + "fileSavedSuccessfully": "บันทึกไฟล์สำเร็จแล้ว", + "failedToSaveFile": "ไม่สามารถบันทึกไฟล์ได้", + "confirmDeleteSingleItem": "คุณแน่ใจหรือไม่ว่าต้องการลบ \"{{name}} \" อย่างถาวร?", + "confirmDeleteMultipleItems": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการ {{count}} อย่างถาวร?", + "confirmDeleteMultipleItemsWithFolders": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการ {{count}} รายการอย่างถาวร ซึ่งรวมถึงโฟลเดอร์และเนื้อหาภายในด้วย", + "confirmDeleteFolder": "คุณแน่ใจหรือไม่ว่าต้องการลบโฟลเดอร์ \"{{name}}\" และเนื้อหาทั้งหมดในนั้นอย่างถาวร?", + "permanentDeleteWarning": "การดำเนินการนี้ไม่สามารถยกเลิกได้ รายการดังกล่าวจะถูกลบออกจากเซิร์ฟเวอร์อย่างถาวร", + "moveToTrashWarning": "ไฟล์ดังกล่าวจะถูกย้ายไปยังถังขยะของ Termix และสามารถกู้คืนได้ก่อนที่ระยะเวลาการเก็บรักษาจะหมดลง", + "itemsMovedToTrash": "{{count}} รายการถูกย้ายไปยังถังขยะ", + "trash": "ขยะ", + "trashDescription": "รายการที่ถูกลบจะยังคงอยู่เป็นเวลา {{days}} วันบนโฮสต์นี้", + "trashLoadFailed": "ไม่สามารถโหลดถังขยะได้", + "trashEmpty": "ถังขยะว่างเปล่า", + "trashRestore": "คืนค่า", + "trashRestored": "กู้คืนแล้ว {{name}}", + "trashRestoreFailed": "ไม่สามารถกู้คืนรายการได้ เส้นทางเดิมอาจมีอยู่แล้ว", + "trashDeletePermanently": "ลบอย่างถาวร", + "trashDeleteConfirm": "ลบ {{name}}อย่างถาวรหรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้", + "trashPermanentlyDeleted": "ลบถาวร {{name}}", + "trashDeleteFailed": "ไม่สามารถลบรายการอย่างถาวรได้", + "trashEmptyAction": "ขยะทิ้ง", + "trashEmptyConfirm": "ต้องการลบทุกอย่างในถังขยะอย่างถาวรหรือไม่? การกระทำนี้ไม่สามารถย้อนกลับได้", + "trashEmptied": "ขยะถูกเก็บแล้ว", + "trashRetention": "ล้างข้อมูลอัตโนมัติหลังจาก (วัน)", + "trashRetentionSaved": "การปรับปรุงการเก็บรักษาขยะ", + "trashRetentionFailed": "ไม่สามารถอัปเดตการเก็บรักษาข้อมูลถังขยะได้", + "trashUnavailableConfirm": "ไม่สามารถย้ายรายการนี้ไปยังถังขยะได้ เนื่องจากไฟล์นี้อยู่ในระบบไฟล์อื่น หรือต้องใช้สิทธิ์ระดับสูง ต้องการลบอย่างถาวรแทนหรือไม่?", + "recent": "ล่าสุด", + "pinned": "ปักหมุด", + "folderShortcuts": "ทางลัดโฟลเดอร์", + "failedToReconnectSSH": "ไม่สามารถเชื่อมต่อเซสชัน SSH ใหม่ได้", + "openTerminalHere": "เปิดเทอร์มินัลที่นี่", + "run": "วิ่ง", + "openTerminalInFolder": "เปิดเทอร์มินัลในโฟลเดอร์นี้", + "openTerminalInFileLocation": "เปิดเทอร์มินัลที่ตำแหน่งไฟล์", + "runningFile": "การวิ่ง - {{file}}", + "onlyRunExecutableFiles": "สามารถเรียกใช้งานได้เฉพาะไฟล์ปฏิบัติการเท่านั้น", + "directories": "รายชื่อ", + "removedFromRecentFiles": "ลบ \"{{name}}\" ออกจากไฟล์ล่าสุดแล้ว", + "removeFailed": "ลบสิ่งที่ล้มเหลว", + "unpinnedSuccessfully": "ยกเลิกการตรึง \"{{name}}\" สำเร็จแล้ว", + "unpinFailed": "ยกเลิกการตรึงไม่สำเร็จ", + "removedShortcut": "ลบทางลัด \"{{name}} \" ออกแล้ว", + "removeShortcutFailed": "การลบทางลัดล้มเหลว", + "clearedAllRecentFiles": "ลบไฟล์ล่าสุดทั้งหมดแล้ว", + "clearFailed": "เคลียร์ล้มเหลว", + "removeFromRecentFiles": "ลบออกจากไฟล์ล่าสุด", + "clearAllRecentFiles": "ลบไฟล์ล่าสุดทั้งหมด", + "unpinFile": "ยกเลิกการตรึงไฟล์", + "removeShortcut": "ลบทางลัด", + "pinFile": "ไฟล์พิน", + "addToShortcuts": "เพิ่มไปยังทางลัด", + "pasteFailed": "การวางล้มเหลว", + "noUndoableActions": "ไม่มีการกระทำใดที่ย้อนกลับไม่ได้", + "undoCopySuccess": "ยกเลิกการคัดลอก: ลบไฟล์ที่คัดลอก {{count}} ไฟล์", + "undoCopyFailedDelete": "การยกเลิกการกระทำล้มเหลว: ไม่สามารถลบไฟล์ที่คัดลอกไว้ได้", + "undoCopyFailedNoInfo": "การยกเลิกการกระทำล้มเหลว: ไม่พบข้อมูลไฟล์ที่คัดลอกไว้", + "undoMoveSuccess": "ยกเลิกการย้าย: ย้ายไฟล์ {{count}} ไฟล์กลับไปยังตำแหน่งเดิม", + "undoMoveFailedMove": "การยกเลิกไม่สำเร็จ: ไม่สามารถย้ายไฟล์ใดๆ กลับได้", + "undoMoveFailedNoInfo": "การยกเลิกไม่สำเร็จ: ไม่พบข้อมูลไฟล์ที่ถูกย้าย", + "undoDeleteNotSupported": "ไม่สามารถยกเลิกการลบได้: ไฟล์ถูกลบออกจากเซิร์ฟเวอร์อย่างถาวรแล้ว", + "undoTypeNotSupported": "ประเภทการดำเนินการยกเลิกที่ไม่รองรับ", + "undoOperationFailed": "การยกเลิกการดำเนินการล้มเหลว", + "unknownError": "ข้อผิดพลาดที่ไม่ทราบสาเหตุ", + "confirm": "ยืนยัน", + "find": "หา...", + "replace": "แทนที่", + "downloadInstead": "ดาวน์โหลดแทน", + "keyboardShortcuts": "แป้นพิมพ์ลัด", + "searchAndReplace": "ค้นหาและแทนที่", + "editing": "การแก้ไข", + "search": "ค้นหา", + "findNext": "ค้นหาถัดไป", + "findPrevious": "ค้นหาก่อนหน้า", + "save": "บันทึก", + "selectAll": "เลือกทั้งหมด", + "undo": "เลิกทำ", + "redo": "ทำซ้ำ", + "moveLineUp": "เคลื่อนแถว", + "moveLineDown": "เลื่อนเส้นลง", + "toggleComment": "สลับการแสดงความคิดเห็น", + "autoComplete": "การเติมข้อความอัตโนมัติ", + "imageLoadError": "ไม่สามารถโหลดรูปภาพได้", + "startTyping": "เริ่มพิมพ์...", + "unknownSize": "ขนาดไม่ทราบแน่ชัด", + "fileIsEmpty": "ไฟล์ว่างเปล่า", + "largeFileWarning": "คำเตือนเกี่ยวกับไฟล์ขนาดใหญ่", + "largeFileWarningDesc": "ไฟล์นี้มีขนาด {{size}} ซึ่งอาจทำให้เกิดปัญหาด้านประสิทธิภาพเมื่อเปิดเป็นข้อความ", + "fileNotFoundAndRemoved": "ไม่พบไฟล์ \"{{name}}\" และไฟล์ดังกล่าวถูกลบออกจากไฟล์ล่าสุด/ไฟล์ปักหมุดแล้ว", + "failedToLoadFile": "ไม่สามารถโหลดไฟล์ได้: {{error}}", + "serverErrorOccurred": "เกิดข้อผิดพลาดของเซิร์ฟเวอร์ โปรดลองใหม่อีกครั้งในภายหลัง", + "autoSaveFailed": "การบันทึกอัตโนมัติล้มเหลว", + "fileAutoSaved": "ไฟล์ถูกบันทึกอัตโนมัติ", + "moveFileFailed": "การย้ายล้มเหลว {{name}}", + "moveOperationFailed": "การดำเนินการย้ายล้มเหลว", + "canOnlyCompareFiles": "สามารถเปรียบเทียบไฟล์ได้เพียงสองไฟล์เท่านั้น", + "comparingFiles": "เปรียบเทียบไฟล์: {{file1}} และ {{file2}}", + "dragFailed": "การดำเนินการลากล้มเหลว", + "filePinnedSuccessfully": "ไฟล์ \"{{name}}\" ถูกปักหมุดสำเร็จแล้ว", + "pinFileFailed": "ไม่สามารถตรึงไฟล์ได้", + "fileUnpinnedSuccessfully": "ไฟล์ \"{{name}}\" ถูกยกเลิกการตรึงสำเร็จแล้ว", + "unpinFileFailed": "ไม่สามารถยกเลิกการตรึงไฟล์ได้", + "shortcutAddedSuccessfully": "เพิ่มทางลัดโฟลเดอร์ \"{{name}}\" สำเร็จแล้ว", + "addShortcutFailed": "ไม่สามารถเพิ่มทางลัดได้", + "operationCompletedSuccessfully": "{{operation}} {{count}} รายการสำเร็จ", + "operationCompleted": "{{operation}} {{count}} รายการ", + "downloadFileSuccess": "ดาวน์โหลดไฟล์ {{name}} สำเร็จแล้ว", + "downloadFileFailed": "การดาวน์โหลดล้มเหลว", + "downloadingFile": "กำลังดาวน์โหลด {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "ย้ายไปที่ {{name}}", + "diffCompareWith": "เปรียบเทียบความแตกต่างกับ {{name}}", + "dragOutsideToDownload": "ลากออกนอกหน้าต่างเพื่อดาวน์โหลดไฟล์ ({{count}} ไฟล์)", + "newFolderDefault": "โฟลเดอร์ใหม่", + "newFileDefault": "ไฟล์ใหม่.txt", + "successfullyMovedItems": "ย้ายรายการ {{count}} รายการไปยัง {{target}} สำเร็จแล้ว", + "move": "เคลื่อนไหว", + "searchInFile": "ค้นหาในไฟล์ (Ctrl+F)", + "showKeyboardShortcuts": "แสดงทางลัดแป้นพิมพ์", + "decreaseFontSize": "ลดขนาดตัวอักษร", + "increaseFontSize": "เพิ่มขนาดตัวอักษร", + "startWritingMarkdown": "เริ่มเขียนเนื้อหา Markdown ของคุณได้เลย...", + "loadingFileComparison": "กำลังโหลดการเปรียบเทียบไฟล์...", + "reload": "โหลดซ้ำ", + "compare": "เปรียบเทียบ", + "sideBySide": "เคียงข้างกัน", + "inline": "อินไลน์", + "fileComparison": "การเปรียบเทียบไฟล์: {{file1}} กับ {{file2}}", + "fileTooLarge": "ไฟล์มีขนาดใหญ่เกินไป: {{error}}", + "sshConnectionFailed": "การเชื่อมต่อ SSH ล้มเหลว โปรดตรวจสอบการเชื่อมต่อของคุณกับ {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "ไม่สามารถโหลดไฟล์ได้: {{error}}", + "connecting": "กำลังเชื่อมต่อ...", + "connectedSuccessfully": "เชื่อมต่อสำเร็จแล้ว", + "totpVerificationFailed": "การตรวจสอบ TOTP ล้มเหลว", + "warpgateVerificationFailed": "การตรวจสอบสิทธิ์วาร์ปไม่สำเร็จ", + "authenticationFailed": "การตรวจสอบสิทธิ์ล้มเหลว", + "incorrectPassphrase": "รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง", + "verificationCodePrompt": "รหัสยืนยัน:", + "changePermissions": "เปลี่ยนสิทธิ์การเข้าถึง", + "currentPermissions": "สิทธิ์การเข้าถึงปัจจุบัน", + "owner": "เจ้าของ", + "group": "กลุ่ม", + "others": "คนอื่น", + "read": "อ่าน", + "write": "เขียน", + "execute": "ดำเนินการ", + "permissionsChangedSuccessfully": "สิทธิ์การเข้าถึงเปลี่ยนแปลงสำเร็จแล้ว", + "failedToChangePermissions": "ไม่สามารถเปลี่ยนสิทธิ์ได้", + "name": "ชื่อ", + "sortByName": "ชื่อ", + "sortByDate": "วันที่แก้ไข", + "sortBySize": "ขนาด", + "ascending": "ขึ้นไป", + "descending": "ลง", + "root": "ราก", + "new": "ใหม่", + "sortBy": "เรียงลำดับตาม", + "items": "รายการ", + "selected": "เลือกแล้ว", + "editor": "บรรณาธิการ", + "octal": "แปด", + "storage": "พื้นที่จัดเก็บ", + "disk": "ดิสก์", + "used": "ใช้แล้ว", + "of": "ของ", + "toggleSidebar": "สลับแถบด้านข้าง", + "compactLayout": "ใช้รูปแบบการจัดวางที่กะทัดรัด", + "comfortableLayout": "ใช้รูปแบบการจัดวางที่สะดวกสบาย", + "cannotLoadPdf": "ไม่สามารถโหลดไฟล์ PDF ได้", + "pdfLoadError": "เกิดข้อผิดพลาดในการโหลดไฟล์ PDF นี้", + "loadingPdf": "กำลังโหลดไฟล์ PDF...", + "loadingPage": "กำลังโหลดหน้าเว็บ..." + }, + "sftpTransfer": { + "title": "เอสเอฟทีพี", + "description": "โอนไฟล์ระหว่างโฮสต์ SSH สองเครื่อง", + "sourceServer": "เซิร์ฟเวอร์ต้นทาง", + "destinationServer": "เซิร์ฟเวอร์ปลายทาง", + "selectHost": "เลือกโฮสต์", + "noHostSelected": "ไม่ได้เลือกโฮสต์", + "move": "เคลื่อนไหว", + "copy": "สำเนา", + "upload": "อัปโหลด", + "itemsSelected": "{{count}} รายการที่เลือก", + "itemsSelected_other": "รายการที่เลือก {{count}} รายการ", + "ready": "พร้อม", + "loadingHosts": "กำลังโหลดโฮสต์...", + "name": "ชื่อ", + "modified": "แก้ไขแล้ว", + "size": "ขนาด", + "noFiles": "ไม่มีไฟล์", + "open": "เปิด", + "up": "ขึ้น", + "refresh": "รีเฟรช", + "copyToTarget": "คัดลอกไปยังไดเร็กทอรีเป้าหมาย", + "rename": "เปลี่ยนชื่อ", + "delete": "ลบ", + "editPermissions": "แก้ไขสิทธิ์การเข้าถึง", + "createNewFolder": "สร้างโฟลเดอร์ใหม่", + "deleteSelectedItems": "ลบรายการที่เลือก", + "deleteConfirm": "ลบรายการที่เลือก {{count}} รายการหรือไม่?", + "deleteConfirm_other": "ลบรายการที่เลือก {{count}} รายการหรือไม่?", + "cancel": "ยกเลิก", + "save": "บันทึก", + "folderCreated": "สร้างโฟลเดอร์แล้ว", + "itemRenamed": "เปลี่ยนชื่อรายการ", + "actionFailed": "การดำเนินการล้มเหลว", + "itemsDeleted": "{{count}} รายการถูกลบ", + "itemsDeleted_other": "รายการที่ถูกลบ {{count}} รายการ", + "deleteFailed": "การลบล้มเหลว", + "permissionsUpdated": "สิทธิ์การเข้าถึงได้รับการอัปเดตแล้ว", + "noRemoteSessionConnected": "ไม่มีการเชื่อมต่อเซสชันระยะไกล", + "failedToListFiles": "ไม่สามารถแสดงรายการไฟล์ได้", + "failedToRefresh": "ไม่สามารถรีเฟรชได้", + "destinationInsideSource": "ปลายทางต้องไม่อยู่ภายในเส้นทางต้นทางที่เลือกไว้", + "sameHostNoOp": "ต้นทางและปลายทางเป็นโฟลเดอร์เดียวกัน" + }, + "hosts": { + "fileManager": "ตัวจัดการไฟล์", + "tabFiles": "ไฟล์", + "enableFileManagerMonitor": "เปิดใช้งานตัวจัดการไฟล์", + "enableFileManagerMonitorDesc": "เรียกดูและจัดการไฟล์บนโฮสต์นี้ผ่านทาง SFTP", + "scpLegacyLabel": "โหมด SCP ดั้งเดิม", + "scpLegacyDesc": "สำหรับเซิร์ฟเวอร์ที่ไม่รองรับระบบย่อย SFTP (เช่น เซิร์ฟเวอร์ SSH แบบฝังตัวหรือแบบขั้นต่ำ) ให้ใช้การถ่ายโอนไฟล์แบบเดิม", + "defaultPathLabel": "เส้นทางเริ่มต้น", + "fileManagerPathHint": "โฟลเดอร์ที่จะเปิดเมื่อโปรแกรมจัดการไฟล์เริ่มทำงานสำหรับโฮสต์นี้", + "fileManagerUrlCopied": "คัดลอก URL ของตัวจัดการไฟล์แล้ว", + "copyFileManagerUrlAction": "คัดลอก URL ของตัวจัดการไฟล์" + }, + "settings": { + "host": { + "enable": { + "label": "เปิดใช้งานตัวจัดการไฟล์" + }, + "defaultPath": { + "label": "เส้นทางเริ่มต้น" + }, + "scpLegacy": { + "label": "โหมด SCP ดั้งเดิม" + } + } + }, + "nav": { + "sftp": "เอสเอฟทีพี", + "files": "ไฟล์" + } +} diff --git a/plugins/file-manager/locales/translated/tr_TR.json b/plugins/file-manager/locales/translated/tr_TR.json new file mode 100644 index 000000000..d49c32e49 --- /dev/null +++ b/plugins/file-manager/locales/translated/tr_TR.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Hiçbir sunucu seçilmedi", + "initializingEditor": "Düzenleyici başlatılıyor...", + "file": "Dosya", + "folder": "Klasör", + "uploadFile": "Dosya Yükle", + "downloadFile": "İndir", + "extractArchive": "Arşivi Çıkar", + "extractingArchive": "{{name}} çıkartılıyor...", + "archiveExtractedSuccessfully": "{{name}} başarıyla çıkartıldı.", + "extractFailed": "Çıkartma başarısız.", + "compressFile": "Dosyayı Sıkıştır", + "compressFiles": "Dosyaları Sıkıştır", + "compressFilesDesc": "{{count}} öğeyi arşive sıkıştır", + "archiveName": "Arşiv Adı", + "enterArchiveName": "Arşiv adını girin...", + "compressionFormat": "Sıkıştırma Biçimi", + "selectedFiles": "Seçili dosyalar", + "andMoreFiles": "ve {{count}} tane daha...", + "compress": "Sıkıştır", + "compressingFiles": "{{count}} öğe {{name}} içine sıkıştırılıyor...", + "filesCompressedSuccessfully": "{{name}} başarıyla oluşturuldu.", + "compressFailed": "Sıkıştırma başarısız.", + "edit": "Düzenle", + "preview": "Önizle", + "previous": "Önceki", + "next": "Sonraki", + "pageXOfY": "Sayfa {{current}} / {{total}}", + "zoomOut": "Uzaklaştır", + "zoomIn": "Yakınlaştır", + "newFile": "Yeni Dosya", + "newFolder": "Yeni Klasör", + "rename": "Yeniden Adlandır", + "uploading": "Yükleniyor...", + "uploadingFile": "{{name}} yükleniyor...", + "fileName": "Dosya Adı", + "folderName": "Klasör Adı", + "fileUploadedSuccessfully": "\"{{name}}\" dosyası başarıyla yüklendi.", + "failedToUploadFile": "Dosya yüklenemedi.", + "uploadingFolderFiles": "{{count}} dosya yükleniyor...", + "uploadedFolderFiles": "{{count}} dosya başarıyla yüklendi", + "uploadedFolderPartial": "{{uploaded}} dosya yüklendi, {{failed}} başarısız oldu", + "fileDownloadedSuccessfully": "\"{{name}}\" dosyası başarıyla indirildi.", + "failedToDownloadFile": "Dosya indirilemedi.", + "fileCreatedSuccessfully": "\"{{name}}\" dosyası başarıyla oluşturuldu.", + "folderCreatedSuccessfully": "\"{{name}}\" klasörü başarıyla oluşturuldu.", + "failedToCreateItem": "Öğe oluşturulamadı", + "operationFailed": "{{operation}} işlemi {{name}} için başarısız: {{error}}", + "failedToResolveSymlink": "Sembolik bağlantı çözümlenemedi", + "itemsDeletedSuccessfully": "{{count}} öğe başarıyla silindi", + "failedToDeleteItems": "Öğeler silinemedi", + "sudoPasswordRequired": "Yönetici Parolası Gerekli", + "enterSudoPassword": "Bu işleme devam etmek için sudo parolasını girin", + "sudoPassword": "Sudo parolası", + "sudoOperationFailed": "Sudo işlemi başarısız", + "sudoAuthFailed": "Sudo kimlik doğrulaması başarısız", + "dragFilesToUpload": "Yüklemek için dosyaları buraya bırakın", + "dropToUploadHere": "Yüklemek için sürükle bırak özelliğini kullanın.", + "dropToDownloadHere": "İndirmek için buraya tıklayın", + "localFiles": "Yerel", + "showLocalFiles": "Yerel dosyaları göster (bölünmüş görünüm)", + "hideLocalFiles": "Yerel dosyaları gizle", + "localHome": "Ana klasör", + "localPathPlaceholder": "Yerel klasör yolu", + "localShowHidden": "Gizli dosyaları göster", + "localHideHidden": "Gizli dosyaları gizle", + "localRevealInFileManager": "Sistem dosya yöneticisinde göster", + "localCannotOpenFolder": "Klasör açılamıyor.", + "localItemCount": "{{count}} öğe", + "localSelectedCount": "{{count}} seçildi", + "back": "Geri", + "forward": "İleri", + "up": "Yukarı", + "kind": "Tür", + "localUploadingProgress": "{{total}}'ten {{current}} yükleniyor", + "localDownloadingProgress": "{{total}}'ten {{current}} indiriliyor", + "localTransferInFlight": "{{count}} hemen", + "localPreparingDownload": "İndirme işlemi hazırlanıyor...", + "localTransferCancelled": "Transfer iptal edildi.", + "localNothingToTransfer": "Aktarılacak bir şey yok.", + "localUploadComplete": "{{count}} dosya yüklendi", + "localUploadFailed": "Yükleme başarısız oldu", + "localUploadPartial": "{{done}} dosya yüklendi, {{failed}} başarısız oldu", + "localDownloadComplete": "{{count}} dosya indirildi", + "localDownloadFailed": "İndirme başarısız oldu", + "localDownloadPartial": "{{done}} dosya indirildi, {{failed}} başarısız oldu", + "localReplaceExistingPrompt": "Bu klasörde zaten {{count}} öğe mevcut. Bunların yerine yenilerini mi koymak istiyorsunuz?", + "localReplace": "Yer değiştirmek", + "localSkipExisting": "Atlamak", + "localSkippedExisting": "{{count}} mevcut öğe atlandı", + "localUnsafeNamesSkipped": "Bu bilgisayarda adları kullanılamayan {{count}} öğe atlandı.", + "localOpen": "Açık", + "localOpenFolder": "Klasörü aç", + "localUploadToRemote": "Sunucuya yükle", + "localUploadToRemoteMany": "Sunucuya {{count}} öğe yükle", + "localMoveToTrash": "Çöp Kutusuna Taşı", + "localMoveToTrashMany": "{{count}} öğeyi Çöp Kutusuna Taşı", + "localTrashConfirmSingle": "\"{{name}}\" öğesini Çöp Kutusuna taşı?", + "localTrashConfirmFolder": "\"{{name}}\" klasörünü ve tüm içeriğini Çöp Kutusu'na taşı?", + "localTrashConfirmMany": "{{count}} öğeyi Çöp Kutusuna taşı?", + "localTrashed": "{{count}} öğe Çöp Kutusu'na taşındı", + "localTrashFailed": "Çöp kutusuna taşınamadı", + "localRenameFailed": "Yeniden adlandırma başarısız oldu.", + "localCreateFailed": "Öğe oluşturulamadı.", + "goToParentFolder": "Üst klasöre git", + "resizeSidebar": "Kenar çubuğunu yeniden boyutlandırmak için sürükleyin (sıfırlamak için çift tıklayın)", + "resizeLocalPane": "Yerel bölmeyi yeniden boyutlandırmak için sürükleyin (sıfırlamak için çift tıklayın).", + "columns": "Sütunlar", + "columnsHint": "Sütunları seçmek için sağ tıklayın.", + "emptyFolder": "Bu klasör boş", + "searchFiles": "Dosya ara...", + "upload": "Yükle", + "selectHostToStart": "Dosya yönetimini başlatmak için bir sunucu seçin", + "sshRequiredForFileManager": "Dosya yöneticisi SSH gerektirir. Bu sunucuda SSH etkin değil.", + "failedToConnect": "SSH'ye bağlanılamadı", + "failedToLoadDirectory": "Dizin yüklenemedi", + "noSSHConnection": "SSH bağlantısı yok", + "copy": "Kopyala", + "cut": "Kes", + "paste": "Yapıştır", + "copyPath": "Yolu Kopyala", + "copyPaths": "Yolları Kopyala", + "delete": "Sil", + "properties": "Özellikler", + "refresh": "Yenile", + "downloadFiles": "{{count}} dosyayı tarayıcıya indir", + "copyFiles": "{{count}} öğeyi kopyala", + "cutFiles": "{{count}} öğeyi kes", + "deleteFiles": "{{count}} öğeyi sil", + "filesCopiedToClipboard": "{{count}} öğe panoya kopyalandı", + "filesCutToClipboard": "{{count}} öğe panoya kesildi", + "pathCopiedToClipboard": "Yol panoya kopyalandı", + "pathsCopiedToClipboard": "{{count}} yol panoya kopyalandı", + "failedToCopyPath": "Yol panoya kopyalanamadı", + "copyFolderLink": "Klasör Bağlantısını Kopyala", + "copyCurrentFolderLink": "Geçerli Klasörün Bağlantısını Kopyala", + "folderLinkCopied": "Klasör bağlantısı panoya kopyalandı", + "failedToCopyFolderLink": "Klasör bağlantısı kopyalanamadı", + "movedItems": "{{count}} öğe taşındı", + "failedToDeleteItem": "Öğe silinemedi", + "itemRenamedSuccessfully": "{{type}} başarıyla yeniden adlandırıldı", + "failedToRenameItem": "Öğe yeniden adlandırılamadı", + "download": "İndir", + "openExternalEditor": "Harici Olarak Aç", + "chooseExternalEditor": "Harici düzenleyici seç", + "externalEditorSelected": "Harici düzenleyici seçildi", + "externalEditorDesktopOnly": "Harici düzenleyici yalnızca masaüstü uygulamasında kullanılabilir", + "externalEditorOpened": "Harici düzenleyicide açıldı. Orada kaydetmek değişiklikleri sunucuya yükler.", + "failedToOpenExternalEditor": "Harici düzenleyici açılamadı", + "failedToSelectExternalEditor": "Harici düzenleyici seçilemedi", + "permissions": "İzinler", + "size": "Boyut", + "modified": "Değiştirilme", + "path": "Yol", + "confirmDelete": "{{name}} öğesini silmek istediğinize emin misiniz?", + "permissionDenied": "İzin reddedildi", + "serverError": "Sunucu Hatası", + "fileSavedSuccessfully": "Dosya başarıyla kaydedildi", + "failedToSaveFile": "Dosya kaydedilemedi", + "confirmDeleteSingleItem": "\"{{name}}\" öğesini kalıcı olarak silmek istediğinize emin misiniz?", + "confirmDeleteMultipleItems": "{{count}} öğeyi kalıcı olarak silmek istediğinize emin misiniz?", + "confirmDeleteMultipleItemsWithFolders": "{{count}} öğeyi kalıcı olarak silmek istediğinize emin misiniz? Bu, klasörleri ve içeriklerini de kapsar.", + "confirmDeleteFolder": "\"{{name}}\" klasörünü ve tüm içeriğini kalıcı olarak silmek istediğinize emin misiniz?", + "permanentDeleteWarning": "Bu işlem geri alınamaz. Öğe(ler) sunucudan kalıcı olarak silinecek.", + "moveToTrashWarning": "Öğeler Termix Çöp Kutusu'na taşınacak ve saklama süresi dolmadan önce geri yüklenebilecektir.", + "itemsMovedToTrash": "{{count}} öğe Çöp Kutusuna taşındı", + "trash": "Çöp", + "trashDescription": "Silinen öğeler bu sunucuda {{days}} gün boyunca saklanır.", + "trashLoadFailed": "Çöp kutusu yüklenemedi.", + "trashEmpty": "Çöp kutusu boş.", + "trashRestore": "Eski haline getirmek", + "trashRestored": "Geri Yüklendi {{name}}", + "trashRestoreFailed": "Öğeyi geri yükleme başarısız oldu. Orijinal yol zaten mevcut olabilir.", + "trashDeletePermanently": "Kalıcı olarak sil", + "trashDeleteConfirm": "{{name}}öğesini kalıcı olarak silmek istiyor musunuz? Bu işlem geri alınamaz.", + "trashPermanentlyDeleted": "Kalıcı olarak silindi {{name}}", + "trashDeleteFailed": "Öğeyi kalıcı olarak silme işlemi başarısız oldu.", + "trashEmptyAction": "Çöpü Boşaltın", + "trashEmptyConfirm": "Çöp kutusundaki tüm öğeleri kalıcı olarak silmek mi istiyorsunuz? Bu işlem geri alınamaz.", + "trashEmptied": "Çöp boşaltıldı.", + "trashRetention": "Otomatik temizleme süresi (gün)", + "trashRetentionSaved": "Çöp saklama süresi güncellendi", + "trashRetentionFailed": "Çöp kutusu saklama süresi güncellenemedi.", + "trashUnavailableConfirm": "Bu öğe Çöp Kutusu'na taşınamaz, genellikle başka bir dosya sisteminde olduğu veya yükseltilmiş izinler gerektirdiği içindir. Bunun yerine kalıcı olarak silin.", + "recent": "Son Kullanılanlar", + "pinned": "Sabitlenmiş", + "folderShortcuts": "Klasör Kısayolları", + "failedToReconnectSSH": "SSH oturumuna yeniden bağlanılamadı", + "openTerminalHere": "Terminali Burada Aç", + "run": "Çalıştır", + "openTerminalInFolder": "Bu Klasörde Terminal Aç", + "openTerminalInFileLocation": "Dosya Konumunda Terminal Aç", + "runningFile": "Çalıştırılıyor - {{file}}", + "onlyRunExecutableFiles": "Yalnızca çalıştırılabilir dosyalar çalıştırılabilir", + "directories": "Dizinler", + "removedFromRecentFiles": "\"{{name}}\" son dosyalardan kaldırıldı", + "removeFailed": "Kaldırma başarısız", + "unpinnedSuccessfully": "\"{{name}}\" sabitlemesi başarıyla kaldırıldı", + "unpinFailed": "Sabitleme kaldırma başarısız", + "removedShortcut": "\"{{name}}\" kısayolu kaldırıldı", + "removeShortcutFailed": "Kısayol kaldırma başarısız", + "clearedAllRecentFiles": "Tüm son dosyalar temizlendi", + "clearFailed": "Temizleme başarısız", + "removeFromRecentFiles": "Son dosyalardan kaldır", + "clearAllRecentFiles": "Tüm son dosyaları temizle", + "unpinFile": "Dosya sabitlemesini kaldır", + "removeShortcut": "Kısayolu kaldır", + "pinFile": "Dosyayı sabitle", + "addToShortcuts": "Kısayollara ekle", + "pasteFailed": "Yapıştırma başarısız", + "noUndoableActions": "Geri alınabilir işlem yok", + "undoCopySuccess": "Kopyalama işlemi geri alındı: {{count}} kopyalanmış dosya silindi", + "undoCopyFailedDelete": "Geri alma başarısız: Hiçbir kopyalanmış dosya silinemedi", + "undoCopyFailedNoInfo": "Geri alma başarısız: Kopyalanmış dosya bilgisi bulunamadı", + "undoMoveSuccess": "Taşıma işlemi geri alındı: {{count}} dosya orijinal konumuna geri taşındı", + "undoMoveFailedMove": "Geri alma başarısız: Hiçbir dosya geri taşınamadı", + "undoMoveFailedNoInfo": "Geri alma başarısız: Taşınmış dosya bilgisi bulunamadı", + "undoDeleteNotSupported": "Silme işlemi geri alınamaz: Dosyalar sunucudan kalıcı olarak silindi", + "undoTypeNotSupported": "Desteklenmeyen geri alma işlem türü", + "undoOperationFailed": "Geri alma işlemi başarısız", + "unknownError": "Bilinmeyen hata", + "confirm": "Onayla", + "find": "Bul...", + "replace": "Değiştir", + "downloadInstead": "Bunun Yerine İndir", + "keyboardShortcuts": "Klavye Kısayolları", + "searchAndReplace": "Ara ve Değiştir", + "editing": "Düzenleme", + "search": "Ara", + "findNext": "Sonrakini Bul", + "findPrevious": "Öncekini Bul", + "save": "Kaydet", + "selectAll": "Tümünü Seç", + "undo": "Geri Al", + "redo": "Yinele", + "moveLineUp": "Satırı Yukarı Taşı", + "moveLineDown": "Satırı Aşağı Taşı", + "toggleComment": "Yorumu Aç/Kapat", + "autoComplete": "Otomatik Tamamla", + "imageLoadError": "Görüntü yüklenemedi", + "startTyping": "Yazmaya başlayın...", + "unknownSize": "Bilinmeyen boyut", + "fileIsEmpty": "Dosya boş", + "largeFileWarning": "Büyük Dosya Uyarısı", + "largeFileWarningDesc": "Bu dosya {{size}} boyutunda, metin olarak açıldığında performans sorunlarına neden olabilir.", + "fileNotFoundAndRemoved": "\"{{name}}\" dosyası bulunamadı ve son kullanılanlar/sabitlenmiş dosyalardan kaldırıldı.", + "failedToLoadFile": "Dosya yüklenemedi: {{error}}", + "serverErrorOccurred": "Sunucu hatası oluştu. Lütfen daha sonra tekrar deneyin.", + "autoSaveFailed": "Otomatik kaydetme başarısız oldu", + "fileAutoSaved": "Dosya otomatik kaydedildi", + "moveFileFailed": "{{name}} taşınamadı", + "moveOperationFailed": "Taşıma işlemi başarısız oldu", + "canOnlyCompareFiles": "Yalnızca iki dosya karşılaştırılabilir", + "comparingFiles": "Dosyalar karşılaştırılıyor: {{file1}} ve {{file2}}", + "dragFailed": "Sürükleme işlemi başarısız oldu", + "filePinnedSuccessfully": "\"{{name}}\" dosyası başarıyla sabitlendi", + "pinFileFailed": "Dosya sabitleme başarısız oldu", + "fileUnpinnedSuccessfully": "\"{{name}}\" dosyasının sabitlemesi başarıyla kaldırıldı", + "unpinFileFailed": "Sabitlenmiş dosya kaldırılamadı", + "shortcutAddedSuccessfully": "Klasör kısayolu \"{{name}}\" başarıyla eklendi", + "addShortcutFailed": "Kısayol eklenemedi", + "operationCompletedSuccessfully": "{{count}} öğe başarıyla {{operation}}", + "operationCompleted": "{{count}} öğe {{operation}}", + "downloadFileSuccess": "{{name}} dosyası başarıyla indirildi", + "downloadFileFailed": "İndirme başarısız oldu", + "downloadingFile": "{{name}}… indiriliyor", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "{{name}} konumuna taşı", + "diffCompareWith": "{{name}} ile diff karşılaştır", + "dragOutsideToDownload": "İndirmek için pencere dışına sürükleyin ({{count}} dosya)", + "newFolderDefault": "Yeni Klasör", + "newFileDefault": "Yeni Dosya.txt", + "successfullyMovedItems": "{{count}} öğe başarıyla {{target}} konumuna taşındı", + "move": "Taşı", + "searchInFile": "Dosyada ara (Ctrl+F)", + "showKeyboardShortcuts": "Klavye kısayollarını göster", + "decreaseFontSize": "Yazı tipi boyutunu küçült", + "increaseFontSize": "Yazı tipi boyutunu büyüt", + "startWritingMarkdown": "Markdown içeriğinizi yazmaya başlayın...", + "loadingFileComparison": "Dosya karşılaştırması yükleniyor...", + "reload": "Yeniden yükle", + "compare": "Karşılaştır", + "sideBySide": "Yan Yana", + "inline": "Satır İçi", + "fileComparison": "Dosya Karşılaştırması: {{file1}} vs {{file2}}", + "fileTooLarge": "Dosya çok büyük: {{error}}", + "sshConnectionFailed": "SSH bağlantısı başarısız oldu. Lütfen {{name}} ({{ip}}:{{port}}) ile bağlantınızı kontrol edin", + "loadFileFailed": "Dosya yüklenemedi: {{error}}", + "connecting": "Bağlanıyor...", + "connectedSuccessfully": "Bağlantı başarıyla kuruldu", + "totpVerificationFailed": "TOTP doğrulaması başarısız oldu", + "warpgateVerificationFailed": "Warpgate kimlik doğrulaması başarısız oldu", + "authenticationFailed": "Kimlik doğrulama başarısız oldu", + "incorrectPassphrase": "Yanlış parola. Lütfen tekrar deneyin.", + "verificationCodePrompt": "Doğrulama kodu:", + "changePermissions": "İzinleri Değiştir", + "currentPermissions": "Mevcut İzinler", + "owner": "Sahip", + "group": "Grup", + "others": "Diğerleri", + "read": "Okuma", + "write": "Yazma", + "execute": "Çalıştırma", + "permissionsChangedSuccessfully": "İzinler başarıyla değiştirildi", + "failedToChangePermissions": "İzinler değiştirilemedi", + "name": "Ad", + "sortByName": "Ad", + "sortByDate": "Değiştirilme Tarihi", + "sortBySize": "Boyut", + "ascending": "Artan", + "descending": "Azalan", + "root": "Kök", + "new": "Yeni", + "sortBy": "Sırala", + "items": "Öğeler", + "selected": "Seçili", + "editor": "Düzenleyici", + "octal": "Sekizlik", + "storage": "Depolama", + "disk": "Disk", + "used": "Kullanılan", + "of": " / ", + "toggleSidebar": "Kenar Çubuğunu Göster/Gizle", + "compactLayout": "Kompakt düzeni kullanın.", + "comfortableLayout": "Rahat bir düzen kullanın.", + "cannotLoadPdf": "PDF yüklenemiyor", + "pdfLoadError": "Bu PDF dosyası yüklenirken bir hata oluştu.", + "loadingPdf": "PDF yükleniyor...", + "loadingPage": "Sayfa yükleniyor..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "İki SSH sunucusu arasında dosya aktarımı.", + "sourceServer": "Kaynak Sunucusu", + "destinationServer": "Hedef Sunucu", + "selectHost": "Sunucu seçin", + "noHostSelected": "Sunucu seçilmedi.", + "move": "Taşınmak", + "copy": "Kopyala", + "upload": "Yüklemek", + "itemsSelected": "{{count}} seçilen öğe", + "itemsSelected_other": "{{count}} öğe seçildi", + "ready": "Hazır", + "loadingHosts": "Sunucular yükleniyor...", + "name": "İsim", + "modified": "Değiştirildi", + "size": "Boyut", + "noFiles": "Dosya yok", + "open": "Açık", + "up": "Yukarı", + "refresh": "Yenile", + "copyToTarget": "Hedef Dizine Kopyala", + "rename": "Yeniden isimlendirmek", + "delete": "Silmek", + "editPermissions": "Düzenleme İzinleri", + "createNewFolder": "Yeni Klasör Oluştur", + "deleteSelectedItems": "Seçili Öğeleri Sil", + "deleteConfirm": "Delete {{count}} selected item?", + "deleteConfirm_other": "Delete {{count}} selected items?", + "cancel": "İptal etmek", + "save": "Kaydetmek", + "folderCreated": "Klasör oluşturuldu", + "itemRenamed": "Öğe yeniden adlandırıldı.", + "actionFailed": "İşlem başarısız oldu", + "itemsDeleted": "{{count}} öğe silindi", + "itemsDeleted_other": "{{count}} öğe silindi", + "deleteFailed": "Silme işlemi başarısız oldu.", + "permissionsUpdated": "İzinler güncellendi", + "noRemoteSessionConnected": "Bağlantı kurulmuş uzaktan oturum yok.", + "failedToListFiles": "Dosyaları listeleme başarısız oldu.", + "failedToRefresh": "Yenileme başarısız oldu", + "destinationInsideSource": "Hedef, seçilen kaynak yolunun içinde olamaz.", + "sameHostNoOp": "Kaynak ve hedef aynı klasördedir." + }, + "hosts": { + "fileManager": "Dosya Yöneticisi", + "tabFiles": "Dosyalar", + "enableFileManagerMonitor": "Dosya Yöneticisini Etkinleştir", + "enableFileManagerMonitorDesc": "Bu sunucudaki dosyalara SFTP üzerinden göz atın ve yönetin", + "scpLegacyLabel": "SCP Eski Modu", + "scpLegacyDesc": "SFTP alt sistemini desteklemeyen sunucular için eski dosya aktarımını kullan (ör. gömülü veya minimal SSH sunucuları).", + "defaultPathLabel": "Varsayılan Yol", + "fileManagerPathHint": "Dosya yöneticisi bu sunucu için başlatıldığında açılacak dizin.", + "fileManagerUrlCopied": "Dosya Yöneticisi URL'si kopyalandı", + "copyFileManagerUrlAction": "Dosya Yöneticisi URL'sini Kopyala" + }, + "settings": { + "host": { + "enable": { + "label": "Dosya Yöneticisini Etkinleştir" + }, + "defaultPath": { + "label": "Varsayılan Yol" + }, + "scpLegacy": { + "label": "SCP Eski Modu" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Dosyalar" + } +} diff --git a/plugins/file-manager/locales/translated/uk_UA.json b/plugins/file-manager/locales/translated/uk_UA.json new file mode 100644 index 000000000..2bba2fda1 --- /dev/null +++ b/plugins/file-manager/locales/translated/uk_UA.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Хост не вибрано", + "initializingEditor": "Ініціалізація редактора...", + "file": "Файл", + "folder": "Папка", + "uploadFile": "Завантажити файл", + "downloadFile": "Завантажити", + "extractArchive": "Витягти архів", + "extractingArchive": "Видобування {{name}}...", + "archiveExtractedSuccessfully": "{{name}} успішно видобуто", + "extractFailed": "Не вдалося витягти", + "compressFile": "Стиснути файл", + "compressFiles": "Стиснути файли", + "compressFilesDesc": "Стиснути {{count}} елементів в архів", + "archiveName": "Назва архіву", + "enterArchiveName": "Введіть назву архіву...", + "compressionFormat": "Формат стиснення", + "selectedFiles": "Вибрані файли", + "andMoreFiles": "та {{count}} більше...", + "compress": "Стиснути", + "compressingFiles": "Стискання {{count}} елементів у {{name}}...", + "filesCompressedSuccessfully": "{{name}} успішно створено", + "compressFailed": "Стиснення не вдалося", + "edit": "Редагувати", + "preview": "Попередній перегляд", + "previous": "Попередній", + "next": "Далі", + "pageXOfY": "Сторінка {{current}} з {{total}}", + "zoomOut": "Зменшити масштаб", + "zoomIn": "Збільшити масштаб", + "newFile": "Новий файл", + "newFolder": "Нова папка", + "rename": "Перейменувати", + "uploading": "Завантаження...", + "uploadingFile": "Завантаження {{name}}...", + "fileName": "Ім'я файлу", + "folderName": "Назва папки", + "fileUploadedSuccessfully": "Файл «{{name}}» успішно завантажено", + "failedToUploadFile": "Не вдалося завантажити файл", + "uploadingFolderFiles": "Завантаження {{count}} файлів...", + "uploadedFolderFiles": "Успішно завантажено {{count}} файл(ів)", + "uploadedFolderPartial": "Завантажено {{uploaded}} файл(ів), {{failed}} не вдалося", + "fileDownloadedSuccessfully": "Файл «{{name}}» успішно завантажено", + "failedToDownloadFile": "Не вдалося завантажити файл", + "fileCreatedSuccessfully": "Файл «{{name}}» успішно створено", + "folderCreatedSuccessfully": "Папка «{{name}}» успішно створена", + "failedToCreateItem": "Не вдалося створити елемент", + "operationFailed": "Операція {{operation}} не вдалася для {{name}}: {{error}}", + "failedToResolveSymlink": "Не вдалося вирішити проблему з символічним посиланням", + "itemsDeletedSuccessfully": "{{count}} елементів успішно видалено", + "failedToDeleteItems": "Не вдалося видалити елементи", + "sudoPasswordRequired": "Потрібен пароль адміністратора", + "enterSudoPassword": "Введіть пароль sudo, щоб продовжити цю операцію", + "sudoPassword": "Пароль Sudo", + "sudoOperationFailed": "Операція Sudo не вдалася", + "sudoAuthFailed": "Не вдалося виконати автентифікацію Sudo", + "dragFilesToUpload": "Перетягніть сюди файли для завантаження", + "dropToUploadHere": "Перетягніть сюди, щоб завантажити", + "dropToDownloadHere": "Перетягніть сюди, щоб завантажити", + "localFiles": "Місцевий", + "showLocalFiles": "Показати локальні файли (розділений вигляд)", + "hideLocalFiles": "Приховати локальні файли", + "localHome": "Домашня папка", + "localPathPlaceholder": "Шлях до локальної папки", + "localShowHidden": "Показати приховані файли", + "localHideHidden": "Приховати приховані файли", + "localRevealInFileManager": "Показати в системному файловому менеджері", + "localCannotOpenFolder": "Не вдається відкрити папку", + "localItemCount": "{{count}} елементів", + "localSelectedCount": "{{count}} вибрано", + "back": "Назад", + "forward": "Вперед", + "up": "Вгору", + "kind": "Вид", + "localUploadingProgress": "Завантаження {{current}} з {{total}}", + "localDownloadingProgress": "Завантаження {{current}} з {{total}}", + "localTransferInFlight": "{{count}} одразу", + "localPreparingDownload": "Підготовка завантаження...", + "localTransferCancelled": "Переказ скасовано", + "localNothingToTransfer": "Нічого для перенесення", + "localUploadComplete": "Завантажено {{count}} файл(ів)", + "localUploadFailed": "Не вдалося завантажити", + "localUploadPartial": "Завантажено {{done}} файл(ів), {{failed}} не вдалося", + "localDownloadComplete": "Завантажено {{count}} файл(ів)", + "localDownloadFailed": "Завантаження не вдалося", + "localDownloadPartial": "Завантажено {{done}} файл(ів), {{failed}} не вдалося", + "localReplaceExistingPrompt": "{{count}} елементів вже є в цій папці. Замінити їх?", + "localReplace": "Замінити", + "localSkipExisting": "Пропустити", + "localSkippedExisting": "Пропущено {{count}} існуючих елементів", + "localUnsafeNamesSkipped": "Пропущено {{count}} елементів, назви яких не можна використовувати на цьому комп’ютері", + "localOpen": "ВІДЧИНЕНО", + "localOpenFolder": "Відкрити папку", + "localUploadToRemote": "Завантажити на сервер", + "localUploadToRemoteMany": "Завантажити {{count}} елементів на сервер", + "localMoveToTrash": "Перемістити в кошик", + "localMoveToTrashMany": "Перемістити {{count}} елементів до кошика", + "localTrashConfirmSingle": "Перемістити \"{{name}}\" до кошика?", + "localTrashConfirmFolder": "Перемістити папку «{{name}}» та весь її вміст до кошика?", + "localTrashConfirmMany": "Перемістити {{count}} елементів до кошика?", + "localTrashed": "Переміщено {{count}} елементів до кошика", + "localTrashFailed": "Не вдалося перемістити в кошик", + "localRenameFailed": "Не вдалося перейменувати", + "localCreateFailed": "Не вдалося створити елемент", + "goToParentFolder": "Перейти до батьківської папки", + "resizeSidebar": "Перетягніть, щоб змінити розмір бічної панелі (двічі клацніть, щоб скинути налаштування)", + "resizeLocalPane": "Перетягніть, щоб змінити розмір локальної панелі (двічі клацніть, щоб скинути налаштування)", + "columns": "Колонки", + "columnsHint": "Клацніть правою кнопкою миші, щоб вибрати стовпці", + "emptyFolder": "Ця папка порожня", + "searchFiles": "Пошук файлів...", + "upload": "Завантажити", + "selectHostToStart": "Виберіть хост, щоб розпочати керування файлами", + "sshRequiredForFileManager": "Файловий менеджер вимагає SSH. На цьому хості SSH не ввімкнено.", + "failedToConnect": "Не вдалося підключитися до SSH", + "failedToLoadDirectory": "Не вдалося завантажити каталог", + "noSSHConnection": "SSH-з'єднання недоступне", + "copy": "Копіювати", + "cut": "Вирізати", + "paste": "Вставити", + "copyPath": "Копіювати шлях", + "copyPaths": "Копіювати шляхи", + "delete": "Видалити", + "properties": "Властивості", + "refresh": "Оновити", + "downloadFiles": "Завантажити {{count}} файлів у браузер", + "copyFiles": "Копіювати {{count}} елементів", + "cutFiles": "Вирізати {{count}} елементів", + "deleteFiles": "Видалити {{count}} елементів", + "filesCopiedToClipboard": "{{count}} елементів, скопійованих у буфер обміну", + "filesCutToClipboard": "{{count}} елементів, вирізаних у буфер обміну", + "pathCopiedToClipboard": "Шлях скопійовано в буфер обміну", + "pathsCopiedToClipboard": "{{count}} шляхів скопійовано в буфер обміну", + "failedToCopyPath": "Не вдалося скопіювати шлях до буфера обміну", + "copyFolderLink": "Копіювати посилання до папки", + "copyCurrentFolderLink": "Копіювати посилання до поточної папки", + "folderLinkCopied": "Посилання на папку скопійовано в буфер обміну", + "failedToCopyFolderLink": "Не вдалося скопіювати посилання на папку", + "movedItems": "Переміщено {{count}} елементів", + "failedToDeleteItem": "Не вдалося видалити елемент", + "itemRenamedSuccessfully": "{{type}} успішно перейменовано", + "failedToRenameItem": "Не вдалося перейменувати елемент", + "download": "Завантажити", + "openExternalEditor": "Відкрити зовні", + "chooseExternalEditor": "Виберіть зовнішнього редактора", + "externalEditorSelected": "Вибрано зовнішнього редактора", + "externalEditorDesktopOnly": "Зовнішній редактор доступний лише в настільній програмі", + "externalEditorOpened": "Відкрито в зовнішньому редакторі. Збереження там завантажить зміни назад на сервер.", + "failedToOpenExternalEditor": "Не вдалося відкрити зовнішній редактор", + "failedToSelectExternalEditor": "Не вдалося вибрати зовнішній редактор", + "permissions": "Дозволи", + "size": "Розмір", + "modified": "Змінено", + "path": "Шлях", + "confirmDelete": "Ви впевнені, що хочете видалити {{name}}?", + "permissionDenied": "Дозвіл відхилено", + "serverError": "Помилка сервера", + "fileSavedSuccessfully": "Файл успішно збережено", + "failedToSaveFile": "Не вдалося зберегти файл", + "confirmDeleteSingleItem": "Ви впевнені, що хочете остаточно видалити \"{{name}}\"?", + "confirmDeleteMultipleItems": "Ви впевнені, що хочете остаточно видалити {{count}} елементів?", + "confirmDeleteMultipleItemsWithFolders": "Ви впевнені, що хочете остаточно видалити {{count}} елементів? Це стосується папок та їхнього вмісту.", + "confirmDeleteFolder": "Ви впевнені, що хочете остаточно видалити папку «{{name}}» та весь її вміст?", + "permanentDeleteWarning": "Цю дію неможливо скасувати. Елемент(и) буде назавжди видалено із сервера.", + "moveToTrashWarning": "Елемент(и) буде переміщено до кошика Termix і може бути відновлено до закінчення терміну зберігання.", + "itemsMovedToTrash": "{{count}} елементів переміщено до кошика", + "trash": "Сміття", + "trashDescription": "Видалені елементи зберігаються на цьому хості протягом {{days}} днів.", + "trashLoadFailed": "Не вдалося завантажити кошик", + "trashEmpty": "Кошик порожній", + "trashRestore": "Відновити", + "trashRestored": "Відновлено {{name}}", + "trashRestoreFailed": "Не вдалося відновити елемент. Можливо, початковий шлях уже існує.", + "trashDeletePermanently": "Видалити назавжди", + "trashDeleteConfirm": "Видалити {{name}}назавжди? Цю дію не можна скасувати.", + "trashPermanentlyDeleted": "Видалено назавжди {{name}}", + "trashDeleteFailed": "Не вдалося остаточно видалити елемент", + "trashEmptyAction": "Очистити кошик", + "trashEmptyConfirm": "Видалити всі елементи з кошика назавжди? Цю дію не можна скасувати.", + "trashEmptied": "Кошик очищено", + "trashRetention": "Автоматичне очищення через (дні)", + "trashRetentionSaved": "Зберігання в кошику оновлено", + "trashRetentionFailed": "Не вдалося оновити налаштування зберігання в кошику", + "trashUnavailableConfirm": "Цей елемент не можна перемістити до кошика, зазвичай тому, що він знаходиться в іншій файловій системі або потребує підвищених прав. Видалити його назавжди?", + "recent": "Нещодавні", + "pinned": "Закріплено", + "folderShortcuts": "Ярлики папок", + "failedToReconnectSSH": "Не вдалося відновити сеанс SSH", + "openTerminalHere": "Відкрити термінал тут", + "run": "Бігти", + "openTerminalInFolder": "Відкрити термінал у цій папці", + "openTerminalInFileLocation": "Відкрити термінал у місці розташування файлу", + "runningFile": "Біг – {{file}}", + "onlyRunExecutableFiles": "Можна запускати лише виконувані файли", + "directories": "Довідники", + "removedFromRecentFiles": "Видалено \"{{name}}\" з останніх файлів", + "removeFailed": "Видалити не вдалося", + "unpinnedSuccessfully": "Відкріплено \"{{name}}\" успішно", + "unpinFailed": "Не вдалося відкріпити", + "removedShortcut": "Видалено ярлик «{{name}}»", + "removeShortcutFailed": "Не вдалося видалити ярлик", + "clearedAllRecentFiles": "Очищено всі нещодавні файли", + "clearFailed": "Очистити не вдалося", + "removeFromRecentFiles": "Видалити з останніх файлів", + "clearAllRecentFiles": "Очистити всі нещодавні файли", + "unpinFile": "Відкріпити файл", + "removeShortcut": "Видалити ярлик", + "pinFile": "Закріпити файл", + "addToShortcuts": "Додати до ярликів", + "pasteFailed": "Вставити не вдалося", + "noUndoableActions": "Немає дій, які можна скасувати", + "undoCopySuccess": "Скасовано копіювання: Видалено {{count}} скопійованих файлів", + "undoCopyFailedDelete": "Не вдалося скасувати: не вдалося видалити жодного скопійованого файлу", + "undoCopyFailedNoInfo": "Не вдалося скасувати: не вдалося знайти інформацію про скопійований файл", + "undoMoveSuccess": "Скасовано операцію переміщення: {{count}} файлів переміщено назад у початкове розташування", + "undoMoveFailedMove": "Не вдалося скасувати: не вдалося перемістити жодного файлу назад", + "undoMoveFailedNoInfo": "Не вдалося скасувати: не вдалося знайти інформацію про переміщений файл", + "undoDeleteNotSupported": "Операцію видалення неможливо скасувати: файли остаточно видалено із сервера", + "undoTypeNotSupported": "Непідтримуваний тип операції скасування", + "undoOperationFailed": "Не вдалося скасувати операцію", + "unknownError": "Невідома помилка", + "confirm": "Підтвердити", + "find": "Знайти...", + "replace": "Замінити", + "downloadInstead": "Завантажити замість цього", + "keyboardShortcuts": "Комбінації клавіш", + "searchAndReplace": "Пошук і заміна", + "editing": "Редагування", + "search": "Пошук", + "findNext": "Знайти далі", + "findPrevious": "Знайти попередній", + "save": "Зберегти", + "selectAll": "Вибрати все", + "undo": "Скасувати", + "redo": "Повторити", + "moveLineUp": "Перемістити лінію вгору", + "moveLineDown": "Перемістити рядок вниз", + "toggleComment": "Перемикач коментарів", + "autoComplete": "Автоматичне завершення", + "imageLoadError": "Не вдалося завантажити зображення", + "startTyping": "Почніть друкувати...", + "unknownSize": "Невідомий розмір", + "fileIsEmpty": "Файл порожній", + "largeFileWarning": "Попередження про великий файл", + "largeFileWarningDesc": "Розмір цього файлу становить {{size}} , що може спричинити проблеми з продуктивністю під час відкриття його як тексту.", + "fileNotFoundAndRemoved": "Файл «{{name}}» не знайдено та видалено з нещодавніх/закріплених файлів", + "failedToLoadFile": "Не вдалося завантажити файл: {{error}}", + "serverErrorOccurred": "Сталася помилка сервера. Будь ласка, спробуйте пізніше.", + "autoSaveFailed": "Не вдалося автоматично зберегти", + "fileAutoSaved": "Файл автоматично збережено", + "moveFileFailed": "Не вдалося перемістити {{name}}", + "moveOperationFailed": "Не вдалося перемістити", + "canOnlyCompareFiles": "Можна порівняти лише два файли", + "comparingFiles": "Порівняння файлів: {{file1}} та {{file2}}", + "dragFailed": "Не вдалося перетягнути", + "filePinnedSuccessfully": "Файл «{{name}}» успішно закріплено", + "pinFileFailed": "Не вдалося закріпити файл", + "fileUnpinnedSuccessfully": "Файл «{{name}}» успішно відкріплено", + "unpinFileFailed": "Не вдалося відкріпити файл", + "shortcutAddedSuccessfully": "Ярлик папки «{{name}}» успішно додано", + "addShortcutFailed": "Не вдалося додати ярлик", + "operationCompletedSuccessfully": "{{operation}} {{count}} елементів успішно", + "operationCompleted": "{{operation}} {{count}} елементів", + "downloadFileSuccess": "Файл {{name}} успішно завантажено", + "downloadFileFailed": "Завантаження не вдалося", + "downloadingFile": "Завантаження {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Перейти до {{name}}", + "diffCompareWith": "Різниця у порівнянні з {{name}}", + "dragOutsideToDownload": "Перетягніть за межі вікна, щоб завантажити ({{count}} файлів)", + "newFolderDefault": "НоваПапка", + "newFileDefault": "НовийФайл.txt", + "successfullyMovedItems": "Елементів {{count}} успішно переміщено до {{target}}", + "move": "Перемістити", + "searchInFile": "Пошук у файлі (Ctrl+F)", + "showKeyboardShortcuts": "Показати комбінації клавіш", + "decreaseFontSize": "Зменшити розмір шрифту", + "increaseFontSize": "Збільшити розмір шрифту", + "startWritingMarkdown": "Почніть писати свій контент у форматі Markdown...", + "loadingFileComparison": "Завантаження порівняння файлів...", + "reload": "Перезавантажити", + "compare": "Порівняти", + "sideBySide": "Пліч-о-пліч", + "inline": "Вбудований", + "fileComparison": "Порівняння файлів: {{file1}} проти {{file2}}", + "fileTooLarge": "Файл занадто великий: {{error}}", + "sshConnectionFailed": "Помилка SSH-з’єднання. Будь ласка, перевірте ваше з’єднання з {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Не вдалося завантажити файл: {{error}}", + "connecting": "Підключення...", + "connectedSuccessfully": "Підключено успішно", + "totpVerificationFailed": "Не вдалося перевірити TOTP", + "warpgateVerificationFailed": "Автентифікація Warpgate не вдалася", + "authenticationFailed": "Помилка автентифікації", + "incorrectPassphrase": "Неправильна парольна фраза. Спробуйте ще раз.", + "verificationCodePrompt": "Код підтвердження:", + "changePermissions": "Змінити дозволи", + "currentPermissions": "Поточні дозволи", + "owner": "Власник", + "group": "Група", + "others": "Інші", + "read": "Читати", + "write": "Написати", + "execute": "Виконати", + "permissionsChangedSuccessfully": "Дозволи успішно змінено", + "failedToChangePermissions": "Не вдалося змінити дозволи", + "name": "Ім'я", + "sortByName": "Ім'я", + "sortByDate": "Дата зміни", + "sortBySize": "Розмір", + "ascending": "Зростаючий", + "descending": "Спадання", + "root": "Корінь", + "new": "Новий", + "sortBy": "Сортувати за", + "items": "Елементи", + "selected": "Вибрано", + "editor": "Редактор", + "octal": "Вісімкове", + "storage": "Зберігання", + "disk": "Диск", + "used": "Використано", + "of": "з", + "toggleSidebar": "Перемикання бічної панелі", + "compactLayout": "Використовуйте компактний макет", + "comfortableLayout": "Використовуйте зручний макет", + "cannotLoadPdf": "Не вдається завантажити PDF", + "pdfLoadError": "Під час завантаження цього PDF-файлу сталася помилка.", + "loadingPdf": "Завантаження PDF-файлу...", + "loadingPage": "Завантаження сторінки..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Передача файлів між двома SSH-хостами.", + "sourceServer": "Вихідний сервер", + "destinationServer": "Цільовий сервер", + "selectHost": "Виберіть хоста", + "noHostSelected": "Хост не вибрано", + "move": "Перемістити", + "copy": "Копіювати", + "upload": "Завантажити", + "itemsSelected": "Вибрано елемент {{count}}", + "itemsSelected_other": "Вибрано {{count}} елементів", + "ready": "Готовий", + "loadingHosts": "Завантаження хостів...", + "name": "Ім'я", + "modified": "Змінено", + "size": "Розмір", + "noFiles": "Немає файлів", + "open": "ВІДЧИНЕНО", + "up": "Вгору", + "refresh": "Оновити", + "copyToTarget": "Копіювати до цільового каталогу", + "rename": "Перейменувати", + "delete": "Видалити", + "editPermissions": "Редагувати дозволи", + "createNewFolder": "Створити нову папку", + "deleteSelectedItems": "Видалити вибрані елементи", + "deleteConfirm": "Видалити {{count}} вибраний елемент?", + "deleteConfirm_other": "Видалити {{count}} вибраних елементів?", + "cancel": "Скасувати", + "save": "Зберегти", + "folderCreated": "Папку створено", + "itemRenamed": "Елемент перейменовано", + "actionFailed": "Дія не вдалася", + "itemsDeleted": "{{count}} елемент видалено", + "itemsDeleted_other": "{{count}} елементів видалено", + "deleteFailed": "Видалити не вдалося", + "permissionsUpdated": "Дозволи оновлено", + "noRemoteSessionConnected": "Немає підключеного віддаленого сеансу", + "failedToListFiles": "Не вдалося переглянути файли", + "failedToRefresh": "Не вдалося оновити", + "destinationInsideSource": "Пункт призначення не може бути всередині вибраного вихідного шляху.", + "sameHostNoOp": "Джерело та призначення – це одна й та сама папка." + }, + "hosts": { + "fileManager": "Файловий менеджер", + "tabFiles": "Файли", + "enableFileManagerMonitor": "Увімкнути файловий менеджер", + "enableFileManagerMonitorDesc": "Переглядайте та керуйте файлами на цьому хості через SFTP", + "scpLegacyLabel": "Застарілий режим SCP", + "scpLegacyDesc": "Використовуйте застарілу передачу файлів для серверів, які не підтримують підсистему SFTP (наприклад, вбудовані або мінімальні SSH-сервери).", + "defaultPathLabel": "Шлях за замовчуванням", + "fileManagerPathHint": "Каталог, який потрібно відкрити під час запуску файлового менеджера для цього хоста.", + "fileManagerUrlCopied": "URL-адресу файлового менеджера скопійовано", + "copyFileManagerUrlAction": "Копіювати URL-адресу файлового менеджера" + }, + "settings": { + "host": { + "enable": { + "label": "Увімкнути файловий менеджер" + }, + "defaultPath": { + "label": "Шлях за замовчуванням" + }, + "scpLegacy": { + "label": "Застарілий режим SCP" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Файли" + } +} diff --git a/plugins/file-manager/locales/translated/vi_VN.json b/plugins/file-manager/locales/translated/vi_VN.json new file mode 100644 index 000000000..c95ae42c8 --- /dev/null +++ b/plugins/file-manager/locales/translated/vi_VN.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "Chưa chọn máy chủ", + "initializingEditor": "Đang khởi tạo trình soạn thảo...", + "file": "Tệp", + "folder": "Thư mục", + "uploadFile": "Tải tệp lên", + "downloadFile": "Tải xuống", + "extractArchive": "Giải nén lưu trữ", + "extractingArchive": "Đang giải nén {{name}}...", + "archiveExtractedSuccessfully": "{{name}} đã được giải nén thành công", + "extractFailed": "Giải nén thất bại", + "compressFile": "Nén tập tin", + "compressFiles": "Nén tập tin", + "compressFilesDesc": "Nén {{count}} mục vào một tệp nén", + "archiveName": "Tên tệp lưu trữ", + "enterArchiveName": "Nhập tên tệp lưu trữ...", + "compressionFormat": "Định dạng nén", + "selectedFiles": "Tập tin đã chọn", + "andMoreFiles": "và {{count}} tập tin khác...", + "compress": "Nén", + "compressingFiles": "Đang nén {{count}} mục vào {{name}}...", + "filesCompressedSuccessfully": "{{name}} đã được tạo thành công", + "compressFailed": "Nén thất bại", + "edit": "Chỉnh sửa", + "preview": "Xem trước", + "previous": "Trước", + "next": "Tiếp", + "pageXOfY": "Trang {{current}} / {{total}}", + "zoomOut": "Thu nhỏ", + "zoomIn": "Phóng to", + "newFile": "Tập tin mới", + "newFolder": "Thư mục mới", + "rename": "Đổi tên", + "uploading": "Đang tải lên...", + "uploadingFile": "Đang tải lên {{name}}...", + "fileName": "Tên tập tin", + "folderName": "Tên thư mục", + "fileUploadedSuccessfully": "Tập tin \"{{name}}\" đã được tải lên thành công", + "failedToUploadFile": "Tải tập tin lên thất bại", + "uploadingFolderFiles": "Đang tải lên {{count}} tập tin...", + "uploadedFolderFiles": "Đã tải lên {{count}} tập tin thành công", + "uploadedFolderPartial": "Đã tải lên {{uploaded}} tập tin, {{failed}} thất bại", + "fileDownloadedSuccessfully": "Tập tin \"{{name}}\" đã được tải xuống thành công", + "failedToDownloadFile": "Tải tập tin xuống thất bại", + "fileCreatedSuccessfully": "Tập tin \"{{name}}\" đã được tạo thành công", + "folderCreatedSuccessfully": "Thư mục \"{{name}}\" đã được tạo thành công", + "failedToCreateItem": "Không thể tạo mục", + "operationFailed": "Thao tác {{operation}} thất bại đối với {{name}}: {{error}}", + "failedToResolveSymlink": "Không thể theo dõi symlink", + "itemsDeletedSuccessfully": "Đã xóa thành công {{count}} mục", + "failedToDeleteItems": "Không thể xóa các mục", + "sudoPasswordRequired": "Yêu cầu mật khẩu quản trị", + "enterSudoPassword": "Nhập mật khẩu sudo để tiếp tục thao tác này", + "sudoPassword": "Mật khẩu sudo", + "sudoOperationFailed": "Thao tác sudo thất bại", + "sudoAuthFailed": "Xác thực sudo thất bại", + "dragFilesToUpload": "Kéo tệp vào đây để tải lên", + "dropToUploadHere": "Kéo thả để tải lên tại đây", + "dropToDownloadHere": "Kéo thả để tải xuống tại đây", + "localFiles": "Địa phương", + "showLocalFiles": "Hiển thị các tệp cục bộ (chế độ chia đôi màn hình)", + "hideLocalFiles": "Ẩn các tệp cục bộ", + "localHome": "Thư mục trang chủ", + "localPathPlaceholder": "Đường dẫn thư mục cục bộ", + "localShowHidden": "Hiển thị các tệp ẩn", + "localHideHidden": "Ẩn các tệp tin ẩn", + "localRevealInFileManager": "Hiển thị trong trình quản lý tệp hệ thống", + "localCannotOpenFolder": "Không thể mở thư mục", + "localItemCount": "{{count}} mục", + "localSelectedCount": "{{count}} đã chọn", + "back": "Mặt sau", + "forward": "Phía trước", + "up": "Hướng lên", + "kind": "Loại", + "localUploadingProgress": "Đang tải lên {{current}} trong số {{total}}", + "localDownloadingProgress": "Đang tải xuống {{current}} của {{total}}", + "localTransferInFlight": "{{count}} ngay lập tức", + "localPreparingDownload": "Đang chuẩn bị tải xuống...", + "localTransferCancelled": "Giao dịch bị hủy", + "localNothingToTransfer": "Không có gì để chuyển", + "localUploadComplete": "Đã tải lên {{count}} tập tin", + "localUploadFailed": "Tải lên thất bại", + "localUploadPartial": "Đã tải lên {{done}} tập tin, {{failed}} thất bại", + "localDownloadComplete": "Đã tải xuống {{count}} tập tin", + "localDownloadFailed": "Tải xuống thất bại", + "localDownloadPartial": "Đã tải xuống {{done}} tập tin, {{failed}} thất bại", + "localReplaceExistingPrompt": "{{count}} mục đã tồn tại trong thư mục này. Bạn có muốn thay thế chúng không?", + "localReplace": "Thay thế", + "localSkipExisting": "Nhảy", + "localSkippedExisting": "Đã bỏ qua {{count}} mục hiện có", + "localUnsafeNamesSkipped": "Đã bỏ qua {{count}} mục mà tên của chúng không thể sử dụng trên máy tính này.", + "localOpen": "Mở", + "localOpenFolder": "Mở thư mục", + "localUploadToRemote": "Tải lên máy chủ", + "localUploadToRemoteMany": "Tải lên {{count}} mục lên máy chủ", + "localMoveToTrash": "Chuyển vào thùng rác", + "localMoveToTrashMany": "Di chuyển {{count}} mục vào Thùng rác", + "localTrashConfirmSingle": "Di chuyển \"{{name}}\" vào Thùng rác?", + "localTrashConfirmFolder": "Di chuyển thư mục \"{{name}}\" và tất cả nội dung bên trong vào Thùng rác?", + "localTrashConfirmMany": "Di chuyển {{count}} mục vào Thùng rác?", + "localTrashed": "Đã chuyển {{count}} mục vào Thùng rác", + "localTrashFailed": "Không thể chuyển vào Thùng rác", + "localRenameFailed": "Đổi tên không thành công", + "localCreateFailed": "Không thể tạo mục", + "goToParentFolder": "Đi đến thư mục cha", + "resizeSidebar": "Kéo để thay đổi kích thước thanh bên (nhấp đúp để đặt lại về kích thước ban đầu)", + "resizeLocalPane": "Kéo chuột để thay đổi kích thước khung cục bộ (nhấp đúp chuột để đặt lại về kích thước ban đầu)", + "columns": "Cột", + "columnsHint": "Nhấp chuột phải để chọn cột", + "emptyFolder": "Thư mục này trống", + "searchFiles": "Tìm kiếm tệp...", + "upload": "Tải lên", + "selectHostToStart": "Chọn máy chủ để bắt đầu quản lý tệp", + "sshRequiredForFileManager": "Trình quản lý tệp yêu cầu SSH. Máy chủ này không bật SSH.", + "failedToConnect": "Kết nối SSH thất bại", + "failedToLoadDirectory": "Không thể tải thư mục", + "noSSHConnection": "Không có kết nối SSH", + "copy": "Sao chép", + "cut": "Cắt", + "paste": "Dán", + "copyPath": "Sao chép đường dẫn", + "copyPaths": "Sao chép các đường dẫn", + "delete": "Xóa", + "properties": "Thuộc tính", + "refresh": "Làm mới", + "downloadFiles": "Tải {{count}} tệp xuống trình duyệt", + "copyFiles": "Sao chép {{count}} mục", + "cutFiles": "Cắt {{count}} mục", + "deleteFiles": "Xóa {{count}} mục", + "filesCopiedToClipboard": "Đã sao chép {{count}} mục vào clipboard", + "filesCutToClipboard": "Đã cắt {{count}} mục vào clipboard", + "pathCopiedToClipboard": "Đã sao chép đường dẫn vào clipboard", + "pathsCopiedToClipboard": "Đã sao chép {{count}} đường dẫn vào clipboard", + "failedToCopyPath": "Không thể sao chép đường dẫn vào clipboard", + "copyFolderLink": "Sao chép liên kết đến thư mục", + "copyCurrentFolderLink": "Sao chép liên kết đến thư mục hiện tại", + "folderLinkCopied": "Đã sao chép liên kết thư mục vào clipboard", + "failedToCopyFolderLink": "Không thể sao chép liên kết thư mục", + "movedItems": "Đã di chuyển {{count}} mục", + "failedToDeleteItem": "Không thể xóa mục", + "itemRenamedSuccessfully": "Đã đổi tên {{type}} thành công", + "failedToRenameItem": "Không thể đổi tên mục", + "download": "Tải xuống", + "openExternalEditor": "Mở bên ngoài", + "chooseExternalEditor": "Chọn trình soạn thảo bên ngoài", + "externalEditorSelected": "Đã chọn trình soạn thảo bên ngoài", + "externalEditorDesktopOnly": "Trình soạn thảo bên ngoài chỉ khả dụng trong ứng dụng desktop", + "externalEditorOpened": "Đã mở bằng trình soạn thảo bên ngoài. Lưu ở đó sẽ tải các thay đổi lên máy chủ.", + "failedToOpenExternalEditor": "Không thể mở trình soạn thảo bên ngoài", + "failedToSelectExternalEditor": "Không thể chọn trình soạn thảo bên ngoài", + "permissions": "Quyền", + "size": "Kích thước", + "modified": "Đã sửa", + "path": "Đường dẫn", + "confirmDelete": "Bạn có chắc chắn muốn xóa {{name}}?", + "permissionDenied": "Quyền bị từ chối", + "serverError": "Lỗi máy chủ", + "fileSavedSuccessfully": "Đã lưu tệp thành công", + "failedToSaveFile": "Không thể lưu tệp", + "confirmDeleteSingleItem": "Bạn có chắc chắn muốn xóa vĩnh viễn \"{{name}}\"?", + "confirmDeleteMultipleItems": "Bạn có chắc chắn muốn xóa vĩnh viễn {{count}} mục?", + "confirmDeleteMultipleItemsWithFolders": "Bạn có chắc chắn muốn xóa vĩnh viễn {{count}} mục? Điều này bao gồm các thư mục và nội dung của chúng.", + "confirmDeleteFolder": "Bạn có chắc chắn muốn xóa vĩnh viễn thư mục \"{{name}}\" và tất cả nội dung của nó?", + "permanentDeleteWarning": "Hành động này không thể hoàn tác. (Các) mục sẽ bị xóa vĩnh viễn khỏi máy chủ.", + "moveToTrashWarning": "Các mục này sẽ được chuyển vào Thùng rác Termix và có thể được khôi phục trước khi thời hạn lưu giữ hết hạn.", + "itemsMovedToTrash": "{{count}} mục đã được chuyển vào Thùng rác", + "trash": "Rác", + "trashDescription": "Các mục đã xóa sẽ được lưu giữ trong {{days}} ngày trên máy chủ này.", + "trashLoadFailed": "Không thể tải Thùng rác", + "trashEmpty": "Thùng rác trống", + "trashRestore": "Khôi phục", + "trashRestored": "Đã khôi phục {{name}}", + "trashRestoreFailed": "Không thể khôi phục mục. Đường dẫn gốc có thể đã tồn tại.", + "trashDeletePermanently": "Xóa vĩnh viễn", + "trashDeleteConfirm": "Xóa vĩnh viễn {{name}}? Thao tác này không thể hoàn tác.", + "trashPermanentlyDeleted": "Đã xóa vĩnh viễn {{name}}", + "trashDeleteFailed": "Không thể xóa mục vĩnh viễn", + "trashEmptyAction": "Đổ rác", + "trashEmptyConfirm": "Xóa vĩnh viễn mọi mục trong Thùng rác? Thao tác này không thể hoàn tác.", + "trashEmptied": "Thùng rác đã được đổ", + "trashRetention": "Tự động dọn dẹp sau (ngày)", + "trashRetentionSaved": "Cập nhật thời gian lưu trữ rác", + "trashRetentionFailed": "Không thể cập nhật thời gian lưu trữ thùng rác.", + "trashUnavailableConfirm": "Không thể chuyển mục này vào Thùng rác, thường là do nó nằm trên hệ thống tệp khác hoặc yêu cầu quyền quản trị. Thay vào đó, hãy xóa vĩnh viễn mục này?", + "recent": "Gần đây", + "pinned": "Đã ghim", + "folderShortcuts": "Lối tắt thư mục", + "failedToReconnectSSH": "Không thể kết nối lại phiên SSH", + "openTerminalHere": "Mở terminal tại đây", + "run": "Chạy", + "openTerminalInFolder": "Mở terminal trong thư mục này", + "openTerminalInFileLocation": "Mở terminal tại vị trí tệp", + "runningFile": "Đang chạy - {{file}}", + "onlyRunExecutableFiles": "Chỉ chạy được tệp thực thi", + "directories": "Thư mục", + "removedFromRecentFiles": "Đã xóa \"{{name}}\" khỏi tệp gần đây", + "removeFailed": "Xóa thất bại", + "unpinnedSuccessfully": "Đã bỏ ghim \"{{name}}\" thành công", + "unpinFailed": "Bỏ ghim thất bại", + "removedShortcut": "Đã xóa lối tắt \"{{name}}\"", + "removeShortcutFailed": "Xóa lối tắt thất bại", + "clearedAllRecentFiles": "Đã xóa toàn bộ tệp gần đây", + "clearFailed": "Xóa thất bại", + "removeFromRecentFiles": "Xóa khỏi tệp gần đây", + "clearAllRecentFiles": "Xóa toàn bộ tệp gần đây", + "unpinFile": "Bỏ ghim tệp", + "removeShortcut": "Xóa lối tắt", + "pinFile": "Ghim tệp", + "addToShortcuts": "Thêm vào lối tắt", + "pasteFailed": "Dán thất bại", + "noUndoableActions": "Không có hành động hoàn tác được", + "undoCopySuccess": "Đã hoàn tác sao chép: Đã xóa {{count}} tệp đã sao chép", + "undoCopyFailedDelete": "Hoàn tác thất bại: Không thể xóa tệp đã sao chép", + "undoCopyFailedNoInfo": "Hoàn tác thất bại: Không tìm thấy thông tin tệp đã sao chép", + "undoMoveSuccess": "Đã hoàn tác di chuyển: Đã di chuyển {{count}} tệp về vị trí ban đầu", + "undoMoveFailedMove": "Hoàn tác thất bại: Không thể di chuyển tệp về vị trí cũ", + "undoMoveFailedNoInfo": "Hoàn tác thất bại: Không tìm thấy thông tin tệp đã di chuyển", + "undoDeleteNotSupported": "Không thể hoàn tác xóa: Tệp đã bị xóa vĩnh viễn khỏi máy chủ", + "undoTypeNotSupported": "Loại thao tác hoàn tác không được hỗ trợ", + "undoOperationFailed": "Thao tác hoàn tác thất bại", + "unknownError": "Lỗi không xác định", + "confirm": "Xác nhận", + "find": "Tìm kiếm...", + "replace": "Thay thế", + "downloadInstead": "Tải xuống thay thế", + "keyboardShortcuts": "Phím tắt", + "searchAndReplace": "Tìm & Thay thế", + "editing": "Chỉnh sửa", + "search": "Tìm kiếm", + "findNext": "Tìm tiếp", + "findPrevious": "Tìm trước", + "save": "Lưu", + "selectAll": "Chọn tất cả", + "undo": "Hoàn tác", + "redo": "Làm lại", + "moveLineUp": "Di chuyển dòng lên", + "moveLineDown": "Di chuyển dòng xuống", + "toggleComment": "Chuyển đổi chú thích", + "autoComplete": "Tự động hoàn thành", + "imageLoadError": "Lỗi tải ảnh", + "startTyping": "Bắt đầu nhập...", + "unknownSize": "Kích thước không xác định", + "fileIsEmpty": "Tệp trống", + "largeFileWarning": "Cảnh báo tệp lớn", + "largeFileWarningDesc": "Tệp này có kích thước {{size}}, có thể gây ra vấn đề hiệu suất khi mở dưới dạng văn bản.", + "fileNotFoundAndRemoved": "Tệp \"{{name}}\" không tìm thấy và đã bị xóa khỏi tệp gần đây/đã ghim", + "failedToLoadFile": "Lỗi tải tệp: {{error}}", + "serverErrorOccurred": "Đã xảy ra lỗi máy chủ. Vui lòng thử lại sau.", + "autoSaveFailed": "Tự động lưu không thành công", + "fileAutoSaved": "Tệp đã được tự động lưu", + "moveFileFailed": "Không thể di chuyển {{name}}", + "moveOperationFailed": "Thao tác di chuyển không thành công", + "canOnlyCompareFiles": "Chỉ có thể so sánh hai tệp", + "comparingFiles": "Đang so sánh tệp: {{file1}} và {{file2}}", + "dragFailed": "Thao tác kéo không thành công", + "filePinnedSuccessfully": "Tệp \"{{name}}\" đã được ghim thành công", + "pinFileFailed": "Không thể ghim tệp", + "fileUnpinnedSuccessfully": "Tệp \"{{name}}\" đã được bỏ ghim thành công", + "unpinFileFailed": "Không thể bỏ ghim tệp", + "shortcutAddedSuccessfully": "Đã thêm lối tắt thư mục \"{{name}}\" thành công", + "addShortcutFailed": "Không thể thêm lối tắt", + "operationCompletedSuccessfully": "Đã {{operation}} thành công {{count}} mục", + "operationCompleted": "Đã {{operation}} {{count}} mục", + "downloadFileSuccess": "Đã tải xuống tệp {{name}} thành công", + "downloadFileFailed": "Tải xuống thất bại", + "downloadingFile": "Đang tải xuống {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "Di chuyển tới {{name}}", + "diffCompareWith": "So sánh khác biệt với {{name}}", + "dragOutsideToDownload": "Kéo ra ngoài cửa sổ để tải xuống ({{count}} tệp)", + "newFolderDefault": "Thư mục mới", + "newFileDefault": "Tệp mới.txt", + "successfullyMovedItems": "Đã di chuyển thành công {{count}} mục tới {{target}}", + "move": "Di chuyển", + "searchInFile": "Tìm trong tệp (Ctrl+F)", + "showKeyboardShortcuts": "Hiển thị phím tắt", + "decreaseFontSize": "Giảm cỡ chữ", + "increaseFontSize": "Tăng cỡ chữ", + "startWritingMarkdown": "Bắt đầu viết nội dung markdown...", + "loadingFileComparison": "Đang tải so sánh tệp...", + "reload": "Tải lại", + "compare": "So sánh", + "sideBySide": "Song song", + "inline": "Trong dòng", + "fileComparison": "So sánh tệp: {{file1}} vs {{file2}}", + "fileTooLarge": "Tệp quá lớn: {{error}}", + "sshConnectionFailed": "Kết nối SSH thất bại. Vui lòng kiểm tra kết nối tới {{name}} ({{ip}}:{{port}})", + "loadFileFailed": "Không thể tải tệp: {{error}}", + "connecting": "Đang kết nối...", + "connectedSuccessfully": "Đã kết nối thành công", + "totpVerificationFailed": "Xác minh TOTP thất bại", + "warpgateVerificationFailed": "Xác thực Warpgate thất bại", + "authenticationFailed": "Xác thực thất bại", + "incorrectPassphrase": "Cụm mật khẩu không đúng. Vui lòng thử lại.", + "verificationCodePrompt": "Mã xác minh:", + "changePermissions": "Thay đổi quyền", + "currentPermissions": "Quyền hiện tại", + "owner": "Chủ sở hữu", + "group": "Nhóm", + "others": "Khác", + "read": "Đọc", + "write": "Ghi", + "execute": "Thực thi", + "permissionsChangedSuccessfully": "Thay đổi quyền thành công", + "failedToChangePermissions": "Không thể thay đổi quyền", + "name": "Tên", + "sortByName": "Tên", + "sortByDate": "Ngày sửa đổi", + "sortBySize": "Kích thước", + "ascending": "Tăng dần", + "descending": "Giảm dần", + "root": "Thư mục gốc", + "new": "Mới", + "sortBy": "Sắp xếp theo", + "items": "Mục", + "selected": "Đã chọn", + "editor": "Soạn thảo", + "octal": "Bát phân", + "storage": "Lưu trữ", + "disk": "Đĩa", + "used": "Đã dùng", + "of": "của", + "toggleSidebar": "Ẩn/Hiện thanh bên", + "compactLayout": "Sử dụng bố cục nhỏ gọn", + "comfortableLayout": "Sử dụng bố cục thoải mái", + "cannotLoadPdf": "Không thể tải PDF", + "pdfLoadError": "Đã xảy ra lỗi khi tải tệp PDF này.", + "loadingPdf": "Đang tải PDF...", + "loadingPage": "Đang tải trang..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "Truyền tệp giữa hai máy chủ SSH.", + "sourceServer": "Máy chủ nguồn", + "destinationServer": "Máy chủ đích", + "selectHost": "Chọn máy chủ", + "noHostSelected": "Chưa chọn máy chủ nào", + "move": "Di chuyển", + "copy": "Sao chép", + "upload": "Tải lên", + "itemsSelected": "{{count}} mục đã chọn", + "itemsSelected_other": "{{count}} mục đã chọn", + "ready": "Sẵn sàng", + "loadingHosts": "Đang tải máy chủ...", + "name": "Tên", + "modified": "Đã sửa đổi", + "size": "Kích cỡ", + "noFiles": "Không có tệp nào", + "open": "Mở", + "up": "Hướng lên", + "refresh": "Làm cho khỏe lại", + "copyToTarget": "Sao chép vào thư mục đích", + "rename": "Đổi tên", + "delete": "Xóa bỏ", + "editPermissions": "Chỉnh sửa quyền", + "createNewFolder": "Tạo thư mục mới", + "deleteSelectedItems": "Xóa các mục đã chọn", + "deleteConfirm": "Xóa mục đã chọn {{count}}?", + "deleteConfirm_other": "Xóa {{count}} mục đã chọn?", + "cancel": "Hủy bỏ", + "save": "Cứu", + "folderCreated": "Thư mục đã được tạo", + "itemRenamed": "Mục đã được đổi tên", + "actionFailed": "Hành động thất bại", + "itemsDeleted": "{{count}} mục đã bị xóa", + "itemsDeleted_other": "{{count}} mục đã bị xóa", + "deleteFailed": "Xóa không thành công", + "permissionsUpdated": "Quyền hạn đã được cập nhật", + "noRemoteSessionConnected": "Không có phiên kết nối từ xa nào được thiết lập.", + "failedToListFiles": "Không thể liệt kê các tệp.", + "failedToRefresh": "Không thể làm mới", + "destinationInsideSource": "Đường dẫn đích không được nằm bên trong đường dẫn nguồn đã chọn.", + "sameHostNoOp": "Thư mục nguồn và thư mục đích đều nằm trong cùng một thư mục." + }, + "hosts": { + "fileManager": "Trình quản lý tệp", + "tabFiles": "Tệp", + "enableFileManagerMonitor": "Bật Trình quản lý tập tin", + "enableFileManagerMonitorDesc": "Duyệt và quản lý tập tin trên máy này qua SFTP", + "scpLegacyLabel": "Chế độ SCP cũ", + "scpLegacyDesc": "Sử dụng truyền tập tin kiểu cũ cho các máy chủ không hỗ trợ hệ thống con SFTP (ví dụ: máy chủ SSH nhúng hoặc tối thiểu).", + "defaultPathLabel": "Đường dẫn mặc định", + "fileManagerPathHint": "Thư mục sẽ mở khi khởi chạy Trình quản lý tập tin cho máy này.", + "fileManagerUrlCopied": "Đã sao chép URL Trình quản lý tệp", + "copyFileManagerUrlAction": "Sao chép URL File Manager" + }, + "settings": { + "host": { + "enable": { + "label": "Bật Trình quản lý tập tin" + }, + "defaultPath": { + "label": "Đường dẫn mặc định" + }, + "scpLegacy": { + "label": "Chế độ SCP cũ" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "Tệp" + } +} diff --git a/plugins/file-manager/locales/translated/zh_CN.json b/plugins/file-manager/locales/translated/zh_CN.json new file mode 100644 index 000000000..a182b99cf --- /dev/null +++ b/plugins/file-manager/locales/translated/zh_CN.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "未选择主机", + "initializingEditor": "正在初始化编辑器...", + "file": "文件", + "folder": "文件夹", + "uploadFile": "上传文件", + "downloadFile": "下载", + "extractArchive": "解压归档文件", + "extractingArchive": "正在解压 {{name}}...", + "archiveExtractedSuccessfully": "{{name}} 解压成功", + "extractFailed": "解压失败", + "compressFile": "压缩文件", + "compressFiles": "压缩文件", + "compressFilesDesc": "将 {{count}} 个项目压缩成一个归档文件", + "archiveName": "归档名称", + "enterArchiveName": "输入归档名称...", + "compressionFormat": "压缩格式", + "selectedFiles": "已选文件", + "andMoreFiles": "还有 {{count}} 个...", + "compress": "压缩", + "compressingFiles": "正在将 {{count}} 个项目压缩成 {{name}}...", + "filesCompressedSuccessfully": "{{name}} 创建成功", + "compressFailed": "压缩失败", + "edit": "编辑", + "preview": "预览", + "previous": "上一个", + "next": "下一个", + "pageXOfY": "第 {{current}} 页,共 {{total}} 页", + "zoomOut": "缩小", + "zoomIn": "放大", + "newFile": "新建文件", + "newFolder": "新建文件夹", + "rename": "重命名", + "uploading": "上传中...", + "uploadingFile": "正在上传 {{name}}...", + "fileName": "文件名", + "folderName": "文件夹名称", + "fileUploadedSuccessfully": "文件 \"{{name}}\" 上传成功", + "failedToUploadFile": "上传文件失败", + "uploadingFolderFiles": "正在上传 {{count}} 文件...", + "uploadedFolderFiles": "已成功上传 {{count}} 个文件", + "uploadedFolderPartial": "上传了 {{uploaded}} 个文件, {{failed}} 个文件失败", + "fileDownloadedSuccessfully": "文件 \"{{name}}\" 下载成功", + "failedToDownloadFile": "下载文件失败", + "fileCreatedSuccessfully": "文件 \"{{name}}\" 创建成功", + "folderCreatedSuccessfully": "文件夹 \"{{name}}\" 创建成功", + "failedToCreateItem": "创建项目失败", + "operationFailed": "对 {{name}} 的 {{operation}} 操作失败:{{error}}", + "failedToResolveSymlink": "解析符号链接失败", + "itemsDeletedSuccessfully": "{{count}} 个项目删除成功", + "failedToDeleteItems": "删除项目失败", + "sudoPasswordRequired": "需要管理员密码", + "enterSudoPassword": "输入 sudo 密码以继续此操作", + "sudoPassword": "Sudo 密码", + "sudoOperationFailed": "Sudo 操作失败", + "sudoAuthFailed": "Sudo 认证失败", + "dragFilesToUpload": "将文件拖放到此处以上传", + "dropToUploadHere": "在此处上传", + "dropToDownloadHere": "点击此处下载", + "localFiles": "当地的", + "showLocalFiles": "显示本地文件(分屏显示)", + "hideLocalFiles": "隐藏本地文件", + "localHome": "主页", + "localPathPlaceholder": "本地文件夹路径", + "localShowHidden": "显示隐藏文件", + "localHideHidden": "隐藏隐藏文件", + "localRevealInFileManager": "在系统文件管理器中显示", + "localCannotOpenFolder": "无法打开文件夹", + "localItemCount": "{{count}} 项目", + "localSelectedCount": "{{count}} 已选择", + "back": "后退", + "forward": "向前", + "up": "向上", + "kind": "种类", + "localUploadingProgress": "正在上传 {{current}} 中的 {{total}}", + "localDownloadingProgress": "正在下载 {{current}} 中的 {{total}}", + "localTransferInFlight": "{{count}} 立刻", + "localPreparingDownload": "正在准备下载……", + "localTransferCancelled": "转账已取消", + "localNothingToTransfer": "无需转移", + "localUploadComplete": "已上传 {{count}} 个文件", + "localUploadFailed": "上传失败", + "localUploadPartial": "上传了 {{done}} 个文件, {{failed}} 个文件失败", + "localDownloadComplete": "已下载 {{count}} 个文件", + "localDownloadFailed": "下载失败", + "localDownloadPartial": "下载了 {{done}} 个文件, {{failed}} 个文件失败", + "localReplaceExistingPrompt": "此文件夹中已存在 {{count}} 个项目。是否替换它们?", + "localReplace": "代替", + "localSkipExisting": "跳过", + "localSkippedExisting": "已跳过 {{count}} 个现有项目", + "localUnsafeNamesSkipped": "已跳过 {{count}} 个名称无法在此计算机上使用的项目。", + "localOpen": "打开", + "localOpenFolder": "打开文件夹", + "localUploadToRemote": "上传到服务器", + "localUploadToRemoteMany": "上传 {{count}} 项到服务器", + "localMoveToTrash": "移至垃圾桶", + "localMoveToTrashMany": "将 {{count}} 个物品移至垃圾桶", + "localTrashConfirmSingle": "将“{{name}}”移至回收站?", + "localTrashConfirmFolder": "将文件夹“{{name}}”及其所有内容移至回收站?", + "localTrashConfirmMany": "将 {{count}} 个项目移至回收站?", + "localTrashed": "已将 {{count}} 个项目移至回收站", + "localTrashFailed": "无法移至垃圾桶", + "localRenameFailed": "重命名失败", + "localCreateFailed": "无法创建项目", + "goToParentFolder": "转到上文件夹", + "resizeSidebar": "拖动即可调整侧边栏大小(双击即可重置)", + "resizeLocalPane": "拖动即可调整本地窗格大小(双击可重置)", + "columns": "列", + "columnsHint": "右键单击选择列", + "emptyFolder": "此文件夹为空", + "searchFiles": "搜索文件...", + "upload": "上传", + "selectHostToStart": "选择主机以开始文件管理", + "sshRequiredForFileManager": "文件管理器需要 SSH。此主机未启用 SSH。", + "failedToConnect": "连接 SSH 失败", + "failedToLoadDirectory": "加载目录失败", + "noSSHConnection": "没有可用的 SSH 连接", + "copy": "复制", + "cut": "剪切", + "paste": "粘贴", + "copyPath": "复制路径", + "copyPaths": "复制路径", + "delete": "删除", + "properties": "属性", + "refresh": "刷新", + "downloadFiles": "将 {{count}} 个文件下载到浏览器", + "copyFiles": "复制 {{count}} 个项目", + "cutFiles": "剪切 {{count}} 个项目", + "deleteFiles": "删除 {{count}} 个项目", + "filesCopiedToClipboard": "已将 {{count}} 个项目复制到剪贴板", + "filesCutToClipboard": "已将 {{count}} 个项目剪切到剪贴板", + "pathCopiedToClipboard": "路径已复制到剪贴板", + "pathsCopiedToClipboard": "已将 {{count}} 条路径复制到剪贴板", + "failedToCopyPath": "无法将路径复制到剪贴板", + "copyFolderLink": "复制文件夹链接", + "copyCurrentFolderLink": "复制当前文件夹链接", + "folderLinkCopied": "文件夹链接已复制到剪贴板", + "failedToCopyFolderLink": "无法复制文件夹链接", + "movedItems": "已移动 {{count}} 个项目", + "failedToDeleteItem": "删除项目失败", + "itemRenamedSuccessfully": "{{type}} 重命名成功", + "failedToRenameItem": "重命名项目失败", + "download": "下载", + "openExternalEditor": "在外部打开", + "chooseExternalEditor": "选择外部编辑器", + "externalEditorSelected": "已选择外部编辑器", + "externalEditorDesktopOnly": "外部编辑器仅在桌面应用中可用", + "externalEditorOpened": "已在外部编辑器中打开。在其中保存文件会将更改上传回服务器。", + "failedToOpenExternalEditor": "无法打开外部编辑器", + "failedToSelectExternalEditor": "无法选择外部编辑器", + "permissions": "权限", + "size": "大小", + "modified": "修改时间", + "path": "路径", + "confirmDelete": "确定要删除 {{name}} 吗?", + "permissionDenied": "权限被拒绝", + "serverError": "服务器错误", + "fileSavedSuccessfully": "文件已成功保存", + "failedToSaveFile": "保存文件失败", + "confirmDeleteSingleItem": "确定要永久删除“{{name}}”吗?", + "confirmDeleteMultipleItems": "确定要永久删除{{count}}个项目吗?", + "confirmDeleteMultipleItemsWithFolders": "确定要永久删除{{count}}个项目吗?这包括文件夹及其内容。", + "confirmDeleteFolder": "确定要永久删除文件夹“{{name}}”及其所有内容吗?", + "permanentDeleteWarning": "此操作无法撤销。项目将从服务器永久删除。", + "moveToTrashWarning": "该项目将被移至 Termix 回收站,可在保留期到期前恢复。", + "itemsMovedToTrash": "{{count}} 项已移至回收站", + "trash": "回收站", + "trashDescription": "已删除的项目将在本主机上保留 {{days}} 天。", + "trashLoadFailed": "加载回收站失败", + "trashEmpty": "回收站是空的", + "trashRestore": "恢复", + "trashRestored": "已恢复 {{name}}", + "trashRestoreFailed": "恢复项目失败。原始路径可能已存在。", + "trashDeletePermanently": "永久删除", + "trashDeleteConfirm": "永久删除 {{name}}?此操作无法撤销。", + "trashPermanentlyDeleted": "永久删除 {{name}}", + "trashDeleteFailed": "无法永久删除项目", + "trashEmptyAction": "清空回收站", + "trashEmptyConfirm": "永久删除回收站中的所有项目?此操作无法撤销。", + "trashEmptied": "回收站已清空", + "trashRetention": "自动清理(N 天后)", + "trashRetentionSaved": "回收站保留设置已更新", + "trashRetentionFailed": "更新回收站保留设置失败", + "trashUnavailableConfirm": "此项目无法移至回收站,通常是因为它位于其他文件系统或需要更高的权限。是否将其永久删除?", + "recent": "最近", + "pinned": "已固定", + "folderShortcuts": "文件夹快捷方式", + "failedToReconnectSSH": "重新连接SSH会话失败", + "openTerminalHere": "在此处打开终端", + "run": "运行", + "openTerminalInFolder": "在此文件夹中打开终端", + "openTerminalInFileLocation": "在文件位置打开终端", + "runningFile": "正在运行 - {{file}}", + "onlyRunExecutableFiles": "只能运行可执行文件", + "directories": "目录", + "removedFromRecentFiles": "已从最近文件中移除“{{name}}”", + "removeFailed": "移除失败", + "unpinnedSuccessfully": "已成功取消固定“{{name}}”", + "unpinFailed": "取消固定失败", + "removedShortcut": "已移除快捷方式“{{name}}”", + "removeShortcutFailed": "移除快捷方式失败", + "clearedAllRecentFiles": "已清空所有最近文件", + "clearFailed": "清空失败", + "removeFromRecentFiles": "从最近文件中移除", + "clearAllRecentFiles": "清空所有最近文件", + "unpinFile": "取消固定文件", + "removeShortcut": "移除快捷方式", + "pinFile": "固定文件", + "addToShortcuts": "添加到快捷方式", + "pasteFailed": "粘贴失败", + "noUndoableActions": "无可撤销的操作", + "undoCopySuccess": "已撤销复制操作:已删除{{count}}个已复制的文件", + "undoCopyFailedDelete": "撤销失败:无法删除任何已复制的文件", + "undoCopyFailedNoInfo": "撤销失败:找不到已复制的文件信息", + "undoMoveSuccess": "已撤销移动操作:已将{{count}}个文件移回原始位置", + "undoMoveFailedMove": "撤销失败:无法将任何文件移回", + "undoMoveFailedNoInfo": "撤销失败:找不到移动的文件信息", + "undoDeleteNotSupported": "删除操作无法撤销:文件已从服务器永久删除", + "undoTypeNotSupported": "不支持的撤销操作类型", + "undoOperationFailed": "撤销操作失败", + "unknownError": "未知错误", + "confirm": "确认", + "find": "查找...", + "replace": "替换", + "downloadInstead": "改为下载", + "keyboardShortcuts": "键盘快捷键", + "searchAndReplace": "查找和替换", + "editing": "编辑", + "search": "搜索", + "findNext": "查找下一个", + "findPrevious": "查找上一个", + "save": "保存", + "selectAll": "全选", + "undo": "撤销", + "redo": "重做", + "moveLineUp": "上移一行", + "moveLineDown": "下移一行", + "toggleComment": "切换注释", + "autoComplete": "自动补全", + "imageLoadError": "加载图像失败", + "startTyping": "开始键入...", + "unknownSize": "未知大小", + "fileIsEmpty": "文件为空", + "largeFileWarning": "大文件警告", + "largeFileWarningDesc": "该文件大小为 {{size}},以文本方式打开可能会导致性能问题。", + "fileNotFoundAndRemoved": "文件 \"{{name}}\" 未找到,已从最近/固定文件中移除", + "failedToLoadFile": "加载文件失败:{{error}}", + "serverErrorOccurred": "服务器发生错误,请稍后重试。", + "autoSaveFailed": "自动保存失败", + "fileAutoSaved": "文件已自动保存", + "moveFileFailed": "移动 {{name}} 失败", + "moveOperationFailed": "移动操作失败", + "canOnlyCompareFiles": "只能比较两个文件", + "comparingFiles": "正在比较文件:{{file1}} 和 {{file2}}", + "dragFailed": "拖动操作失败", + "filePinnedSuccessfully": "文件 \"{{name}}\" 固定成功", + "pinFileFailed": "固定文件失败", + "fileUnpinnedSuccessfully": "文件 \"{{name}}\" 取消固定成功", + "unpinFileFailed": "取消固定文件失败", + "shortcutAddedSuccessfully": "文件夹快捷方式 \"{{name}}\" 添加成功", + "addShortcutFailed": "添加快捷方式失败", + "operationCompletedSuccessfully": "成功{{operation}}了{{count}}个项目", + "operationCompleted": "{{operation}}了{{count}}个项目", + "downloadFileSuccess": "文件 {{name}} 下载成功", + "downloadFileFailed": "下载失败", + "downloadingFile": "正在下载 {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "移动到 {{name}}", + "diffCompareWith": "与 {{name}} 对比差异", + "dragOutsideToDownload": "拖到窗口外以下载 ({{count}} 个文件)", + "newFolderDefault": "新建文件夹", + "newFileDefault": "新建文件.txt", + "successfullyMovedItems": "成功移动 {{count}} 个项目到 {{target}}", + "move": "移动", + "searchInFile": "在文件中搜索 (Ctrl+F)", + "showKeyboardShortcuts": "显示键盘快捷键", + "decreaseFontSize": "缩小字体", + "increaseFontSize": "放大字体", + "startWritingMarkdown": "开始编写Markdown内容...", + "loadingFileComparison": "加载文件比较中...", + "reload": "重新加载", + "compare": "比较", + "sideBySide": "并排", + "inline": "内联", + "fileComparison": "文件比较: {{file1}} 对比 {{file2}}", + "fileTooLarge": "文件过大: {{error}}", + "sshConnectionFailed": "SSH 连接失败。请检查您到 {{name}} ({{ip}}:{{port}}) 的连接", + "loadFileFailed": "加载文件失败: {{error}}", + "connecting": "连接中...", + "connectedSuccessfully": "连接成功", + "totpVerificationFailed": "TOTP 验证失败", + "warpgateVerificationFailed": "Warpgate 认证失败", + "authenticationFailed": "认证失败", + "incorrectPassphrase": "密码错误。请重试。", + "verificationCodePrompt": "验证码:", + "changePermissions": "更改权限", + "currentPermissions": "当前权限", + "owner": "所有者", + "group": "组", + "others": "其他", + "read": "读取", + "write": "写入", + "execute": "执行", + "permissionsChangedSuccessfully": "权限修改成功", + "failedToChangePermissions": "权限修改失败", + "name": "名称", + "sortByName": "名称", + "sortByDate": "修改日期", + "sortBySize": "大小", + "ascending": "升序", + "descending": "降序", + "root": "根目录", + "new": "新建", + "sortBy": "排序方式", + "items": "项目", + "selected": "已选择", + "editor": "编辑器", + "octal": "八进制", + "storage": "存储", + "disk": "磁盘", + "used": "已用", + "of": "/", + "toggleSidebar": "切换侧边栏", + "compactLayout": "采用紧凑型布局", + "comfortableLayout": "采用舒适的布局", + "cannotLoadPdf": "无法加载 PDF", + "pdfLoadError": "加载此 PDF 文件时出错。", + "loadingPdf": "正在加载 PDF...", + "loadingPage": "正在加载页面..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "在两台SSH主机之间传输文件。", + "sourceServer": "源服务器", + "destinationServer": "目标服务器", + "selectHost": "选择主机", + "noHostSelected": "未选择主机", + "move": "移动", + "copy": "复制", + "upload": "上传", + "itemsSelected": "{{count}} 已选择项目", + "itemsSelected_other": "{{count}} 已选择项目", + "ready": "准备好", + "loadingHosts": "正在加载主机...", + "name": "姓名", + "modified": "修改的", + "size": "尺寸", + "noFiles": "没有文件", + "open": "打开", + "up": "向上", + "refresh": "刷新", + "copyToTarget": "复制到目标目录", + "rename": "重命名", + "delete": "删除", + "editPermissions": "编辑权限", + "createNewFolder": "创建新文件夹", + "deleteSelectedItems": "删除选定项目", + "deleteConfirm": "删除 {{count}} 选定项目?", + "deleteConfirm_other": "删除 {{count}} 选定项目?", + "cancel": "取消", + "save": "节省", + "folderCreated": "已创建文件夹", + "itemRenamed": "项目已重命名", + "actionFailed": "操作失败", + "itemsDeleted": "{{count}} 项目已删除", + "itemsDeleted_other": "{{count}} 项已删除", + "deleteFailed": "删除失败", + "permissionsUpdated": "权限已更新", + "noRemoteSessionConnected": "未连接远程会话", + "failedToListFiles": "文件列表失败", + "failedToRefresh": "刷新失败", + "destinationInsideSource": "目标路径不能位于所选源路径内。", + "sameHostNoOp": "源文件夹和目标文件夹是同一个文件夹。" + }, + "hosts": { + "fileManager": "文件管理器", + "tabFiles": "文件", + "enableFileManagerMonitor": "启用文件管理器", + "enableFileManagerMonitorDesc": "通过 SFTP 浏览和管理此主机上的文件", + "scpLegacyLabel": "SCP 传统模式", + "scpLegacyDesc": "对不支持 SFTP 子系统的服务器使用传统文件传输(例如嵌入式或最小化 SSH 服务器)。", + "defaultPathLabel": "默认路径", + "fileManagerPathHint": "为此主机启动文件管理器时要打开的目录。", + "fileManagerUrlCopied": "文件管理器 URL 已复制", + "copyFileManagerUrlAction": "复制文件管理器 URL" + }, + "settings": { + "host": { + "enable": { + "label": "启用文件管理器" + }, + "defaultPath": { + "label": "默认路径" + }, + "scpLegacy": { + "label": "SCP 传统模式" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "文件" + } +} diff --git a/plugins/file-manager/locales/translated/zh_TW.json b/plugins/file-manager/locales/translated/zh_TW.json new file mode 100644 index 000000000..eccc24e6c --- /dev/null +++ b/plugins/file-manager/locales/translated/zh_TW.json @@ -0,0 +1,405 @@ +{ + "fileManager": { + "noHostSelected": "未選取主機", + "initializingEditor": "正在初始化編輯器…", + "file": "檔案", + "folder": "資料夾", + "uploadFile": "上傳檔案", + "downloadFile": "下載", + "extractArchive": "解壓縮", + "extractingArchive": "正在解壓縮 {{name}}...", + "archiveExtractedSuccessfully": "{{name}} 解壓縮成功", + "extractFailed": "解壓縮失敗", + "compressFile": "壓縮檔案", + "compressFiles": "壓縮檔案", + "compressFilesDesc": "將 {{count}} 個項目壓縮為一個壓縮檔", + "archiveName": "壓縮檔名稱", + "enterArchiveName": "輸入壓縮檔名稱...", + "compressionFormat": "壓縮格式", + "selectedFiles": "已選取的檔案", + "andMoreFiles": "還有 {{count}} 個...", + "compress": "壓縮", + "compressingFiles": "正在將 {{count}} 個項目壓縮為 {{name}}...", + "filesCompressedSuccessfully": "{{name}} 建立成功", + "compressFailed": "壓縮失敗", + "edit": "編輯", + "preview": "預覽", + "previous": "上一個", + "next": "下一個", + "pageXOfY": "第 {{current}} 頁,共 {{total}} 頁", + "zoomOut": "縮小", + "zoomIn": "放大", + "newFile": "新增檔案", + "newFolder": "新增資料夾", + "rename": "重新命名", + "uploading": "上傳中...", + "uploadingFile": "正在上傳 {{name}}...", + "fileName": "檔案名稱", + "folderName": "資料夾名稱", + "fileUploadedSuccessfully": "檔案「{{name}}」上傳成功", + "failedToUploadFile": "上傳檔案失敗", + "uploadingFolderFiles": "正在上傳 {{count}} 檔案...", + "uploadedFolderFiles": "已成功上傳 {{count}} 個文件", + "uploadedFolderPartial": "上傳了 {{uploaded}} 個文件, {{failed}} 個文件失敗", + "fileDownloadedSuccessfully": "檔案「{{name}}」下載成功", + "failedToDownloadFile": "下載檔案失敗", + "fileCreatedSuccessfully": "檔案「{{name}}」建立成功", + "folderCreatedSuccessfully": "資料夾「{{name}}」建立成功", + "failedToCreateItem": "建立項目失敗", + "operationFailed": "{{operation}} 操作在 {{name}} 上失敗:{{error}}", + "failedToResolveSymlink": "無法解析符號連結", + "itemsDeletedSuccessfully": "成功刪除 {{count}} 個項目", + "failedToDeleteItems": "刪除項目失敗", + "sudoPasswordRequired": "需要管理員密碼", + "enterSudoPassword": "請輸入 sudo 密碼以繼續此操作", + "sudoPassword": "Sudo 密碼", + "sudoOperationFailed": "Sudo 操作失敗", + "sudoAuthFailed": "Sudo 驗證失敗", + "dragFilesToUpload": "拖放檔案至此以上傳", + "dropToUploadHere": "在此處上傳", + "dropToDownloadHere": "點此下載", + "localFiles": "當地的", + "showLocalFiles": "顯示本地文件(分割畫面顯示)", + "hideLocalFiles": "隱藏本地文件", + "localHome": "首頁", + "localPathPlaceholder": "本機資料夾路徑", + "localShowHidden": "顯示隱藏文件", + "localHideHidden": "隱藏隱藏文件", + "localRevealInFileManager": "在系統檔案管理器中顯示", + "localCannotOpenFolder": "無法開啟資料夾", + "localItemCount": "{{count}} 項目", + "localSelectedCount": "{{count}} 已選擇", + "back": "後退", + "forward": "向前", + "up": "向上", + "kind": "種類", + "localUploadingProgress": "正在上傳 {{current}} 中的 {{total}}", + "localDownloadingProgress": "正在下載 {{current}} 中的 {{total}}", + "localTransferInFlight": "{{count}} 立刻", + "localPreparingDownload": "正在準備下載…", + "localTransferCancelled": "轉帳已取消", + "localNothingToTransfer": "無需轉移", + "localUploadComplete": "已上傳 {{count}} 個文件", + "localUploadFailed": "上傳失敗", + "localUploadPartial": "上傳了 {{done}} 個文件, {{failed}} 個文件失敗", + "localDownloadComplete": "已下載 {{count}} 個文件", + "localDownloadFailed": "下載失敗", + "localDownloadPartial": "下載了 {{done}} 個文件, {{failed}} 個文件失敗", + "localReplaceExistingPrompt": "此資料夾中已存在 {{count}} 個項目。是否替換它們?", + "localReplace": "代替", + "localSkipExisting": "跳過", + "localSkippedExisting": "已跳過 {{count}} 個現有項目", + "localUnsafeNamesSkipped": "已跳過 {{count}} 個名稱無法在此電腦上使用的項目。", + "localOpen": "打開", + "localOpenFolder": "打開資料夾", + "localUploadToRemote": "上傳到伺服器", + "localUploadToRemoteMany": "上傳 {{count}} 項目到伺服器", + "localMoveToTrash": "移至垃圾桶", + "localMoveToTrashMany": "將 {{count}} 個物品移至垃圾桶", + "localTrashConfirmSingle": "將「{{name}}」移至回收站?", + "localTrashConfirmFolder": "將資料夾「{{name}}」及其所有內容移至回收站?", + "localTrashConfirmMany": "將 {{count}} 個項目移至回收站?", + "localTrashed": "已將 {{count}} 個項目移至回收站", + "localTrashFailed": "無法移至垃圾桶", + "localRenameFailed": "重新命名失敗", + "localCreateFailed": "無法建立專案", + "goToParentFolder": "轉到上資料夾", + "resizeSidebar": "拖曳即可調整側邊欄大小(雙擊即可重設)", + "resizeLocalPane": "拖曳即可調整本機窗格大小(雙擊可重設)", + "columns": "列", + "columnsHint": "右鍵單擊選擇列", + "emptyFolder": "此資料夾為空", + "searchFiles": "搜尋檔案...", + "upload": "上傳", + "selectHostToStart": "選擇主機以開始檔案管理", + "sshRequiredForFileManager": "檔案管理員需要 SSH。此主機未啟用 SSH。", + "failedToConnect": "無法連線至 SSH", + "failedToLoadDirectory": "載入目錄失敗", + "noSSHConnection": "沒有可用的 SSH 連線", + "copy": "複製", + "cut": "剪下", + "paste": "貼上", + "copyPath": "複製路徑", + "copyPaths": "複製路徑", + "delete": "刪除", + "properties": "內容", + "refresh": "重新整理", + "downloadFiles": "下載 {{count}} 個檔案到瀏覽器", + "copyFiles": "複製 {{count}} 個項目", + "cutFiles": "剪下 {{count}} 個項目", + "deleteFiles": "刪除 {{count}} 個項目", + "filesCopiedToClipboard": "{{count}} 個項目已複製到剪貼簿", + "filesCutToClipboard": "{{count}} 個項目已剪下到剪貼簿", + "pathCopiedToClipboard": "路徑已複製到剪貼簿", + "pathsCopiedToClipboard": "{{count}} 個路徑已複製到剪貼簿", + "failedToCopyPath": "無法複製路徑到剪貼簿", + "copyFolderLink": "複製資料夾連結", + "copyCurrentFolderLink": "複製目前資料夾連結", + "folderLinkCopied": "資料夾連結已複製到剪貼簿", + "failedToCopyFolderLink": "無法複製資料夾連結", + "movedItems": "已移動 {{count}} 個項目", + "failedToDeleteItem": "無法刪除項目", + "itemRenamedSuccessfully": "{{type}} 重新命名成功", + "failedToRenameItem": "無法重新命名項目", + "download": "下載", + "openExternalEditor": "以外部編輯器開啟", + "chooseExternalEditor": "選擇外部編輯器", + "externalEditorSelected": "已選擇外部編輯器", + "externalEditorDesktopOnly": "外部編輯器僅限桌面版應用程式使用", + "externalEditorOpened": "已於外部編輯器開啟。在此儲存將上傳變更回伺服器。", + "failedToOpenExternalEditor": "無法開啟外部編輯器", + "failedToSelectExternalEditor": "無法選擇外部編輯器", + "permissions": "權限", + "size": "大小", + "modified": "修改時間", + "path": "路徑", + "confirmDelete": "確定要刪除 {{name}} 嗎?", + "permissionDenied": "權限不足", + "serverError": "伺服器錯誤", + "fileSavedSuccessfully": "檔案儲存成功", + "failedToSaveFile": "無法儲存檔案", + "confirmDeleteSingleItem": "確定要永久刪除「{{name}}」嗎?", + "confirmDeleteMultipleItems": "確定要永久刪除 {{count}} 個項目嗎?", + "confirmDeleteMultipleItemsWithFolders": "確定要永久刪除 {{count}} 個項目嗎?這包含資料夾及其內容。", + "confirmDeleteFolder": "確定要永久刪除資料夾「{{name}}」及其所有內容嗎?", + "permanentDeleteWarning": "此動作無法復原。項目將從伺服器永久刪除。", + "moveToTrashWarning": "該物品將移至 Termix 回收站,可在保留期到期前恢復。", + "itemsMovedToTrash": "{{count}} 項已移至回收站", + "trash": "垃圾", + "trashDescription": "已刪除的項目將保留在本主機上 {{days}} 天。", + "trashLoadFailed": "載入回收站失敗", + "trashEmpty": "垃圾桶是空的", + "trashRestore": "恢復", + "trashRestored": "已恢復 {{name}}", + "trashRestoreFailed": "恢復專案失敗。原始路徑可能已存在。", + "trashDeletePermanently": "永久刪除", + "trashDeleteConfirm": "永久刪除 {{name}}?此操作無法撤銷。", + "trashPermanentlyDeleted": "永久刪除 {{name}}", + "trashDeleteFailed": "無法永久刪除項目", + "trashEmptyAction": "清空垃圾桶", + "trashEmptyConfirm": "永久刪除回收站中的所有項目?此操作無法撤銷。", + "trashEmptied": "垃圾已清空", + "trashRetention": "自動清理(天數)後", + "trashRetentionSaved": "垃圾保留狀況更新", + "trashRetentionFailed": "更新垃圾回收站保留策略失敗", + "trashUnavailableConfirm": "此項目無法移至回收站,通常是因為它位於其他檔案系統或需要更高的權限。是否將其永久刪除?", + "recent": "最近", + "pinned": "已釘選", + "folderShortcuts": "資料夾捷徑", + "failedToReconnectSSH": "無法重新連線 SSH 工作階段", + "openTerminalHere": "在此處開啟終端機", + "run": "執行", + "openTerminalInFolder": "在此資料夾中開啟終端機", + "openTerminalInFileLocation": "在檔案位置開啟終端機", + "runningFile": "執行中 - {{file}}", + "onlyRunExecutableFiles": "只能執行可執行檔案", + "directories": "目錄", + "removedFromRecentFiles": "已從最近檔案中移除 \"{{name}}\"", + "removeFailed": "移除失敗", + "unpinnedSuccessfully": "已成功取消釘選 \"{{name}}\"", + "unpinFailed": "取消釘選失敗", + "removedShortcut": "已移除捷徑 \"{{name}}\"", + "removeShortcutFailed": "移除捷徑失敗", + "clearedAllRecentFiles": "已清除所有最近檔案", + "clearFailed": "清除失敗", + "removeFromRecentFiles": "從最近檔案中移除", + "clearAllRecentFiles": "清除所有最近檔案", + "unpinFile": "取消釘選檔案", + "removeShortcut": "移除捷徑", + "pinFile": "釘選檔案", + "addToShortcuts": "加入捷徑", + "pasteFailed": "貼上失敗", + "noUndoableActions": "沒有可復原的動作", + "undoCopySuccess": "已復原複製操作:已刪除 {{count}} 個複製的檔案", + "undoCopyFailedDelete": "復原失敗:無法刪除任何複製的檔案", + "undoCopyFailedNoInfo": "復原失敗:找不到複製檔案的資訊", + "undoMoveSuccess": "已復原移動操作:已將 {{count}} 個檔案移回原始位置", + "undoMoveFailedMove": "復原失敗:無法移回任何檔案", + "undoMoveFailedNoInfo": "復原失敗:找不到移動檔案的資訊", + "undoDeleteNotSupported": "刪除操作無法復原:檔案已從伺服器永久刪除", + "undoTypeNotSupported": "不支援的復原操作類型", + "undoOperationFailed": "復原操作失敗", + "unknownError": "未知錯誤", + "confirm": "確認", + "find": "尋找...", + "replace": "取代", + "downloadInstead": "改為下載", + "keyboardShortcuts": "鍵盤快捷鍵", + "searchAndReplace": "搜尋與取代", + "editing": "編輯", + "search": "搜尋", + "findNext": "尋找下一個", + "findPrevious": "尋找上一個", + "save": "儲存", + "selectAll": "全選", + "undo": "復原", + "redo": "重做", + "moveLineUp": "將行上移", + "moveLineDown": "將行下移", + "toggleComment": "切換註解", + "autoComplete": "自動完成", + "imageLoadError": "無法載入圖片", + "startTyping": "開始輸入...", + "unknownSize": "未知大小", + "fileIsEmpty": "檔案為空", + "largeFileWarning": "大檔案警告", + "largeFileWarningDesc": "此檔案大小為 {{size}},以文字模式開啟可能會影響效能。", + "fileNotFoundAndRemoved": "找不到檔案 \"{{name}}\",已從最近使用的/已釘選的檔案中移除", + "failedToLoadFile": "無法載入檔案:{{error}}", + "serverErrorOccurred": "伺服器發生錯誤,請稍後再試。", + "autoSaveFailed": "自動儲存失敗", + "fileAutoSaved": "檔案已自動儲存", + "moveFileFailed": "無法移動 {{name}}", + "moveOperationFailed": "移動操作失敗", + "canOnlyCompareFiles": "只能比較兩個檔案", + "comparingFiles": "正在比較檔案:{{file1}} 與 {{file2}}", + "dragFailed": "拖曳操作失敗", + "filePinnedSuccessfully": "已成功釘選檔案 \"{{name}}\"", + "pinFileFailed": "釘選檔案失敗", + "fileUnpinnedSuccessfully": "已成功取消釘選檔案 \"{{name}}\"", + "unpinFileFailed": "取消釘選檔案失敗", + "shortcutAddedSuccessfully": "資料夾捷徑 \"{{name}}\" 已成功新增", + "addShortcutFailed": "新增捷徑失敗", + "operationCompletedSuccessfully": "已成功{{operation}} {{count}} 個項目", + "operationCompleted": "{{operation}} {{count}} 個項目", + "downloadFileSuccess": "檔案 {{name}} 已成功下載", + "downloadFileFailed": "下載失敗", + "downloadingFile": "正在下載 {{name}}…", + "downloadProgressBytes": "{{transferred}} / {{total}}", + "moveTo": "移動至 {{name}}", + "diffCompareWith": "與 {{name}} 進行差異比對", + "dragOutsideToDownload": "拖曳至視窗外以下載 ({{count}} 個檔案)", + "newFolderDefault": "新資料夾", + "newFileDefault": "新檔案.txt", + "successfullyMovedItems": "已成功移動 {{count}} 個項目至 {{target}}", + "move": "移動", + "searchInFile": "在檔案中搜尋 (Ctrl+F)", + "showKeyboardShortcuts": "顯示鍵盤快捷鍵", + "decreaseFontSize": "縮小字型大小", + "increaseFontSize": "放大字型大小", + "startWritingMarkdown": "開始撰寫您的 Markdown 內容...", + "loadingFileComparison": "載入檔案比對中...", + "reload": "重新載入", + "compare": "比對", + "sideBySide": "並排", + "inline": "內嵌", + "fileComparison": "檔案比對:{{file1}} vs {{file2}}", + "fileTooLarge": "檔案過大:{{error}}", + "sshConnectionFailed": "SSH 連線失敗。請檢查與 {{name}} ({{ip}}:{{port}}) 的連線", + "loadFileFailed": "載入檔案失敗:{{error}}", + "connecting": "連線中...", + "connectedSuccessfully": "已成功連線", + "totpVerificationFailed": "TOTP 驗證失敗", + "warpgateVerificationFailed": "Warpgate 驗證失敗", + "authenticationFailed": "驗證失敗", + "incorrectPassphrase": "密碼片語錯誤。請再試一次。", + "verificationCodePrompt": "驗證碼:", + "changePermissions": "變更權限", + "currentPermissions": "目前權限", + "owner": "擁有者", + "group": "群組", + "others": "其他人", + "read": "讀取", + "write": "寫入", + "execute": "執行", + "permissionsChangedSuccessfully": "權限已成功變更", + "failedToChangePermissions": "無法變更權限", + "name": "名稱", + "sortByName": "名稱", + "sortByDate": "修改日期", + "sortBySize": "大小", + "ascending": "遞增", + "descending": "遞減", + "root": "根目錄", + "new": "新增", + "sortBy": "排序依據", + "items": "項目", + "selected": "已選取", + "editor": "編輯器", + "octal": "八進位", + "storage": "儲存空間", + "disk": "磁碟", + "used": "已使用", + "of": " / ", + "toggleSidebar": "切換側邊欄", + "compactLayout": "採用緊湊型佈局", + "comfortableLayout": "採用舒適的佈局", + "cannotLoadPdf": "無法載入 PDF", + "pdfLoadError": "載入此 PDF 檔案時發生錯誤。", + "loadingPdf": "正在載入 PDF...", + "loadingPage": "正在載入頁面..." + }, + "sftpTransfer": { + "title": "SFTP", + "description": "在兩台SSH主機之間傳輸檔案。", + "sourceServer": "來源伺服器", + "destinationServer": "目標伺服器", + "selectHost": "選擇主機", + "noHostSelected": "未選擇主機", + "move": "移動", + "copy": "複製", + "upload": "上傳", + "itemsSelected": "{{count}} 已選擇項目", + "itemsSelected_other": "{{count}} 已選擇項目", + "ready": "準備好", + "loadingHosts": "正在加載主機...", + "name": "姓名", + "modified": "修改的", + "size": "尺寸", + "noFiles": "沒有文件", + "open": "打開", + "up": "向上", + "refresh": "重新整理", + "copyToTarget": "複製到目標目錄", + "rename": "重新命名", + "delete": "刪除", + "editPermissions": "編輯權限", + "createNewFolder": "建立新資料夾", + "deleteSelectedItems": "刪除選定項目", + "deleteConfirm": "刪除 {{count}} 選取項目?", + "deleteConfirm_other": "刪除 {{count}} 選取項目?", + "cancel": "取消", + "save": "節省", + "folderCreated": "已建立資料夾", + "itemRenamed": "項目已重新命名", + "actionFailed": "操作失敗", + "itemsDeleted": "{{count}} 項目已刪除", + "itemsDeleted_other": "{{count}} 項目已刪除", + "deleteFailed": "刪除失敗", + "permissionsUpdated": "權限已更新", + "noRemoteSessionConnected": "未連接遠端會話", + "failedToListFiles": "文件列表失敗", + "failedToRefresh": "刷新失敗", + "destinationInsideSource": "目標路徑不能位於所選來源路徑內。", + "sameHostNoOp": "來源資料夾和目標資料夾是同一個資料夾。" + }, + "hosts": { + "fileManager": "檔案管理員", + "tabFiles": "檔案", + "enableFileManagerMonitor": "啟用檔案管理員", + "enableFileManagerMonitorDesc": "透過 SFTP 瀏覽並管理此主機上的檔案", + "scpLegacyLabel": "SCP 舊版模式", + "scpLegacyDesc": "對不支援 SFTP 子系統的伺服器(例如嵌入式或精簡型 SSH 伺服器)使用舊版檔案傳輸。", + "defaultPathLabel": "預設路徑", + "fileManagerPathHint": "啟動此主機的檔案管理員時開啟的目錄。", + "fileManagerUrlCopied": "檔案管理員 URL 已複製", + "copyFileManagerUrlAction": "複製檔案管理員 URL" + }, + "settings": { + "host": { + "enable": { + "label": "啟用檔案管理員" + }, + "defaultPath": { + "label": "預設路徑" + }, + "scpLegacy": { + "label": "SCP 舊版模式" + } + } + }, + "nav": { + "sftp": "SFTP", + "files": "檔案" + } +} diff --git a/plugins/file-manager/manifest.json b/plugins/file-manager/manifest.json new file mode 100644 index 000000000..0a4ea61d1 --- /dev/null +++ b/plugins/file-manager/manifest.json @@ -0,0 +1,97 @@ +{ + "id": "file-manager", + "name": "File Manager", + "version": "1.0.0", + "description": "Browse, edit and transfer files over SFTP, including host-to-host transfers.", + "author": { + "name": "Termix" + }, + "license": "MIT", + "repository": "https://github.com/Termix-SSH/Termix", + "category": "Files & Transfer", + "icon": "FolderSearch", + "engine": { + "termix": ">=2.9.0", + "api": "1" + }, + "capabilities": [ + "hosts:read", + "credentials:use", + "ssh:connect", + "db:own", + "network:serve", + "ui:surface" + ], + "provides": [ + { + "service": "files.sftp", + "version": "1.0.0", + "permission": "file-manager.use" + } + ], + "contributes": { + "tabs": [ + { + "id": "files", + "titleKey": "nav.files", + "icon": "FolderSearch", + "openFrom": ["host-context-menu", "palette"] + }, + { + "id": "sftp", + "titleKey": "nav.sftp", + "icon": "ArrowLeftRight", + "openFrom": ["host-context-menu", "palette"] + } + ], + "actions": [ + { + "id": "files.openHost", + "titleKey": "nav.files", + "handler": "openHost", + "permission": "file-manager.use" + }, + { + "id": "files.openEditor", + "titleKey": "nav.files", + "handler": "openEditor", + "permission": "file-manager.use" + } + ], + "permissions": [ + { + "name": "use", + "titleKey": "permissions.use.title", + "descriptionKey": "permissions.use.description", + "defaultRoles": ["admin", "user"] + } + ], + "settings": { + "host": { + "enableKey": "enableFileManager", + "enableLabelKey": "settings.host.enable.label", + "fields": [ + { + "key": "defaultPath", + "type": "string", + "labelKey": "settings.host.defaultPath.label", + "requires": "enableFileManager" + }, + { + "key": "scpLegacy", + "type": "boolean", + "labelKey": "settings.host.scpLegacy.label", + "requires": "enableFileManager" + } + ] + } + }, + "hostCapability": { + "key": "enableFileManager", + "labelKey": "hostCapability.label", + "editorTab": "files" + } + }, + "locales": "locales", + "platforms": ["linux", "win32", "darwin"] +} diff --git a/plugins/file-manager/migrations/mysql/0001_adopt_file_manager_tables.sql b/plugins/file-manager/migrations/mysql/0001_adopt_file_manager_tables.sql new file mode 100644 index 000000000..c3c121b7b --- /dev/null +++ b/plugins/file-manager/migrations/mysql/0001_adopt_file_manager_tables.sql @@ -0,0 +1,49 @@ +-- file-manager 0001: adopt_file_manager_tables +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS `file_manager_recent` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `user_id` varchar(255) NOT NULL, + `host_id` int NOT NULL, + `name` text NOT NULL, + `path` text NOT NULL, + `last_opened` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE +); +RENAME TABLE `file_manager_recent` TO `p_file_manager_recent`; +CREATE TABLE IF NOT EXISTS `file_manager_pinned` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `user_id` varchar(255) NOT NULL, + `host_id` int NOT NULL, + `name` text NOT NULL, + `path` text NOT NULL, + `pinned_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE +); +RENAME TABLE `file_manager_pinned` TO `p_file_manager_pinned`; +CREATE TABLE IF NOT EXISTS `file_manager_shortcuts` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `user_id` varchar(255) NOT NULL, + `host_id` int NOT NULL, + `name` text NOT NULL, + `path` text NOT NULL, + `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE +); +RENAME TABLE `file_manager_shortcuts` TO `p_file_manager_shortcuts`; +CREATE TABLE IF NOT EXISTS `transfer_recent` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `user_id` varchar(255) NOT NULL, + `source_host_id` int NOT NULL, + `dest_host_id` int NOT NULL, + `dest_path` text NOT NULL, + `dest_path_label` text NOT NULL, + `last_used` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`source_host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`dest_host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE +); +RENAME TABLE `transfer_recent` TO `p_file_manager_transfer_recent`; diff --git a/plugins/file-manager/migrations/postgres/0001_adopt_file_manager_tables.sql b/plugins/file-manager/migrations/postgres/0001_adopt_file_manager_tables.sql new file mode 100644 index 000000000..c357cfef4 --- /dev/null +++ b/plugins/file-manager/migrations/postgres/0001_adopt_file_manager_tables.sql @@ -0,0 +1,53 @@ +-- file-manager 0001: adopt_file_manager_tables +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "file_manager_recent" ( + "id" serial PRIMARY KEY, + "user_id" varchar(255) NOT NULL, + "host_id" integer NOT NULL, + "name" text NOT NULL, + "path" text NOT NULL, + "last_opened" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_file_manager_recent_user" ON "file_manager_recent" ("user_id", "host_id"); +ALTER TABLE "file_manager_recent" RENAME TO "p_file_manager_recent"; +CREATE TABLE IF NOT EXISTS "file_manager_pinned" ( + "id" serial PRIMARY KEY, + "user_id" varchar(255) NOT NULL, + "host_id" integer NOT NULL, + "name" text NOT NULL, + "path" text NOT NULL, + "pinned_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_file_manager_pinned_user" ON "file_manager_pinned" ("user_id", "host_id"); +ALTER TABLE "file_manager_pinned" RENAME TO "p_file_manager_pinned"; +CREATE TABLE IF NOT EXISTS "file_manager_shortcuts" ( + "id" serial PRIMARY KEY, + "user_id" varchar(255) NOT NULL, + "host_id" integer NOT NULL, + "name" text NOT NULL, + "path" text NOT NULL, + "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_file_manager_shortcuts_user" ON "file_manager_shortcuts" ("user_id", "host_id"); +ALTER TABLE "file_manager_shortcuts" RENAME TO "p_file_manager_shortcuts"; +CREATE TABLE IF NOT EXISTS "transfer_recent" ( + "id" serial PRIMARY KEY, + "user_id" varchar(255) NOT NULL, + "source_host_id" integer NOT NULL, + "dest_host_id" integer NOT NULL, + "dest_path" text NOT NULL, + "dest_path_label" text NOT NULL, + "last_used" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("source_host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE, + FOREIGN KEY ("dest_host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_transfer_recent_user" ON "transfer_recent" ("user_id"); +ALTER TABLE "transfer_recent" RENAME TO "p_file_manager_transfer_recent"; diff --git a/plugins/file-manager/migrations/snapshot.json b/plugins/file-manager/migrations/snapshot.json new file mode 100644 index 000000000..44d803d8c --- /dev/null +++ b/plugins/file-manager/migrations/snapshot.json @@ -0,0 +1,161 @@ +{ + "version": 1, + "tables": { + "recent": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "hostId": { + "type": "refHost", + "notNull": true + }, + "name": { + "type": "text", + "notNull": true + }, + "path": { + "type": "text", + "notNull": true + }, + "lastOpened": { + "type": "timestamp", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [ + { + "name": "idx_file_manager_recent_user", + "columns": ["userId", "hostId"] + } + ], + "adopts": "file_manager_recent" + }, + "pinned": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "hostId": { + "type": "refHost", + "notNull": true + }, + "name": { + "type": "text", + "notNull": true + }, + "path": { + "type": "text", + "notNull": true + }, + "pinnedAt": { + "type": "timestamp", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [ + { + "name": "idx_file_manager_pinned_user", + "columns": ["userId", "hostId"] + } + ], + "adopts": "file_manager_pinned" + }, + "shortcuts": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "hostId": { + "type": "refHost", + "notNull": true + }, + "name": { + "type": "text", + "notNull": true + }, + "path": { + "type": "text", + "notNull": true + }, + "createdAt": { + "type": "timestamp", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [ + { + "name": "idx_file_manager_shortcuts_user", + "columns": ["userId", "hostId"] + } + ], + "adopts": "file_manager_shortcuts" + }, + "transfer_recent": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "sourceHostId": { + "type": "refHost", + "notNull": true + }, + "destHostId": { + "type": "refHost", + "notNull": true + }, + "destPath": { + "type": "text", + "notNull": true + }, + "destPathLabel": { + "type": "text", + "notNull": true + }, + "lastUsed": { + "type": "timestamp", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [ + { + "name": "idx_transfer_recent_user", + "columns": ["userId"] + } + ], + "adopts": "transfer_recent" + } + } +} diff --git a/plugins/file-manager/migrations/sqlite/0001_adopt_file_manager_tables.sql b/plugins/file-manager/migrations/sqlite/0001_adopt_file_manager_tables.sql new file mode 100644 index 000000000..9c4907ab5 --- /dev/null +++ b/plugins/file-manager/migrations/sqlite/0001_adopt_file_manager_tables.sql @@ -0,0 +1,53 @@ +-- file-manager 0001: adopt_file_manager_tables +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "file_manager_recent" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "user_id" text NOT NULL, + "host_id" integer NOT NULL, + "name" text NOT NULL, + "path" text NOT NULL, + "last_opened" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_file_manager_recent_user" ON "file_manager_recent" ("user_id", "host_id"); +ALTER TABLE "file_manager_recent" RENAME TO "p_file_manager_recent"; +CREATE TABLE IF NOT EXISTS "file_manager_pinned" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "user_id" text NOT NULL, + "host_id" integer NOT NULL, + "name" text NOT NULL, + "path" text NOT NULL, + "pinned_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_file_manager_pinned_user" ON "file_manager_pinned" ("user_id", "host_id"); +ALTER TABLE "file_manager_pinned" RENAME TO "p_file_manager_pinned"; +CREATE TABLE IF NOT EXISTS "file_manager_shortcuts" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "user_id" text NOT NULL, + "host_id" integer NOT NULL, + "name" text NOT NULL, + "path" text NOT NULL, + "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_file_manager_shortcuts_user" ON "file_manager_shortcuts" ("user_id", "host_id"); +ALTER TABLE "file_manager_shortcuts" RENAME TO "p_file_manager_shortcuts"; +CREATE TABLE IF NOT EXISTS "transfer_recent" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "user_id" text NOT NULL, + "source_host_id" integer NOT NULL, + "dest_host_id" integer NOT NULL, + "dest_path" text NOT NULL, + "dest_path_label" text NOT NULL, + "last_used" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE, + FOREIGN KEY ("source_host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE, + FOREIGN KEY ("dest_host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE +); +CREATE INDEX IF NOT EXISTS "idx_transfer_recent_user" ON "transfer_recent" ("user_id"); +ALTER TABLE "transfer_recent" RENAME TO "p_file_manager_transfer_recent"; diff --git a/plugins/file-manager/package.json b/plugins/file-manager/package.json new file mode 100644 index 000000000..5d3d466fe --- /dev/null +++ b/plugins/file-manager/package.json @@ -0,0 +1,15 @@ +{ + "name": "@termix-plugin/file-manager", + "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/file-manager/src/backend/action-routes.ts b/plugins/file-manager/src/backend/action-routes.ts new file mode 100644 index 000000000..92f861d54 --- /dev/null +++ b/plugins/file-manager/src/backend/action-routes.ts @@ -0,0 +1,483 @@ +import type { Express } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { execChannel, type SSHSession } from "./session.js"; + +type FileActionRoutesDeps = { + ctx: PluginContext; + sshSessions: Record; + scheduleSessionCleanup: (sessionId: string) => void; + verifySessionOwnership: (session: SSHSession, userId: string) => boolean; +}; + +export function registerFileActionRoutes( + app: Express, + { + ctx, + sshSessions, + scheduleSessionCleanup, + verifySessionOwnership, + }: FileActionRoutesDeps, +): void { + /** + * @openapi + * /plugin-api/file-manager/copyItem: + * post: + * summary: Copy a file or directory + * description: Copies a file or directory on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * sourcePath: + * type: string + * targetDir: + * type: string + * hostId: + * type: integer + * userId: + * type: string + * responses: + * 200: + * description: Item copied successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 500: + * description: Failed to copy item. + */ + app.post("/copyItem", async (req, res) => { + const { sessionId, sourcePath, targetDir, hostId } = req.body; + const userId = ctx.currentActor()!; + + if (!sessionId || !sourcePath || !targetDir) { + return res.status(400).json({ error: "Missing required parameters" }); + } + + const sshConn = sshSessions[sessionId]; + if (!sshConn || !sshConn.isConnected) { + return res + .status(400) + .json({ error: "SSH session not found or not connected" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + sshConn.lastActive = Date.now(); + scheduleSessionCleanup(sessionId); + + const sourceName = sourcePath.split("/").pop() || "copied_item"; + + const timestamp = Date.now().toString().slice(-8); + const uniqueName = `${sourceName}_copy_${timestamp}`; + const targetPath = `${targetDir}/${uniqueName}`; + + const escapedSource = sourcePath.replace(/'/g, "'\"'\"'"); + const escapedTarget = targetPath.replace(/'/g, "'\"'\"'"); + + const copyCommand = `cp '${escapedSource}' '${escapedTarget}' && echo "COPY_SUCCESS"`; + + const commandTimeout = setTimeout(() => { + ctx.log.error( + `Copy command timed out after 60 seconds: ${sourcePath} -> ${targetPath} (${copyCommand})`, + ); + if (!res.headersSent) { + res.status(500).json({ + error: "Copy operation timed out", + toast: { + type: "error", + message: + "Copy operation timed out. SSH connection may be unstable.", + }, + }); + } + }, 60000); + + execChannel(sshConn, copyCommand, (err, stream) => { + if (err) { + clearTimeout(commandTimeout); + ctx.log.error("SSH copyItem error:", err); + if (!res.headersSent) { + return res.status(500).json({ error: err.message }); + } + return; + } + + let errorData = ""; + let stdoutData = ""; + + stream.on("data", (data: Buffer) => { + const output = data.toString(); + stdoutData += output; + stream.stderr.on("data", (data: Buffer) => { + const output = data.toString(); + errorData += output; + }); + + stream.on("close", (code) => { + clearTimeout(commandTimeout); + + if (code !== 0) { + const fullErrorInfo = + errorData || stdoutData || "No error message available"; + ctx.log.error( + `SSH copyItem command failed with code ${code}: ${sourcePath} -> ${targetPath} (${copyCommand}) ${fullErrorInfo}`, + ); + if (!res.headersSent) { + return res.status(500).json({ + error: `Copy failed: ${fullErrorInfo}`, + toast: { + type: "error", + message: `Copy failed: ${fullErrorInfo}`, + }, + debug: { + sourcePath, + targetPath, + exitCode: code, + command: copyCommand, + }, + }); + } + return; + } + + const copySuccessful = + stdoutData.includes("COPY_SUCCESS") || code === 0; + + if (copySuccessful) { + ctx.log.info( + `Item copied successfully: ${sourcePath} -> ${targetPath} (sessionId=${sessionId}, hostId=${hostId})`, + ); + + if (!res.headersSent) { + res.json({ + message: "Item copied successfully", + sourcePath, + targetPath, + uniqueName, + toast: { + type: "success", + message: `Successfully copied to: ${uniqueName}`, + }, + }); + } + } else { + ctx.log.warn( + `Copy completed but without success confirmation: ${sourcePath} -> ${targetPath} (sessionId=${sessionId}, code=${code}, stdout=${stdoutData.substring(0, 200)})`, + ); + + if (!res.headersSent) { + res.json({ + message: "Copy may have completed", + sourcePath, + targetPath, + uniqueName, + toast: { + type: "warning", + message: `Copy completed but verification uncertain for: ${uniqueName}`, + }, + }); + } + } + }); + + stream.on("error", (streamErr) => { + clearTimeout(commandTimeout); + ctx.log.error("SSH copyItem stream error:", streamErr); + if (!res.headersSent) { + res + .status(500) + .json({ error: `Stream error: ${streamErr.message}` }); + } + }); + }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/executeFile: + * post: + * summary: Execute a file + * description: Executes a file on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * filePath: + * type: string + * responses: + * 200: + * description: File execution result. + * 400: + * description: Missing required parameters or SSH connection not available. + * 500: + * description: Failed to execute file. + */ + app.post("/executeFile", async (req, res) => { + const { sessionId, filePath } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sshConn || !sshConn.isConnected) { + ctx.log.error( + `SSH connection not found or not connected for executeFile: ${sessionId} (hasConnection=${!!sshConn}, isConnected=${sshConn?.isConnected})`, + ); + return res.status(400).json({ error: "SSH connection not available" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!filePath) { + return res.status(400).json({ error: "File path is required" }); + } + + const escapedPath = filePath.replace(/'/g, "'\"'\"'"); + + const checkCommand = `test -x '${escapedPath}' && echo "EXECUTABLE" || echo "NOT_EXECUTABLE"`; + + execChannel(sshConn, checkCommand, (checkErr, checkStream) => { + if (checkErr) { + ctx.log.error("SSH executeFile check error:", checkErr); + return res + .status(500) + .json({ error: "Failed to check file executability" }); + } + + let checkResult = ""; + checkStream.on("data", (data) => { + checkResult += data.toString(); + }); + + checkStream.on("close", () => { + if (!checkResult.includes("EXECUTABLE")) { + return res.status(400).json({ error: "File is not executable" }); + } + + const executeCommand = `cd "$(dirname '${escapedPath}')" && '${escapedPath}' 2>&1; echo "EXIT_CODE:$?"`; + + execChannel(sshConn, executeCommand, (err, stream) => { + if (err) { + ctx.log.error("SSH executeFile error:", err); + return res.status(500).json({ error: "Failed to execute file" }); + } + + let output = ""; + let errorOutput = ""; + + stream.on("data", (data) => { + output += data.toString(); + }); + + stream.stderr.on("data", (data) => { + errorOutput += data.toString(); + }); + + stream.on("close", (code) => { + const exitCodeMatch = output.match(/EXIT_CODE:(\d+)$/); + const actualExitCode = exitCodeMatch + ? parseInt(exitCodeMatch[1]) + : code; + const cleanOutput = output.replace(/EXIT_CODE:\d+$/, "").trim(); + + ctx.log.info( + `File execution completed: ${sessionId} (${filePath}, exitCode=${actualExitCode}, outputLength=${cleanOutput.length}, errorLength=${errorOutput.length})`, + ); + + res.json({ + success: true, + exitCode: actualExitCode, + output: cleanOutput, + error: errorOutput, + timestamp: new Date().toISOString(), + }); + }); + + stream.on("error", (streamErr) => { + ctx.log.error("SSH executeFile stream error:", streamErr); + if (!res.headersSent) { + res.status(500).json({ error: "Execution stream error" }); + } + }); + }); + }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/changePermissions: + * post: + * summary: Change file permissions + * description: Changes the permissions of a file on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * permissions: + * type: string + * responses: + * 200: + * description: Permissions changed successfully. + * 400: + * description: Missing required parameters or SSH connection not available. + * 408: + * description: Permission change timed out. + * 500: + * description: Failed to change permissions. + */ + app.post("/changePermissions", async (req, res) => { + const { sessionId, path, permissions } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sshConn || !sshConn.isConnected) { + ctx.log.error( + `SSH connection not found or not connected for changePermissions: ${sessionId} (hasConnection=${!!sshConn}, isConnected=${sshConn?.isConnected})`, + ); + return res.status(400).json({ error: "SSH connection not available" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!path) { + return res.status(400).json({ error: "File path is required" }); + } + + if (!permissions || !/^\d{3,4}$/.test(permissions)) { + return res.status(400).json({ + error: "Valid permissions required (e.g., 755, 644)", + }); + } + + sshConn.lastActive = Date.now(); + scheduleSessionCleanup(sessionId); + + const octalPerms = permissions.slice(-3); + const escapedPath = path.replace(/'/g, "'\"'\"'"); + const command = `chmod ${octalPerms} '${escapedPath}' && echo "SUCCESS"`; + + ctx.log.info( + `Changing file permissions: ${sessionId} (${path}, permissions=${octalPerms})`, + ); + + const commandTimeout = setTimeout(() => { + if (!res.headersSent) { + ctx.log.error( + `changePermissions command timeout: ${sessionId} (${path}, permissions=${octalPerms})`, + ); + res.status(408).json({ + error: "Permission change timed out. SSH connection may be unstable.", + }); + } + }, 10000); + + execChannel(sshConn, command, (err, stream) => { + if (err) { + clearTimeout(commandTimeout); + ctx.log.error( + `SSH changePermissions exec error: ${sessionId} (${path}, permissions=${octalPerms})`, + err, + ); + if (!res.headersSent) { + return res + .status(500) + .json({ error: "Failed to change permissions" }); + } + return; + } + + let outputData = ""; + let errorOutput = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (data: Buffer) => { + errorOutput += data.toString(); + }); + + stream.on("close", (code) => { + clearTimeout(commandTimeout); + + if (outputData.includes("SUCCESS")) { + ctx.log.info( + `File permissions changed successfully: ${sessionId} (${path}, permissions=${octalPerms})`, + ); + + if (!res.headersSent) { + res.json({ + success: true, + message: "Permissions changed successfully", + }); + } + return; + } + + if (code !== 0) { + ctx.log.error( + `chmod command failed: ${sessionId} (${path}, permissions=${octalPerms}, exitCode=${code}, error=${errorOutput})`, + ); + if (!res.headersSent) { + return res.status(500).json({ + error: errorOutput || "Failed to change permissions", + }); + } + return; + } + + ctx.log.info( + `File permissions changed successfully: ${sessionId} (${path}, permissions=${octalPerms})`, + ); + + if (!res.headersSent) { + res.json({ + success: true, + message: "Permissions changed successfully", + }); + } + }); + + stream.on("error", (streamErr) => { + clearTimeout(commandTimeout); + ctx.log.error( + `SSH changePermissions stream error: ${sessionId} (${path}, permissions=${octalPerms})`, + streamErr, + ); + if (!res.headersSent) { + res + .status(500) + .json({ error: "Stream error while changing permissions" }); + } + }); + }); + }); +} diff --git a/plugins/file-manager/src/backend/bookmark-routes.ts b/plugins/file-manager/src/backend/bookmark-routes.ts new file mode 100644 index 000000000..200e874a2 --- /dev/null +++ b/plugins/file-manager/src/backend/bookmark-routes.ts @@ -0,0 +1,645 @@ +import type { Express, Request, Response } from "express"; +import { and, desc, eq } from "drizzle-orm"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +// The tables come from ctx.db.define, which the SDK hands back untyped, and +// the drizzle handle is the server's own. Typed at this module's edge instead. +type Table = any; +type Drizzle = any; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +export interface BookmarkTables { + table: Table; + pinnedTable: Table; + shortcutsTable: Table; + transferRecentTable: Table; +} + +function isNonEmptyString(value: unknown): value is string { + return typeof value === "string" && value.length > 0; +} + +/** + * Recent/pinned/shortcut files and recent transfer destinations, adopted from + * core's host.ts and host-file-manager-bookmark-routes.ts. Kept simple, direct + * Drizzle calls rather than a repository layer, since each is a handful of + * lines and nothing else in the plugin reuses them. + */ +export function registerBookmarkRoutes( + app: Express, + ctx: PluginContext, + tables: BookmarkTables, +): void { + const { table, pinnedTable, shortcutsTable, transferRecentTable } = tables; + const client = () => ctx.db.client(); + + /** + * @openapi + * /plugin-api/file-manager/recent: + * get: + * summary: Get recent files + * description: Retrieves a list of recent files for a specific host. + * tags: + * - File Manager + * parameters: + * - in: query + * name: hostId + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: A list of recent files. + * 400: + * description: Invalid userId or hostId. + * 500: + * description: Failed to fetch recent files. + */ + app.get("/recent", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const hostIdQuery = Array.isArray(req.query.hostId) + ? req.query.hostId[0] + : req.query.hostId; + const hostId = hostIdQuery ? parseInt(hostIdQuery as string) : null; + + if (!isNonEmptyString(userId)) { + return res.status(400).json({ error: "Invalid userId" }); + } + if (!hostId) { + return res.status(400).json({ error: "Host ID is required" }); + } + + try { + const drizzle = await client(); + const rows = await drizzle + .select() + .from(table) + .where(and(eq(table.userId, userId), eq(table.hostId, hostId))) + .orderBy(desc(table.lastOpened)) + .limit(20); + res.json(rows); + } catch (err) { + ctx.log.error("Failed to fetch recent files", err as Error); + res.status(500).json({ error: "Failed to fetch recent files" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/recent: + * post: + * summary: Add recent file + * description: Adds a file to the list of recent files for a host. + * tags: + * - File Manager + * responses: + * 200: + * description: Recent file added. + * 400: + * description: Invalid data. + * 500: + * description: Failed to add recent file. + */ + app.post("/recent", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const { hostId, path, name } = req.body; + + if (!isNonEmptyString(userId) || !hostId || !path) { + return res.status(400).json({ error: "Invalid data" }); + } + + try { + const drizzle = await client(); + const existing = await drizzle + .select() + .from(table) + .where( + and( + eq(table.userId, userId), + eq(table.hostId, hostId), + eq(table.path, path), + ), + ) + .limit(1); + + const now = new Date().toISOString(); + if (existing[0]) { + await drizzle + .update(table) + .set({ name: name ?? existing[0].name, lastOpened: now }) + .where(eq(table.id, existing[0].id)); + } else { + await drizzle.insert(table).values({ + userId, + hostId, + path, + name: name ?? path, + lastOpened: now, + }); + } + await ctx.db.persist(); + res.json({ message: "Recent file added" }); + } catch (err) { + ctx.log.error("Failed to add recent file", err as Error); + res.status(500).json({ error: "Failed to add recent file" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/recent: + * delete: + * summary: Remove recent file + * tags: + * - File Manager + * responses: + * 200: + * description: Recent file removed. + * 400: + * description: Invalid data. + * 500: + * description: Failed to remove recent file. + */ + app.delete("/recent", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const { hostId, path } = req.body; + + if (!isNonEmptyString(userId) || !hostId || !path) { + return res.status(400).json({ error: "Invalid data" }); + } + + try { + const drizzle = await client(); + await drizzle + .delete(table) + .where( + and( + eq(table.userId, userId), + eq(table.hostId, hostId), + eq(table.path, path), + ), + ); + await ctx.db.persist(); + res.json({ message: "Recent file removed" }); + } catch (err) { + ctx.log.error("Failed to remove recent file", err as Error); + res.status(500).json({ error: "Failed to remove recent file" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/pinned: + * get: + * summary: Get pinned files + * tags: + * - File Manager + * parameters: + * - in: query + * name: hostId + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: A list of pinned files. + * 400: + * description: Invalid userId or hostId. + * 500: + * description: Failed to fetch pinned files. + */ + app.get("/pinned", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const hostIdQuery = Array.isArray(req.query.hostId) + ? req.query.hostId[0] + : req.query.hostId; + const hostId = hostIdQuery ? parseInt(hostIdQuery as string) : null; + + if (!isNonEmptyString(userId)) { + return res.status(400).json({ error: "Invalid userId" }); + } + if (!hostId) { + return res.status(400).json({ error: "Host ID is required" }); + } + + try { + const drizzle = await client(); + const rows = await drizzle + .select() + .from(pinnedTable) + .where( + and(eq(pinnedTable.userId, userId), eq(pinnedTable.hostId, hostId)), + ) + .orderBy(desc(pinnedTable.pinnedAt)); + res.json(rows); + } catch (err) { + ctx.log.error("Failed to fetch pinned files", err as Error); + res.status(500).json({ error: "Failed to fetch pinned files" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/pinned: + * post: + * summary: Add pinned file + * tags: + * - File Manager + * responses: + * 200: + * description: File pinned. + * 400: + * description: Invalid data. + * 409: + * description: File already pinned. + * 500: + * description: Failed to pin file. + */ + app.post("/pinned", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const { hostId, path, name } = req.body; + + if (!isNonEmptyString(userId) || !hostId || !path) { + return res.status(400).json({ error: "Invalid data" }); + } + + try { + const drizzle = await client(); + const existing = await drizzle + .select() + .from(pinnedTable) + .where( + and( + eq(pinnedTable.userId, userId), + eq(pinnedTable.hostId, hostId), + eq(pinnedTable.path, path), + ), + ) + .limit(1); + + if (existing[0]) { + return res.status(409).json({ error: "File already pinned" }); + } + + await drizzle.insert(pinnedTable).values({ + userId, + hostId, + path, + name: name ?? path, + pinnedAt: new Date().toISOString(), + }); + await ctx.db.persist(); + res.json({ message: "File pinned" }); + } catch (err) { + ctx.log.error("Failed to pin file", err as Error); + res.status(500).json({ error: "Failed to pin file" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/pinned: + * delete: + * summary: Remove pinned file + * tags: + * - File Manager + * responses: + * 200: + * description: Pinned file removed. + * 400: + * description: Invalid data. + * 500: + * description: Failed to remove pinned file. + */ + app.delete("/pinned", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const { hostId, path } = req.body; + + if (!isNonEmptyString(userId) || !hostId || !path) { + return res.status(400).json({ error: "Invalid data" }); + } + + try { + const drizzle = await client(); + await drizzle + .delete(pinnedTable) + .where( + and( + eq(pinnedTable.userId, userId), + eq(pinnedTable.hostId, hostId), + eq(pinnedTable.path, path), + ), + ); + await ctx.db.persist(); + res.json({ message: "Pinned file removed" }); + } catch (err) { + ctx.log.error("Failed to remove pinned file", err as Error); + res.status(500).json({ error: "Failed to remove pinned file" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/shortcuts: + * get: + * summary: Get shortcuts + * tags: + * - File Manager + * parameters: + * - in: query + * name: hostId + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: A list of shortcuts. + * 400: + * description: Invalid userId or hostId. + * 500: + * description: Failed to fetch shortcuts. + */ + app.get("/shortcuts", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const hostIdQuery = Array.isArray(req.query.hostId) + ? req.query.hostId[0] + : req.query.hostId; + const hostId = hostIdQuery ? parseInt(hostIdQuery as string) : null; + + if (!isNonEmptyString(userId)) { + return res.status(400).json({ error: "Invalid userId" }); + } + if (!hostId) { + return res.status(400).json({ error: "Host ID is required" }); + } + + try { + const drizzle = await client(); + const rows = await drizzle + .select() + .from(shortcutsTable) + .where( + and( + eq(shortcutsTable.userId, userId), + eq(shortcutsTable.hostId, hostId), + ), + ) + .orderBy(desc(shortcutsTable.createdAt)); + res.json(rows); + } catch (err) { + ctx.log.error("Failed to fetch shortcuts", err as Error); + res.status(500).json({ error: "Failed to fetch shortcuts" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/shortcuts: + * post: + * summary: Add shortcut + * tags: + * - File Manager + * responses: + * 200: + * description: Shortcut added. + * 400: + * description: Invalid data. + * 409: + * description: Shortcut already exists. + * 500: + * description: Failed to add shortcut. + */ + app.post("/shortcuts", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const { hostId, path, name } = req.body; + + if (!isNonEmptyString(userId) || !hostId || !path) { + return res.status(400).json({ error: "Invalid data" }); + } + + try { + const drizzle = await client(); + const existing = await drizzle + .select() + .from(shortcutsTable) + .where( + and( + eq(shortcutsTable.userId, userId), + eq(shortcutsTable.hostId, hostId), + eq(shortcutsTable.path, path), + ), + ) + .limit(1); + + if (existing[0]) { + return res.status(409).json({ error: "Shortcut already exists" }); + } + + await drizzle.insert(shortcutsTable).values({ + userId, + hostId, + path, + name: name ?? path, + createdAt: new Date().toISOString(), + }); + await ctx.db.persist(); + res.json({ message: "Shortcut added" }); + } catch (err) { + ctx.log.error("Failed to add shortcut", err as Error); + res.status(500).json({ error: "Failed to add shortcut" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/shortcuts: + * delete: + * summary: Remove shortcut + * tags: + * - File Manager + * responses: + * 200: + * description: Shortcut removed. + * 400: + * description: Invalid data. + * 500: + * description: Failed to remove shortcut. + */ + app.delete("/shortcuts", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const { hostId, path } = req.body; + + if (!isNonEmptyString(userId) || !hostId || !path) { + return res.status(400).json({ error: "Invalid data" }); + } + + try { + const drizzle = await client(); + await drizzle + .delete(shortcutsTable) + .where( + and( + eq(shortcutsTable.userId, userId), + eq(shortcutsTable.hostId, hostId), + eq(shortcutsTable.path, path), + ), + ); + await ctx.db.persist(); + res.json({ message: "Shortcut removed" }); + } catch (err) { + ctx.log.error("Failed to remove shortcut", err as Error); + res.status(500).json({ error: "Failed to remove shortcut" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/transfer-recent: + * get: + * summary: Get recent transfer destinations + * description: Retrieves recently used host-to-host transfer destinations for a source host. + * tags: + * - File Manager + * parameters: + * - in: query + * name: sourceHostId + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: A list of recent transfer destinations. + * 400: + * description: Invalid userId or sourceHostId. + * 500: + * description: Failed to fetch recent destinations. + */ + app.get("/transfer-recent", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const sourceHostIdQuery = Array.isArray(req.query.sourceHostId) + ? req.query.sourceHostId[0] + : req.query.sourceHostId; + const sourceHostId = sourceHostIdQuery + ? parseInt(sourceHostIdQuery as string) + : null; + + if (!isNonEmptyString(userId)) { + return res.status(400).json({ error: "Invalid userId" }); + } + if (!sourceHostId) { + return res.status(400).json({ error: "Source host ID is required" }); + } + + try { + const drizzle = await client(); + const rows = await drizzle + .select() + .from(transferRecentTable) + .where( + and( + eq(transferRecentTable.userId, userId), + eq(transferRecentTable.sourceHostId, sourceHostId), + ), + ) + .orderBy(desc(transferRecentTable.lastUsed)) + .limit(10); + res.json(rows); + } catch (err) { + ctx.log.error( + "Failed to fetch transfer recent destinations", + err as Error, + ); + res.status(500).json({ error: "Failed to fetch recent destinations" }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/transfer-recent: + * post: + * summary: Save a recent transfer destination + * tags: + * - File Manager + * responses: + * 200: + * description: Recent destination saved. + * 400: + * description: Invalid data. + * 500: + * description: Failed to save recent destination. + */ + app.post("/transfer-recent", async (req: Request, res: Response) => { + const userId = ctx.currentActor(); + const { sourceHostId, destHostId, destPath, destPathLabel } = req.body; + + if ( + !isNonEmptyString(userId) || + !sourceHostId || + !destHostId || + !destPath + ) { + return res.status(400).json({ error: "Invalid data" }); + } + + try { + const drizzle = await client(); + const existing = await drizzle + .select() + .from(transferRecentTable) + .where( + and( + eq(transferRecentTable.userId, userId), + eq(transferRecentTable.sourceHostId, sourceHostId), + eq(transferRecentTable.destHostId, destHostId), + eq(transferRecentTable.destPath, destPath), + ), + ) + .limit(1); + + const now = new Date().toISOString(); + if (existing[0]) { + await drizzle + .update(transferRecentTable) + .set({ + destPathLabel: destPathLabel ?? existing[0].destPathLabel, + lastUsed: now, + }) + .where(eq(transferRecentTable.id, existing[0].id)); + } else { + await drizzle.insert(transferRecentTable).values({ + userId, + sourceHostId, + destHostId, + destPath, + destPathLabel: destPathLabel ?? destPath, + lastUsed: now, + }); + } + + // Keep only the 10 most recent destinations per source host. + const all = await drizzle + .select() + .from(transferRecentTable) + .where( + and( + eq(transferRecentTable.userId, userId), + eq(transferRecentTable.sourceHostId, sourceHostId), + ), + ) + .orderBy(desc(transferRecentTable.lastUsed)); + const stale = all.slice(10); + for (const row of stale) { + await drizzle + .delete(transferRecentTable) + .where(eq(transferRecentTable.id, row.id)); + } + + await ctx.db.persist(); + res.json({ message: "Recent destination saved" }); + } catch (err) { + ctx.log.error("Failed to save transfer recent destination", err as Error); + res.status(500).json({ error: "Failed to save recent destination" }); + } + }); +} diff --git a/plugins/file-manager/src/backend/content-routes.ts b/plugins/file-manager/src/backend/content-routes.ts new file mode 100644 index 000000000..6e8f55d2b --- /dev/null +++ b/plugins/file-manager/src/backend/content-routes.ts @@ -0,0 +1,1539 @@ +import type { Express, Request, Response } from "express"; +import Busboy from "busboy"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { + execBuffer, + execChannel, + execWithSudo, + execWithSudoBuffer, + getSessionSftp, + type SSHSession, +} from "./session.js"; +import { detectBinary } from "./utils.js"; + +type FileContentRoutesDeps = { + ctx: PluginContext; + sshSessions: Record; + verifySessionOwnership: (session: SSHSession, userId: string) => boolean; +}; + +const getRequiredQueryParam = ( + value: string | string[] | undefined, +): string | undefined => { + if (Array.isArray(value)) return value[0]; + return value; +}; + +const parseByteOffset = (value: string | undefined): number | null => { + if (!value) return null; + const parsed = Number(value); + if (!Number.isSafeInteger(parsed) || parsed < 0) return null; + return parsed; +}; + +export function registerFileContentRoutes( + app: Express, + { ctx, sshSessions, verifySessionOwnership }: FileContentRoutesDeps, +): void { + /** + * @openapi + * /plugin-api/file-manager/identifySymlink: + * get: + * summary: Identify symbolic link + * description: Identifies the target of a symbolic link. + * tags: + * - File Manager + * parameters: + * - in: query + * name: sessionId + * required: true + * schema: + * type: string + * - in: query + * name: path + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Symbolic link information. + * 400: + * description: Missing required parameters or SSH connection not established. + * 500: + * description: Failed to identify symbolic link. + */ + app.get("/identifySymlink", (req, res) => { + const sessionId = req.query.sessionId as string; + const sshConn = sshSessions[sessionId]; + const linkPath = req.query.path as string; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!linkPath) { + return res.status(400).json({ error: "Link path is required" }); + } + + sshConn.lastActive = Date.now(); + + const escapedPath = linkPath.replace(/'/g, "'\"'\"'"); + const command = `stat -L -c "%F" '${escapedPath}' && readlink -f '${escapedPath}'`; + + execChannel(sshConn, command, (err, stream) => { + if (err) { + ctx.log.error("SSH identifySymlink error:", err); + return res.status(500).json({ error: err.message }); + } + + let data = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.on("close", (code) => { + if (code !== 0) { + ctx.log.error( + `SSH identifySymlink command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + return res + .status(500) + .json({ error: `Command failed: ${errorData}` }); + } + + const [fileType, target] = data.trim().split("\n"); + + res.json({ + path: linkPath, + target: target, + type: fileType.toLowerCase().includes("directory") + ? "directory" + : "file", + }); + }); + + stream.on("error", (streamErr) => { + ctx.log.error("SSH identifySymlink stream error:", streamErr); + if (!res.headersSent) { + res.status(500).json({ error: `Stream error: ${streamErr.message}` }); + } + }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/resolvePath: + * get: + * summary: Resolve a path with environment variables + * description: Expands environment variables and ~ in a path via the SSH session. + * tags: + * - File Manager + * parameters: + * - in: query + * name: sessionId + * required: true + * schema: + * type: string + * - in: query + * name: path + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The resolved absolute path. + * 400: + * description: Missing required parameters. + * 500: + * description: Failed to resolve path. + */ + app.get("/resolvePath", (req, res) => { + const sessionId = req.query.sessionId as string; + const sshConn = sshSessions[sessionId]; + const rawPath = req.query.path as string; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!rawPath) { + return res.status(400).json({ error: "Path is required" }); + } + + sshConn.lastActive = Date.now(); + + let command: string; + if (rawPath.startsWith("~")) { + const rest = rawPath.substring(1).replace(/'/g, "'\"'\"'"); + command = `echo ~'${rest}'`; + } else { + const escapedPath = rawPath.replace(/'/g, "'\"'\"'"); + command = `echo '${escapedPath}'`; + } + + execChannel(sshConn, command, (err, stream) => { + if (err) { + ctx.log.error("SSH resolvePath error:", err); + return res.status(500).json({ error: err.message }); + } + + let data = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.on("close", (code) => { + if (code !== 0) { + ctx.log.error( + `SSH resolvePath command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + return res.json({ resolvedPath: rawPath }); + } + + const resolved = data.trim(); + res.json({ resolvedPath: resolved || rawPath }); + }); + + stream.on("error", (streamErr) => { + ctx.log.error("SSH resolvePath stream error:", streamErr); + if (!res.headersSent) { + res.json({ resolvedPath: rawPath }); + } + }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/readFile: + * get: + * summary: Read a file + * description: Reads the content of a file from the remote host. + * tags: + * - File Manager + * parameters: + * - in: query + * name: sessionId + * required: true + * schema: + * type: string + * - in: query + * name: path + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The content of the file. + * 400: + * description: Missing required parameters or file too large. + * 404: + * description: File not found. + * 500: + * description: Failed to read file. + */ + app.get("/readFile", async (req, res) => { + const sessionId = req.query.sessionId as string; + const sshConn = sshSessions[sessionId]; + const filePath = req.query.path as string; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!filePath) { + return res.status(400).json({ error: "File path is required" }); + } + + ctx.log.info(`Reading file: ${sessionId} (${filePath})`); + sshConn.lastActive = Date.now(); + + const MAX_READ_SIZE = 500 * 1024 * 1024; + const escapedPath = filePath.replace(/'/g, "'\"'\"'"); + + const isPermissionDenied = (message: string) => + message.toLowerCase().includes("permission denied"); + const isFileNotFound = (message: string) => { + const lower = message.toLowerCase(); + return ( + lower.includes("no such file or directory") || + lower.includes("cannot access") || + lower.includes("not found") || + lower.includes("resource not found") + ); + }; + + try { + let sizeResult = await execBuffer( + sshConn, + `stat -c%s '${escapedPath}' 2>/dev/null || wc -c < '${escapedPath}'`, + ); + let sizeError = sizeResult.stderr || sizeResult.stdout.toString("utf8"); + + if ( + sizeResult.code !== 0 && + isPermissionDenied(sizeError) && + sshConn.sudoPassword + ) { + sizeResult = await execWithSudoBuffer( + sshConn, + `stat -c%s '${escapedPath}'`, + sshConn.sudoPassword, + ); + sizeError = sizeResult.stderr || sizeResult.stdout.toString("utf8"); + } + + if (sizeResult.code !== 0) { + const missing = isFileNotFound(sizeError); + const permissionDenied = isPermissionDenied(sizeError); + ctx.log.error(`File size check failed: ${sizeError}`); + return res.status(missing ? 404 : permissionDenied ? 403 : 500).json({ + error: `Cannot check file size: ${sizeError}`, + fileNotFound: missing, + needsSudo: permissionDenied, + }); + } + + const fileSize = parseInt(sizeResult.stdout.toString("utf8").trim(), 10); + if (isNaN(fileSize)) { + ctx.log.error( + `Invalid file size response: ${sizeResult.stdout.toString("utf8")}`, + ); + return res.status(500).json({ error: "Cannot determine file size" }); + } + + if (fileSize > MAX_READ_SIZE) { + ctx.log.warn( + `File too large for reading: ${sessionId} (${filePath}, size=${fileSize}, max=${MAX_READ_SIZE})`, + ); + return res.status(400).json({ + error: `File too large to open in editor. Maximum size is ${MAX_READ_SIZE / 1024 / 1024}MB, file is ${(fileSize / 1024 / 1024).toFixed(2)}MB. Use download instead.`, + fileSize, + maxSize: MAX_READ_SIZE, + tooLarge: true, + }); + } + + let contentResult = await execBuffer( + sshConn, + `cat '${escapedPath}'`, + MAX_READ_SIZE, + ); + let contentError = + contentResult.stderr || contentResult.stdout.toString("utf8"); + + if ( + contentResult.code !== 0 && + isPermissionDenied(contentError) && + sshConn.sudoPassword + ) { + contentResult = await execWithSudoBuffer( + sshConn, + `cat '${escapedPath}'`, + sshConn.sudoPassword, + MAX_READ_SIZE, + ); + contentError = + contentResult.stderr || contentResult.stdout.toString("utf8"); + } + + if (contentResult.exceededLimit) { + return res.status(400).json({ + error: `File grew beyond the ${MAX_READ_SIZE / 1024 / 1024}MB read limit`, + maxSize: MAX_READ_SIZE, + tooLarge: true, + }); + } + + if (contentResult.code !== 0) { + const missing = isFileNotFound(contentError); + const permissionDenied = isPermissionDenied(contentError); + ctx.log.error( + `SSH readFile command failed with code ${contentResult.code}: ${contentError.replace(/\n/g, " ").trim()}`, + ); + return res.status(missing ? 404 : permissionDenied ? 403 : 500).json({ + error: `Command failed: ${contentError}`, + fileNotFound: missing, + needsSudo: permissionDenied, + }); + } + + const isBinary = detectBinary(contentResult.stdout); + ctx.log.info( + `File read successfully: ${sessionId} (${filePath}, bytes=${contentResult.stdout.length})`, + ); + return res.json({ + content: contentResult.stdout.toString(isBinary ? "base64" : "utf8"), + path: filePath, + encoding: isBinary ? "base64" : "utf8", + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + ctx.log.error("SSH readFile error:", error as Error); + return res.status(500).json({ error: message }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/writeFile: + * post: + * summary: Write to a file + * description: Writes content to a file on the remote host and preserves the existing permissions when the file already exists. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * content: + * type: string + * responses: + * 200: + * description: File written successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 500: + * description: Failed to write file. + */ + app.post("/writeFile", async (req, res) => { + const { sessionId, path: filePath, content } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!filePath) { + return res.status(400).json({ error: "File path is required" }); + } + + if (content === undefined) { + return res.status(400).json({ error: "File content is required" }); + } + + const contentLength = + typeof content === "string" ? content.length : Buffer.byteLength(content); + ctx.log.info( + `Writing file: ${sessionId} (${filePath}, bytes=${contentLength})`, + ); + sshConn.lastActive = Date.now(); + + let preservedMode: number | undefined; + + const restoreOriginalMode = ( + sftp: import("ssh2").SFTPWrapper | null, + onComplete: () => void, + ) => { + if (preservedMode === undefined) { + onComplete(); + return; + } + + const permissions = preservedMode.toString(8); + + if (sftp) { + sftp.chmod(filePath, preservedMode, (chmodErr) => { + if (chmodErr) { + ctx.log.warn( + `Failed to restore file permissions after save: ${sessionId} (${filePath}, permissions=${permissions}, error=${chmodErr.message})`, + ); + } else { + ctx.log.info( + `Restored file permissions after save: ${sessionId} (${filePath}, permissions=${permissions})`, + ); + } + + onComplete(); + }); + return; + } + + const escapedPath = filePath.replace(/'/g, "'\"'\"'"); + const chmodCommand = `chmod ${permissions} '${escapedPath}' && echo "SUCCESS"`; + + execChannel(sshConn, chmodCommand, (err, stream) => { + if (err) { + ctx.log.warn( + `Failed to restore file permissions after save: ${sessionId} (${filePath}, permissions=${permissions}, error=${err.message})`, + ); + onComplete(); + return; + } + + let outputData = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.on("close", (code) => { + if (outputData.includes("SUCCESS")) { + ctx.log.info( + `Restored file permissions after save: ${sessionId} (${filePath}, permissions=${permissions})`, + ); + } else { + ctx.log.warn( + `Failed to restore file permissions after save: ${sessionId} (${filePath}, permissions=${permissions}, exitCode=${code}, error=${errorData || "Permission restore command did not report success"})`, + ); + } + + onComplete(); + }); + + stream.on("error", (streamErr) => { + ctx.log.warn( + `Failed to restore file permissions after save: ${sessionId} (${filePath}, permissions=${permissions}, error=${streamErr.message})`, + ); + onComplete(); + }); + }); + }; + + const trySFTP = () => { + try { + ctx.log.info( + `Opening SFTP channel for writeFile: ${sessionId} (${filePath})`, + ); + getSessionSftp(sshConn) + .then((sftp) => { + let fileBuffer; + try { + if (typeof content === "string") { + try { + const testBuffer = Buffer.from(content, "base64"); + if (testBuffer.toString("base64") === content) { + fileBuffer = testBuffer; + } else { + fileBuffer = Buffer.from(content, "utf8"); + } + } catch { + fileBuffer = Buffer.from(content, "utf8"); + } + } else if (Buffer.isBuffer(content)) { + fileBuffer = content; + } else { + fileBuffer = Buffer.from(content); + } + } catch (bufferErr) { + ctx.log.error("Buffer conversion error:", bufferErr as Error); + if (!res.headersSent) { + return res + .status(500) + .json({ error: "Invalid file content format" }); + } + return; + } + + sftp.stat(filePath, (statErr, stats) => { + try { + if (statErr) { + ctx.log.warn( + `Failed to read existing file permissions before save: ${sessionId} (${filePath}, error=${statErr.message})`, + ); + } else if (stats.isFile()) { + preservedMode = stats.mode & 0o7777; + } + + const writeStream = sftp.createWriteStream(filePath); + + let hasError = false; + let hasFinished = false; + let isFinalizing = false; + + const finalizeSuccess = () => { + if (hasError || hasFinished) return; + hasFinished = true; + isFinalizing = false; + ctx.log.info( + `File written successfully: ${sessionId} (${filePath}, bytes=${fileBuffer.length})`, + ); + if (!res.headersSent) { + res.json({ + message: "File written successfully", + path: filePath, + toast: { + type: "success", + message: `File written: ${filePath}`, + }, + }); + } + }; + + writeStream.on("error", (streamErr) => { + if (hasError || hasFinished || isFinalizing) return; + hasError = true; + isFinalizing = false; + ctx.log.warn( + `SFTP write failed, trying fallback method: ${streamErr.message}`, + ); + tryFallbackMethod(); + }); + + const finishWrite = () => { + if (hasError || hasFinished || isFinalizing) return; + isFinalizing = true; + restoreOriginalMode(sftp, finalizeSuccess); + }; + + writeStream.on("finish", () => { + finishWrite(); + }); + + writeStream.on("close", () => { + finishWrite(); + }); + + try { + writeStream.write(fileBuffer); + writeStream.end(); + } catch (writeErr) { + if (hasError || hasFinished) return; + hasError = true; + isFinalizing = false; + ctx.log.warn( + `SFTP write operation failed, trying fallback method: ${(writeErr as Error).message}`, + ); + tryFallbackMethod(); + } + } catch (callbackErr) { + ctx.log.warn( + `SFTP stat callback error, trying fallback method: ${(callbackErr as Error).message}`, + ); + tryFallbackMethod(); + } + }); + }) + .catch((err: Error) => { + ctx.log.warn(`SFTP failed, trying fallback method: ${err.message}`); + tryFallbackMethod(); + }); + } catch (sftpErr) { + ctx.log.warn( + `SFTP connection error, trying fallback method: ${(sftpErr as Error).message}`, + ); + tryFallbackMethod(); + } + }; + + const tryFallbackMethod = () => { + if (!sshConn?.isConnected) { + if (!res.headersSent) { + return res.status(500).json({ error: "SSH session disconnected" }); + } + return; + } + try { + let contentBuffer: Buffer; + if (typeof content === "string") { + try { + contentBuffer = Buffer.from(content, "base64"); + if (contentBuffer.toString("base64") !== content) { + contentBuffer = Buffer.from(content, "utf8"); + } + } catch { + contentBuffer = Buffer.from(content, "utf8"); + } + } else if (Buffer.isBuffer(content)) { + contentBuffer = content; + } else { + contentBuffer = Buffer.from(content); + } + const base64Content = contentBuffer.toString("base64"); + const escapedPath = filePath.replace(/'/g, "'\"'\"'"); + + const writeCommand = `echo '${base64Content}' | base64 -d > '${escapedPath}' && echo "SUCCESS"`; + + execChannel(sshConn, writeCommand, (err, stream) => { + if (err) { + ctx.log.error("Fallback write command failed:", err); + if (!res.headersSent) { + return res.status(500).json({ + error: `Write failed: ${err.message}`, + toast: { + type: "error", + message: `Write failed: ${err.message}`, + }, + }); + } + return; + } + + let outputData = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.stderr.on("error", (stderrErr) => { + ctx.log.error("Fallback write stderr error:", stderrErr); + }); + + stream.on("close", (code) => { + if (outputData.includes("SUCCESS")) { + restoreOriginalMode(null, () => { + if (!res.headersSent) { + res.json({ + message: "File written successfully", + path: filePath, + toast: { + type: "success", + message: `File written: ${filePath}`, + }, + }); + } + }); + } else { + const isPermDenied = errorData + .toLowerCase() + .includes("permission denied"); + if (isPermDenied && sshConn.sudoPassword) { + execWithSudo( + sshConn, + `bash -c "echo '${base64Content}' | base64 -d > '${escapedPath}' && echo SUCCESS"`, + sshConn.sudoPassword, + ) + .then(({ stdout, code: sudoCode }) => { + if (sudoCode === 0 && stdout.includes("SUCCESS")) { + restoreOriginalMode(null, () => { + if (!res.headersSent) { + res.json({ + message: "File written successfully", + path: filePath, + }); + } + }); + } else if (!res.headersSent) { + res + .status(403) + .json({ error: "Permission denied", needsSudo: true }); + } + }) + .catch(() => { + if (!res.headersSent) { + res + .status(403) + .json({ error: "Permission denied", needsSudo: true }); + } + }); + return; + } + ctx.log.error( + `Fallback write failed with code ${code}: ${errorData}`, + ); + if (!res.headersSent) { + res.status(500).json({ + error: `Write failed: ${errorData}`, + needsSudo: isPermDenied, + toast: { + type: "error", + message: `Write failed: ${errorData}`, + }, + }); + } + } + }); + + stream.on("error", (streamErr) => { + ctx.log.error("Fallback write stream error:", streamErr); + if (!res.headersSent) { + res + .status(500) + .json({ error: `Write stream error: ${streamErr.message}` }); + } + }); + }); + } catch (fallbackErr) { + ctx.log.error("Fallback method failed:", fallbackErr as Error); + if (!res.headersSent) { + res.status(500).json({ + error: `All write methods failed: ${(fallbackErr as Error).message}`, + }); + } + } + }; + + trySFTP(); + }); + + /** + * @openapi + * /plugin-api/file-manager/uploadFile: + * post: + * summary: Upload a file + * description: Uploads a file to the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * content: + * type: string + * fileName: + * type: string + * responses: + * 200: + * description: File uploaded successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 500: + * description: Failed to upload file. + */ + app.post("/uploadFile", async (req, res) => { + const { sessionId, path: filePath, content, fileName } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!filePath || !fileName || content === undefined) { + return res + .status(400) + .json({ error: "File path, name, and content are required" }); + } + + sshConn.lastActive = Date.now(); + + const contentSize = + typeof content === "string" + ? Buffer.byteLength(content, "utf8") + : content.length; + + const fullPath = filePath.endsWith("/") + ? filePath + fileName + : filePath + "/" + fileName; + const uploadStartTime = Date.now(); + ctx.log.info( + `File upload started: ${sessionId} (${fullPath}, bytes=${contentSize})`, + ); + + const trySFTP = () => { + try { + ctx.log.info( + `Opening SFTP channel for uploadFile: ${sessionId} (${fullPath})`, + ); + getSessionSftp(sshConn) + .then((sftp) => { + let fileBuffer; + try { + if (typeof content === "string") { + fileBuffer = Buffer.from(content, "base64"); + } else if (Buffer.isBuffer(content)) { + fileBuffer = content; + } else { + fileBuffer = Buffer.from(content); + } + } catch (bufferErr) { + ctx.log.error("Buffer conversion error:", bufferErr as Error); + if (!res.headersSent) { + return res + .status(500) + .json({ error: "Invalid file content format" }); + } + return; + } + + const writeStream = sftp.createWriteStream(fullPath); + + let hasError = false; + let hasFinished = false; + + writeStream.on("error", (streamErr) => { + if (hasError || hasFinished) return; + hasError = true; + ctx.log.warn( + `SFTP write failed, trying fallback method: ${streamErr.message} (sessionId=${sessionId}, fileName=${fileName}, fileSize=${contentSize})`, + ); + tryFallbackMethod(); + }); + + writeStream.on("finish", () => { + if (hasError || hasFinished) return; + hasFinished = true; + ctx.log.info( + `File upload completed: ${sessionId} (${fullPath}, bytes=${fileBuffer.length}, duration=${Date.now() - uploadStartTime}ms)`, + ); + if (!res.headersSent) { + res.json({ + message: "File uploaded successfully", + path: fullPath, + toast: { + type: "success", + message: `File uploaded: ${fullPath}`, + }, + }); + } + }); + + writeStream.on("close", () => { + if (hasError || hasFinished) return; + hasFinished = true; + ctx.log.info( + `File upload completed: ${sessionId} (${fullPath}, bytes=${fileBuffer.length}, duration=${Date.now() - uploadStartTime}ms)`, + ); + if (!res.headersSent) { + res.json({ + message: "File uploaded successfully", + path: fullPath, + toast: { + type: "success", + message: `File uploaded: ${fullPath}`, + }, + }); + } + }); + + try { + writeStream.write(fileBuffer); + writeStream.end(); + } catch (writeErr) { + if (hasError || hasFinished) return; + hasError = true; + ctx.log.warn( + `SFTP write operation failed, trying fallback method: ${(writeErr as Error).message}`, + ); + tryFallbackMethod(); + } + }) + .catch((err: Error) => { + ctx.log.warn(`SFTP failed, trying fallback method: ${err.message}`); + tryFallbackMethod(); + }); + } catch (sftpErr) { + ctx.log.warn( + `SFTP connection error, trying fallback method: ${(sftpErr as Error).message}`, + ); + tryFallbackMethod(); + } + }; + + const tryFallbackMethod = () => { + if (!sshConn?.isConnected) { + if (!res.headersSent) { + return res.status(500).json({ error: "SSH session disconnected" }); + } + return; + } + try { + let contentBuffer: Buffer; + if (typeof content === "string") { + try { + contentBuffer = Buffer.from(content, "base64"); + if (contentBuffer.toString("base64") !== content) { + contentBuffer = Buffer.from(content, "utf8"); + } + } catch { + contentBuffer = Buffer.from(content, "utf8"); + } + } else if (Buffer.isBuffer(content)) { + contentBuffer = content; + } else { + contentBuffer = Buffer.from(content); + } + const base64Content = contentBuffer.toString("base64"); + const chunkSize = 1000000; + const chunks = []; + + for (let i = 0; i < base64Content.length; i += chunkSize) { + chunks.push(base64Content.slice(i, i + chunkSize)); + } + + if (!sshConn?.isConnected) { + ctx.log.error( + `SSH connection lost before fallback upload: ${sessionId} (${fullPath})`, + ); + if (!res.headersSent) { + return res + .status(500) + .json({ error: "SSH connection lost during upload" }); + } + return; + } + + if (chunks.length === 1) { + const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); + + const writeCommand = `echo '${chunks[0]}' | base64 -d > '${escapedPath}' && echo "SUCCESS"`; + + execChannel(sshConn, writeCommand, (err, stream) => { + if (err) { + ctx.log.error("Fallback upload command failed:", err); + if (!res.headersSent) { + return res + .status(500) + .json({ error: `Upload failed: ${err.message}` }); + } + return; + } + + let outputData = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.stderr.on("error", (stderrErr) => { + ctx.log.error("Fallback upload stderr error:", stderrErr); + }); + + stream.on("close", (code) => { + if (outputData.includes("SUCCESS")) { + if (!res.headersSent) { + res.json({ + message: "File uploaded successfully", + path: fullPath, + toast: { + type: "success", + message: `File uploaded: ${fullPath}`, + }, + }); + } + } else { + ctx.log.error( + `Fallback upload failed with code ${code}: ${errorData}`, + ); + if (!res.headersSent) { + res.status(500).json({ + error: `Upload failed: ${errorData}`, + toast: { + type: "error", + message: `Upload failed: ${errorData}`, + }, + }); + } + } + }); + + stream.on("error", (streamErr) => { + ctx.log.error("Fallback upload stream error:", streamErr); + if (!res.headersSent) { + res + .status(500) + .json({ error: `Upload stream error: ${streamErr.message}` }); + } + }); + }); + } else { + const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); + + let writeCommand = `> '${escapedPath}'`; + + chunks.forEach((chunk) => { + writeCommand += ` && echo '${chunk}' | base64 -d >> '${escapedPath}'`; + }); + + writeCommand += ` && echo "SUCCESS"`; + + execChannel(sshConn, writeCommand, (err, stream) => { + if (err) { + ctx.log.error("Chunked fallback upload failed:", err); + if (!res.headersSent) { + return res + .status(500) + .json({ error: `Chunked upload failed: ${err.message}` }); + } + return; + } + + let outputData = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.stderr.on("error", (stderrErr) => { + ctx.log.error("Chunked fallback upload stderr error:", stderrErr); + }); + + stream.on("close", (code) => { + if (outputData.includes("SUCCESS")) { + if (!res.headersSent) { + res.json({ + message: "File uploaded successfully", + path: fullPath, + toast: { + type: "success", + message: `File uploaded: ${fullPath}`, + }, + }); + } + } else { + ctx.log.error( + `Chunked fallback upload failed with code ${code}: ${errorData}`, + ); + if (!res.headersSent) { + res.status(500).json({ + error: `Chunked upload failed: ${errorData}`, + toast: { + type: "error", + message: `Chunked upload failed: ${errorData}`, + }, + }); + } + } + }); + + stream.on("error", (streamErr) => { + ctx.log.error("Chunked fallback upload stream error:", streamErr); + if (!res.headersSent) { + res.status(500).json({ + error: `Chunked upload stream error: ${streamErr.message}`, + }); + } + }); + }); + } + } catch (fallbackErr) { + ctx.log.error("Fallback method failed:", fallbackErr as Error); + if (!res.headersSent) { + res.status(500).json({ + error: `All upload methods failed: ${(fallbackErr as Error).message}`, + }); + } + } + }; + + trySFTP(); + }); + + /** + * @openapi + * /plugin-api/file-manager/uploadFileStream: + * post: + * summary: Stream-upload a file via multipart form + * description: Uploads a file to the remote host by streaming multipart form data directly into an SFTP write stream, avoiding full in-memory buffering. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * required: + * - sessionId + * - path + * - file + * properties: + * sessionId: + * type: string + * path: + * type: string + * file: + * type: string + * format: binary + * responses: + * 200: + * description: File uploaded successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 500: + * description: Failed to upload file. + */ + app.post("/uploadFileStream", (req: Request, res: Response) => { + const userId = ctx.currentActor()!; + + const contentType = req.headers["content-type"] || ""; + if (!contentType.includes("multipart/form-data")) { + return res + .status(400) + .json({ error: "Expected multipart/form-data request" }); + } + + let sessionId: string | undefined; + let remotePath: string | undefined; + let fileName: string | undefined; + let uploadStartTime: number; + let resolved = false; + let requestAborted = false; + let cleanupStarted = false; + let destroyUpload: (() => void) | undefined; + + const abortUpload = () => { + if (req.complete || cleanupStarted) return; + requestAborted = true; + cleanupStarted = true; + destroyUpload?.(); + }; + + req.once("aborted", abortUpload); + req.once("error", abortUpload); + req.once("close", abortUpload); + + const bb = Busboy({ headers: req.headers }); + + bb.on("field", (name: string, value: string) => { + if (name === "sessionId") sessionId = value; + if (name === "path") remotePath = value; + }); + + bb.on( + "file", + ( + fieldname: string, + fileStream: NodeJS.ReadableStream, + info: { filename: string; encoding: string; mimeType: string }, + ) => { + fileName = info.filename; + + if (!sessionId || !remotePath || !fileName) { + fileStream.resume(); + if (!resolved) { + resolved = true; + res.status(400).json({ error: "Missing sessionId or path field" }); + } + return; + } + + const sshConn = sshSessions[sessionId]; + if (!sshConn?.isConnected) { + fileStream.resume(); + if (!resolved) { + resolved = true; + res.status(400).json({ error: "SSH connection not established" }); + } + return; + } + + if (!verifySessionOwnership(sshConn, userId)) { + fileStream.resume(); + if (!resolved) { + resolved = true; + res.status(403).json({ error: "Session access denied" }); + } + return; + } + + sshConn.lastActive = Date.now(); + uploadStartTime = Date.now(); + + const fullPath = remotePath.endsWith("/") + ? remotePath + fileName + : remotePath + "/" + fileName; + + ctx.log.info( + `Streaming file upload started: ${sessionId} (${fullPath})`, + ); + + getSessionSftp(sshConn) + .then((sftp) => { + const writeStream = sftp.createWriteStream(fullPath); + const removePartialFile = () => { + writeStream.destroy(); + sftp.unlink(fullPath, (err) => { + if (err) { + ctx.log.error( + "Failed to remove partial file after aborted upload:", + err, + ); + } + }); + }; + destroyUpload = removePartialFile; + + if (requestAborted) { + removePartialFile(); + return; + } + + writeStream.on("error", (err) => { + ctx.log.error("SFTP write stream error during upload:", err); + if (!resolved && !requestAborted) { + resolved = true; + res + .status(500) + .json({ error: `Upload failed: ${err.message}` }); + } + }); + + const completeUpload = () => { + if (resolved || requestAborted) return; + resolved = true; + ctx.log.info( + `Streaming file upload completed: ${sessionId} (${fullPath}, duration=${Date.now() - uploadStartTime}ms)`, + ); + res.json({ + message: "File uploaded successfully", + path: fullPath, + toast: { + type: "success", + message: `File uploaded: ${fullPath}`, + }, + }); + }; + + let sourceEnded = false; + fileStream.on("end", () => { + sourceEnded = true; + }); + writeStream.on("finish", completeUpload); + // ssh2's SFTP WriteStream destroys itself inside _final (autoClose), + // and a Writable destroyed before its final callback never emits + // 'finish' on current Node. 'close' fires once the handle has been + // closed after the last write was acknowledged, so it is the + // completion signal that reliably arrives -- but only trust it + // when the whole body was consumed and nothing aborted the upload. + writeStream.on("close", () => { + if (sourceEnded) completeUpload(); + }); + + fileStream.on("error", (err) => { + ctx.log.error("File read stream error during upload:", err); + writeStream.destroy(); + if (!resolved && !requestAborted) { + resolved = true; + res + .status(500) + .json({ error: `Upload stream error: ${err.message}` }); + } + }); + + (fileStream as NodeJS.ReadableStream).pipe( + writeStream as unknown as NodeJS.WritableStream, + ); + }) + .catch((err: Error) => { + fileStream.resume(); + ctx.log.error("SFTP session error during stream upload:", err); + if (!resolved && !requestAborted) { + resolved = true; + res.status(500).json({ error: `SFTP error: ${err.message}` }); + } + }); + }, + ); + + bb.on("error", (err: Error) => { + ctx.log.error("Busboy parse error during stream upload:", err); + if (!resolved && !requestAborted) { + resolved = true; + res.status(500).json({ error: `Upload parse error: ${err.message}` }); + } + }); + + req.pipe(bb); + }); + + /** + * @openapi + * /plugin-api/file-manager/uploadFileChunk: + * post: + * summary: Upload one raw file chunk + * description: Writes a raw request body to the remote file at the supplied byte offset, allowing browser clients to avoid multipart/FormData 2GB limits. + * tags: + * - File Manager + */ + app.post("/uploadFileChunk", (req: Request, res: Response) => { + const userId = ctx.currentActor()!; + const sessionId = getRequiredQueryParam(req.query.sessionId as string); + const remotePath = getRequiredQueryParam(req.query.path as string); + const fileName = getRequiredQueryParam(req.query.fileName as string); + const offset = parseByteOffset( + getRequiredQueryParam(req.query.offset as string), + ); + const totalSize = parseByteOffset( + getRequiredQueryParam(req.query.totalSize as string), + ); + + if (!sessionId || !remotePath || !fileName) { + req.resume(); + return res + .status(400) + .json({ error: "Missing sessionId, path, or fileName" }); + } + + if (offset === null || totalSize === null || offset > totalSize) { + req.resume(); + return res.status(400).json({ error: "Invalid upload offset" }); + } + + const sshConn = sshSessions[sessionId]; + if (!sshConn?.isConnected) { + req.resume(); + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + req.resume(); + return res.status(403).json({ error: "Session access denied" }); + } + + sshConn.lastActive = Date.now(); + const fullPath = remotePath.endsWith("/") + ? remotePath + fileName + : remotePath + "/" + fileName; + let resolved = false; + let bytesWritten = 0; + const uploadStartTime = Date.now(); + + getSessionSftp(sshConn) + .then((sftp) => { + const writeStream = sftp.createWriteStream(fullPath, { + flags: offset === 0 ? "w" : "r+", + start: offset, + }); + + const fail = (status: number, error: string) => { + if (resolved) return; + resolved = true; + req.unpipe(writeStream as unknown as NodeJS.WritableStream); + writeStream.destroy(); + res.status(status).json({ error }); + }; + + writeStream.on("error", (err) => { + ctx.log.error("SFTP write stream error during chunk upload:", err); + fail(500, `Upload failed: ${err.message}`); + }); + + let sourceEnded = false; + const completeChunk = () => { + if (resolved) return; + resolved = true; + const nextOffset = offset + bytesWritten; + ctx.log.info( + `File chunk upload completed: ${sessionId} (${fullPath}, offset=${offset}, bytesWritten=${bytesWritten}, nextOffset=${nextOffset}, totalSize=${totalSize}, duration=${Date.now() - uploadStartTime}ms)`, + ); + res.json({ + message: "File chunk uploaded successfully", + path: fullPath, + offset, + bytesWritten, + nextOffset, + complete: nextOffset >= totalSize, + }); + }; + writeStream.on("finish", completeChunk); + // See uploadFileStream: ssh2's WriteStream does not emit 'finish' + // on current Node; 'close' after the body ended is completion. + writeStream.on("close", () => { + if (sourceEnded) completeChunk(); + }); + + req.on("error", (err) => { + ctx.log.error("Request stream error during chunk upload:", err); + fail(500, `Upload stream error: ${err.message}`); + }); + + if (Buffer.isBuffer(req.body)) { + // Some middleware already drained the request into req.body; + // there is nothing left to pipe, so write what it collected. + bytesWritten = req.body.length; + sourceEnded = true; + writeStream.end(req.body); + return; + } + + req.on("data", (chunk: Buffer) => { + bytesWritten += chunk.length; + }); + req.on("end", () => { + sourceEnded = true; + }); + + req.pipe(writeStream as unknown as NodeJS.WritableStream); + }) + .catch((err: Error) => { + req.resume(); + ctx.log.error("SFTP session error during chunk upload:", err); + if (!resolved) { + resolved = true; + res.status(500).json({ error: `SFTP error: ${err.message}` }); + } + }); + }); +} diff --git a/plugins/file-manager/src/backend/direct-transfer-routing.ts b/plugins/file-manager/src/backend/direct-transfer-routing.ts new file mode 100644 index 000000000..e490d34fb --- /dev/null +++ b/plugins/file-manager/src/backend/direct-transfer-routing.ts @@ -0,0 +1,72 @@ +export interface DirectTransferEndpoint { + host: string; + port: number; + username: string; +} + +export const DIRECT_TRANSFER_MIN_IMPROVEMENT = 0.2; +export const DIRECT_TRANSFER_MIN_BYTES = 32 * 1024 * 1024; + +export function shouldBenchmarkDirectTransfer(totalBytes: number): boolean { + return totalBytes >= DIRECT_TRANSFER_MIN_BYTES; +} + +export function quoteShell(value: string): string { + return `'${value.replace(/'/g, `'"'"'`)}'`; +} + +function formatHost(host: string): string { + return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; +} + +export function buildDirectSshCommand( + endpoint: DirectTransferEndpoint, +): string { + const target = `${endpoint.username}@${formatHost(endpoint.host)}`; + return [ + "ssh", + "-o BatchMode=yes", + "-o ConnectTimeout=5", + "-o StrictHostKeyChecking=yes", + `-p ${endpoint.port}`, + quoteShell(target), + ].join(" "); +} + +export function buildDirectProbeCommand( + endpoint: DirectTransferEndpoint, +): string { + return `${buildDirectSshCommand(endpoint)} ${quoteShell("command -v rsync >/dev/null")}`; +} + +export function buildDirectRsyncCommand( + endpoint: DirectTransferEndpoint, + sourcePaths: string[], + destPath: string, + destIsDirectory: boolean, +): string { + const sshTransport = buildDirectSshCommand(endpoint).replace(/ '[^']+'$/, ""); + const targetHost = `${endpoint.username}@${formatHost(endpoint.host)}`; + const targetPath = destIsDirectory + ? `${destPath.replace(/\/+$/, "") || "/"}/` + : destPath; + const sources = sourcePaths.map(quoteShell).join(" "); + const target = quoteShell(`${targetHost}:${targetPath}`); + + return [ + "rsync -a --partial --append-verify --protect-args --info=progress2", + `-e ${quoteShell(sshTransport)}`, + "--", + sources, + target, + ].join(" "); +} + +export function shouldUseDirectTransfer( + directMs: number, + relayMs: number, + minImprovement = DIRECT_TRANSFER_MIN_IMPROVEMENT, +): boolean { + if (directMs <= 0 || relayMs <= 0) return false; + return directMs <= relayMs * (1 - minImprovement); +} diff --git a/plugins/file-manager/src/backend/download-routes.ts b/plugins/file-manager/src/backend/download-routes.ts new file mode 100644 index 000000000..21ed7c9cf --- /dev/null +++ b/plugins/file-manager/src/backend/download-routes.ts @@ -0,0 +1,316 @@ +import type { Express } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { getMimeType } from "./utils.js"; +import { execChannel, getSessionSftp, type SSHSession } from "./session.js"; + +type FileDownloadRoutesDeps = { + ctx: PluginContext; + sshSessions: Record; + scheduleSessionCleanup: (sessionId: string) => void; + verifySessionOwnership: (session: SSHSession, userId: string) => boolean; +}; + +function escapeSingleQuotes(path: string): string { + return path.replace(/'/g, "'\"'\"'"); +} + +function downloadViaExec( + session: SSHSession, + filePath: string, +): Promise { + return new Promise((resolve, reject) => { + const escaped = escapeSingleQuotes(filePath); + execChannel(session, `cat '${escaped}'`, (err, stream) => { + if (err) return reject(err); + const chunks: Buffer[] = []; + let stderr = ""; + stream.on("data", (chunk: Buffer) => chunks.push(chunk)); + stream.stderr.on("data", (chunk: Buffer) => { + stderr += chunk.toString(); + }); + stream.on("close", (code: number) => { + if (code !== 0) { + return reject( + new Error(stderr.trim() || `cat exited with code ${code}`), + ); + } + resolve(Buffer.concat(chunks)); + }); + stream.on("error", reject); + }); + }); +} + +export function registerFileDownloadRoutes( + app: Express, + { + ctx, + sshSessions, + scheduleSessionCleanup, + verifySessionOwnership, + }: FileDownloadRoutesDeps, +): void { + /** + * @openapi + * /plugin-api/file-manager/downloadFile: + * post: + * summary: Download a file + * description: Downloads a file from the remote host. Uses SCP legacy mode (cat over exec) when the host has scpLegacy enabled, otherwise uses SFTP. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * hostId: + * type: integer + * userId: + * type: string + * responses: + * 200: + * description: The file content. + * 400: + * description: Missing required parameters or file too large. + * 500: + * description: Failed to download file. + */ + app.post("/downloadFile", async (req, res) => { + const { sessionId, path: filePath, hostId } = req.body; + const userId = ctx.currentActor()!; + const downloadStartTime = Date.now(); + + if (!sessionId || !filePath) { + ctx.log.warn( + `Missing download parameters: ${sessionId} (hasFilePath=${!!filePath})`, + ); + return res.status(400).json({ error: "Missing download parameters" }); + } + + ctx.log.info(`File download started: ${sessionId} (${filePath})`); + + const sshConn = sshSessions[sessionId]; + if (!sshConn || !sshConn.isConnected) { + ctx.log.warn( + `SSH session not found or not connected for download: ${sessionId} (isConnected=${sshConn?.isConnected})`, + ); + return res + .status(400) + .json({ error: "SSH session not found or not connected" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + sshConn.lastActive = Date.now(); + scheduleSessionCleanup(sessionId); + + if (sshConn.scpLegacy) { + ctx.log.info( + `Downloading file via legacy exec/cat (SCP legacy mode): ${sessionId} (${filePath})`, + ); + + try { + const data = await downloadViaExec(sshConn, filePath); + const fileName = filePath.split("/").pop() || "download"; + ctx.log.info( + `File download completed (legacy mode): ${sessionId} (${filePath}, hostId=${hostId}, bytes=${data.length}, duration=${Date.now() - downloadStartTime}ms)`, + ); + return res.json({ + content: data.toString("base64"), + fileName, + size: data.length, + mimeType: getMimeType(fileName), + path: filePath, + }); + } catch (err) { + ctx.log.error("Legacy exec/cat download failed:", err as Error); + return res.status(500).json({ + error: `Failed to download file: ${(err as Error).message}`, + }); + } + } + + ctx.log.info( + `Opening SFTP channel for downloadFile: ${sessionId} (${filePath})`, + ); + + getSessionSftp(sshConn) + .then((sftp) => { + sftp.stat(filePath, (statErr, stats) => { + if (statErr) { + ctx.log.error("File stat failed for download:", statErr); + return res + .status(500) + .json({ error: `Cannot access file: ${statErr.message}` }); + } + + if (!stats.isFile()) { + ctx.log.warn( + `Attempted to download non-file: ${sessionId} (${filePath}, isFile=${stats.isFile()}, isDirectory=${stats.isDirectory()})`, + ); + return res + .status(400) + .json({ error: "Cannot download directories or special files" }); + } + + const MAX_FILE_SIZE = 5 * 1024 * 1024 * 1024; + if (stats.size > MAX_FILE_SIZE) { + ctx.log.warn( + `File too large for download: ${sessionId} (${filePath}, fileSize=${stats.size}, maxSize=${MAX_FILE_SIZE})`, + ); + return res.status(400).json({ + error: `File too large. Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB, file is ${(stats.size / 1024 / 1024).toFixed(2)}MB`, + }); + } + + sftp.readFile(filePath, (readErr, data) => { + if (readErr) { + ctx.log.error("File read failed for download:", readErr); + return res + .status(500) + .json({ error: `Failed to read file: ${readErr.message}` }); + } + + const base64Content = data.toString("base64"); + const fileName = filePath.split("/").pop() || "download"; + ctx.log.info( + `File download completed: ${sessionId} (${filePath}, hostId=${hostId}, bytes=${stats.size}, duration=${Date.now() - downloadStartTime}ms)`, + ); + + res.json({ + content: base64Content, + fileName: fileName, + size: stats.size, + mimeType: getMimeType(fileName), + path: filePath, + }); + }); + }); + }) + .catch((err) => { + ctx.log.error("SFTP connection failed for download:", err); + return res.status(500).json({ error: "SFTP connection failed" }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/downloadFileStream: + * post: + * summary: Stream-download a file + * description: Downloads a file as a binary stream. Uses SCP legacy mode (cat over exec) when the host has scpLegacy enabled, otherwise uses SFTP. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * responses: + * 200: + * description: Binary file stream. + * 400: + * description: Missing required parameters. + * 500: + * description: Download failed. + */ + app.post("/downloadFileStream", async (req, res) => { + const { sessionId, path: filePath } = req.body; + const userId = ctx.currentActor()!; + + if (!sessionId || !filePath) { + return res.status(400).json({ error: "Missing download parameters" }); + } + + const sshConn = sshSessions[sessionId]; + if (!sshConn?.isConnected) { + return res + .status(400) + .json({ error: "SSH session not found or not connected" }); + } + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + sshConn.lastActive = Date.now(); + + const fileName = filePath.split("/").pop() || "download"; + res.setHeader("Content-Type", "application/octet-stream"); + res.setHeader( + "Content-Disposition", + `attachment; filename="${encodeURIComponent(fileName)}"`, + ); + + if (sshConn.scpLegacy) { + ctx.log.info( + `Streaming file via legacy exec/cat (SCP legacy mode): ${sessionId} (${filePath})`, + ); + + const escaped = escapeSingleQuotes(filePath); + execChannel(sshConn, `cat '${escaped}'`, (err, stream) => { + if (err) { + if (!res.headersSent) { + res.status(500).json({ error: `Download failed: ${err.message}` }); + } + return; + } + stream.on("error", (streamErr: Error) => { + if (!res.headersSent) { + res + .status(500) + .json({ error: `Download failed: ${streamErr.message}` }); + } else { + res.destroy(); + } + }); + stream.pipe(res); + }); + return; + } + + try { + const sftp = await getSessionSftp(sshConn); + const stats = await new Promise<{ size: number; isFile: () => boolean }>( + (resolve, reject) => { + sftp.stat(filePath, (err, s) => (err ? reject(err) : resolve(s))); + }, + ); + + if (!stats.isFile()) { + return res.status(400).json({ error: "Cannot download directories" }); + } + + res.setHeader("Content-Length", String(stats.size)); + + const readStream = sftp.createReadStream(filePath); + readStream.on("error", (err) => { + if (!res.headersSent) { + res.status(500).json({ error: `Download failed: ${err.message}` }); + } else { + res.destroy(); + } + }); + readStream.pipe(res); + } catch (err) { + if (!res.headersSent) { + res + .status(500) + .json({ error: `Download failed: ${(err as Error).message}` }); + } + } + }); +} diff --git a/plugins/file-manager/src/backend/error-message.ts b/plugins/file-manager/src/backend/error-message.ts new file mode 100644 index 000000000..6ef95453a --- /dev/null +++ b/plugins/file-manager/src/backend/error-message.ts @@ -0,0 +1,6 @@ +export function getErrorMessage( + error: unknown, + fallback = "Unknown error", +): string { + return error instanceof Error ? error.message : fallback; +} diff --git a/plugins/file-manager/src/backend/index.ts b/plugins/file-manager/src/backend/index.ts new file mode 100644 index 000000000..25432ad97 --- /dev/null +++ b/plugins/file-manager/src/backend/index.ts @@ -0,0 +1,2214 @@ +// Core imports below point at TypeScript source so tsc can type-check them. +// The plugin build rewrites the prefix to the compiled output path; see +// packages/plugin-sdk/cli/lib/legacy-core-imports.mjs. +import express, { type Router } from "express"; +import cookieParser from "cookie-parser"; +import axios from "axios"; +import { Client as SSHClient } from "ssh2"; +import type { PluginContext, PluginSshHost } from "@termix/plugin-sdk/backend"; +import { getErrorMessage } from "./error-message.js"; +import { setPluginSsh, pluginSsh } from "./ssh.js"; +import { setPluginCtx } from "./plugin-ctx.js"; +import { AuthManager } from "../../../../src/backend/utils/auth-manager.js"; +import { logger } from "../../../../src/backend/utils/logger.js"; +import { + logAudit, + getAuditUsername, + getRequestMeta, +} from "../../../../src/backend/utils/audit-logger.js"; +import { createCurrentHostResolutionRepository } from "../../../../src/backend/database/repositories/factory.js"; +import { + resolveHostById, + resolveHostBySyncId, +} from "../../../../src/backend/hosts/host-resolver.js"; +import { createConnectionLog } from "../../../../src/backend/hosts/connection-log.js"; +import { JumpHostChainError } from "../../../../src/backend/hosts/jump-host-chain.js"; +import { + hostAddressMismatch, + HostAddressMismatchError, + HostNotOnThisServerError, + resolveServerHostId, +} from "../../../../src/backend/hosts/host-identity.js"; +import type { + LogEntry, + ConnectionStage, +} from "../../../../src/types/connection-log.js"; +import { + ChannelOpenSerializer, + execChannel, + getSessionSftp, + type PendingTOTPSession, + type SSHSession, +} from "./session.js"; +import { registerFileListingRoutes } from "./list-routes.js"; +import { registerFileContentRoutes } from "./content-routes.js"; +import { registerFileOperationRoutes } from "./operation-routes.js"; +import { registerFileDownloadRoutes } from "./download-routes.js"; +import { registerFileActionRoutes } from "./action-routes.js"; +import { registerBookmarkRoutes } from "./bookmark-routes.js"; +import { + startHostTransfer, + getTransferStatus, + listActiveTransfers, + probeHungStreamTransfers, + requestTransferCancel, + cleanupCancelledTransfer, + retryHostTransfer, + previewArchiveTransferMethod, + type HostTransferDeps, +} from "./transfer-engine.js"; +import { createFilesService } from "./service.js"; +import { tables } from "./tables.js"; + +const fileLogger = logger; + +/** Cosmetic only: whether the proxy step is worth a connection-log line. */ +function hasSocks5Config(host: PluginSshHost): boolean { + const chain = Array.isArray(host.socks5ProxyChain) + ? host.socks5ProxyChain + : []; + return !!host.useSocks5 && (!!host.socks5Host || chain.length > 0); +} + +/** + * The host id came from whichever database the client is displaying. If this + * server has a different machine under that id, refuse rather than browse, + * edit or delete files on the wrong host. + */ +function assertResolvedHost( + clientIp: unknown, + hostSyncId: string | null | undefined, + resolvedHost: { ip?: string } | null | undefined, + hostId: number, + userId: string, +): void { + if (hostSyncId) { + if (resolvedHost) return; + fileLogger.error( + "Refusing SFTP connection: host is not known to this server", + undefined, + { operation: "file_manager_host_sync_id_unknown", hostId, userId }, + ); + throw new HostNotOnThisServerError(); + } + + if (!hostAddressMismatch(clientIp, resolvedHost?.ip)) return; + + fileLogger.error( + "Refusing SFTP connection: host id resolves to a different address here", + undefined, + { + operation: "file_manager_host_id_mismatch", + hostId, + userId, + clientIp, + resolvedIp: resolvedHost?.ip, + }, + ); + throw new HostAddressMismatchError(); +} + +export async function activate(ctx: PluginContext) { + setPluginSsh(ctx.ssh); + setPluginCtx(ctx); + ctx.disposables.add(() => setPluginSsh(null)); + ctx.disposables.add(() => setPluginCtx(null)); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + // ctx.db.define hands back an untyped table object; typed at this module's + // edge, the same way plugins/workspaces/src/backend/repository.ts does. + const table: any = await ctx.db.define(tables[0]); + const pinnedTable: any = await ctx.db.define(tables[1]); + const shortcutsTable: any = await ctx.db.define(tables[2]); + const transferRecentTable: any = await ctx.db.define(tables[3]); + /* eslint-enable @typescript-eslint/no-explicit-any */ + + // The user row survives a password-reset data wipe (the DEK could not be + // recovered, not that the account is gone), so a refUser() cascade never + // fires for it. Core asks every plugin holding user data to drop its own + // rows on this topic instead. + ctx.events.on("user.data_wiped", (payload) => { + const userId = (payload as { userId?: string } | undefined)?.userId; + if (!userId) return; + void (async () => { + try { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const drizzle = await ctx.db.client(); + /* eslint-enable @typescript-eslint/no-explicit-any */ + const { eq } = await import("drizzle-orm"); + await drizzle.delete(table).where(eq(table.userId, userId)); + await drizzle.delete(pinnedTable).where(eq(pinnedTable.userId, userId)); + await drizzle + .delete(shortcutsTable) + .where(eq(shortcutsTable.userId, userId)); + await drizzle + .delete(transferRecentTable) + .where(eq(transferRecentTable.userId, userId)); + await ctx.db.persist(); + } catch (error) { + ctx.log.error( + "Failed to wipe file manager data for user", + error as Error, + ); + } + })(); + }); + + const sshSessions: Record = {}; + const pendingTOTPSessions: Record = {}; + const activeListRequests: Record = {}; + + function cleanupSession(sessionId: string) { + const session = sshSessions[sessionId]; + if (!session) return; + if (session.activeOperations > 0) { + fileLogger.warn( + `Deferring session cleanup for ${sessionId} - ${session.activeOperations} active operations`, + { + operation: "cleanup_deferred", + sessionId, + activeOperations: session.activeOperations, + }, + ); + scheduleSessionCleanup(sessionId); + return; + } + try { + if (session.sftp) { + session.sftp.end(); + session.sftp = undefined; + } + } catch { + // expected + } + try { + session.client.end(); + } catch { + // expected + } + clearTimeout(session.timeout); + delete sshSessions[sessionId]; + } + + function scheduleSessionCleanup(sessionId: string) { + const session = sshSessions[sessionId]; + if (!session) return; + if (session.timeout) clearTimeout(session.timeout); + session.timeout = setTimeout( + () => cleanupSession(sessionId), + 30 * 60 * 1000, + ); + } + + // Pending TOTP/Warpgate parking sessions expire on their own; sweep stale + // ones so a session left mid-handshake doesn't leak an open ssh2 Client. + const pendingSweep = setInterval(() => { + const now = Date.now(); + for (const sessionId of Object.keys(pendingTOTPSessions)) { + const session = pendingTOTPSessions[sessionId]; + if (now - session.createdAt > 300000) { + try { + session.client.end(); + } catch { + // expected + } + delete pendingTOTPSessions[sessionId]; + } + } + }, 60000); + ctx.disposables.add(() => clearInterval(pendingSweep)); + ctx.disposables.add(() => { + for (const sessionId of Object.keys(sshSessions)) cleanupSession(sessionId); + }); + + function verifySessionOwnership( + session: SSHSession, + userId: string, + ): boolean { + return !session.userId || session.userId === userId; + } + + function resolveBrowseHostId( + browseSessionId: string, + browseSession: SSHSession, + ): number | undefined { + if (browseSession.hostId) return browseSession.hostId; + const parsed = Number.parseInt(browseSessionId, 10); + return Number.isFinite(parsed) ? parsed : undefined; + } + + async function openDedicatedTransferSession( + browseSessionId: string, + dedicatedSessionId: string, + userId: string, + transferId: string, + options?: { allowBrowseDisconnected?: boolean }, + ): Promise { + const browseSession = sshSessions[browseSessionId]; + if (!options?.allowBrowseDisconnected && !browseSession?.isConnected) { + throw new Error("Browse SSH session not connected"); + } + if (browseSession && !verifySessionOwnership(browseSession, userId)) { + throw new Error("Session access denied"); + } + + const hostId = browseSession + ? resolveBrowseHostId(browseSessionId, browseSession) + : (() => { + const parsed = Number.parseInt(browseSessionId, 10); + return Number.isFinite(parsed) ? parsed : undefined; + })(); + if (!hostId) { + throw new Error("Cannot open transfer connection: unknown host"); + } + + const host = await resolveHostById(hostId, userId); + if (!host) { + throw new Error("Host not found for transfer connection"); + } + + const existingSession = sshSessions[dedicatedSessionId]; + if ( + existingSession?.isConnected && + verifySessionOwnership(existingSession, userId) + ) { + return existingSession; + } + + fileLogger.info("Opening dedicated transfer SSH session", { + operation: "transfer_ssh_connect", + transferId, + browseSessionId, + dedicatedSessionId, + hostId, + ip: host.ip, + port: host.port, + username: host.username, + }); + + const connection = await ctx.ssh.connect( + host as unknown as PluginSshHost, + { + purpose: "file-transfer", + timeoutMs: 60000, + overrides: { tcpKeepAliveInitialDelay: 5000 }, + }, + ); + const client = connection.client; + + const session: SSHSession = { + client, + isConnected: true, + lastActive: Date.now(), + activeOperations: 0, + channelOpener: new ChannelOpenSerializer(), + userId, + ip: host.ip, + port: host.port, + hostId: host.id, + username: host.username, + transferDedicated: true, + transferId, + browseSessionId, + }; + + client.on("close", () => { + fileLogger.info("Dedicated transfer SSH connection closed", { + operation: "transfer_ssh_disconnected", + transferId, + dedicatedSessionId, + browseSessionId, + hostId, + }); + const existing = sshSessions[dedicatedSessionId]; + if (existing) { + existing.isConnected = false; + closeDedicatedTransferSession(dedicatedSessionId); + } + }); + + sshSessions[dedicatedSessionId] = session; + return session; + } + + function closeDedicatedTransferSession(sessionId: string): void { + const session = sshSessions[sessionId]; + if (!session?.transferDedicated) return; + + fileLogger.info("Closing dedicated transfer SSH session", { + operation: "transfer_ssh_close", + sessionId, + transferId: session.transferId, + browseSessionId: session.browseSessionId, + }); + + try { + if (session.sftp) { + session.sftp.end(); + session.sftp = undefined; + } + } catch { + // expected + } + session.sftpPending = undefined; + + try { + session.client.end(); + } catch { + // expected + } + + clearTimeout(session.timeout); + delete sshSessions[sessionId]; + } + + const app = express(); + app.use(ctx.rbac.require("use") as never); + app.use(cookieParser()); + // uploadFileChunk streams its octet-stream body straight into SFTP; letting + // the JSON/urlencoded parsers run is harmless (they ignore a body they do + // not claim), but the raw parser only applies to that one route so a chunk + // isn't buffered whole in memory first. + const rawBodyParser = express.raw({ + limit: "5gb", + type: "application/octet-stream", + }); + app.use((req, res, next) => { + if (req.path === "/uploadFileChunk") return rawBodyParser(req, res, next); + next(); + }); + app.use((_req, res, next) => { + res.setHeader("Cache-Control", "no-store"); + next(); + }); + + app.use((req, res, next) => { + if ( + req.path === "/connect" || + req.path === "/connect-totp" || + req.path === "/connect-warpgate" + ) { + return next(); + } + const sessionId = (req.query.sessionId as string) || req.body?.sessionId; + if (!sessionId) return next(); + const session = sshSessions[sessionId]; + if (!session) return next(); + const userId = ctx.currentActor(); + if (!userId || !verifySessionOwnership(session, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + next(); + }); + + /** + * @openapi + * /plugin-api/file-manager/connect: + * post: + * summary: Connect to SSH for file management + * description: Establishes an SSH/SFTP connection for file manager operations. Supports password, key-based, and keyboard-interactive authentication, as well as jump hosts and SOCKS5 proxies. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - sessionId + * - ip + * - port + * - username + * properties: + * sessionId: + * type: string + * hostId: + * type: number + * ip: + * type: string + * port: + * type: number + * username: + * type: string + * password: + * type: string + * sshKey: + * type: string + * keyPassword: + * type: string + * authType: + * type: string + * enum: [password, key, none] + * credentialId: + * type: number + * jumpHosts: + * type: array + * items: + * type: object + * properties: + * hostId: + * type: number + * useSocks5: + * type: boolean + * socks5Host: + * type: string + * socks5Port: + * type: number + * socks5Username: + * type: string + * socks5Password: + * type: string + * socks5ProxyChain: + * type: array + * responses: + * 200: + * description: SSH connection established, or requires TOTP/Warpgate authentication. + * 400: + * description: Missing required parameters or invalid SSH key format. + * 401: + * description: Authentication required. + * 500: + * description: SSH connection failed. + */ + app.post("/connect", async (req, res) => { + const { + sessionId, + hostId, + syncId: hostSyncId, + ip, + port, + username, + password, + sshKey, + keyPassword, + authType, + credentialId, + jumpHosts, + useSocks5, + socks5Host, + socks5Port, + socks5Username, + socks5Password, + socks5ProxyChain, + } = req.body; + + const userId = ctx.currentActor(); + const connectionLogs: Array> = []; + + connectionLogs.push( + createConnectionLog( + "info", + "sftp_connecting", + `Initiating SFTP connection to ${username}@${ip}:${port}`, + ), + ); + + if (!userId) { + fileLogger.error("SSH connection rejected: no authenticated user", { + operation: "file_connect_auth", + sessionId, + }); + connectionLogs.push( + createConnectionLog( + "error", + "sftp_auth", + "Authentication required - no user session", + ), + ); + return res + .status(401) + .json({ error: "Authentication required", connectionLogs }); + } + + if (!sessionId || !ip || !username || !port) { + fileLogger.warn("Missing SSH connection parameters for file manager", { + operation: "file_connect", + sessionId, + hasIp: !!ip, + hasUsername: !!username, + hasPort: !!port, + }); + connectionLogs.push( + createConnectionLog( + "error", + "sftp_connecting", + "Missing required connection parameters", + ), + ); + return res + .status(400) + .json({ error: "Missing SSH connection parameters", connectionLogs }); + } + + if (sshSessions[sessionId]?.isConnected) { + cleanupSession(sessionId); + } + + if (pendingTOTPSessions[sessionId]) { + try { + pendingTOTPSessions[sessionId].client.end(); + } catch { + // expected + } + delete pendingTOTPSessions[sessionId]; + } + + const client = new SSHClient(); + + connectionLogs.push( + createConnectionLog( + "info", + "sftp_auth", + "Resolving authentication credentials", + ), + ); + + let resolvedCredentials = { + password, + sshKey, + keyPassword, + authType, + sudoPassword: undefined as string | undefined, + certPublicKey: undefined as string | undefined, + }; + let resolvedIp = ip; + let resolvedPort = port; + let resolvedUsername = username; + let resolvedTerminalConfig: Record | undefined; + let resolvedJumpHosts = jumpHosts; + let resolvedScpLegacy = false; + let resolvedUseSocks5 = useSocks5; + let resolvedSocks5Host = socks5Host; + let resolvedSocks5Port = socks5Port; + let resolvedSocks5Username = socks5Username; + let resolvedSocks5Password = socks5Password; + let resolvedSocks5ProxyChain = socks5ProxyChain; + let resolvedVaultProfileId: number | null = null; + + const resolveFrom = async () => { + const resolvedHost = hostSyncId + ? await resolveHostBySyncId(hostSyncId, userId) + : await resolveHostById(hostId, userId); + assertResolvedHost(ip, hostSyncId, resolvedHost, hostId, userId); + if (!resolvedHost) return; + resolvedIp = resolvedHost.ip; + resolvedPort = resolvedHost.port; + resolvedUsername = resolvedHost.username; + resolvedCredentials = { + password: resolvedHost.password, + sshKey: resolvedHost.key, + keyPassword: keyPassword || resolvedHost.keyPassword, + authType: resolvedHost.authType, + sudoPassword: resolvedHost.sudoPassword as string | undefined, + certPublicKey: (resolvedHost as { certPublicKey?: string }) + .certPublicKey, + }; + resolvedTerminalConfig = resolvedHost.terminalConfig as unknown as + Record | undefined; + resolvedScpLegacy = resolvedHost.scpLegacy ?? false; + resolvedVaultProfileId = + (resolvedHost.vaultProfile as { id?: number } | undefined)?.id ?? null; + if (resolvedHost.useSocks5) { + resolvedUseSocks5 = resolvedHost.useSocks5; + resolvedSocks5Host = resolvedHost.socks5Host; + resolvedSocks5Port = resolvedHost.socks5Port; + resolvedSocks5Username = resolvedHost.socks5Username; + resolvedSocks5Password = resolvedHost.socks5Password; + resolvedSocks5ProxyChain = resolvedHost.socks5ProxyChain; + } + if ( + (!resolvedJumpHosts || resolvedJumpHosts.length === 0) && + resolvedHost.jumpHosts && + resolvedHost.jumpHosts.length > 0 + ) { + resolvedJumpHosts = resolvedHost.jumpHosts; + connectionLogs.push( + createConnectionLog( + "info", + "jump", + `Loaded ${resolvedHost.jumpHosts.length} jump host(s) from server-side host data`, + ), + ); + } + connectionLogs.push( + createConnectionLog( + "info", + "sftp_auth", + "Credentials resolved from server-side host data", + ), + ); + }; + + if (hostId && userId && !password && !sshKey) { + try { + await resolveFrom(); + } catch (error) { + if ( + error instanceof HostAddressMismatchError || + error instanceof HostNotOnThisServerError + ) + throw error; + fileLogger.warn(`Failed to resolve host credentials for ${hostId}`, { + operation: "ssh_credentials", + hostId, + error: getErrorMessage(error), + }); + } + } else if (credentialId && hostId && userId) { + try { + await resolveFrom(); + } catch (error) { + if ( + error instanceof HostAddressMismatchError || + error instanceof HostNotOnThisServerError + ) + throw error; + fileLogger.warn(`Failed to resolve credentials for host ${hostId}`, { + operation: "ssh_credentials", + hostId, + credentialId, + error: getErrorMessage(error), + }); + } + } + + let serverHostId = hostId; + if (hostSyncId && userId) { + const resolvedHost = await resolveHostBySyncId(hostSyncId, userId); + assertResolvedHost(ip, hostSyncId, resolvedHost, hostId, userId); + serverHostId = resolveServerHostId(hostId, resolvedHost) ?? hostId; + } + + const effectiveAuthType = + resolvedCredentials.authType || + (resolvedCredentials.sshKey + ? "key" + : resolvedCredentials.password + ? "password" + : undefined); + if (!effectiveAuthType) { + fileLogger.warn( + "No valid authentication method provided for file manager", + { operation: "file_connect", sessionId, hostId }, + ); + connectionLogs.push( + createConnectionLog( + "error", + "sftp_auth", + "No valid authentication method provided", + ), + ); + return res.status(400).json({ + error: "Either password or SSH key must be provided", + connectionLogs, + }); + } + + const connectTarget = { + id: serverHostId, + ip: resolvedIp, + port: resolvedPort, + username: resolvedUsername, + userId, + authType: effectiveAuthType, + password: resolvedCredentials.password, + key: resolvedCredentials.sshKey, + keyPassword: resolvedCredentials.keyPassword, + certPublicKey: resolvedCredentials.certPublicKey, + terminalConfig: resolvedTerminalConfig ?? null, + vaultProfile: resolvedVaultProfileId + ? { id: resolvedVaultProfileId } + : null, + jumpHosts: resolvedJumpHosts, + useSocks5: resolvedUseSocks5, + socks5Host: resolvedSocks5Host, + socks5Port: resolvedSocks5Port, + socks5Username: resolvedSocks5Username, + socks5Password: resolvedSocks5Password, + socks5ProxyChain: resolvedSocks5ProxyChain, + } as PluginSshHost; + + const ssh = pluginSsh(); + const prepared = await ssh.prepare(connectTarget, { + purpose: "file-manager", + client, + serverHostId, + log: (level, message) => + connectionLogs.push(createConnectionLog(level, "sftp_auth", message)), + }); + const config = prepared.config as Record; + + if (prepared.outcome.status !== "ready") { + const outcome = prepared.outcome; + if ( + outcome.status === "error" && + outcome.code === "passphrase-required" + ) { + return res.json({ status: "passphrase_required", connectionLogs }); + } + fileLogger.warn("File manager SSH auth could not be prepared", { + operation: "file_connect", + sessionId, + hostId, + authType: effectiveAuthType, + error: outcome.message, + }); + connectionLogs.push( + createConnectionLog("error", "sftp_auth", outcome.message), + ); + if (outcome.status === "interaction-required") { + return res.status(401).json({ + error: outcome.message, + requiresAuthInteraction: outcome.interaction, + ...(outcome.flag ? { [outcome.flag]: true } : {}), + connectionLogs, + }); + } + return res.status(400).json({ + error: + outcome.status === "error" && outcome.code === "invalid-key" + ? "Invalid SSH key format" + : outcome.message, + connectionLogs, + }); + } + + const credentialless = !ssh.requiresSecret(effectiveAuthType); + + let responseSent = false; + + connectionLogs.push( + createConnectionLog("info", "dns", `Resolving DNS for ${ip}`), + ); + connectionLogs.push( + createConnectionLog("info", "tcp", `Connecting to ${ip}:${port}`), + ); + connectionLogs.push( + createConnectionLog("info", "handshake", "Initiating SSH handshake"), + ); + connectionLogs.push( + createConnectionLog( + "info", + "sftp_connecting", + "Establishing SSH connection...", + ), + ); + + client.on("ready", () => { + if (responseSent) return; + responseSent = true; + fileLogger.info("File manager SSH connection established", { + operation: "file_ssh_connected", + sessionId, + userId, + hostId: serverHostId, + ip, + port, + username, + }); + connectionLogs.push( + createConnectionLog( + "success", + "connected", + "SSH connection established successfully", + ), + ); + connectionLogs.push( + createConnectionLog( + "success", + "sftp_connected", + "SFTP session established successfully", + ), + ); + sshSessions[sessionId] = { + client, + isConnected: true, + lastActive: Date.now(), + activeOperations: 0, + channelOpener: new ChannelOpenSerializer(), + userId, + ip, + port, + hostId: serverHostId, + username, + sudoPassword: resolvedCredentials.sudoPassword, + scpLegacy: resolvedScpLegacy, + }; + scheduleSessionCleanup(sessionId); + + if (userId) { + const { ipAddress, userAgent } = getRequestMeta(req); + void (async () => { + await logAudit({ + userId, + username: await getAuditUsername(userId), + action: "file_manager_connect", + resourceType: "host", + resourceId: serverHostId ? String(serverHostId) : undefined, + resourceName: `${username}@${ip}:${port}`, + ipAddress, + userAgent, + success: true, + }); + })(); + } + + res.json({ + status: "success", + message: "SSH connection established", + connectionLogs, + }); + + if (hostId && userId) { + void logFileManagerActivity(hostId, userId, username, ip, port); + } + }); + + client.on("error", (err) => { + if (responseSent) return; + responseSent = true; + fileLogger.error("SSH connection failed for file manager", { + operation: "file_connect", + sessionId, + hostId, + ip, + port, + username, + error: err.message, + }); + + let errorStage: ConnectionStage; + if ( + err.message.includes("ENOTFOUND") || + err.message.includes("getaddrinfo") + ) { + errorStage = "dns"; + connectionLogs.push( + createConnectionLog( + "error", + errorStage, + `DNS resolution failed: ${err.message}`, + ), + ); + } else if ( + err.message.includes("ECONNREFUSED") || + err.message.includes("ETIMEDOUT") + ) { + errorStage = "tcp"; + connectionLogs.push( + createConnectionLog( + "error", + errorStage, + `TCP connection failed: ${err.message}`, + ), + ); + } else if ( + err.message.includes("handshake") || + err.message.includes("key exchange") + ) { + errorStage = "handshake"; + connectionLogs.push( + createConnectionLog( + "error", + errorStage, + `SSH handshake failed: ${err.message}`, + ), + ); + } else if ( + err.message.includes("authentication") || + err.message.includes("Authentication") + ) { + errorStage = "auth"; + connectionLogs.push( + createConnectionLog( + "error", + errorStage, + `Authentication failed: ${err.message}`, + ), + ); + } else if (err.message.includes("verification failed")) { + errorStage = "handshake"; + connectionLogs.push( + createConnectionLog( + "error", + errorStage, + "SSH host key has changed. For security, please open a Terminal connection to this host first to verify and accept the new key fingerprint.", + ), + ); + } else { + connectionLogs.push( + createConnectionLog( + "error", + "error", + `SSH connection failed: ${err.message}`, + ), + ); + } + + if ( + err.message.includes("Cannot parse privateKey") && + err.message.includes("no passphrase") + ) { + res.json({ status: "passphrase_required", connectionLogs }); + } else if ( + credentialless && + (err.message.includes("authentication") || + err.message.includes("All configured authentication methods failed")) + ) { + res.json({ + status: "auth_required", + reason: "no_keyboard", + connectionLogs, + }); + } else { + res + .status(500) + .json({ status: "error", message: err.message, connectionLogs }); + } + }); + + client.on("close", () => { + fileLogger.info("File manager SSH connection closed", { + operation: "file_ssh_disconnected", + sessionId, + userId, + hostId, + }); + if (sshSessions[sessionId]) sshSessions[sessionId].isConnected = false; + cleanupSession(sessionId); + }); + + const parkForAnswer = ( + finish: (responses: string[]) => void, + prompts: Array<{ prompt: string; echo: boolean }>, + promptIndex: number, + isWarpgate = false, + ) => { + pendingTOTPSessions[sessionId] = { + client, + finish, + config, + createdAt: Date.now(), + sessionId, + hostId, + ip, + port, + username, + userId, + prompts, + totpPromptIndex: promptIndex, + resolvedPassword: resolvedCredentials.password, + totpAttempts: 0, + ...(isWarpgate ? { isWarpgate: true } : {}), + }; + }; + + client.on( + "keyboard-interactive", + ( + name: string, + instructions: string, + _instructionsLang: string, + prompts: Array<{ prompt: string; echo: boolean }>, + finish: (responses: string[]) => void, + ) => { + const decision = ssh.classifyKeyboardInteractive( + { name, instructions, prompts }, + connectTarget, + ); + const autoFinish = () => + finish(ssh.autoResponses(prompts, resolvedCredentials.password)); + + if (decision.kind === "auto") { + finish(decision.responses); + return; + } + + if (decision.kind === "warpgate") { + if (responseSent) return; + responseSent = true; + connectionLogs.push( + createConnectionLog( + "info", + "sftp_auth", + "Warpgate authentication required", + { url: decision.url }, + ), + ); + parkForAnswer(finish, prompts, -1, true); + res.json({ + requires_warpgate: true, + sessionId, + url: decision.url, + securityKey: decision.securityKey, + connectionLogs, + }); + return; + } + + const promptIndex = decision.promptIndex; + const promptText = prompts[promptIndex].prompt; + const isPasswordPrompt = /password/i.test(promptText); + + if ( + decision.kind === "input" && + !(decision as { isPush?: boolean }).isPush && + !isPasswordPrompt + ) { + autoFinish(); + return; + } + + if (decision.kind === "input" && isPasswordPrompt && credentialless) { + if (responseSent) return; + responseSent = true; + client.end(); + res.json({ status: "auth_required", reason: "no_keyboard" }); + return; + } + + if (responseSent || pendingTOTPSessions[sessionId]) { + autoFinish(); + return; + } + responseSent = true; + parkForAnswer(finish, prompts, promptIndex); + + if (decision.kind === "input" && isPasswordPrompt) { + res.json({ + requires_totp: true, + sessionId, + prompt: promptText, + isPassword: true, + }); + return; + } + + connectionLogs.push( + createConnectionLog( + "info", + "sftp_auth", + "TOTP verification required", + { + prompt: promptText, + }, + ), + ); + res.json({ + requires_totp: true, + sessionId, + prompt: promptText, + connectionLogs, + }); + }, + ); + + const jumpHostCount: number = Array.isArray(resolvedJumpHosts) + ? resolvedJumpHosts.length + : 0; + + if (jumpHostCount > 0) { + if (hasSocks5Config(connectTarget)) { + connectionLogs.push( + createConnectionLog( + "info", + "proxy", + "Connecting via proxy + jump hosts", + ), + ); + } + connectionLogs.push( + createConnectionLog( + "info", + "jump", + `Connecting via ${jumpHostCount} jump host(s)`, + ), + ); + } else if (hasSocks5Config(connectTarget)) { + connectionLogs.push( + createConnectionLog("info", "proxy", "Connecting via proxy", { + proxyHost: resolvedSocks5Host, + proxyPort: resolvedSocks5Port || 1080, + }), + ); + } + + try { + const transport = await ssh.openTransport(connectTarget, config); + if (transport.via === "proxy") { + connectionLogs.push( + createConnectionLog( + "success", + "proxy", + "Proxy connected successfully", + ), + ); + } + if (transport.jumpClient) { + const jumpClient = transport.jumpClient as SSHClient; + client.on("close", () => jumpClient.end()); + } + } catch (error) { + fileLogger.error("File manager transport failed", error, { + operation: "file_transport", + sessionId, + hostId, + }); + const stage = + error instanceof JumpHostChainError || jumpHostCount > 0 + ? "jump" + : "proxy"; + connectionLogs.push( + createConnectionLog("error", stage, getErrorMessage(error)), + ); + return res.status(500).json({ + error: + error instanceof JumpHostChainError + ? `Failed to connect through jump hosts: ${error.message}` + : getErrorMessage(error), + connectionLogs, + }); + } + + client.connect(config); + }); + + async function logFileManagerActivity( + hostId: number, + userId: string, + username: string, + ip: string, + port: number, + ): Promise { + try { + const host = await createCurrentHostResolutionRepository().findHostById( + hostId, + userId, + ); + const hostName = + host?.userId === userId && host.name + ? host.name + : `${username}@${ip}:${port}`; + + const authManager = AuthManager.getInstance(); + await axios.post( + "http://localhost:30006/activity/log", + { type: "file_manager", hostId, hostName }, + { + headers: { + Authorization: `Bearer ${await authManager.generateJWTToken(userId)}`, + }, + }, + ); + } catch (error) { + fileLogger.warn("Failed to log file manager activity", { + operation: "activity_log_error", + userId, + hostId, + error: getErrorMessage(error), + }); + } + } + + /** + * @openapi + * /plugin-api/file-manager/connect-totp: + * post: + * summary: Verify TOTP and complete connection + * description: Verifies the TOTP code and completes the SSH connection for file manager. + * tags: + * - File Manager + * responses: + * 200: + * description: TOTP verified, SSH connection established. + * 400: + * description: Session ID and TOTP code required. + * 401: + * description: Invalid TOTP code or authentication required. + * 404: + * description: TOTP session expired. + * 408: + * description: TOTP session timeout. + */ + app.post("/connect-totp", async (req, res) => { + const { sessionId, totpCode } = req.body; + const userId = ctx.currentActor(); + + if (!userId) { + fileLogger.error("TOTP verification rejected: no authenticated user", { + operation: "file_totp_auth", + sessionId, + }); + return res.status(401).json({ error: "Authentication required" }); + } + if (!sessionId || !totpCode) { + return res + .status(400) + .json({ error: "Session ID and TOTP code required" }); + } + + const session = pendingTOTPSessions[sessionId]; + if (!session) { + fileLogger.warn("TOTP session not found or expired", { + operation: "file_totp_verify", + sessionId, + userId, + }); + return res + .status(404) + .json({ error: "TOTP session expired. Please reconnect." }); + } + + if (Date.now() - session.createdAt > 180000) { + delete pendingTOTPSessions[sessionId]; + try { + session.client.end(); + } catch { + // expected + } + return res + .status(408) + .json({ error: "TOTP session timeout. Please reconnect." }); + } + + const responses = (session.prompts || []).map((p, index) => { + if (index === session.totpPromptIndex) return totpCode; + if (/password/i.test(p.prompt) && session.resolvedPassword) { + return session.resolvedPassword; + } + return ""; + }); + + let responseSent = false; + + session.client.once("ready", () => { + if (responseSent) return; + responseSent = true; + clearTimeout(responseTimeout); + delete pendingTOTPSessions[sessionId]; + + setTimeout(() => { + sshSessions[sessionId] = { + client: session.client, + isConnected: true, + lastActive: Date.now(), + activeOperations: 0, + channelOpener: new ChannelOpenSerializer(), + userId, + ip: session.ip, + port: session.port, + hostId: session.hostId, + username: session.username, + }; + scheduleSessionCleanup(sessionId); + + res.json({ + status: "success", + message: "TOTP verified, SSH connection established", + }); + + if (session.hostId && session.userId) { + void logFileManagerActivity( + session.hostId, + session.userId, + session.username ?? "", + session.ip ?? "", + session.port ?? 22, + ); + } + }, 200); + }); + + session.client.once("error", (err) => { + if (responseSent) return; + responseSent = true; + clearTimeout(responseTimeout); + delete pendingTOTPSessions[sessionId]; + fileLogger.error("TOTP verification failed", { + operation: "file_totp_verify", + sessionId, + userId, + error: err.message, + }); + res.status(401).json({ status: "error", message: "Invalid TOTP code" }); + }); + + const responseTimeout = setTimeout(() => { + if (!responseSent) { + responseSent = true; + delete pendingTOTPSessions[sessionId]; + res.status(408).json({ error: "TOTP verification timeout" }); + } + }, 60000); + + session.finish(responses); + }); + + /** + * @openapi + * /plugin-api/file-manager/connect-warpgate: + * post: + * summary: Complete Warpgate authentication + * description: Submits empty response to complete Warpgate authentication after user completes browser auth. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - sessionId + * properties: + * sessionId: + * type: string + * responses: + * 200: + * description: Warpgate authentication completed successfully. + * 401: + * description: Authentication failed or unauthorized. + * 404: + * description: Warpgate session expired. + * 408: + * description: Warpgate session timeout. + */ + app.post("/connect-warpgate", async (req, res) => { + const { sessionId } = req.body; + const userId = ctx.currentActor(); + + if (!userId) { + return res.status(401).json({ error: "Authentication required" }); + } + if (!sessionId) { + return res.status(400).json({ error: "Session ID required" }); + } + + const session = pendingTOTPSessions[sessionId]; + if (!session) { + return res + .status(404) + .json({ error: "Warpgate session expired. Please reconnect." }); + } + if (!session.isWarpgate) { + return res + .status(400) + .json({ error: "Session is not a Warpgate session" }); + } + if (Date.now() - session.createdAt > 300000) { + delete pendingTOTPSessions[sessionId]; + try { + session.client.end(); + } catch { + // expected + } + return res + .status(408) + .json({ error: "Warpgate session timeout. Please reconnect." }); + } + + let responseSent = false; + const responseTimeout = setTimeout(() => { + if (!responseSent) { + responseSent = true; + delete pendingTOTPSessions[sessionId]; + res.status(408).json({ error: "Warpgate verification timeout" }); + } + }, 60000); + + session.client.once("ready", () => { + if (responseSent) return; + responseSent = true; + clearTimeout(responseTimeout); + delete pendingTOTPSessions[sessionId]; + + setTimeout(() => { + sshSessions[sessionId] = { + client: session.client, + isConnected: true, + lastActive: Date.now(), + activeOperations: 0, + channelOpener: new ChannelOpenSerializer(), + userId, + ip: session.ip, + port: session.port, + hostId: session.hostId, + username: session.username, + }; + scheduleSessionCleanup(sessionId); + + res.json({ + status: "success", + message: "Warpgate verified, SSH connection established", + }); + + if (session.hostId && session.userId) { + void logFileManagerActivity( + session.hostId, + session.userId, + session.username ?? "", + session.ip ?? "", + session.port ?? 22, + ); + } + }, 200); + }); + + session.client.once("error", () => { + if (responseSent) return; + responseSent = true; + clearTimeout(responseTimeout); + delete pendingTOTPSessions[sessionId]; + res + .status(401) + .json({ status: "error", message: "Warpgate authentication failed" }); + }); + + session.finish([""]); + }); + + /** + * @openapi + * /plugin-api/file-manager/disconnect: + * post: + * summary: Disconnect from SSH + * tags: + * - File Manager + * responses: + * 200: + * description: SSH connection disconnected. + */ + app.post("/disconnect", (req, res) => { + const { sessionId } = req.body; + const userId = ctx.currentActor(); + const session = sshSessions[sessionId]; + if (session && (!userId || !verifySessionOwnership(session, userId))) { + return res.status(403).json({ error: "Session access denied" }); + } + cleanupSession(sessionId); + res.json({ status: "success", message: "SSH connection disconnected" }); + }); + + /** + * @openapi + * /plugin-api/file-manager/sudo-password: + * post: + * summary: Set sudo password for session + * tags: + * - File Manager + * responses: + * 200: + * description: Sudo password set successfully. + * 400: + * description: Invalid session. + */ + app.post("/sudo-password", (req, res) => { + const { sessionId, password } = req.body; + const userId = ctx.currentActor(); + const session = sshSessions[sessionId]; + if (!session || !session.isConnected) { + return res.status(400).json({ error: "Invalid or disconnected session" }); + } + if (!userId || !verifySessionOwnership(session, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + session.sudoPassword = password; + session.lastActive = Date.now(); + res.json({ status: "success", message: "Sudo password set" }); + }); + + /** + * @openapi + * /plugin-api/file-manager/status: + * get: + * summary: Get SSH connection status + * tags: + * - File Manager + * parameters: + * - in: query + * name: sessionId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: SSH connection status. + */ + app.get("/status", (req, res) => { + const sessionId = req.query.sessionId as string; + const userId = ctx.currentActor(); + const session = sshSessions[sessionId]; + if (session && (!userId || !verifySessionOwnership(session, userId))) { + return res.status(403).json({ error: "Session access denied" }); + } + res.json({ status: "success", connected: !!session?.isConnected }); + }); + + /** + * @openapi + * /plugin-api/file-manager/keepalive: + * post: + * summary: Keep SSH session alive + * tags: + * - File Manager + * responses: + * 200: + * description: Session keepalive successful. + * 400: + * description: Session ID is required or session not found. + */ + app.post("/keepalive", async (req, res) => { + const { sessionId } = req.body; + const userId = ctx.currentActor(); + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + const session = sshSessions[sessionId]; + if (!session || !session.isConnected) { + return res + .status(400) + .json({ + error: "SSH session not found or not connected", + connected: false, + }); + } + if (!userId || !verifySessionOwnership(session, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + session.lastActive = Date.now(); + scheduleSessionCleanup(sessionId); + + if (session.sftp && !session.sftpPending) { + try { + await new Promise((resolve, reject) => { + session.sftp!.stat("/", (err) => (err ? reject(err) : resolve())); + }); + } catch { + session.sftp = undefined; + } + } + + res.json({ + status: "success", + connected: true, + message: "Session keepalive successful", + lastActive: session.lastActive, + }); + }); + + registerFileListingRoutes(app, { + ctx, + sshSessions, + activeListRequests, + verifySessionOwnership, + }); + registerFileContentRoutes(app, { ctx, sshSessions, verifySessionOwnership }); + registerFileOperationRoutes(app, { + ctx, + sshSessions, + verifySessionOwnership, + }); + registerFileDownloadRoutes(app, { + ctx, + sshSessions, + scheduleSessionCleanup, + verifySessionOwnership, + }); + registerFileActionRoutes(app, { + ctx, + sshSessions, + scheduleSessionCleanup, + verifySessionOwnership, + }); + registerBookmarkRoutes(app, ctx, { + table, + pinnedTable, + shortcutsTable, + transferRecentTable, + }); + + /** + * @openapi + * /plugin-api/file-manager/extractArchive: + * post: + * summary: Extract archive file + * description: Extracts an archive file (.tar, .tar.gz, .tgz, .zip, .tar.bz2, .tbz2, .tar.xz, .txz) to a specified or default location on the remote host. + * tags: + * - File Manager + * responses: + * 200: + * description: Archive extracted successfully. + * 400: + * description: Missing required parameters, SSH connection not established, or unsupported archive format. + * 500: + * description: Failed to extract archive. + */ + app.post("/extractArchive", async (req, res) => { + const { sessionId, archivePath, extractPath } = req.body; + const userId = ctx.currentActor(); + + if (!sessionId || !archivePath) { + return res.status(400).json({ error: "Missing required parameters" }); + } + const session = sshSessions[sessionId]; + if (!session || !session.isConnected) { + return res.status(400).json({ error: "SSH session not connected" }); + } + if (!userId || !verifySessionOwnership(session, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + session.lastActive = Date.now(); + scheduleSessionCleanup(sessionId); + + const fileName = archivePath.split("/").pop() || ""; + const fileExt = fileName.toLowerCase(); + + let extractCommand: string; + const targetPath = + extractPath || archivePath.substring(0, archivePath.lastIndexOf("/")); + + const escapedArchive = archivePath.replace(/'/g, "'\"'\"'"); + const escapedTarget = targetPath.replace(/'/g, "'\"'\"'"); + const escapedDecompressed = archivePath + .replace(/\.gz$/, "") + .replace(/'/g, "'\"'\"'"); + + if (fileExt.endsWith(".tar.gz") || fileExt.endsWith(".tgz")) { + extractCommand = `tar -xzf '${escapedArchive}' -C '${escapedTarget}'`; + } else if (fileExt.endsWith(".tar.bz2") || fileExt.endsWith(".tbz2")) { + extractCommand = `tar -xjf '${escapedArchive}' -C '${escapedTarget}'`; + } else if (fileExt.endsWith(".tar.xz")) { + extractCommand = `tar -xJf '${escapedArchive}' -C '${escapedTarget}'`; + } else if (fileExt.endsWith(".tar")) { + extractCommand = `tar -xf '${escapedArchive}' -C '${escapedTarget}'`; + } else if (fileExt.endsWith(".zip")) { + extractCommand = `unzip -o '${escapedArchive}' -d '${escapedTarget}'`; + } else if (fileExt.endsWith(".gz") && !fileExt.endsWith(".tar.gz")) { + extractCommand = `gunzip -c '${escapedArchive}' > '${escapedDecompressed}'`; + } else if (fileExt.endsWith(".bz2") && !fileExt.endsWith(".tar.bz2")) { + extractCommand = `bunzip2 -k '${escapedArchive}'`; + } else if (fileExt.endsWith(".xz") && !fileExt.endsWith(".tar.xz")) { + extractCommand = `unxz -k '${escapedArchive}'`; + } else if (fileExt.endsWith(".7z")) { + extractCommand = `7z x '${escapedArchive}' -o'${escapedTarget}'`; + } else if (fileExt.endsWith(".rar")) { + extractCommand = `unrar x '${escapedArchive}' '${escapedTarget}/'`; + } else { + return res.status(400).json({ error: "Unsupported archive format" }); + } + + fileLogger.info("Extracting archive", { + operation: "extract_archive", + sessionId, + archivePath, + extractPath: targetPath, + command: extractCommand, + }); + + execChannel(session, extractCommand, (err, stream) => { + if (err) { + fileLogger.error("SSH exec error during extract:", err, { + operation: "extract_archive", + sessionId, + archivePath, + }); + return res + .status(500) + .json({ error: "Failed to execute extract command" }); + } + + let errorOutput = ""; + stream.on("data", () => {}); + stream.stderr.on("data", (data: Buffer) => { + errorOutput += data.toString(); + }); + stream.on("close", (code: number) => { + if (code !== 0) { + fileLogger.error("Extract command failed", { + operation: "extract_archive", + sessionId, + archivePath, + exitCode: code, + error: errorOutput, + }); + + let friendlyError = errorOutput || "Failed to extract archive"; + if ( + errorOutput.includes("command not found") || + errorOutput.includes("not found") + ) { + let missingCmd = ""; + let installHint = ""; + if (fileExt.endsWith(".zip")) { + missingCmd = "unzip"; + installHint = + "apt install unzip / yum install unzip / brew install unzip"; + } else if ( + fileExt.endsWith(".tar.gz") || + fileExt.endsWith(".tgz") || + fileExt.endsWith(".tar.bz2") || + fileExt.endsWith(".tbz2") || + fileExt.endsWith(".tar.xz") || + fileExt.endsWith(".tar") + ) { + missingCmd = "tar"; + installHint = "Usually pre-installed on Linux/Unix systems"; + } else if (fileExt.endsWith(".gz")) { + missingCmd = "gunzip"; + installHint = + "apt install gzip / yum install gzip / Usually pre-installed"; + } else if (fileExt.endsWith(".bz2")) { + missingCmd = "bunzip2"; + installHint = + "apt install bzip2 / yum install bzip2 / brew install bzip2"; + } else if (fileExt.endsWith(".xz")) { + missingCmd = "unxz"; + installHint = + "apt install xz-utils / yum install xz / brew install xz"; + } else if (fileExt.endsWith(".7z")) { + missingCmd = "7z"; + installHint = + "apt install p7zip-full / yum install p7zip / brew install p7zip"; + } else if (fileExt.endsWith(".rar")) { + missingCmd = "unrar"; + installHint = + "apt install unrar / yum install unrar / brew install unrar"; + } + if (missingCmd) { + friendlyError = `Command '${missingCmd}' not found on remote server. Please install it first: ${installHint}`; + } + } + return res.status(500).json({ error: friendlyError }); + } + + fileLogger.success("Archive extracted successfully", { + operation: "extract_archive", + sessionId, + archivePath, + extractPath: targetPath, + }); + res.json({ + success: true, + message: "Archive extracted successfully", + extractPath: targetPath, + }); + }); + + stream.on("error", (streamErr) => { + fileLogger.error("SSH extractArchive stream error:", streamErr, { + operation: "extract_archive", + sessionId, + archivePath, + }); + if (!res.headersSent) { + res + .status(500) + .json({ error: "Stream error while extracting archive" }); + } + }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/compressFiles: + * post: + * summary: Compress files + * description: Compresses files and/or directories on the remote host. + * tags: + * - File Manager + * responses: + * 200: + * description: Files compressed successfully. + * 400: + * description: Missing required parameters or unsupported compression format. + * 500: + * description: Failed to compress files. + */ + app.post("/compressFiles", async (req, res) => { + const { sessionId, paths, archiveName, format } = req.body; + const userId = ctx.currentActor(); + + if ( + !sessionId || + !paths || + !Array.isArray(paths) || + paths.length === 0 || + !archiveName + ) { + return res.status(400).json({ error: "Missing required parameters" }); + } + const session = sshSessions[sessionId]; + if (!session || !session.isConnected) { + return res.status(400).json({ error: "SSH session not connected" }); + } + if (!userId || !verifySessionOwnership(session, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + session.lastActive = Date.now(); + scheduleSessionCleanup(sessionId); + + const compressionFormat = format || "zip"; + let compressCommand: string; + + const firstPath = paths[0]; + const workingDir = + firstPath.substring(0, firstPath.lastIndexOf("/")) || "/"; + const escapeShell = (s: string) => s.replace(/'/g, "'\"'\"'"); + const fileNames = paths + .map((p) => { + const name = p.split("/").pop(); + return `'./${escapeShell(name || "")}'`; + }) + .join(" "); + + let archivePath = ""; + if (archiveName.includes("/")) { + archivePath = archiveName; + } else { + archivePath = workingDir.endsWith("/") + ? `${workingDir}${archiveName}` + : `${workingDir}/${archiveName}`; + } + + const escapedDir = escapeShell(workingDir); + const escapedArchive = escapeShell(archivePath); + + if (compressionFormat === "zip") { + compressCommand = `cd '${escapedDir}' && zip -r '${escapedArchive}' -- ${fileNames}`; + } else if (compressionFormat === "tar.gz" || compressionFormat === "tgz") { + compressCommand = `cd '${escapedDir}' && tar -czf '${escapedArchive}' -- ${fileNames}`; + } else if ( + compressionFormat === "tar.bz2" || + compressionFormat === "tbz2" + ) { + compressCommand = `cd '${escapedDir}' && tar -cjf '${escapedArchive}' -- ${fileNames}`; + } else if (compressionFormat === "tar.xz") { + compressCommand = `cd '${escapedDir}' && tar -cJf '${escapedArchive}' -- ${fileNames}`; + } else if (compressionFormat === "tar") { + compressCommand = `cd '${escapedDir}' && tar -cf '${escapedArchive}' -- ${fileNames}`; + } else if (compressionFormat === "7z") { + compressCommand = `cd '${escapedDir}' && 7z a '${escapedArchive}' -- ${fileNames}`; + } else { + return res.status(400).json({ error: "Unsupported compression format" }); + } + + fileLogger.info("Compressing files", { + operation: "compress_files", + sessionId, + paths, + archivePath, + format: compressionFormat, + command: compressCommand, + }); + + execChannel(session, compressCommand, (err, stream) => { + if (err) { + fileLogger.error("SSH exec error during compress:", err, { + operation: "compress_files", + sessionId, + paths, + }); + return res + .status(500) + .json({ error: "Failed to execute compress command" }); + } + + let errorOutput = ""; + stream.on("data", () => {}); + stream.stderr.on("data", (data: Buffer) => { + errorOutput += data.toString(); + }); + stream.on("close", (code: number) => { + if (code !== 0) { + fileLogger.error("Compress command failed", { + operation: "compress_files", + sessionId, + paths, + archivePath, + exitCode: code, + error: errorOutput, + }); + + let friendlyError = errorOutput || "Failed to compress files"; + if ( + errorOutput.includes("command not found") || + errorOutput.includes("not found") + ) { + const commandMap: Record = + { + zip: { + cmd: "zip", + install: + "apt install zip / yum install zip / brew install zip", + }, + "tar.gz": { + cmd: "tar", + install: "Usually pre-installed on Linux/Unix systems", + }, + "tar.bz2": { + cmd: "tar", + install: "Usually pre-installed on Linux/Unix systems", + }, + "tar.xz": { + cmd: "tar", + install: "Usually pre-installed on Linux/Unix systems", + }, + tar: { + cmd: "tar", + install: "Usually pre-installed on Linux/Unix systems", + }, + "7z": { + cmd: "7z", + install: + "apt install p7zip-full / yum install p7zip / brew install p7zip", + }, + }; + const info = commandMap[compressionFormat]; + if (info) { + friendlyError = `Command '${info.cmd}' not found on remote server. Please install it first: ${info.install}`; + } + } + return res.status(500).json({ error: friendlyError }); + } + + fileLogger.success("Files compressed successfully", { + operation: "compress_files", + sessionId, + paths, + archivePath, + format: compressionFormat, + }); + res.json({ + success: true, + message: "Files compressed successfully", + archivePath, + }); + }); + + stream.on("error", (streamErr) => { + fileLogger.error("SSH compressFiles stream error:", streamErr, { + operation: "compress_files", + sessionId, + paths, + }); + if (!res.headersSent) { + res + .status(500) + .json({ error: "Stream error while compressing files" }); + } + }); + }); + }); + + const hostTransferDeps: HostTransferDeps = { + sshSessions, + getSessionSftp, + execChannel, + verifySessionOwnership, + openDedicatedTransferSession, + closeDedicatedTransferSession, + }; + + app.post("/transferMethodPreview", async (req, res) => { + const { + sourceSessionId, + destSessionId, + sourcePaths, + destPath, + methodPreference, + } = req.body; + if ( + !sourceSessionId || + !destSessionId || + !destPath || + !sourcePaths || + !Array.isArray(sourcePaths) || + sourcePaths.length === 0 + ) { + return res.status(400).json({ error: "Missing required parameters" }); + } + try { + const userId = ctx.currentActor(); + const preview = await previewArchiveTransferMethod(hostTransferDeps, { + sourceSessionId, + destSessionId, + sourcePaths, + destPath, + methodPreference: + methodPreference === "tar" || methodPreference === "item_sftp" + ? methodPreference + : "auto", + userId: userId ?? "", + }); + res.json(preview); + } catch (err) { + fileLogger.error("Failed to preview transfer method", err, { + operation: "host_transfer", + sourceSessionId, + destSessionId, + sourcePaths, + }); + res + .status(500) + .json({ error: getErrorMessage(err, "Failed to preview method") }); + } + }); + + app.post("/transferToHost", async (req, res) => { + const { + sourceSessionId, + sourcePaths, + destSessionId, + destPath, + move, + methodPreference, + parallelSegmentCount: parallelSegmentCountRaw, + } = req.body; + const userId = ctx.currentActor(); + + if ( + !sourceSessionId || + !destSessionId || + !destPath || + !sourcePaths || + !Array.isArray(sourcePaths) || + sourcePaths.length === 0 + ) { + return res.status(400).json({ error: "Missing required parameters" }); + } + + const sourceSession = sshSessions[sourceSessionId]; + const destSession = sshSessions[destSessionId]; + if (!sourceSession?.isConnected || !destSession?.isConnected) { + return res.status(400).json({ error: "SSH session not connected" }); + } + if ( + !userId || + !verifySessionOwnership(sourceSession, userId) || + !verifySessionOwnership(destSession, userId) + ) { + return res.status(403).json({ error: "Session access denied" }); + } + + sourceSession.lastActive = Date.now(); + destSession.lastActive = Date.now(); + scheduleSessionCleanup(sourceSessionId); + scheduleSessionCleanup(destSessionId); + + try { + const rawParallel = Number(parallelSegmentCountRaw); + const parallelSegmentCount = Number.isFinite(rawParallel) + ? Math.max(1, Math.min(8, Math.floor(rawParallel))) + : undefined; + + const { transferId } = startHostTransfer(hostTransferDeps, { + sourceSessionId, + sourcePaths, + destSessionId, + destPath, + move: !!move, + userId, + methodPreference: + methodPreference === "tar" || methodPreference === "item_sftp" + ? methodPreference + : "auto", + parallelSegmentCount, + }); + + res.json({ transferId }); + } catch (err) { + fileLogger.error("Failed to start host transfer", err, { + operation: "host_transfer", + sourceSessionId, + destSessionId, + sourcePaths, + }); + res.status(500).json({ error: "Failed to start transfer" }); + } + }); + + app.get("/activeTransfers", async (req, res) => { + const userId = ctx.currentActor(); + if (!userId) + return res.status(401).json({ error: "Authentication required" }); + await probeHungStreamTransfers(hostTransferDeps); + res.json({ transfers: listActiveTransfers(userId) }); + }); + + app.get("/transferStatus/:transferId", async (req, res) => { + const userId = ctx.currentActor(); + const transferId = req.params.transferId; + if (!userId) + return res.status(401).json({ error: "Authentication required" }); + await probeHungStreamTransfers(hostTransferDeps); + const status = getTransferStatus(transferId, userId); + if (!status) return res.status(404).json({ error: "Transfer not found" }); + res.json(status); + }); + + app.post("/transferCancel/:transferId", (req, res) => { + const userId = ctx.currentActor(); + const transferId = req.params.transferId; + const cancelled = userId + ? requestTransferCancel(transferId, userId) + : false; + if (!cancelled) + return res + .status(404) + .json({ error: "Transfer not found or not running" }); + res.json({ ok: true }); + }); + + app.post("/transferCleanup/:transferId", async (req, res) => { + const userId = ctx.currentActor(); + const transferId = req.params.transferId; + if (!userId) + return res.status(401).json({ error: "Authentication required" }); + try { + const result = await cleanupCancelledTransfer( + hostTransferDeps, + transferId, + userId, + ); + res.json(result); + } catch (err) { + const message = getErrorMessage(err, "Failed to clean up transfer"); + const status = message === "Transfer not found" ? 404 : 400; + res.status(status).json({ error: message }); + } + }); + + app.post("/transferRetry/:transferId", (req, res) => { + const userId = ctx.currentActor(); + const transferId = req.params.transferId; + if (!userId) + return res.status(401).json({ error: "Authentication required" }); + const retried = retryHostTransfer(hostTransferDeps, transferId, userId); + if (!retried) + return res + .status(404) + .json({ error: "Transfer not found or not retryable" }); + res.json({ ok: true, transferId }); + }); + + ctx.http.router({ bodyLimit: "1gb" }).use(app); + + const service = createFilesService(ctx, { + sshSessions, + verifySessionOwnership, + }); + ctx.services.provide("files.sftp", service); + + ctx.log.info("File manager mounted at /plugin-api/file-manager"); +} + +export async function deactivate() { + // Sessions are closed by the disposable registered in activate; the http + // router, db tables and service are all disposed by core. +} diff --git a/plugins/file-manager/src/backend/list-routes.ts b/plugins/file-manager/src/backend/list-routes.ts new file mode 100644 index 000000000..b40bb798f --- /dev/null +++ b/plugins/file-manager/src/backend/list-routes.ts @@ -0,0 +1,465 @@ +import { getErrorMessage } from "./error-message.js"; +import type { Express } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { execChannel, getSessionSftp, type SSHSession } from "./session.js"; +import { + formatMtime, + isExecutableFile, + modeToPermissions, + parseLsDateToTimestamp, +} from "./utils.js"; + +type FileListingRoutesDeps = { + ctx: PluginContext; + sshSessions: Record; + activeListRequests: Record; + verifySessionOwnership: (session: SSHSession, userId: string) => boolean; +}; + +export function registerFileListingRoutes( + app: Express, + { + ctx, + sshSessions, + activeListRequests, + verifySessionOwnership, + }: FileListingRoutesDeps, +): void { + /** + * @openapi + * /plugin-api/file-manager/listFiles: + * get: + * summary: List files in a directory + * description: Lists the files and directories in a given path on the remote host. + * tags: + * - File Manager + * parameters: + * - in: query + * name: sessionId + * required: true + * schema: + * type: string + * - in: query + * name: path + * required: true + * schema: + * type: string + * responses: + * 200: + * description: A list of files and directories. + * 400: + * description: Session ID is required or SSH connection not established. + * 500: + * description: Failed to list files. + */ + app.get("/listFiles", (req, res) => { + const sessionId = req.query.sessionId as string; + const sshConn = sshSessions[sessionId]; + const sshPath = (req.query.path as string) || "/"; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + // Drop concurrent requests for the same session+path — each would open + // a new SSH channel and can exceed the server's per-connection channel limit. + const listKey = `${sessionId}:${sshPath}`; + if (activeListRequests[listKey]) { + return res + .status(409) + .json({ error: "List request already in progress" }); + } + activeListRequests[listKey] = true; + res.on("finish", () => { + delete activeListRequests[listKey]; + }); + + sshConn.lastActive = Date.now(); + sshConn.activeOperations++; + const trySFTP = () => { + try { + ctx.log.info( + `Opening SFTP channel for listFiles: ${sessionId} (${sshPath})`, + ); + getSessionSftp(sshConn) + .then((sftp) => { + sftp.readdir(sshPath, (readdirErr, list) => { + if (readdirErr) { + ctx.log.warn( + `SFTP readdir failed, trying fallback: ${readdirErr.message}`, + ); + tryFallbackMethod(); + return; + } + + const symlinks: Array<{ index: number; path: string }> = []; + const files: Array<{ + name: string; + type: string; + size: number | undefined; + modified: string; + modifiedTimestamp: number; + permissions: string; + owner: string; + group: string; + linkTarget: string | undefined; + path: string; + executable: boolean; + }> = []; + + for (const entry of list) { + if (entry.filename === "." || entry.filename === "..") continue; + + const attrs = entry.attrs; + const permissions = modeToPermissions(attrs.mode); + const isDirectory = attrs.isDirectory(); + const isLink = attrs.isSymbolicLink(); + + const fileEntry = { + name: entry.filename, + type: isDirectory ? "directory" : isLink ? "link" : "file", + size: isDirectory ? undefined : attrs.size, + modified: formatMtime(attrs.mtime), + modifiedTimestamp: attrs.mtime, + permissions, + owner: String(attrs.uid), + group: String(attrs.gid), + linkTarget: undefined as string | undefined, + path: `${sshPath.endsWith("/") ? sshPath : sshPath + "/"}${entry.filename}`, + executable: + !isDirectory && !isLink + ? isExecutableFile(permissions, entry.filename) + : false, + }; + + if (isLink) { + symlinks.push({ index: files.length, path: fileEntry.path }); + } + + files.push(fileEntry); + } + + if (symlinks.length === 0) { + sshConn.activeOperations--; + return res.json({ files, path: sshPath }); + } + + let resolved = 0; + let responded = false; + + const sendResponse = () => { + if (responded) return; + responded = true; + sshConn.activeOperations--; + res.json({ files, path: sshPath }); + }; + + const readlinkTimeout = setTimeout(sendResponse, 5000); + + for (const link of symlinks) { + sftp.readlink(link.path, (linkErr, target) => { + resolved++; + if (!linkErr && target) { + files[link.index].linkTarget = target; + } + if (resolved === symlinks.length) { + clearTimeout(readlinkTimeout); + sendResponse(); + } + }); + } + }); + }) + .catch((err: Error) => { + ctx.log.warn( + `SFTP failed for listFiles, trying fallback: ${err.message}`, + ); + const isChannelFailure = + err.message.toLowerCase().includes("channel open failure") || + err.message.toLowerCase().includes("open failed"); + if (isChannelFailure) { + sshConn.isConnected = false; + sshConn.sftp = undefined; + } + tryFallbackMethod(); + }); + } catch (sftpErr: unknown) { + const errMsg = getErrorMessage(sftpErr); + ctx.log.warn(`SFTP connection error, trying fallback: ${errMsg}`); + tryFallbackMethod(); + } + }; + + const tryFallbackMethod = () => { + if (!sshConn?.isConnected) { + sshConn.activeOperations--; + return res + .status(503) + .json({ error: "SSH session disconnected", disconnected: true }); + } + try { + const escapedPath = sshPath.replace(/'/g, "'\"'\"'"); + execChannel( + sshConn, + `command ls -la --color=never '${escapedPath}'`, + (err, stream) => { + if (err) { + sshConn.activeOperations--; + ctx.log.error("SSH listFiles error:", err); + return res.status(500).json({ error: err.message }); + } + + let data = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.on("close", (code) => { + if (code !== 0) { + const isPermissionDenied = + errorData.toLowerCase().includes("permission denied") || + errorData.toLowerCase().includes("access denied"); + + if (isPermissionDenied) { + if (sshConn.sudoPassword) { + ctx.log.info( + `Permission denied for listFiles, retrying with sudo: ${sshPath}`, + ); + tryWithSudo(); + return; + } + + sshConn.activeOperations--; + ctx.log.warn( + `Permission denied for listFiles, sudo required: ${sshPath}`, + ); + return res.status(403).json({ + error: `Permission denied: Cannot access ${sshPath}`, + needsSudo: true, + path: sshPath, + }); + } + + sshConn.activeOperations--; + ctx.log.error( + `SSH listFiles command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + return res + .status(500) + .json({ error: `Command failed: ${errorData}` }); + } + sshConn.activeOperations--; + + const lines = data.split("\n").filter((line) => line.trim()); + const files = []; + + for (let i = 1; i < lines.length; i++) { + const line = lines[i]; + const parts = line.split(/\s+/); + if (parts.length >= 9) { + const permissions = parts[0]; + const owner = parts[2]; + const group = parts[3]; + const size = parseInt(parts[4], 10); + + let dateStr = ""; + const nameStartIndex = 8; + + if (parts[5] && parts[6] && parts[7]) { + dateStr = `${parts[5]} ${parts[6]} ${parts[7]}`; + } + + const name = parts.slice(nameStartIndex).join(" "); + const isDirectory = permissions.startsWith("d"); + const isLink = permissions.startsWith("l"); + + if (name === "." || name === "..") continue; + + let actualName = name; + let linkTarget = undefined; + if (isLink && name.includes(" -> ")) { + const linkParts = name.split(" -> "); + actualName = linkParts[0]; + linkTarget = linkParts[1]; + } + + files.push({ + name: actualName, + type: isDirectory ? "directory" : isLink ? "link" : "file", + size: isDirectory ? undefined : size, + modified: dateStr, + modifiedTimestamp: parseLsDateToTimestamp(dateStr), + permissions, + owner, + group, + linkTarget, + path: `${sshPath.endsWith("/") ? sshPath : sshPath + "/"}${actualName}`, + executable: + !isDirectory && !isLink + ? isExecutableFile(permissions, actualName) + : false, + }); + } + } + + res.json({ files, path: sshPath }); + }); + }, + ); + } catch (execErr: unknown) { + sshConn.activeOperations--; + const errMsg = getErrorMessage(execErr); + ctx.log.error(`Fallback listFiles exec failed: ${errMsg}`); + if (!res.headersSent) { + return res.status(500).json({ error: errMsg }); + } + } + }; + + const tryWithSudo = () => { + try { + const escapedPath = sshPath.replace(/'/g, "'\"'\"'"); + const escapedPassword = sshConn.sudoPassword!.replace(/'/g, "'\"'\"'"); + const sudoCommand = `echo '${escapedPassword}' | sudo -S /bin/ls -la --color=never '${escapedPath}' 2>&1`; + + execChannel(sshConn, sudoCommand, (err, stream) => { + if (err) { + sshConn.activeOperations--; + ctx.log.error("SSH sudo listFiles error:", err); + return res.status(500).json({ error: err.message }); + } + + let data = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + }); + + stream.on("close", (code) => { + sshConn.activeOperations--; + + data = data.replace(/\[sudo\] password for .+?:\s*/g, ""); + + if ( + data.toLowerCase().includes("sorry, try again") || + data.toLowerCase().includes("incorrect password") || + errorData.toLowerCase().includes("sorry, try again") + ) { + sshConn.sudoPassword = undefined; + return res.status(403).json({ + error: "Sudo authentication failed. Please try again.", + needsSudo: true, + sudoFailed: true, + path: sshPath, + }); + } + + if (code !== 0 && !data.trim()) { + ctx.log.error( + `SSH sudo listFiles failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + return res + .status(500) + .json({ error: `Sudo command failed: ${errorData || data}` }); + } + + const lines = data.split("\n").filter((line) => line.trim()); + const files: Array<{ + name: string; + type: string; + size: number | undefined; + modified: string; + modifiedTimestamp: number; + permissions: string; + owner: string; + group: string; + linkTarget: string | undefined; + path: string; + executable: boolean; + }> = []; + + for (let i = 1; i < lines.length; i++) { + const line = lines[i]; + const parts = line.split(/\s+/); + if (parts.length >= 9) { + const permissions = parts[0]; + const owner = parts[2]; + const group = parts[3]; + const size = parseInt(parts[4], 10); + + let dateStr = ""; + const nameStartIndex = 8; + + if (parts[5] && parts[6] && parts[7]) { + dateStr = `${parts[5]} ${parts[6]} ${parts[7]}`; + } + + const name = parts.slice(nameStartIndex).join(" "); + const isDirectory = permissions.startsWith("d"); + const isLink = permissions.startsWith("l"); + + if (name === "." || name === "..") continue; + + let actualName = name; + let linkTarget = undefined; + if (isLink && name.includes(" -> ")) { + const linkParts = name.split(" -> "); + actualName = linkParts[0]; + linkTarget = linkParts[1]; + } + + files.push({ + name: actualName, + type: isDirectory ? "directory" : isLink ? "link" : "file", + size: isDirectory ? undefined : size, + modified: dateStr, + modifiedTimestamp: parseLsDateToTimestamp(dateStr), + permissions, + owner, + group, + linkTarget, + path: `${sshPath.endsWith("/") ? sshPath : sshPath + "/"}${actualName}`, + executable: + !isDirectory && !isLink + ? isExecutableFile(permissions, actualName) + : false, + }); + } + } + + res.json({ files, path: sshPath }); + }); + }); + } catch (execErr: unknown) { + sshConn.activeOperations--; + const errMsg = getErrorMessage(execErr); + ctx.log.error(`Sudo listFiles exec failed: ${errMsg}`); + if (!res.headersSent) { + return res.status(500).json({ error: errMsg }); + } + } + }; + + trySFTP(); + }); +} diff --git a/plugins/file-manager/src/backend/operation-commands.ts b/plugins/file-manager/src/backend/operation-commands.ts new file mode 100644 index 000000000..0eea91c95 --- /dev/null +++ b/plugins/file-manager/src/backend/operation-commands.ts @@ -0,0 +1,39 @@ +import { isWindowsSftpPath, sftpPathToLocalPath } from "./transfer-paths.js"; + +export interface DeleteCommand { + command: string; + commandWithSuccess: string; +} + +function quotePosixPath(path: string): string { + return `'${path.replace(/'/g, "'\"'\"'")}'`; +} + +function quotePowerShellLiteral(value: string): string { + return `'${value.replace(/'/g, "''")}'`; +} + +export function buildDeleteCommand( + itemPath: string, + isDirectory: boolean, +): DeleteCommand { + if (isWindowsSftpPath(itemPath)) { + const path = quotePowerShellLiteral(sftpPathToLocalPath(itemPath)); + const command = isDirectory + ? `Remove-Item -LiteralPath ${path} -Recurse -Force -ErrorAction Stop` + : `Remove-Item -LiteralPath ${path} -Force -ErrorAction Stop`; + + return { + command, + commandWithSuccess: `${command}; if ($?) { Write-Output "SUCCESS" }`, + }; + } + + const path = quotePosixPath(itemPath); + const command = isDirectory ? `rm -rf ${path}` : `rm -f ${path}`; + + return { + command, + commandWithSuccess: `${command} && echo "SUCCESS"`, + }; +} diff --git a/plugins/file-manager/src/backend/operation-routes.ts b/plugins/file-manager/src/backend/operation-routes.ts new file mode 100644 index 000000000..a28993e92 --- /dev/null +++ b/plugins/file-manager/src/backend/operation-routes.ts @@ -0,0 +1,864 @@ +import type { Express, Request, Response } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { + execChannel, + execWithSudo, + getSessionSftp, + type SSHSession, +} from "./session.js"; +import { ensureDirectoryTreeSftp } from "./transfer-sftp-dir.js"; +import { buildDeleteCommand } from "./operation-commands.js"; +import { + emptyTrash, + listTrash, + moveToTrash, + permanentlyDeleteTrashItem, + restoreTrashItem, +} from "./trash-service.js"; +import { PermissionManager } from "../../../../src/backend/utils/permission-manager.js"; + +type FileOperationRoutesDeps = { + ctx: PluginContext; + sshSessions: Record; + verifySessionOwnership: (session: SSHSession, userId: string) => boolean; +}; + +const TRASH_RETENTION_DAYS_KEY = "trashRetentionDays"; + +export function registerFileOperationRoutes( + app: Express, + { ctx, sshSessions, verifySessionOwnership }: FileOperationRoutesDeps, +): void { + // NOTE: trash retention is an install-wide admin setting, not a per-host + // access check, so it stays on PermissionManager.isAdmin rather than + // ctx.hosts.checkAccess (see rule 5 scope: host resolution/access only). + const permissionManager = PermissionManager.getInstance(); + const getTrashRetentionDays = async (): Promise => { + try { + const value = Number(await ctx.settings.get(TRASH_RETENTION_DAYS_KEY)); + return Number.isInteger(value) && value >= 1 && value <= 3650 ? value : 7; + } catch { + return 7; + } + }; + + async function ownedSession( + req: Request, + res: Response, + ): Promise { + const sessionId = String(req.body?.sessionId ?? req.query?.sessionId ?? ""); + const session = sshSessions[sessionId]; + if (!sessionId || !session?.isConnected) { + res.status(400).json({ error: "SSH connection not established" }); + return null; + } + if (!verifySessionOwnership(session, ctx.currentActor()!)) { + res.status(403).json({ error: "Session access denied" }); + return null; + } + session.lastActive = Date.now(); + return session; + } + + app.get("/trash", async (req, res) => { + const session = await ownedSession(req, res); + if (!session) return; + try { + const retentionDays = await getTrashRetentionDays(); + res.json({ + items: await listTrash(await getSessionSftp(session), retentionDays), + retentionDays, + canManageRetention: await permissionManager.isAdmin( + ctx.currentActor()!, + ), + }); + } catch (error) { + ctx.log.error("Failed to list trash", error as Error); + res.status(500).json({ error: (error as Error).message }); + } + }); + + app.post("/trash/:id/restore", async (req, res) => { + const session = await ownedSession(req, res); + if (!session) return; + try { + res.json({ + item: await restoreTrashItem( + await getSessionSftp(session), + req.params.id, + ), + }); + } catch (error) { + const message = (error as Error).message; + res + .status(message.includes("already exists") ? 409 : 500) + .json({ error: message }); + } + }); + + app.delete("/trash/:id", async (req, res) => { + const session = await ownedSession(req, res); + if (!session) return; + try { + await permanentlyDeleteTrashItem( + await getSessionSftp(session), + req.params.id, + ); + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: (error as Error).message }); + } + }); + + app.delete("/trash", async (req, res) => { + const session = await ownedSession(req, res); + if (!session) return; + try { + res.json({ deleted: await emptyTrash(await getSessionSftp(session)) }); + } catch (error) { + res.status(500).json({ error: (error as Error).message }); + } + }); + + app.put("/trash-retention", async (req, res) => { + const userId = ctx.currentActor()!; + 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 ctx.settings.set(TRASH_RETENTION_DAYS_KEY, String(retentionDays)); + return res.json({ retentionDays }); + }); + /** + * @openapi + * /plugin-api/file-manager/createFile: + * post: + * summary: Create a file + * description: Creates an empty file on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * fileName: + * type: string + * responses: + * 200: + * description: File created successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 403: + * description: Permission denied. + * 500: + * description: Failed to create file. + */ + app.post("/createFile", async (req, res) => { + const { sessionId, path: filePath, fileName } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!filePath || !fileName) { + return res.status(400).json({ error: "File path and name are required" }); + } + + sshConn.lastActive = Date.now(); + + const fullPath = filePath.endsWith("/") + ? filePath + fileName + : filePath + "/" + fileName; + const escapedPath = fullPath.replace(/'/g, "'\"'\"'"); + + const createCommand = `touch '${escapedPath}' && echo "SUCCESS" && exit 0`; + + execChannel(sshConn, createCommand, (err, stream) => { + if (err) { + ctx.log.error("SSH createFile error:", err); + if (!res.headersSent) { + return res.status(500).json({ error: err.message }); + } + return; + } + + let outputData = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + + if (chunk.toString().includes("Permission denied")) { + ctx.log.error(`Permission denied creating file: ${fullPath}`); + if (!res.headersSent) { + return res.status(403).json({ + error: `Permission denied: Cannot create file ${fullPath}. Check directory permissions.`, + }); + } + return; + } + }); + + stream.on("close", (code) => { + if (outputData.includes("SUCCESS")) { + if (!res.headersSent) { + res.json({ + message: "File created successfully", + path: fullPath, + toast: { type: "success", message: `File created: ${fullPath}` }, + }); + } + return; + } + + if (code !== 0) { + ctx.log.error( + `SSH createFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + if (!res.headersSent) { + return res.status(500).json({ + error: `Command failed: ${errorData}`, + toast: { + type: "error", + message: `File creation failed: ${errorData}`, + }, + }); + } + return; + } + + if (!res.headersSent) { + res.json({ + message: "File created successfully", + path: fullPath, + toast: { type: "success", message: `File created: ${fullPath}` }, + }); + } + }); + + stream.on("error", (streamErr) => { + ctx.log.error("SSH createFile stream error:", streamErr); + if (!res.headersSent) { + res.status(500).json({ error: `Stream error: ${streamErr.message}` }); + } + }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/createFolder: + * post: + * summary: Create a folder + * description: Creates a new folder on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * folderName: + * type: string + * responses: + * 200: + * description: Folder created successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 403: + * description: Permission denied. + * 500: + * description: Failed to create folder. + */ + app.post("/createFolder", async (req, res) => { + const { sessionId, path: folderPath, folderName } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!folderPath || !folderName) { + return res + .status(400) + .json({ error: "Folder path and name are required" }); + } + + sshConn.lastActive = Date.now(); + + const fullPath = folderPath.endsWith("/") + ? folderPath + folderName + : folderPath + "/" + folderName; + ctx.log.info(`Creating directory: ${sessionId} (${fullPath})`); + try { + await ensureDirectoryTreeSftp(await getSessionSftp(sshConn), fullPath); + res.json({ + message: "Folder created successfully", + path: fullPath, + toast: { type: "success", message: `Folder created: ${fullPath}` }, + }); + } catch (error) { + ctx.log.error("SFTP createFolder failed", error as Error); + const code = (error as { code?: string | number }).code; + res.status(code === 3 || code === "EACCES" ? 403 : 500).json({ + error: (error as Error).message, + }); + } + }); + + /** + * @openapi + * /plugin-api/file-manager/deleteItem: + * delete: + * summary: Delete a file or directory + * description: Deletes a file or directory on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * path: + * type: string + * isDirectory: + * type: boolean + * responses: + * 200: + * description: Item deleted successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 403: + * description: Permission denied. + * 500: + * description: Failed to delete item. + */ + app.delete("/deleteItem", async (req, res) => { + const { sessionId, path: itemPath, isDirectory, permanent } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!itemPath) { + return res.status(400).json({ error: "Item path is required" }); + } + + ctx.log.info( + `Deleting item: ${sessionId} (${itemPath}, type=${isDirectory ? "directory" : "file"})`, + ); + sshConn.lastActive = Date.now(); + + if (!permanent) { + try { + const sftp = await getSessionSftp(sshConn); + const retentionDays = await getTrashRetentionDays(); + await listTrash(sftp, retentionDays); + const item = await moveToTrash(sftp, itemPath); + ctx.log.info( + `Item moved to trash: ${sessionId} (${itemPath}, trashId=${item.id})`, + ); + return res.json({ + message: "Item moved to trash", + path: itemPath, + trashItem: item, + }); + } catch (error) { + ctx.log.error( + `Failed to move item to trash: ${sessionId} (${itemPath})`, + error as Error, + ); + return res.status(409).json({ + error: (error as Error).message, + trashUnavailable: true, + }); + } + } + + const { command: deleteCommand, commandWithSuccess } = buildDeleteCommand( + itemPath, + Boolean(isDirectory), + ); + + const executeDelete = (useSudo: boolean): Promise => { + return new Promise((resolve) => { + if (useSudo && sshConn.sudoPassword) { + execWithSudo(sshConn, deleteCommand, sshConn.sudoPassword).then( + (result) => { + if ( + result.code === 0 || + (!result.stderr.includes("Permission denied") && + !result.stdout.includes("Permission denied")) + ) { + res.json({ + message: "Item deleted successfully", + path: itemPath, + toast: { + type: "success", + message: `${isDirectory ? "Directory" : "File"} deleted: ${itemPath}`, + }, + }); + } else { + res.status(500).json({ + error: `Delete failed: ${result.stderr || result.stdout}`, + }); + } + resolve(); + }, + ); + return; + } + + execChannel(sshConn, commandWithSuccess, (err, stream) => { + if (err) { + ctx.log.error("SSH deleteItem error:", err); + res.status(500).json({ error: err.message }); + resolve(); + return; + } + + let outputData = ""; + let errorData = ""; + let permissionDenied = false; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + if (chunk.toString().includes("Permission denied")) { + permissionDenied = true; + } + }); + + stream.on("close", (code) => { + if (permissionDenied) { + if (sshConn.sudoPassword) { + executeDelete(true).then(resolve); + return; + } + ctx.log.error(`Permission denied deleting: ${itemPath}`); + res.status(403).json({ + error: `Permission denied: Cannot delete ${itemPath}.`, + needsSudo: true, + }); + resolve(); + return; + } + + if (outputData.includes("SUCCESS")) { + ctx.log.info( + `Item deleted successfully: ${sessionId} (${itemPath})`, + ); + res.json({ + message: "Item deleted successfully", + path: itemPath, + toast: { + type: "success", + message: `${isDirectory ? "Directory" : "File"} deleted: ${itemPath}`, + }, + }); + } else { + const detail = + errorData.trim() || + outputData.trim() || + `command exited with code ${code} and produced no output (the remote shell may not support the delete command)`; + ctx.log.error( + `Delete failed for ${itemPath}: ${detail} (sessionId=${sessionId})`, + ); + res.status(500).json({ + error: `Delete failed: ${detail}`, + }); + } + resolve(); + }); + + stream.on("error", (streamErr) => { + ctx.log.error("SSH deleteItem stream error:", streamErr); + res + .status(500) + .json({ error: `Stream error: ${streamErr.message}` }); + resolve(); + }); + }); + }); + }; + + await executeDelete(false); + }); + + /** + * @openapi + * /plugin-api/file-manager/renameItem: + * put: + * summary: Rename a file or directory + * description: Renames a file or directory on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * oldPath: + * type: string + * newName: + * type: string + * responses: + * 200: + * description: Item renamed successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 403: + * description: Permission denied. + * 500: + * description: Failed to rename item. + */ + app.put("/renameItem", async (req, res) => { + const { sessionId, oldPath, newName } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!oldPath || !newName) { + return res + .status(400) + .json({ error: "Old path and new name are required" }); + } + + sshConn.lastActive = Date.now(); + + const oldDir = oldPath.substring(0, oldPath.lastIndexOf("/") + 1); + const newPath = oldDir + newName; + ctx.log.info(`Renaming item: ${sessionId} (${oldPath} -> ${newPath})`); + const escapedOldPath = oldPath.replace(/'/g, "'\"'\"'"); + const escapedNewPath = newPath.replace(/'/g, "'\"'\"'"); + + const renameCommand = `mv '${escapedOldPath}' '${escapedNewPath}' && echo "SUCCESS" && exit 0`; + + execChannel(sshConn, renameCommand, (err, stream) => { + if (err) { + ctx.log.error("SSH renameItem error:", err); + if (!res.headersSent) { + return res.status(500).json({ error: err.message }); + } + return; + } + + let outputData = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + + if (chunk.toString().includes("Permission denied")) { + ctx.log.error(`Permission denied renaming: ${oldPath}`); + if (!res.headersSent) { + return res.status(403).json({ + error: `Permission denied: Cannot rename ${oldPath}. Check file permissions.`, + }); + } + return; + } + }); + + stream.on("close", (code) => { + if (outputData.includes("SUCCESS")) { + ctx.log.info( + `Item renamed successfully: ${sessionId} (${oldPath} -> ${newPath})`, + ); + if (!res.headersSent) { + res.json({ + message: "Item renamed successfully", + oldPath, + newPath, + toast: { + type: "success", + message: `Item renamed: ${oldPath} -> ${newPath}`, + }, + }); + } + return; + } + + if (code !== 0) { + ctx.log.error( + `SSH renameItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + if (!res.headersSent) { + return res.status(500).json({ + error: `Command failed: ${errorData}`, + toast: { type: "error", message: `Rename failed: ${errorData}` }, + }); + } + return; + } + + ctx.log.info( + `Item renamed successfully: ${sessionId} (${oldPath} -> ${newPath})`, + ); + if (!res.headersSent) { + res.json({ + message: "Item renamed successfully", + oldPath, + newPath, + toast: { + type: "success", + message: `Item renamed: ${oldPath} -> ${newPath}`, + }, + }); + } + }); + + stream.on("error", (streamErr) => { + ctx.log.error("SSH renameItem stream error:", streamErr); + if (!res.headersSent) { + res.status(500).json({ error: `Stream error: ${streamErr.message}` }); + } + }); + }); + }); + + /** + * @openapi + * /plugin-api/file-manager/moveItem: + * put: + * summary: Move a file or directory + * description: Moves a file or directory on the remote host. + * tags: + * - File Manager + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * oldPath: + * type: string + * newPath: + * type: string + * responses: + * 200: + * description: Item moved successfully. + * 400: + * description: Missing required parameters or SSH connection not established. + * 403: + * description: Permission denied. + * 408: + * description: Move operation timed out. + * 500: + * description: Failed to move item. + */ + app.put("/moveItem", async (req, res) => { + const { sessionId, oldPath, newPath } = req.body; + const sshConn = sshSessions[sessionId]; + const userId = ctx.currentActor()!; + + if (!sessionId) { + return res.status(400).json({ error: "Session ID is required" }); + } + + if (!sshConn?.isConnected) { + return res.status(400).json({ error: "SSH connection not established" }); + } + + if (!verifySessionOwnership(sshConn, userId)) { + return res.status(403).json({ error: "Session access denied" }); + } + + if (!oldPath || !newPath) { + return res + .status(400) + .json({ error: "Old path and new path are required" }); + } + + sshConn.lastActive = Date.now(); + + const escapedOldPath = oldPath.replace(/'/g, "'\"'\"'"); + const escapedNewPath = newPath.replace(/'/g, "'\"'\"'"); + + const moveCommand = `mv '${escapedOldPath}' '${escapedNewPath}' && echo "SUCCESS" && exit 0`; + + const commandTimeout = setTimeout(() => { + if (!res.headersSent) { + res.status(408).json({ + error: "Move operation timed out. SSH connection may be unstable.", + toast: { + type: "error", + message: + "Move operation timed out. SSH connection may be unstable.", + }, + }); + } + }, 60000); + + execChannel(sshConn, moveCommand, (err, stream) => { + if (err) { + clearTimeout(commandTimeout); + ctx.log.error("SSH moveItem error:", err); + if (!res.headersSent) { + return res.status(500).json({ error: err.message }); + } + return; + } + + let outputData = ""; + let errorData = ""; + + stream.on("data", (chunk: Buffer) => { + outputData += chunk.toString(); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + errorData += chunk.toString(); + + if (chunk.toString().includes("Permission denied")) { + ctx.log.error(`Permission denied moving: ${oldPath}`); + if (!res.headersSent) { + return res.status(403).json({ + error: `Permission denied: Cannot move ${oldPath}. Check file permissions.`, + toast: { + type: "error", + message: `Permission denied: Cannot move ${oldPath}. Check file permissions.`, + }, + }); + } + return; + } + }); + + stream.on("close", (code) => { + clearTimeout(commandTimeout); + if (outputData.includes("SUCCESS")) { + if (!res.headersSent) { + res.json({ + message: "Item moved successfully", + oldPath, + newPath, + toast: { + type: "success", + message: `Item moved: ${oldPath} -> ${newPath}`, + }, + }); + } + return; + } + + if (code !== 0) { + ctx.log.error( + `SSH moveItem command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, + ); + if (!res.headersSent) { + return res.status(500).json({ + error: `Command failed: ${errorData}`, + toast: { type: "error", message: `Move failed: ${errorData}` }, + }); + } + return; + } + + if (!res.headersSent) { + res.json({ + message: "Item moved successfully", + oldPath, + newPath, + toast: { + type: "success", + message: `Item moved: ${oldPath} -> ${newPath}`, + }, + }); + } + }); + + stream.on("error", (streamErr) => { + clearTimeout(commandTimeout); + ctx.log.error("SSH moveItem stream error:", streamErr); + if (!res.headersSent) { + res.status(500).json({ error: `Stream error: ${streamErr.message}` }); + } + }); + }); + }); +} diff --git a/plugins/file-manager/src/backend/plugin-ctx.ts b/plugins/file-manager/src/backend/plugin-ctx.ts new file mode 100644 index 000000000..a36386f49 --- /dev/null +++ b/plugins/file-manager/src/backend/plugin-ctx.ts @@ -0,0 +1,13 @@ +import type { PluginContext } from "@termix/plugin-sdk/backend"; + +let current: PluginContext | null = null; + +/** Set in activate, cleared on deactivate. */ +export function setPluginCtx(ctx: PluginContext | null): void { + current = ctx; +} + +export function pluginCtx(): PluginContext { + if (!current) throw new Error("The plugin is not active"); + return current; +} diff --git a/plugins/file-manager/src/backend/service.ts b/plugins/file-manager/src/backend/service.ts new file mode 100644 index 000000000..4b3ca9360 --- /dev/null +++ b/plugins/file-manager/src/backend/service.ts @@ -0,0 +1,143 @@ +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { + promisifySftpMkdir, + promisifySftpReaddir, + promisifySftpStat, + promisifySftpUnlink, + promisifySftpRmdir, +} from "./sftp-promisify.js"; + +const DEFAULT_DIR_MODE = 0o755; +import type { SSHSession } from "./session.js"; + +/** What other plugins get from ctx.services.get("files.sftp", {userId}). */ +export interface FilesService { + list: ( + hostId: number, + path: string, + ) => Promise< + Array<{ name: string; type: "file" | "directory" | "link"; size?: number }> + >; + read: (hostId: number, path: string) => Promise; + write: (hostId: number, path: string, content: string) => Promise; + stat: ( + hostId: number, + path: string, + ) => Promise<{ size: number; isDirectory: boolean; mtime: number }>; + delete: (hostId: number, path: string) => Promise; + mkdir: (hostId: number, path: string) => Promise; +} + +/** + * A short-lived SFTP connection for a service call, outside the browse + * session map: a caller reaches this from any request the acting user makes, + * not necessarily one that has an open file-manager tab. + */ +async function withServiceSftp( + ctx: PluginContext, + hostId: number, + fn: (sftp: import("ssh2").SFTPWrapper) => Promise, +): Promise { + return ctx.ssh.withConnection( + hostId, + { pool: "file-manager-service", purpose: "file-manager" }, + (client) => + new Promise((resolve, reject) => { + client.sftp(async (err, sftp) => { + if (err) return reject(err); + try { + resolve(await fn(sftp)); + } catch (error) { + reject(error); + } + }); + }), + ); +} + +export function createFilesService( + ctx: PluginContext, + deps: { + sshSessions: Record; + verifySessionOwnership: (session: SSHSession, userId: string) => boolean; + }, +): FilesService { + void deps; + return { + async list(hostId, path) { + return withServiceSftp(ctx, hostId, async (sftp) => { + const entries = await promisifySftpReaddir(sftp, path); + return entries + .filter((e) => e.filename !== "." && e.filename !== "..") + .map((e) => ({ + name: e.filename, + type: e.attrs.isDirectory() + ? ("directory" as const) + : e.attrs.isSymbolicLink() + ? ("link" as const) + : ("file" as const), + size: e.attrs.isDirectory() ? undefined : e.attrs.size, + })); + }); + }, + + async read(hostId, path) { + return withServiceSftp( + ctx, + hostId, + (sftp) => + new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + const stream = sftp.createReadStream(path); + stream.on("data", (chunk: Buffer) => chunks.push(chunk)); + stream.on("end", () => + resolve(Buffer.concat(chunks).toString("utf8")), + ); + stream.on("error", reject); + }), + ); + }, + + async write(hostId, path, content) { + await withServiceSftp( + ctx, + hostId, + (sftp) => + new Promise((resolve, reject) => { + const stream = sftp.createWriteStream(path, { flags: "w" }); + stream.on("close", () => resolve()); + stream.on("error", reject); + stream.end(content, "utf8"); + }), + ); + }, + + async stat(hostId, path) { + return withServiceSftp(ctx, hostId, async (sftp) => { + const stats = await promisifySftpStat(sftp, path); + return { + size: stats.size, + isDirectory: stats.isDirectory(), + mtime: stats.mtime, + }; + }); + }, + + async delete(hostId, path) { + await withServiceSftp(ctx, hostId, async (sftp) => { + const stats = await promisifySftpStat(sftp, path); + if (stats.isDirectory()) { + await promisifySftpRmdir(sftp, path); + } else { + await promisifySftpUnlink(sftp, path); + } + }); + }, + + async mkdir(hostId, path) { + await withServiceSftp(ctx, hostId, (sftp) => + promisifySftpMkdir(sftp, path, DEFAULT_DIR_MODE), + ); + }, + }; +} diff --git a/plugins/file-manager/src/backend/session.ts b/plugins/file-manager/src/backend/session.ts new file mode 100644 index 000000000..cc8c7df29 --- /dev/null +++ b/plugins/file-manager/src/backend/session.ts @@ -0,0 +1,282 @@ +import type { Client as SSHClient } from "ssh2"; + +// Serializes SSH channel open requests so only one channel negotiation is +// in-flight at a time per session. Once the channel is established the slot +// is released immediately so the next open can proceed; the channels +// themselves remain open concurrently. +export class ChannelOpenSerializer { + private tail: Promise = Promise.resolve(); + + run(action: () => Promise): Promise { + const next = this.tail.then( + () => action(), + () => action(), + ); + this.tail = next.then( + () => {}, + () => {}, + ); + return next; + } +} + +export interface SSHSession { + client: SSHClient; + isConnected: boolean; + lastActive: number; + timeout?: NodeJS.Timeout; + activeOperations: number; + sudoPassword?: string; + sftp?: import("ssh2").SFTPWrapper; + sftpPending?: Promise; + channelOpener: ChannelOpenSerializer; + poolKey?: string; + userId?: string; + hostId?: number; + ip?: string; + port?: number; + username?: string; + transferDedicated?: boolean; + transferId?: string; + browseSessionId?: string; + scpLegacy?: boolean; +} + +export interface PendingTOTPSession { + client: SSHClient; + finish: (responses: string[]) => void; + config: import("ssh2").ConnectConfig; + createdAt: number; + sessionId: string; + hostId?: number; + ip?: string; + port?: number; + username?: string; + userId?: string; + prompts?: Array<{ prompt: string; echo: boolean }>; + totpPromptIndex?: number; + resolvedPassword?: string; + totpAttempts: number; + isWarpgate?: boolean; +} + +export function execWithSudo( + session: SSHSession, + command: string, + sudoPassword: string, +): Promise<{ stdout: string; stderr: string; code: number }> { + return execWithSudoBuffer(session, command, sudoPassword).then((result) => ({ + stdout: result.stdout.toString("utf8"), + stderr: result.stderr, + code: result.code, + })); +} + +export function execWithSudoBuffer( + session: SSHSession, + command: string, + sudoPassword: string, + maxStdoutBytes?: number, +): Promise<{ + stdout: Buffer; + stderr: string; + code: number; + exceededLimit?: boolean; +}> { + return new Promise((resolve) => { + const sudoCommand = `sudo -S -p '' ${command} 2>&1`; + + execChannel(session, sudoCommand, (err, stream) => { + if (err) { + resolve({ stdout: Buffer.alloc(0), stderr: err.message, code: 1 }); + return; + } + + const stdoutChunks: Buffer[] = []; + let stdoutBytes = 0; + let stderr = ""; + let settled = false; + + const finish = ( + code: number, + extra: { exceededLimit?: boolean } = {}, + ) => { + if (settled) return; + settled = true; + resolve({ + stdout: Buffer.concat(stdoutChunks), + stderr, + code, + ...extra, + }); + }; + + stream.on("data", (chunk: Buffer) => { + if (settled) return; + stdoutBytes += chunk.length; + if (maxStdoutBytes !== undefined && stdoutBytes > maxStdoutBytes) { + stderr = `Command output exceeds ${maxStdoutBytes} bytes`; + finish(1, { exceededLimit: true }); + stream.close(); + return; + } + stdoutChunks.push(chunk); + }); + + stream.stderr.on("data", (chunk: Buffer) => { + stderr += chunk.toString(); + }); + + stream.on("close", (code: number) => { + if (settled) return; + let stdout = Buffer.concat(stdoutChunks); + const sudoPromptMatch = stdout + .toString("utf8", 0, Math.min(stdout.length, 256)) + .match(/^\[sudo\] password for .+?:\s*/); + if (sudoPromptMatch) { + stdout = stdout.subarray(Buffer.byteLength(sudoPromptMatch[0])); + } + settled = true; + resolve({ stdout, stderr, code: code || 0 }); + }); + + stream.on("error", (streamErr: Error) => { + stderr = streamErr.message; + finish(1); + }); + + stream.write(`${sudoPassword}\n`); + }); + }); +} + +export function execBuffer( + session: SSHSession, + command: string, + maxStdoutBytes?: number, +): Promise<{ + stdout: Buffer; + stderr: string; + code: number; + exceededLimit?: boolean; +}> { + return new Promise((resolve) => { + execChannel(session, command, (err, stream) => { + if (err) { + resolve({ stdout: Buffer.alloc(0), stderr: err.message, code: 1 }); + return; + } + + const stdoutChunks: Buffer[] = []; + let stdoutBytes = 0; + let stderr = ""; + let settled = false; + + const finish = ( + code: number, + extra: { exceededLimit?: boolean } = {}, + ) => { + if (settled) return; + settled = true; + resolve({ + stdout: Buffer.concat(stdoutChunks), + stderr, + code, + ...extra, + }); + }; + + stream.on("data", (chunk: Buffer) => { + if (settled) return; + stdoutBytes += chunk.length; + if (maxStdoutBytes !== undefined && stdoutBytes > maxStdoutBytes) { + stderr = `Command output exceeds ${maxStdoutBytes} bytes`; + finish(1, { exceededLimit: true }); + stream.close(); + return; + } + stdoutChunks.push(chunk); + }); + stream.stderr.on("data", (chunk: Buffer) => { + stderr += chunk.toString(); + }); + stream.on("close", (code: number) => finish(code || 0)); + stream.on("error", (streamErr: Error) => { + stderr = stderr || streamErr.message; + finish(1); + }); + }); + }); +} + +export function getSessionSftp( + session: SSHSession, +): Promise { + if (session.sftp) { + return Promise.resolve(session.sftp); + } + + if (session.sftpPending) { + return session.sftpPending; + } + + const openOnce = (): Promise => + session.channelOpener.run( + () => + new Promise((resolve, reject) => { + session.client.sftp((err, sftp) => { + if (err) return reject(err); + session.sftp = sftp; + sftp.on("error", () => { + session.sftp = undefined; + }); + sftp.on("close", () => { + session.sftp = undefined; + }); + resolve(sftp); + }); + }), + ); + + session.sftpPending = openOnce() + .catch((err: Error) => { + const isChannelFailure = + err.message.toLowerCase().includes("channel open failure") || + err.message.toLowerCase().includes("open failed"); + if (isChannelFailure) { + return new Promise((resolve, reject) => + setTimeout(() => openOnce().then(resolve, reject), 500), + ); + } + return Promise.reject(err); + }) + .finally(() => { + session.sftpPending = undefined; + }); + + return session.sftpPending; +} + +export function execChannel( + session: SSHSession, + command: string, + callback: ( + err: Error | undefined, + stream: import("ssh2").ClientChannel, + ) => void, +): void { + session.channelOpener + .run( + () => + new Promise((resolve, reject) => { + session.client.exec(command, (err, stream) => { + if (err) return reject(err); + resolve(stream); + }); + }), + ) + .then( + (stream) => callback(undefined, stream), + (err: Error) => callback(err, undefined as never), + ); +} diff --git a/plugins/file-manager/src/backend/sftp-promisify.ts b/plugins/file-manager/src/backend/sftp-promisify.ts new file mode 100644 index 000000000..697b6b449 --- /dev/null +++ b/plugins/file-manager/src/backend/sftp-promisify.ts @@ -0,0 +1,120 @@ +type SFTPWrapper = import("ssh2").SFTPWrapper; + +export const SFTP_OPEN_READ = 0x00000001; +export const SFTP_OPEN_WRITE = 0x00000002 | 0x00000008 | 0x00000010; +export const SFTP_OPEN_WRITE_RESUME = 0x00000001 | 0x00000002 | 0x00000008; + +export function promisifySftpStat( + sftp: SFTPWrapper, + path: string, +): Promise { + return new Promise((resolve, reject) => { + sftp.stat(path, (err, stats) => { + if (err) reject(err); + else resolve(stats); + }); + }); +} + +export function promisifySftpUnlink( + sftp: SFTPWrapper, + path: string, +): Promise { + return new Promise((resolve, reject) => { + sftp.unlink(path, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +export function promisifySftpRmdir( + sftp: SFTPWrapper, + path: string, +): Promise { + return new Promise((resolve, reject) => { + sftp.rmdir(path, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +export function promisifySftpMkdir( + sftp: SFTPWrapper, + path: string, + mode: number, +): Promise { + return new Promise((resolve, reject) => { + sftp.mkdir(path, { mode }, (err) => { + if (err && (err as NodeJS.ErrnoException).code !== "EEXIST") { + reject(err); + } else { + resolve(); + } + }); + }); +} + +export function promisifySftpChmod( + sftp: SFTPWrapper, + path: string, + mode: number, +): Promise { + return new Promise((resolve, reject) => { + sftp.chmod(path, mode, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +export function promisifySftpReaddir( + sftp: SFTPWrapper, + path: string, +): Promise> { + return new Promise((resolve, reject) => { + sftp.readdir(path, (err, list) => { + if (err) reject(err); + else resolve(list); + }); + }); +} + +export function promisifySftpOpen( + sftp: SFTPWrapper, + path: string, + flags: number, + mode: number, +): Promise { + return new Promise((resolve, reject) => { + sftp.open(path, flags, mode, (err, handle) => { + if (err) reject(err); + else resolve(handle); + }); + }); +} + +export function promisifySftpClose( + sftp: SFTPWrapper, + handle: Buffer, +): Promise { + return new Promise((resolve, reject) => { + sftp.close(handle, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +export function promisifySftpFstat( + sftp: SFTPWrapper, + handle: Buffer, +): Promise { + return new Promise((resolve, reject) => { + sftp.fstat(handle, (err, stats) => { + if (err) reject(err); + else resolve(stats); + }); + }); +} diff --git a/plugins/file-manager/src/backend/ssh.ts b/plugins/file-manager/src/backend/ssh.ts new file mode 100644 index 000000000..5c9f1e7ba --- /dev/null +++ b/plugins/file-manager/src/backend/ssh.ts @@ -0,0 +1,79 @@ +import type { Client, ConnectConfig } from "ssh2"; +import type { + PluginSsh, + PluginSshAuthOutcome, + PluginSshConnection, + PluginSshConnectOptions, + PluginSshHost, +} from "@termix/plugin-sdk/backend"; + +let current: PluginSsh | null = null; + +/** Set in activate, cleared on deactivate. */ +export function setPluginSsh(ssh: PluginSsh | null): void { + current = ssh; +} + +export function pluginSsh(): PluginSsh { + if (!current) throw new Error("The plugin is not active"); + return current; +} + +/** withConnection with ssh2's Client type filled in. */ +export function withSshConnection( + host: number | PluginSshHost, + options: PluginSshConnectOptions & { pool: string }, + fn: (client: Client) => Promise, +): Promise { + return pluginSsh().withConnection(host, options, fn); +} + +/** connect with ssh2's Client type filled in. */ +export function connectSsh( + host: number | PluginSshHost, + options?: PluginSshConnectOptions, +): Promise> { + return pluginSsh().connect(host, options); +} + +/** + * Lower-level primitives for the interactive /connect route, which drives its + * own ssh2 Client through the TOTP/Warpgate parking flow rather than letting + * ctx.ssh.connect finish the handshake itself. + */ +export function prepareSsh( + host: PluginSshHost, + options: { + purpose?: "file-manager" | "file-transfer"; + client: Client; + serverHostId?: number; + log?: (level: "info" | "warning" | "error", message: string) => void; + }, +): Promise<{ config: ConnectConfig; outcome: PluginSshAuthOutcome }> { + return pluginSsh().prepare(host, options) as Promise<{ + config: ConnectConfig; + outcome: PluginSshAuthOutcome; + }>; +} + +export function openSshTransport( + host: PluginSshHost, + config: ConnectConfig, +): Promise<{ jumpClient: Client | null; via: string }> { + return pluginSsh().openTransport( + host, + config as unknown as Record, + ) as Promise<{ + jumpClient: Client | null; + via: string; + }>; +} + +export const classifyKeyboardInteractive: PluginSsh["classifyKeyboardInteractive"] = + (round, host) => pluginSsh().classifyKeyboardInteractive(round, host); + +export const autoResponses: PluginSsh["autoResponses"] = (prompts, password) => + pluginSsh().autoResponses(prompts, password); + +export const requiresSecret: PluginSsh["requiresSecret"] = (authType) => + pluginSsh().requiresSecret(authType); diff --git a/plugins/file-manager/src/backend/tables.ts b/plugins/file-manager/src/backend/tables.ts new file mode 100644 index 000000000..9e7fe82e3 --- /dev/null +++ b/plugins/file-manager/src/backend/tables.ts @@ -0,0 +1,105 @@ +import { + adoptLegacyTable, + defineTable, + id, + refHost, + refUser, + text, + timestamp, +} from "@termix/plugin-sdk/db"; + +/** + * The paths and hosts a user recently opened in the file manager. + * + * Adopted from core's file_manager_recent, so the column and index names are + * the legacy ones: the rename carries the existing rows and index across. + */ +export const recent = adoptLegacyTable( + "file_manager_recent", + defineTable( + "recent", + { + id: id(), + userId: refUser(), + hostId: refHost(), + name: text().notNull(), + path: text().notNull(), + lastOpened: timestamp().notNull().defaultNow(), + }, + { + indexes: [ + { name: "idx_file_manager_recent_user", columns: ["userId", "hostId"] }, + ], + }, + ), +); + +/** Paths a user pinned for quick access on a host. */ +export const pinned = adoptLegacyTable( + "file_manager_pinned", + defineTable( + "pinned", + { + id: id(), + userId: refUser(), + hostId: refHost(), + name: text().notNull(), + path: text().notNull(), + pinnedAt: timestamp().notNull().defaultNow(), + }, + { + indexes: [ + { + name: "idx_file_manager_pinned_user", + columns: ["userId", "hostId"], + }, + ], + }, + ), +); + +/** Named folder shortcuts a user saved on a host. */ +export const shortcuts = adoptLegacyTable( + "file_manager_shortcuts", + defineTable( + "shortcuts", + { + id: id(), + userId: refUser(), + hostId: refHost(), + name: text().notNull(), + path: text().notNull(), + createdAt: timestamp().notNull().defaultNow(), + }, + { + indexes: [ + { + name: "idx_file_manager_shortcuts_user", + columns: ["userId", "hostId"], + }, + ], + }, + ), +); + +/** Recently used destinations for host-to-host transfers. */ +export const transferRecent = adoptLegacyTable( + "transfer_recent", + defineTable( + "transfer_recent", + { + id: id(), + userId: refUser(), + sourceHostId: refHost(), + destHostId: refHost(), + destPath: text().notNull(), + destPathLabel: text().notNull(), + lastUsed: timestamp().notNull().defaultNow(), + }, + { + indexes: [{ name: "idx_transfer_recent_user", columns: ["userId"] }], + }, + ), +); + +export const tables = [recent, pinned, shortcuts, transferRecent]; diff --git a/plugins/file-manager/src/backend/transfer-engine.ts b/plugins/file-manager/src/backend/transfer-engine.ts new file mode 100644 index 000000000..1b260bea5 --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-engine.ts @@ -0,0 +1,3411 @@ +import { getErrorMessage } from "./error-message.js"; +import { randomUUID } from "crypto"; +import { performance } from "node:perf_hooks"; +import type { ClientChannel } from "ssh2"; +import { pluginCtx } from "./plugin-ctx.js"; +import { + basename, + dirname, + getWorkingDir, + inferPlatformFromPath, + joinPath, + normalizeSftpPath, + pathsOverlap, + sftpPathToLocalPath, + type TransferPlatform, +} from "./transfer-paths.js"; +import { + buildTransferScanSummary, + estimateIncompressibleSample, + getArchiveTransferReasonKey, + resolveArchiveTransferMethod, + type TransferMethodPreference, + type TransferScanSummary, +} from "./transfer-routing.js"; +import { verifySftpFileIntegrity } from "./transfer-integrity.js"; +import { + promisifySftpChmod, + promisifySftpClose, + promisifySftpFstat, + promisifySftpOpen, + promisifySftpStat, + promisifySftpUnlink, +} from "./sftp-promisify.js"; +import { + buildTransferHopTimings, + computeTransferMbPerSec, + createEmptyXferStats, + createHopWallClock, + createThrottledProgress, + elapsedMs, + hopSpanMs, + mergeXferStats, + noteHopEnd, + noteHopStart, + type PipelinedXferStats, + type TransferTimings, +} from "./transfer-stats.js"; +import { + TransferCancelledError, + TransferStalledError, + isRecoverableTransferError, +} from "./transfer-errors.js"; +import { + escapeShell, + isLocalSshEndpoint, + isPermissionError, + isRootOnlyPath, +} from "./transfer-host-utils.js"; +import { + SFTP_OPEN_READ, + SFTP_OPEN_WRITE, + SFTP_OPEN_WRITE_RESUME, +} from "./sftp-promisify.js"; +import { + collectFileWorkItems, + readSftpSample, + type FileWorkItem, +} from "./transfer-scan.js"; +import { + deletePathSftp, + ensureDirectoryTreeSftp, +} from "./transfer-sftp-dir.js"; +import { + DEFAULT_PARALLEL_SEGMENT_COUNT, + SFTP_XFER_SEGMENT_SIZE, + buildSegmentCopyJobs, + clampParallelSegmentCount, + type SegmentCopyJob, +} from "./transfer-segment-copy.js"; +import { + buildDirectProbeCommand, + buildDirectRsyncCommand, + quoteShell, + shouldBenchmarkDirectTransfer, + shouldUseDirectTransfer, + type DirectTransferEndpoint, +} from "./direct-transfer-routing.js"; +import { + getRecentDirectRouteDecision, + getTransferProfile, + initializeTransferProfiles, + recordDirectRouteBenchmark, + recordDirectRouteOutcome, + recordTransferProfile, + selectTransferTuning, +} from "./transfer-tuning.js"; + +export type { + TransferMethodPreference, + TransferScanSummary, +} from "./transfer-routing.js"; + +export interface TransferMethodPreview { + methodPreference: TransferMethodPreference; + resolvedMethod: "tar" | "item_sftp"; + reasonKey: ReturnType; + sourcePlatform: TransferPlatform; + destPlatform: TransferPlatform; + sourceHasTar: boolean; + destHasTar: boolean; + summary: TransferScanSummary; +} + +type SFTPWrapper = import("ssh2").SFTPWrapper; + +export interface SSHSessionLike { + client: import("ssh2").Client; + isConnected: boolean; + lastActive: number; + activeOperations: number; + sftp?: SFTPWrapper; + sftpPending?: Promise; + channelOpener: { run: (fn: () => Promise) => Promise }; + userId?: string; + ip?: string; + port?: number; + username?: string; + transferPlatform?: TransferPlatform; +} + +export interface HostTransferDeps { + sshSessions: Record; + getSessionSftp: (session: SSHSessionLike) => Promise; + execChannel: ( + session: SSHSessionLike, + command: string, + callback: (err: Error | undefined, stream: ClientChannel) => void, + ) => void; + verifySessionOwnership: (session: SSHSessionLike, userId: string) => boolean; + openDedicatedTransferSession: ( + browseSessionId: string, + dedicatedSessionId: string, + userId: string, + transferId: string, + options?: { allowBrowseDisconnected?: boolean }, + ) => Promise; + closeDedicatedTransferSession: (sessionId: string) => void; +} + +export type TransferPhase = + | "compressing" + | "transferring" + | "benchmarking" + | "verifying" + | "extracting" + | "reconnecting"; +export type TransferStatus = + "running" | "success" | "partial" | "error" | "cancelled"; +export type TransferMethod = "stream" | "tar" | "item_sftp" | "direct_rsync"; + +export type { + TransferHopId, + TransferHopMetrics, + TransferTimings, +} from "./transfer-stats.js"; + +export interface TransferProgress { + transferId: string; + status: TransferStatus; + phase: TransferPhase; + bytesTransferred?: number; + totalBytes?: number; + itemsCompleted?: number; + totalItems?: number; + failedPaths?: string[]; + message?: string; + method?: TransferMethod; + sourcePaths?: string[]; + destPath?: string; + userId?: string; + sourceSessionId?: string; + destSessionId?: string; + dedicatedSourceSessionId?: string; + dedicatedDestSessionId?: string; + startedAt?: number; + lastActivityAt?: number; + reconnectingAt?: number; + timings?: TransferTimings; + sourceDeleted?: boolean; + moveRequested?: boolean; + methodPreference?: TransferMethodPreference; + parallelSegmentCount?: number; + /** Destination paths created or partially written before cancel. */ + destArtifacts?: string[]; + tempArchivePath?: string; + partialDestRemaining?: boolean; + cleanupCompleted?: boolean; + retryable?: boolean; + integrityVerified?: boolean; + requestSnapshot?: { + sourceSessionId: string; + sourcePaths: string[]; + destSessionId: string; + destPath: string; + move?: boolean; + methodPreference?: TransferMethodPreference; + parallelSegmentCount?: number; + }; +} + +export interface TransferRequest { + sourceSessionId: string; + sourcePaths: string[]; + destSessionId: string; + destPath: string; + move?: boolean; + userId: string; + methodPreference?: TransferMethodPreference; + /** Explicit parallel lane override; omitted values are tuned automatically. */ + parallelSegmentCount?: number; +} + +const activeTransfers = new Map(); +const cancelRequestedTransfers = new Set(); +const directRouteCache = new Map< + string, + { useDirect: boolean; expiresAt: number; directMs?: number; relayMs?: number } +>(); + +/** In-flight pipelined SFTP reads; force-closed when the user cancels. */ +interface ActiveXferControl { + abort: (err: Error) => void; + closeResources: () => Promise; +} +const activeXferControls = new Map(); +const cancelWatchdogs = new Map>(); + +function throwIfCancelled(transferId: string): void { + if (cancelRequestedTransfers.has(transferId)) { + throw new TransferCancelledError(); + } +} + +function createTransferShouldAbort(transferId: string): () => boolean { + return () => { + if (cancelRequestedTransfers.has(transferId)) { + return true; + } + const progress = activeTransfers.get(transferId); + return progress !== undefined && progress.status !== "running"; + }; +} + +export function requestTransferCancel( + transferId: string, + userId: string, +): boolean { + const progress = activeTransfers.get(transferId); + if ( + !progress || + progress.userId !== userId || + progress.status !== "running" + ) { + return false; + } + cancelRequestedTransfers.add(transferId); + updateTransfer(transferId, { message: "Cancellation requested" }); + void forceAbortActiveXfer(transferId); + + if (!cancelWatchdogs.has(transferId)) { + cancelWatchdogs.set( + transferId, + setTimeout(() => { + cancelWatchdogs.delete(transferId); + const current = activeTransfers.get(transferId); + if ( + current?.status === "running" && + cancelRequestedTransfers.has(transferId) + ) { + void forceAbortActiveXfer(transferId); + finalizeTransfer( + transferId, + cancelledProgressPatch(current, { + status: "cancelled", + phase: current.phase ?? "transferring", + message: "Transfer cancelled by user", + method: current.method, + sourcePaths: current.sourcePaths, + destPath: current.destPath, + bytesTransferred: current.bytesTransferred, + totalBytes: current.totalBytes, + itemsCompleted: current.itemsCompleted, + totalItems: current.totalItems, + moveRequested: current.moveRequested, + sourceDeleted: false, + }), + ); + pluginCtx().log.warn( + `Force-finalized stuck transfer after cancel: ${transferId}`, + ); + } + }, 8000), + ); + } + + return true; +} + +async function forceAbortActiveXfer(transferId: string): Promise { + const control = activeXferControls.get(transferId); + if (!control) return; + control.abort(new TransferCancelledError()); + await control.closeResources().catch(() => {}); +} +const SMALL_FILE_SYNC_THRESHOLD = 10 * 1024 * 1024; +/** OpenSSH SFTP max packet payload is ~256 KiB; larger chunks cut round-trips. */ +const SFTP_XFER_CHUNK_SIZE = 256 * 1024; +/** Pipelined in-flight READ requests per leg (ssh2 fastGet/fastPut default is 64). */ +const SFTP_XFER_CONCURRENCY = 32; +/** Files above this size use segmented copy; smaller files use a single scheduler run. */ +const SFTP_XFER_SEGMENT_THRESHOLD = 32 * 1024 * 1024; +/** Per-segment attempts before giving up (sequential and parallel). */ +const SFTP_SEQUENTIAL_SEGMENT_MAX_ATTEMPTS = 4; +/** Per-lane attempts in parallel mode (each opens a fresh lane SSH pair quickly). */ +const SFTP_PARALLEL_SEGMENT_MAX_ATTEMPTS = 4; +/** Full copy restarts after segment exhaustion. */ +const SFTP_SEQUENTIAL_COPY_MAX_ATTEMPTS = 2; +const SFTP_PARALLEL_COPY_MAX_ATTEMPTS = 2; +/** Short backoff before opening fresh dedicated SSH sessions. */ +const TRANSFER_SESSION_RESET_DELAYS_MS = [1000, 2000, 3000]; +const TRANSFER_HANDLE_CLOSE_TIMEOUT_MS = 2500; +const HUNG_TRANSFER_MS = 90_000; +const HUNG_RECONNECTING_MS = 180_000; + +interface TransferReconnectContext { + deps: HostTransferDeps; + userId: string; + browseSourceSessionId: string; + browseDestSessionId: string; + dedicatedSourceSessionId: string; + dedicatedDestSessionId: string; + transferId: string; + profileKey?: string; +} + +type TransferReconnectMeta = Omit< + TransferReconnectContext, + "deps" | "transferId" +>; + +function buildTransferReconnectContext( + deps: HostTransferDeps, + transferId: string, + meta: TransferReconnectMeta, +): TransferReconnectContext { + return { deps, transferId, ...meta }; +} + +async function probeDestResumeOffset( + destSftp: SFTPWrapper, + destPath: string, + fileSize: number, +): Promise { + try { + const destStat = await promisifySftpStat(destSftp, destPath); + return Math.min(destStat.size, fileSize); + } catch { + return 0; + } +} + +function buildStreamTransferTimings( + transferId: string, + fileSize: number, + prepareDestMs?: number, +): TransferTimings { + const progress = activeTransfers.get(transferId); + const wallStart = progress?.startedAt ?? Date.now(); + const totalMs = elapsedMs(wallStart); + const prepare = prepareDestMs ?? progress?.timings?.prepareDestMs ?? 0; + const verify = progress?.timings?.verifyMs ?? 0; + const dataMs = Math.max(1, totalMs - prepare - verify); + + return { + ...progress?.timings, + prepareDestMs: prepare > 0 ? prepare : undefined, + transferMs: dataMs, + transferBytes: fileSize, + endToEndMbPerSec: computeTransferMbPerSec(fileSize, dataMs), + totalMs, + }; +} + +async function finalizeStreamTransferIfDestAtSize( + transferId: string, + destSftp: SFTPWrapper, + destPath: string, + expectedSize: number, + extra: Partial = {}, + verify?: () => Promise, +): Promise { + try { + const destSize = await probeDestResumeOffset( + destSftp, + destPath, + expectedSize, + ); + if (destSize < expectedSize) { + return false; + } + + await verify?.(); + + pluginCtx().log.info( + `Destination file complete, finalizing transfer: ${transferId} (destSize=${destSize}, expectedSize=${expectedSize})`, + ); + + finalizeTransfer(transferId, { + status: "success", + phase: "transferring", + method: "stream", + bytesTransferred: expectedSize, + totalBytes: expectedSize, + destPath, + timings: buildStreamTransferTimings(transferId, expectedSize), + ...extra, + }); + return true; + } catch { + return false; + } +} + +async function tryFinalizeStreamTransferIfDestComplete( + deps: HostTransferDeps, + transferId: string, + destSession: SSHSessionLike, + destPath: string, + expectedSize: number, + extra: Partial = {}, + verify?: () => Promise, +): Promise { + try { + const destSftp = await deps.getSessionSftp(destSession); + return finalizeStreamTransferIfDestAtSize( + transferId, + destSftp, + destPath, + expectedSize, + extra, + verify, + ); + } catch { + return false; + } +} + +async function reconnectDedicatedTransferSessions( + ctx: TransferReconnectContext, + parallelWorkers = 0, +): Promise<{ sourceSession: SSHSessionLike; destSession: SSHSessionLike }> { + pluginCtx().log.warn( + `Reconnecting dedicated transfer SSH sessions: ${ctx.transferId} (parallelWorkers=${parallelWorkers})`, + ); + + closeAllTransferSessions(ctx.deps, ctx, parallelWorkers); + + const [sourceSession, destSession] = await Promise.all([ + ctx.deps.openDedicatedTransferSession( + ctx.browseSourceSessionId, + ctx.dedicatedSourceSessionId, + ctx.userId, + ctx.transferId, + { allowBrowseDisconnected: true }, + ), + ctx.deps.openDedicatedTransferSession( + ctx.browseDestSessionId, + ctx.dedicatedDestSessionId, + ctx.userId, + ctx.transferId, + { allowBrowseDisconnected: true }, + ), + ]); + + return { sourceSession, destSession }; +} + +async function resetDedicatedTransferSessions( + ctx: TransferReconnectContext, + attempt: number, + reason: string, +): Promise<{ + sourceSession: SSHSessionLike; + destSession: SSHSessionLike; + sourceSftp: SFTPWrapper; + destSftp: SFTPWrapper; +}> { + pluginCtx().log.warn( + `Resetting transfer SSH sessions (fresh connection): ${ctx.transferId} (attempt=${attempt}, reason=${reason})`, + ); + + closeAllTransferSessions(ctx.deps, ctx, 0); + + const delayMs = + TRANSFER_SESSION_RESET_DELAYS_MS[ + Math.min(attempt - 1, TRANSFER_SESSION_RESET_DELAYS_MS.length - 1) + ] ?? 3000; + await new Promise((resolve) => setTimeout(resolve, delayMs)); + + const { sourceSession, destSession } = + await reconnectDedicatedTransferSessions(ctx, 0); + const sourceSftp = await ctx.deps.getSessionSftp(sourceSession); + const destSftp = await ctx.deps.getSessionSftp(destSession); + + if (ctx.transferId) { + updateTransfer(ctx.transferId, { lastActivityAt: Date.now() }); + } + + return { sourceSession, destSession, sourceSftp, destSftp }; +} + +async function detectTransferPlatform( + deps: HostTransferDeps, + session: SSHSessionLike, + pathHints: string[] = [], +): Promise { + if (session.transferPlatform) return session.transferPlatform; + + for (const path of pathHints) { + const inferred = inferPlatformFromPath(path); + if (inferred === "windows") { + session.transferPlatform = "windows"; + return "windows"; + } + } + + try { + const { code } = await execCommand(deps, session, "uname -s"); + if (code === 0) { + session.transferPlatform = "unix"; + return "unix"; + } + } catch { + /* not unix */ + } + + try { + const { code } = await execCommand( + deps, + session, + 'cmd /c "if defined OS (exit 0) else (exit 1)"', + ); + if (code === 0) { + session.transferPlatform = "windows"; + return "windows"; + } + } catch { + /* not cmd */ + } + + try { + const { code } = await execCommand( + deps, + session, + "powershell -NoProfile -Command \"if ($IsWindows -or $env:OS -like 'Windows*') { exit 0 } else { exit 1 }\"", + ); + if (code === 0) { + session.transferPlatform = "windows"; + return "windows"; + } + } catch { + /* not powershell */ + } + + session.transferPlatform = "unix"; + return "unix"; +} + +function execCommand( + deps: HostTransferDeps, + session: SSHSessionLike, + command: string, + options: { + shouldAbort?: () => boolean; + onOutput?: (chunk: Buffer) => void; + timeoutMs?: number; + } = {}, +): Promise<{ code: number; stderr: string }> { + return new Promise((resolve, reject) => { + deps.execChannel(session, command, (err, stream) => { + if (err) { + reject(err); + return; + } + let settled = false; + const startedAt = Date.now(); + let stderr = ""; + const abortTimer = + options.shouldAbort || options.timeoutMs + ? setInterval(() => { + const timedOut = + options.timeoutMs !== undefined && + Date.now() - startedAt >= options.timeoutMs; + if ((!options.shouldAbort?.() && !timedOut) || settled) return; + settled = true; + clearInterval(abortTimer); + stream.signal("KILL"); + stream.close(); + reject( + timedOut + ? new Error(`Command timed out after ${options.timeoutMs}ms`) + : new TransferCancelledError(), + ); + }, 250) + : undefined; + stream.on("data", (data: Buffer) => { + options.onOutput?.(data); + }); + stream.stderr.on("data", (data: Buffer) => { + stderr += data.toString(); + options.onOutput?.(data); + }); + stream.on("close", (code: number) => { + if (settled) return; + settled = true; + if (abortTimer) clearInterval(abortTimer); + resolve({ code: code ?? 0, stderr }); + }); + stream.on("error", (streamErr: Error) => { + if (settled) return; + settled = true; + if (abortTimer) clearInterval(abortTimer); + reject(streamErr); + }); + }); + }); +} + +async function checkTarAvailable( + deps: HostTransferDeps, + session: SSHSessionLike, +): Promise { + const platform = await detectTransferPlatform(deps, session); + if (platform === "windows") return false; + + try { + const { code } = await execCommand(deps, session, "command -v tar"); + return code === 0; + } catch { + return false; + } +} + +async function ensureDestDirectory( + deps: HostTransferDeps, + destSession: SSHSessionLike, + dirPath: string, + created: Set = new Set(), +): Promise { + const destSftp = await deps.getSessionSftp(destSession); + await ensureDirectoryTreeSftp(destSftp, dirPath, created); +} + +function updateTransfer( + transferId: string, + patch: Partial, +): void { + const current = activeTransfers.get(transferId); + if (current) { + const next: TransferProgress = { ...current, ...patch }; + if ( + patch.bytesTransferred !== undefined && + current.totalBytes !== undefined && + current.totalBytes > 0 + ) { + next.bytesTransferred = Math.min( + patch.bytesTransferred, + current.totalBytes, + ); + } + if ( + patch.bytesTransferred !== undefined && + patch.bytesTransferred !== current.bytesTransferred + ) { + next.lastActivityAt = Date.now(); + } + activeTransfers.set(transferId, next); + } +} + +async function closeTransferHandlesSafe( + handles: TransferFileHandles | null | undefined, +): Promise { + if (!handles) return; + await Promise.race([ + handles.close(), + new Promise((resolve) => + setTimeout(resolve, TRANSFER_HANDLE_CLOSE_TIMEOUT_MS), + ), + ]).catch(() => {}); +} + +function checkHungTransfers(): void { + const now = Date.now(); + for (const [transferId, progress] of activeTransfers) { + if (progress.status !== "running") continue; + + if ( + progress.method === "stream" && + progress.totalBytes && + progress.totalBytes > 0 && + progress.bytesTransferred !== undefined && + progress.bytesTransferred >= progress.totalBytes + ) { + pluginCtx().log.info( + `Transfer progress at 100%, marking complete: ${transferId} (${progress.bytesTransferred}/${progress.totalBytes})`, + ); + finalizeTransfer(transferId, { + status: "success", + phase: "transferring", + method: "stream", + bytesTransferred: progress.totalBytes, + totalBytes: progress.totalBytes, + sourcePaths: progress.sourcePaths, + destPath: progress.destPath, + moveRequested: progress.moveRequested, + timings: buildStreamTransferTimings(transferId, progress.totalBytes), + }); + continue; + } + + if (progress.phase === "reconnecting") { + const reconnectStarted = + progress.reconnectingAt ?? progress.lastActivityAt ?? now; + if (now - reconnectStarted < HUNG_RECONNECTING_MS) continue; + } + + const lastActivity = progress.lastActivityAt ?? progress.startedAt ?? now; + if (now - lastActivity < HUNG_TRANSFER_MS) continue; + + pluginCtx().log.error( + `Force-finalizing unresponsive transfer: ${transferId} (lastActivityAt=${lastActivity}, bytesTransferred=${progress.bytesTransferred})`, + ); + + finalizeTransfer( + transferId, + failedProgressPatch(progress, { + status: "error", + phase: + progress.phase === "reconnecting" ? "transferring" : progress.phase, + message: "Transfer stopped responding", + method: progress.method, + sourcePaths: progress.sourcePaths, + destPath: progress.destPath, + bytesTransferred: Math.min( + progress.bytesTransferred ?? 0, + progress.totalBytes ?? Number.MAX_SAFE_INTEGER, + ), + totalBytes: progress.totalBytes, + itemsCompleted: progress.itemsCompleted, + totalItems: progress.totalItems, + moveRequested: progress.moveRequested, + }), + ); + } +} + +/** Before marking a stalled stream transfer as failed, verify the destination file size. */ +export async function probeHungStreamTransfers( + deps: HostTransferDeps, +): Promise { + const now = Date.now(); + for (const [transferId, progress] of activeTransfers) { + if (progress.status !== "running" || progress.method !== "stream") { + continue; + } + if (!progress.destPath || !progress.totalBytes || !progress.userId) { + continue; + } + + const lastActivity = progress.lastActivityAt ?? progress.startedAt ?? now; + if (now - lastActivity < HUNG_TRANSFER_MS) { + continue; + } + + const browseDestId = progress.destSessionId; + if (!browseDestId) { + continue; + } + + const destId = progress.dedicatedDestSessionId ?? `xfer:${transferId}:dst`; + let destSession = deps.sshSessions[destId]; + if (!destSession?.isConnected) { + try { + destSession = await deps.openDedicatedTransferSession( + browseDestId, + destId, + progress.userId, + transferId, + { allowBrowseDisconnected: true }, + ); + } catch { + continue; + } + } + + await tryFinalizeStreamTransferIfDestComplete( + deps, + transferId, + destSession, + progress.destPath, + progress.totalBytes, + { + sourcePaths: progress.sourcePaths, + moveRequested: progress.moveRequested, + }, + ); + } +} + +function trackDestArtifact(transferId: string, path: string): void { + const normalized = normalizeSftpPath(path); + const current = activeTransfers.get(transferId); + if (!current) return; + const existing = current.destArtifacts ?? []; + if (existing.includes(normalized)) return; + updateTransfer(transferId, { destArtifacts: [...existing, normalized] }); +} + +function buildCleanupPaths(progress: TransferProgress): string[] { + const paths = new Set(progress.destArtifacts ?? []); + + if (progress.tempArchivePath) { + paths.add(normalizeSftpPath(progress.tempArchivePath)); + } + + if (paths.size === 0 && progress.destPath) { + if (progress.method === "stream") { + paths.add(normalizeSftpPath(progress.destPath)); + } else if (progress.sourcePaths?.length) { + for (const sourcePath of progress.sourcePaths) { + paths.add( + normalizeSftpPath(joinPath(progress.destPath, basename(sourcePath))), + ); + } + } + } + + return [...paths]; +} + +function partialDestWasWritten(progress: TransferProgress): boolean { + return ( + (progress.bytesTransferred ?? 0) > 0 || + (progress.itemsCompleted ?? 0) > 0 || + (progress.destArtifacts?.length ?? 0) > 0 + ); +} + +function failedProgressPatch( + current: TransferProgress | undefined, + patch: Partial, +): Partial { + const merged = { ...current, ...patch } as TransferProgress; + const hasPartial = partialDestWasWritten(merged); + return { + ...patch, + partialDestRemaining: hasPartial, + retryable: hasPartial && !!current?.requestSnapshot, + }; +} + +function cancelledProgressPatch( + current: TransferProgress | undefined, + patch: Partial, +): Partial { + const merged = { ...current, ...patch } as TransferProgress; + return { + ...patch, + partialDestRemaining: partialDestWasWritten(merged), + }; +} + +function finalizeTransfer( + transferId: string, + patch: Partial, +): TransferProgress { + cancelRequestedTransfers.delete(transferId); + const watchdog = cancelWatchdogs.get(transferId); + if (watchdog) { + clearTimeout(watchdog); + cancelWatchdogs.delete(transferId); + } + const current = activeTransfers.get(transferId); + const result: TransferProgress = { + transferId, + status: "success", + phase: "transferring", + ...current, + ...patch, + }; + activeTransfers.set(transferId, result); + return result; +} + +async function verifyTransferredFile( + deps: HostTransferDeps, + transferId: string, + reconnectMeta: TransferReconnectMeta, + sourcePath: string, + destPath: string, +): Promise { + updateTransfer(transferId, { phase: "verifying" }); + const verifyStart = Date.now(); + const [sourceSession, destSession] = await Promise.all([ + deps.openDedicatedTransferSession( + reconnectMeta.browseSourceSessionId, + reconnectMeta.dedicatedSourceSessionId, + reconnectMeta.userId, + transferId, + { allowBrowseDisconnected: true }, + ), + deps.openDedicatedTransferSession( + reconnectMeta.browseDestSessionId, + reconnectMeta.dedicatedDestSessionId, + reconnectMeta.userId, + transferId, + { allowBrowseDisconnected: true }, + ), + ]); + const [sourceSftp, destSftp] = await Promise.all([ + deps.getSessionSftp(sourceSession), + deps.getSessionSftp(destSession), + ]); + await verifySftpFileIntegrity( + sourceSftp, + destSftp, + sourcePath, + destPath, + createTransferShouldAbort(transferId), + () => new TransferCancelledError(), + ); + const current = activeTransfers.get(transferId); + updateTransfer(transferId, { + integrityVerified: true, + timings: { + ...current?.timings, + verifyMs: (current?.timings?.verifyMs ?? 0) + elapsedMs(verifyStart), + }, + }); +} + +async function deleteSourcePathsAfterSuccess( + deps: HostTransferDeps, + transferId: string, + sourceSession: SSHSessionLike, + sourcePaths: string[], +): Promise { + const deleteStart = Date.now(); + await deleteSourcePaths(deps, sourceSession, sourcePaths); + const sourceDeleteMs = elapsedMs(deleteStart); + updateTransfer(transferId, { + sourceDeleted: true, + timings: { + ...activeTransfers.get(transferId)?.timings, + sourceDeleteMs, + }, + }); + return sourceDeleteMs; +} + +async function ensureDestParentForFile( + deps: HostTransferDeps, + destSession: SSHSessionLike, + filePath: string, +): Promise { + const parent = dirname(filePath); + if (!parent || isRootOnlyPath(parent)) return; + await ensureDestDirectory(deps, destSession, parent); +} + +async function scanSourcePathsForRouting( + sftp: SFTPWrapper, + sourcePaths: string[], + transferId: string, +) { + const scanItems: Array<{ sourcePath: string; size: number }> = []; + for (const sourcePath of sourcePaths) { + throwIfCancelled(transferId); + const work = await collectFileWorkItems(sftp, sourcePath, "/"); + scanItems.push( + ...work.map((w) => ({ sourcePath: w.sourcePath, size: w.size })), + ); + } + const summary = buildTransferScanSummary(scanItems); + const candidates = [...scanItems] + .filter((item) => item.size > 0) + .sort((a, b) => b.size - a.size) + .slice(0, 3); + let sampledBytes = 0; + let sampledIncompressibleBytes = 0; + for (const item of candidates) { + throwIfCancelled(transferId); + try { + const sample = await readSftpSample(sftp, item.sourcePath, item.size); + sampledBytes += sample.length; + if (estimateIncompressibleSample(sample)) { + sampledIncompressibleBytes += sample.length; + } + } catch { + /* Extension-based routing remains the safe fallback. */ + } + } + if (sampledBytes > 0) { + summary.sampledIncompressibleRatio = + sampledIncompressibleBytes / sampledBytes; + } + return summary; +} + +interface PipelinedXferOptions { + fileSize?: number; + initialOffset?: number; + parallelSegmentCount?: number; + pipelineConcurrency?: number; + onProgress?: (bytes: number) => void; + shouldAbort?: () => boolean; + transferId?: string; + segmentIndex?: number; + reconnect?: TransferReconnectContext; + onResumeOffset?: (offset: number) => void; +} + +function closeAllTransferSessions( + deps: HostTransferDeps, + ctx: TransferReconnectContext, + parallelWorkers: number, +): void { + deps.closeDedicatedTransferSession(ctx.dedicatedSourceSessionId); + deps.closeDedicatedTransferSession(ctx.dedicatedDestSessionId); + for (let i = 0; i < parallelWorkers; i++) { + deps.closeDedicatedTransferSession(`${ctx.dedicatedSourceSessionId}:p${i}`); + deps.closeDedicatedTransferSession(`${ctx.dedicatedDestSessionId}:p${i}`); + } +} + +interface TransferFileHandles { + sourceSftp: SFTPWrapper; + destSftp: SFTPWrapper; + srcHandle: Buffer; + dstHandle: Buffer; + close: () => Promise; +} + +async function openTransferFileHandles( + sourceSftp: SFTPWrapper, + destSftp: SFTPWrapper, + sourcePath: string, + destPath: string, + resume: boolean, +): Promise { + const srcHandle = await promisifySftpOpen( + sourceSftp, + sourcePath, + SFTP_OPEN_READ, + 0o666, + ); + const dstHandle = await promisifySftpOpen( + destSftp, + destPath, + resume ? SFTP_OPEN_WRITE_RESUME : SFTP_OPEN_WRITE, + 0o666, + ); + + return { + sourceSftp, + destSftp, + srcHandle, + dstHandle, + close: async () => { + await promisifySftpClose(sourceSftp, srcHandle).catch(() => {}); + await promisifySftpClose(destSftp, dstHandle).catch(() => {}); + }, + }; +} + +/** + * Parallel SFTP copy using the same scheduling model as ssh2's fastXfer. + * Scoped to [fileBaseOffset, fileBaseOffset + byteLength) so callers can reset + * scheduler state between segments on large files. + */ +async function runFastSftpCopy( + sourceSftp: SFTPWrapper, + srcHandle: Buffer, + destSftp: SFTPWrapper, + dstHandle: Buffer, + byteLength: number, + fileBaseOffset: number, + options: PipelinedXferOptions, + sourceReadClock: ReturnType, + destWriteClock: ReturnType, +): Promise { + let concurrency = options.pipelineConcurrency ?? SFTP_XFER_CONCURRENCY; + let chunkSize = SFTP_XFER_CHUNK_SIZE; + let bufsize = chunkSize * concurrency; + while (bufsize > byteLength && concurrency > 1) { + bufsize -= chunkSize; + concurrency -= 1; + } + if (byteLength <= chunkSize) { + chunkSize = byteLength; + concurrency = 1; + bufsize = byteLength; + } + + const readbuf = Buffer.alloc(bufsize); + const progress = createThrottledProgress(options.onProgress); + let lastActivity = Date.now(); + let stallTimer: ReturnType | undefined; + + await new Promise((resolve, reject) => { + let finished = false; + let hadError = false; + let pdst = 0; + let total = 0; + + const fail = (err: Error) => { + if (hadError) return; + hadError = true; + finished = true; + progress.flush(); + reject(err); + }; + + const succeed = () => { + if (finished) return; + finished = true; + progress.flush(); + resolve(); + }; + + stallTimer = setInterval(() => { + if (finished) return; + if (options.shouldAbort?.()) { + fail(new TransferCancelledError()); + return; + } + if (Date.now() - lastActivity > 45000) { + fail( + new TransferStalledError( + fileBaseOffset + total, + options.segmentIndex, + ), + ); + } + }, 10000); + + const onread = ( + err: Error | undefined, + nb: number, + _data: Buffer | undefined, + localDstPos: number, + datapos: number, + origChunkLen: number, + ) => { + if (err) { + fail(err); + return; + } + if (options.shouldAbort?.()) { + fail(new TransferCancelledError()); + return; + } + + noteHopStart(destWriteClock); + destSftp.write( + dstHandle, + readbuf, + datapos, + nb, + fileBaseOffset + localDstPos, + (writeErr: Error | undefined) => { + noteHopEnd(destWriteClock); + if (writeErr) { + fail(writeErr); + return; + } + + total += nb; + progress.add(nb); + lastActivity = Date.now(); + + if (options.shouldAbort?.()) { + fail(new TransferCancelledError()); + return; + } + + if (nb < origChunkLen) { + singleRead(datapos, localDstPos + nb, origChunkLen - nb); + return; + } + + if (total >= byteLength) { + succeed(); + return; + } + + if (pdst >= byteLength) { + return; + } + + const chunk = Math.min(chunkSize, byteLength - pdst); + singleRead(datapos, pdst, chunk); + pdst += chunk; + }, + ); + }; + + const makeCb = + (psrc: number, localFilePos: number, chunk: number) => + (err: Error | undefined, nb?: number, data?: Buffer) => { + onread(err, nb ?? 0, data, localFilePos, psrc, chunk); + }; + + const singleRead = (psrc: number, localFilePos: number, chunk: number) => { + if (options.shouldAbort?.()) { + fail(new TransferCancelledError()); + return; + } + noteHopStart(sourceReadClock); + sourceSftp.read( + srcHandle, + readbuf, + psrc, + chunk, + fileBaseOffset + localFilePos, + (err, nb, data) => { + noteHopEnd(sourceReadClock); + makeCb(psrc, localFilePos, chunk)(err, nb, data); + }, + ); + }; + + const startReads = () => { + let reads = 0; + let psrc = 0; + while (pdst < byteLength && reads < concurrency) { + const chunk = Math.min(chunkSize, byteLength - pdst); + singleRead(psrc, pdst, chunk); + psrc += chunk; + pdst += chunk; + reads += 1; + } + }; + + startReads(); + }).finally(() => { + if (stallTimer) clearInterval(stallTimer); + }); +} + +async function runFastSftpCopySegmented( + handles: TransferFileHandles, + fileSize: number, + options: PipelinedXferOptions, + sourceReadClock: ReturnType, + destWriteClock: ReturnType, + sourcePath: string, + destPath: string, +): Promise { + const parallel = clampParallelSegmentCount(options.parallelSegmentCount); + if (parallel > 1 && options.reconnect) { + return runFastSftpCopySegmentedParallel( + handles, + fileSize, + { ...options, parallelSegmentCount: parallel }, + sourceReadClock, + destWriteClock, + sourcePath, + destPath, + ); + } + return runFastSftpCopySegmentedSequential( + handles, + fileSize, + options, + sourceReadClock, + destWriteClock, + sourcePath, + destPath, + ); +} + +async function runFastSftpCopySegmentedSequential( + handles: TransferFileHandles, + fileSize: number, + options: PipelinedXferOptions, + sourceReadClock: ReturnType, + destWriteClock: ReturnType, + sourcePath: string, + destPath: string, +): Promise { + const transferId = options.transferId; + const segmentCount = Math.ceil(fileSize / SFTP_XFER_SEGMENT_SIZE); + let offset = options.initialOffset ?? 0; + let currentHandles = handles; + + while (offset < fileSize) { + if (options.shouldAbort?.()) { + throw new TransferCancelledError(); + } + + let segLen = Math.min(SFTP_XFER_SEGMENT_SIZE, fileSize - offset); + const segmentIndex = Math.floor(offset / SFTP_XFER_SEGMENT_SIZE); + let attempts = 0; + + while (true) { + pluginCtx().log.info( + `Transfer segment started: ${transferId} segment ${segmentIndex}/${segmentCount} (offset=${offset}, len=${segLen}, attempt=${attempts + 1})`, + ); + + try { + await runFastSftpCopy( + currentHandles.sourceSftp, + currentHandles.srcHandle, + currentHandles.destSftp, + currentHandles.dstHandle, + segLen, + offset, + { ...options, segmentIndex }, + sourceReadClock, + destWriteClock, + ); + + pluginCtx().log.info( + `Transfer segment completed: ${transferId} segment ${segmentIndex}/${segmentCount} (offset=${offset}, len=${segLen})`, + ); + + offset += segLen; + break; + } catch (err) { + if (err instanceof TransferCancelledError) { + throw err; + } + + const message = getErrorMessage(err, "Segment transfer failed"); + const recoverable = + options.reconnect && + isRecoverableTransferError(err) && + attempts < SFTP_SEQUENTIAL_SEGMENT_MAX_ATTEMPTS; + + pluginCtx().log.error( + `Transfer segment failed: ${transferId} segment ${segmentIndex} (offset=${offset}, len=${segLen}, attempt=${attempts + 1}/${SFTP_SEQUENTIAL_SEGMENT_MAX_ATTEMPTS}, recoverable=${recoverable})`, + err as Error, + ); + + if (!recoverable || !options.reconnect) { + throw new Error( + `Segment ${segmentIndex} failed at offset ${offset}: ${message}`, + ); + } + + attempts += 1; + await closeTransferHandlesSafe(currentHandles); + + const { sourceSftp, destSftp } = await resetDedicatedTransferSessions( + options.reconnect, + attempts, + message, + ); + + let resumeOffset = offset; + try { + resumeOffset = await probeDestResumeOffset( + destSftp, + destPath, + fileSize, + ); + } catch { + /* use last known offset */ + } + + if (resumeOffset > offset) { + pluginCtx().log.info( + `Resuming transfer from destination size: ${transferId} (previousOffset=${offset}, resumeOffset=${resumeOffset}, fileSize=${fileSize})`, + ); + offset = resumeOffset; + options.onResumeOffset?.(offset); + } + + if (offset >= fileSize) { + break; + } + + segLen = Math.min(SFTP_XFER_SEGMENT_SIZE, fileSize - offset); + currentHandles = await openTransferFileHandles( + sourceSftp, + destSftp, + sourcePath, + destPath, + offset > 0, + ); + } + } + } + + return currentHandles; +} + +interface WorkerSessionCache { + sourceSession: SSHSessionLike; + destSession: SSHSessionLike; + sourceSftp: SFTPWrapper; + destSftp: SFTPWrapper; +} + +async function runFastSftpCopySegmentedParallel( + handles: TransferFileHandles, + fileSize: number, + options: PipelinedXferOptions, + sourceReadClock: ReturnType, + destWriteClock: ReturnType, + sourcePath: string, + destPath: string, +): Promise { + const transferId = options.transferId; + const parallel = clampParallelSegmentCount(options.parallelSegmentCount); + const initialOffset = options.initialOffset ?? 0; + const reconnect = options.reconnect!; + + let destResumeSize = initialOffset; + try { + destResumeSize = await probeDestResumeOffset( + handles.destSftp, + destPath, + fileSize, + ); + } catch { + /* use initial offset */ + } + + const jobs = buildSegmentCopyJobs(fileSize, initialOffset, destResumeSize); + if (jobs.length === 0) { + options.onResumeOffset?.(fileSize); + return handles; + } + + pluginCtx().log.info( + `Starting parallel segment transfer: ${transferId} (parallel=${parallel}, segmentJobs=${jobs.length}, fileSize=${fileSize}, destResumeSize=${destResumeSize})`, + ); + + updateTransfer(transferId ?? "", { + parallelSegmentCount: parallel, + bytesTransferred: destResumeSize, + }); + options.onResumeOffset?.(destResumeSize); + + let aggregateBytes = destResumeSize; + let lastDestProbeAt = 0; + const DEST_PROGRESS_PROBE_MS = 1500; + + const refreshDestProgress = async (force = false): Promise => { + const now = Date.now(); + if (!force && now - lastDestProbeAt < DEST_PROGRESS_PROBE_MS) { + return aggregateBytes; + } + lastDestProbeAt = now; + try { + const size = await probeDestResumeOffset( + handles.destSftp, + destPath, + fileSize, + ); + if (size > aggregateBytes) { + aggregateBytes = size; + } else if (force) { + aggregateBytes = size; + } + options.onResumeOffset?.(aggregateBytes); + if (transferId) { + updateTransfer(transferId, { bytesTransferred: aggregateBytes }); + } + } catch { + /* keep last known progress */ + } + return aggregateBytes; + }; + + /** Parallel lanes share one dest file — use dest size, not summed segment deltas. */ + const reportDelta = (_delta: number) => { + void refreshDestProgress(false); + }; + + let nextJobIndex = 0; + const workerCaches = new Map(); + + const resetParallelLane = async ( + workerId: number, + attempt: number, + reason: string, + ): Promise => { + pluginCtx().log.warn( + `Resetting parallel transfer lane (fresh SSH session): ${reconnect.transferId} lane ${workerId} (attempt=${attempt}, reason=${reason})`, + ); + workerCaches.delete(workerId); + reconnect.deps.closeDedicatedTransferSession( + `${reconnect.dedicatedSourceSessionId}:p${workerId}`, + ); + reconnect.deps.closeDedicatedTransferSession( + `${reconnect.dedicatedDestSessionId}:p${workerId}`, + ); + const delayMs = + TRANSFER_SESSION_RESET_DELAYS_MS[ + Math.min(attempt - 1, TRANSFER_SESSION_RESET_DELAYS_MS.length - 1) + ] ?? 3000; + await new Promise((resolve) => setTimeout(resolve, delayMs)); + }; + + const ensureWorkerSessions = async ( + workerId: number, + ): Promise => { + const cached = workerCaches.get(workerId); + if (cached) return cached; + + const srcId = `${reconnect.dedicatedSourceSessionId}:p${workerId}`; + const dstId = `${reconnect.dedicatedDestSessionId}:p${workerId}`; + const sourceSession = await reconnect.deps.openDedicatedTransferSession( + reconnect.browseSourceSessionId, + srcId, + reconnect.userId, + reconnect.transferId, + { allowBrowseDisconnected: true }, + ); + const destSession = await reconnect.deps.openDedicatedTransferSession( + reconnect.browseDestSessionId, + dstId, + reconnect.userId, + reconnect.transferId, + { allowBrowseDisconnected: true }, + ); + const sourceSftp = await reconnect.deps.getSessionSftp(sourceSession); + const destSftp = await reconnect.deps.getSessionSftp(destSession); + const entry: WorkerSessionCache = { + sourceSession, + destSession, + sourceSftp, + destSftp, + }; + workerCaches.set(workerId, entry); + return entry; + }; + + const runSegmentJob = async ( + job: SegmentCopyJob, + workerId: number, + ): Promise => { + let attempts = 0; + + while (true) { + if (options.shouldAbort?.()) { + throw new TransferCancelledError(); + } + + pluginCtx().log.info( + `Transfer segment started: ${transferId} segment ${job.segmentIndex} lane ${workerId} (offset=${job.offset}, len=${job.length}, attempt=${attempts + 1})`, + ); + + const { sourceSftp, destSftp } = await ensureWorkerSessions(workerId); + const laneHandles = await openTransferFileHandles( + sourceSftp, + destSftp, + sourcePath, + destPath, + job.offset > 0, + ); + + try { + await runFastSftpCopy( + laneHandles.sourceSftp, + laneHandles.srcHandle, + laneHandles.destSftp, + laneHandles.dstHandle, + job.length, + job.offset, + { + ...options, + segmentIndex: job.segmentIndex, + onProgress: reportDelta, + }, + sourceReadClock, + destWriteClock, + ); + + pluginCtx().log.info( + `Transfer segment completed: ${transferId} segment ${job.segmentIndex} lane ${workerId} (offset=${job.offset}, len=${job.length})`, + ); + + await closeTransferHandlesSafe(laneHandles); + await refreshDestProgress(true); + return; + } catch (err) { + await closeTransferHandlesSafe(laneHandles); + + if (err instanceof TransferCancelledError) { + throw err; + } + + const message = getErrorMessage(err, "Segment transfer failed"); + const recoverable = + isRecoverableTransferError(err) && + attempts < SFTP_PARALLEL_SEGMENT_MAX_ATTEMPTS; + + pluginCtx().log.error( + `Transfer segment failed: ${transferId} segment ${job.segmentIndex} lane ${workerId} (offset=${job.offset}, len=${job.length}, attempt=${attempts + 1}/${SFTP_PARALLEL_SEGMENT_MAX_ATTEMPTS}, recoverable=${recoverable})`, + err as Error, + ); + + if (!recoverable) { + if ( + transferId && + (await finalizeStreamTransferIfDestAtSize( + transferId, + handles.destSftp, + destPath, + fileSize, + )) + ) { + return; + } + throw new Error( + `Segment ${job.segmentIndex} failed at offset ${job.offset}: ${message}`, + ); + } + + attempts += 1; + await resetParallelLane(workerId, attempts, message); + await refreshDestProgress(true); + } + } + }; + + const runWorker = async (workerId: number) => { + while (true) { + if (options.shouldAbort?.()) { + throw new TransferCancelledError(); + } + + const jobIndex = nextJobIndex; + nextJobIndex += 1; + if (jobIndex >= jobs.length) { + return; + } + + await runSegmentJob(jobs[jobIndex], workerId); + } + }; + + try { + await Promise.all( + Array.from({ length: parallel }, (_, workerId) => runWorker(workerId)), + ); + await refreshDestProgress(true); + } finally { + workerCaches.clear(); + for (let i = 0; i < parallel; i++) { + reconnect.deps.closeDedicatedTransferSession( + `${reconnect.dedicatedSourceSessionId}:p${i}`, + ); + reconnect.deps.closeDedicatedTransferSession( + `${reconnect.dedicatedDestSessionId}:p${i}`, + ); + } + } + + options.onResumeOffset?.(fileSize); + return handles; +} + +function promisifyFastGet( + sftp: SFTPWrapper, + remotePath: string, + localPath: string, + opts: { + concurrency?: number; + chunkSize?: number; + fileSize?: number; + step?: (totalTransferred: number, chunk: number, total: number) => void; + }, +): Promise { + return new Promise((resolve, reject) => { + sftp.fastGet(remotePath, localPath, opts, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +async function pipelinedSftpFile( + sourceSftp: SFTPWrapper, + destSftp: SFTPWrapper, + sourcePath: string, + destPath: string, + options: PipelinedXferOptions = {}, +): Promise { + let fileSize = options.fileSize ?? 0; + const stats = createEmptyXferStats(); + stats.destWriteKind = "sftp"; + + let handles: TransferFileHandles | null = null; + const transferId = options.transferId; + + try { + if (!fileSize) { + const srcProbe = await promisifySftpOpen( + sourceSftp, + sourcePath, + SFTP_OPEN_READ, + 0o666, + ); + try { + const fstats = await promisifySftpFstat(sourceSftp, srcProbe); + fileSize = fstats.size; + } finally { + await promisifySftpClose(sourceSftp, srcProbe).catch(() => {}); + } + } + if (fileSize <= 0) return stats; + + stats.bytes = fileSize; + + const sourceReadClock = createHopWallClock(); + const destWriteClock = createHopWallClock(); + + const runCopyWithRetry = async (): Promise => { + let attempts = 0; + let sftpSource = sourceSftp; + let sftpDest = destSftp; + const parallelLanes = clampParallelSegmentCount( + options.parallelSegmentCount, + ); + const isParallelCopy = parallelLanes > 1; + const maxCopyAttempts = isParallelCopy + ? SFTP_PARALLEL_COPY_MAX_ATTEMPTS + : SFTP_SEQUENTIAL_COPY_MAX_ATTEMPTS; + + while (true) { + const resumeOffset = await probeDestResumeOffset( + sftpDest, + destPath, + fileSize, + ); + if (resumeOffset >= fileSize) { + options.onResumeOffset?.(resumeOffset); + return; + } + if (resumeOffset > 0) { + options.onResumeOffset?.(resumeOffset); + if (attempts === 0) { + pluginCtx().log.info( + `Resuming transfer from destination size: ${transferId} (resumeOffset=${resumeOffset}, fileSize=${fileSize})`, + ); + } + } + + try { + if (handles) { + await handles.close().catch(() => {}); + } + handles = await openTransferFileHandles( + sftpSource, + sftpDest, + sourcePath, + destPath, + resumeOffset > 0, + ); + + if (fileSize <= SFTP_XFER_SEGMENT_THRESHOLD) { + await runFastSftpCopy( + handles.sourceSftp, + handles.srcHandle, + handles.destSftp, + handles.dstHandle, + fileSize - resumeOffset, + resumeOffset, + options, + sourceReadClock, + destWriteClock, + ); + } else { + handles = await runFastSftpCopySegmented( + handles, + fileSize, + { ...options, initialOffset: resumeOffset }, + sourceReadClock, + destWriteClock, + sourcePath, + destPath, + ); + } + return; + } catch (err) { + if (err instanceof TransferCancelledError) { + throw err; + } + + const recoverable = + options.reconnect && + isRecoverableTransferError(err) && + attempts < maxCopyAttempts; + + pluginCtx().log.error( + `Transfer copy attempt failed: ${transferId} (attempt=${attempts + 1}/${maxCopyAttempts}, recoverable=${recoverable}${isParallelCopy ? `, parallelLanes=${parallelLanes}` : ""})`, + err as Error, + ); + + if (!recoverable || !options.reconnect) { + if ( + transferId && + (await finalizeStreamTransferIfDestAtSize( + transferId, + sftpDest, + destPath, + fileSize, + )) + ) { + return; + } + throw err; + } + + attempts += 1; + await closeTransferHandlesSafe(handles); + + if (isParallelCopy) { + closeAllTransferSessions( + options.reconnect.deps, + options.reconnect, + parallelLanes, + ); + const delayMs = + TRANSFER_SESSION_RESET_DELAYS_MS[ + Math.min( + attempts - 1, + TRANSFER_SESSION_RESET_DELAYS_MS.length - 1, + ) + ] ?? 3000; + pluginCtx().log.warn( + `Restarting parallel copy after segment exhaustion: ${transferId} (attempt=${attempts}, delayMs=${delayMs})`, + ); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } else { + const reset = await resetDedicatedTransferSessions( + options.reconnect, + attempts, + getErrorMessage(err, "copy failed"), + ); + sftpSource = reset.sourceSftp; + sftpDest = reset.destSftp; + } + } + } + }; + + if (transferId) { + let abortFn: ((err: Error) => void) | null = null; + const control: ActiveXferControl = { + abort: (err) => abortFn?.(err), + closeResources: async () => { + await closeTransferHandlesSafe(handles); + }, + }; + activeXferControls.set(transferId, control); + + try { + await new Promise((resolve, reject) => { + abortFn = reject; + runCopyWithRetry().then(resolve).catch(reject); + }); + } finally { + activeXferControls.delete(transferId); + } + } else { + await runCopyWithRetry(); + } + + stats.sourceReadSpanMs = hopSpanMs(sourceReadClock); + stats.destWriteSpanMs = hopSpanMs(destWriteClock); + } finally { + if (handles) { + await handles.close().catch(() => {}); + } + } + + return stats; +} + +/** Source SFTP → local filesystem (skips SFTP write when dest SSH target is this host). */ +async function pipelinedSftpToLocalFile( + sourceSftp: SFTPWrapper, + sourcePath: string, + destPath: string, + options: PipelinedXferOptions = {}, +): Promise { + let fileSize = options.fileSize ?? 0; + const stats = createEmptyXferStats(); + stats.destWriteKind = "local"; + + if (!fileSize) { + const srcHandle = await promisifySftpOpen( + sourceSftp, + sourcePath, + SFTP_OPEN_READ, + 0o666, + ); + try { + const fstats = await promisifySftpFstat(sourceSftp, srcHandle); + fileSize = fstats.size; + } finally { + await promisifySftpClose(sourceSftp, srcHandle).catch(() => {}); + } + } + if (fileSize <= 0) return stats; + + stats.bytes = fileSize; + const localPath = sftpPathToLocalPath(destPath); + const progress = createThrottledProgress(options.onProgress); + const transferStart = Date.now(); + + await promisifyFastGet(sourceSftp, sourcePath, localPath, { + concurrency: options.pipelineConcurrency ?? SFTP_XFER_CONCURRENCY, + chunkSize: SFTP_XFER_CHUNK_SIZE, + fileSize, + step: (_total, chunk) => { + if (options.shouldAbort?.()) { + throw new TransferCancelledError(); + } + progress.add(chunk); + }, + }); + + progress.flush(); + stats.sourceReadSpanMs = elapsedMs(transferStart); + stats.destWriteSpanMs = stats.sourceReadSpanMs; + return stats; +} + +async function transferFileData( + sourceSftp: SFTPWrapper, + destSftp: SFTPWrapper, + destSession: SSHSessionLike, + sourcePath: string, + destPath: string, + fileSize: number, + onProgress?: (bytes: number) => void, + shouldAbort?: () => boolean, + transferId?: string, + reconnect?: TransferReconnectContext, + onResumeOffset?: (offset: number) => void, + parallelSegmentCount?: number, +): Promise { + const tuning = selectTransferTuning( + fileSize, + reconnect?.profileKey + ? getTransferProfile(reconnect.profileKey) + : undefined, + parallelSegmentCount, + ); + const pipeOptions: PipelinedXferOptions = { + fileSize, + onProgress, + shouldAbort, + transferId, + reconnect, + onResumeOffset, + parallelSegmentCount: tuning.parallelSegmentCount, + pipelineConcurrency: tuning.pipelineConcurrency, + }; + + if (transferId) { + updateTransfer(transferId, { + parallelSegmentCount: tuning.parallelSegmentCount, + }); + } + + const startedAt = Date.now(); + const shouldProfile = fileSize >= SFTP_XFER_SEGMENT_THRESHOLD; + pluginCtx().log.info( + `Selected adaptive transfer tuning: ${transferId} (fileSize=${fileSize}, parallelSegmentCount=${tuning.parallelSegmentCount}, pipelineConcurrency=${tuning.pipelineConcurrency}, explicitParallelOverride=${parallelSegmentCount !== undefined})`, + ); + try { + const stats = isLocalSshEndpoint(destSession.ip) + ? await pipelinedSftpToLocalFile( + sourceSftp, + sourcePath, + destPath, + pipeOptions, + ) + : await pipelinedSftpFile( + sourceSftp, + destSftp, + sourcePath, + destPath, + pipeOptions, + ); + if (shouldProfile && reconnect?.profileKey) { + recordTransferProfile(reconnect.profileKey, { + bytes: stats.bytes, + durationMs: Math.max(1, Date.now() - startedAt), + lanes: tuning.parallelSegmentCount, + pipelineConcurrency: tuning.pipelineConcurrency, + failed: false, + }); + } + return stats; + } catch (err) { + if (shouldProfile && reconnect?.profileKey) { + recordTransferProfile(reconnect.profileKey, { + bytes: 0, + durationMs: Math.max(1, Date.now() - startedAt), + lanes: tuning.parallelSegmentCount, + pipelineConcurrency: tuning.pipelineConcurrency, + failed: true, + }); + } + throw err; + } +} + +const DIRECT_BENCHMARK_BYTES = 8 * 1024 * 1024; +const DIRECT_ROUTE_CACHE_MS = 10 * 60 * 1000; + +function getDirectEndpoint( + sourceSession: SSHSessionLike, + destSession: SSHSessionLike, +): DirectTransferEndpoint | null { + if ( + !sourceSession.ip || + !destSession.ip || + !destSession.username || + !destSession.port || + sourceSession.transferPlatform !== "unix" || + destSession.transferPlatform !== "unix" + ) { + return null; + } + return { + host: destSession.ip, + port: destSession.port, + username: destSession.username, + }; +} + +function directRouteKey( + sourceSession: SSHSessionLike, + endpoint: DirectTransferEndpoint, +): string { + return `${sourceSession.username ?? ""}@${sourceSession.ip}->${endpoint.username}@${endpoint.host}:${endpoint.port}`; +} + +function transferProfileKey( + sourceSession: SSHSessionLike, + destSession: SSHSessionLike, + userId: string, +): string { + return `${userId}:${sourceSession.username ?? ""}@${sourceSession.ip ?? "local"}:${sourceSession.port ?? 22}->${destSession.username ?? ""}@${destSession.ip ?? "local"}:${destSession.port ?? 22}`; +} + +async function benchmarkTransferRoutes( + deps: HostTransferDeps, + transferId: string, + sourceSession: SSHSessionLike, + destSession: SSHSessionLike, + endpoint: DirectTransferEndpoint, + profileKey?: string, +): Promise<{ useDirect: boolean; directMs?: number; relayMs?: number }> { + const key = directRouteKey(sourceSession, endpoint); + const cached = directRouteCache.get(key); + if (cached && cached.expiresAt > Date.now()) return cached; + const learned = profileKey + ? getRecentDirectRouteDecision(profileKey, DIRECT_ROUTE_CACHE_MS) + : undefined; + if (learned) { + const result = { + ...learned, + expiresAt: Date.now() + DIRECT_ROUTE_CACHE_MS, + }; + directRouteCache.set(key, result); + return result; + } + + updateTransfer(transferId, { phase: "benchmarking" }); + const probe = await execCommand( + deps, + sourceSession, + `command -v rsync >/dev/null && ${buildDirectProbeCommand(endpoint)}`, + { timeoutMs: 8000 }, + ).catch(() => ({ code: 1, stderr: "" })); + if (probe.code !== 0) { + const result = { + useDirect: false, + expiresAt: Date.now() + DIRECT_ROUTE_CACHE_MS, + }; + directRouteCache.set(key, result); + return result; + } + + const probeId = randomUUID(); + const sourceProbe = `/tmp/termix-route-${probeId}.bin`; + const directProbe = `/tmp/termix-route-${probeId}.direct`; + const relayProbe = `/tmp/termix-route-${probeId}.relay`; + const cleanup = async () => { + await Promise.allSettled([ + execCommand(deps, sourceSession, `rm -f ${quoteShell(sourceProbe)}`), + execCommand( + deps, + destSession, + `rm -f ${quoteShell(directProbe)} ${quoteShell(relayProbe)}`, + ), + ]); + }; + + try { + const created = await execCommand( + deps, + sourceSession, + `dd if=/dev/zero of=${quoteShell(sourceProbe)} bs=1048576 count=8 status=none`, + { timeoutMs: 8000 }, + ); + if (created.code !== 0) throw new Error("Failed to create route probe"); + + const directStart = performance.now(); + const direct = await execCommand( + deps, + sourceSession, + buildDirectRsyncCommand(endpoint, [sourceProbe], directProbe, false), + { timeoutMs: 15000 }, + ); + const directMs = performance.now() - directStart; + if (direct.code !== 0) throw new Error("Direct route probe failed"); + + const [sourceSftp, destSftp] = await Promise.all([ + deps.getSessionSftp(sourceSession), + deps.getSessionSftp(destSession), + ]); + const relayStart = performance.now(); + const relayDeadline = Date.now() + 15000; + await transferFileData( + sourceSftp, + destSftp, + destSession, + sourceProbe, + relayProbe, + DIRECT_BENCHMARK_BYTES, + undefined, + () => Date.now() >= relayDeadline, + ); + const relayMs = performance.now() - relayStart; + const profile = profileKey + ? recordDirectRouteBenchmark(profileKey, directMs, relayMs) + : undefined; + const useDirect = + shouldUseDirectTransfer(directMs, relayMs) && + (!profile || profile.outcomeSamples < 2 || profile.failureRate < 0.25); + const result = { + useDirect, + directMs, + relayMs, + expiresAt: Date.now() + DIRECT_ROUTE_CACHE_MS, + }; + directRouteCache.set(key, result); + updateTransfer(transferId, { + timings: { + ...activeTransfers.get(transferId)?.timings, + directBenchmarkMs: directMs, + relayBenchmarkMs: relayMs, + }, + }); + return result; + } catch { + const result = { + useDirect: false, + expiresAt: Date.now() + DIRECT_ROUTE_CACHE_MS, + }; + directRouteCache.set(key, result); + return result; + } finally { + await cleanup(); + } +} + +async function tryAdaptiveDirectTransfer( + deps: HostTransferDeps, + transferId: string, + sourceSession: SSHSessionLike, + destSession: SSHSessionLike, + sourcePaths: string[], + destPath: string, + move: boolean, + reconnectMeta: TransferReconnectMeta, +): Promise { + const endpoint = getDirectEndpoint(sourceSession, destSession); + if (!endpoint) return null; + + const sourceSftp = await deps.getSessionSftp(sourceSession); + const firstStats = await promisifySftpStat(sourceSftp, sourcePaths[0]); + const destIsDirectory = sourcePaths.length > 1 || firstStats.isDirectory(); + const summary = await scanSourcePathsForRouting( + sourceSftp, + sourcePaths, + transferId, + ); + if (!shouldBenchmarkDirectTransfer(summary.totalBytes)) return null; + + const route = await benchmarkTransferRoutes( + deps, + transferId, + sourceSession, + destSession, + endpoint, + reconnectMeta.profileKey, + ); + if (!route.useDirect) return null; + if (destIsDirectory) { + await ensureDestDirectory(deps, destSession, destPath); + } else { + await ensureDestParentForFile(deps, destSession, destPath); + } + + updateTransfer(transferId, { + method: "direct_rsync", + phase: "transferring", + bytesTransferred: 0, + totalBytes: summary.totalBytes, + }); + const transferStart = performance.now(); + let outputBuffer = ""; + + try { + const result = await execCommand( + deps, + sourceSession, + buildDirectRsyncCommand(endpoint, sourcePaths, destPath, destIsDirectory), + { + shouldAbort: createTransferShouldAbort(transferId), + onOutput: (chunk) => { + outputBuffer = `${outputBuffer}${chunk.toString()}`.slice(-2048); + const matches = [...outputBuffer.matchAll(/([\d,]+)\s+(\d+)%/g)]; + const last = matches.at(-1); + if (!last) return; + const bytes = Number(last[1].replace(/,/g, "")); + if (Number.isFinite(bytes)) { + updateTransfer(transferId, { + bytesTransferred: Math.min(bytes, summary.totalBytes), + }); + } + }, + }, + ); + if (result.code !== 0) { + throw new Error(result.stderr || "Direct rsync transfer failed"); + } + + const workItems: FileWorkItem[] = []; + if (destIsDirectory) { + for (const sourcePath of sourcePaths) { + workItems.push( + ...(await collectFileWorkItems(sourceSftp, sourcePath, destPath)), + ); + } + } else { + workItems.push({ + sourcePath: sourcePaths[0], + destPath, + mode: firstStats.mode, + size: firstStats.size, + }); + } + for (const item of workItems) { + await verifyTransferredFile( + deps, + transferId, + reconnectMeta, + item.sourcePath, + item.destPath, + ); + } + if (move) { + await deleteSourcePathsAfterSuccess( + deps, + transferId, + sourceSession, + sourcePaths, + ); + } + + const transferMs = performance.now() - transferStart; + if (reconnectMeta.profileKey) { + recordDirectRouteOutcome( + reconnectMeta.profileKey, + false, + Date.now(), + DIRECT_ROUTE_CACHE_MS, + ); + } + return finalizeTransfer(transferId, { + status: "success", + phase: "verifying", + method: "direct_rsync", + bytesTransferred: summary.totalBytes, + totalBytes: summary.totalBytes, + sourcePaths, + destPath, + sourceDeleted: move, + moveRequested: move, + integrityVerified: true, + timings: { + ...activeTransfers.get(transferId)?.timings, + transferMs, + transferBytes: summary.totalBytes, + endToEndMbPerSec: computeTransferMbPerSec( + summary.totalBytes, + transferMs, + ), + }, + }); + } catch (error) { + if (error instanceof TransferCancelledError) throw error; + pluginCtx().log.warn( + `Direct transfer failed, falling back to relay: ${transferId} (${getErrorMessage(error, "Direct transfer failed")})`, + ); + if (reconnectMeta.profileKey) { + recordDirectRouteOutcome( + reconnectMeta.profileKey, + true, + Date.now(), + DIRECT_ROUTE_CACHE_MS, + ); + } + directRouteCache.set(directRouteKey(sourceSession, endpoint), { + useDirect: false, + expiresAt: Date.now() + DIRECT_ROUTE_CACHE_MS, + }); + updateTransfer(transferId, { + method: undefined, + phase: "transferring", + bytesTransferred: 0, + integrityVerified: false, + }); + return null; + } +} + +async function transferSingleFile( + deps: HostTransferDeps, + transferId: string, + sourceSession: SSHSessionLike, + destSession: SSHSessionLike, + sourcePath: string, + destPath: string, + move: boolean, + reconnectMeta: TransferReconnectMeta, + requestedParallelSegments?: number, +): Promise { + const sourceSftp = await deps.getSessionSftp(sourceSession); + const destSftp = await deps.getSessionSftp(destSession); + const shouldAbort = createTransferShouldAbort(transferId); + + const reconnect = buildTransferReconnectContext( + deps, + transferId, + reconnectMeta, + ); + + const stats = await promisifySftpStat(sourceSftp, sourcePath); + if (!stats.isFile()) { + throw new Error(`Source is not a regular file: ${sourcePath}`); + } + + updateTransfer(transferId, { + method: "stream", + phase: "transferring", + totalBytes: stats.size, + }); + + let bytesTransferred = 0; + try { + const destStat = await promisifySftpStat(destSftp, destPath); + bytesTransferred = Math.min(destStat.size, stats.size); + } catch { + /* destination file does not exist yet */ + } + + updateTransfer(transferId, { bytesTransferred }); + const prepareStart = Date.now(); + await ensureDestParentForFile(deps, destSession, destPath); + const prepareDestMs = elapsedMs(prepareStart); + updateTransfer(transferId, { timings: { prepareDestMs } }); + + const syncProgress = (absoluteOffset: number) => { + bytesTransferred = absoluteOffset; + updateTransfer(transferId, { bytesTransferred: absoluteOffset }); + }; + + const tuning = selectTransferTuning( + stats.size, + reconnect.profileKey ? getTransferProfile(reconnect.profileKey) : undefined, + requestedParallelSegments, + ); + const parallelLanes = tuning.parallelSegmentCount; + const useAbsoluteProgressOnly = parallelLanes > 1; + + throwIfCancelled(transferId); + trackDestArtifact(transferId, destPath); + await transferFileData( + sourceSftp, + destSftp, + destSession, + sourcePath, + destPath, + stats.size, + useAbsoluteProgressOnly + ? undefined + : (n) => { + bytesTransferred += n; + updateTransfer(transferId, { bytesTransferred }); + }, + shouldAbort, + transferId, + reconnect, + syncProgress, + requestedParallelSegments, + ); + + await verifyTransferredFile( + deps, + transferId, + reconnectMeta, + sourcePath, + destPath, + ); + + if (move) { + await deleteSourcePathsAfterSuccess(deps, transferId, sourceSession, [ + sourcePath, + ]); + } + + return finalizeTransfer(transferId, { + status: "success", + phase: "transferring", + method: "stream", + bytesTransferred: stats.size, + totalBytes: stats.size, + sourcePaths: [sourcePath], + destPath, + sourceDeleted: move, + moveRequested: move, + timings: buildStreamTransferTimings(transferId, stats.size, prepareDestMs), + }); +} + +async function cleanupDestItems( + deps: HostTransferDeps, + destSession: SSHSessionLike, + destDir: string, + basenames: string[], +): Promise { + const destSftp = await deps.getSessionSftp(destSession); + for (const name of basenames) { + const target = joinPath(destDir, name); + try { + await deletePathSftp(destSftp, target); + } catch { + /* best effort */ + } + } +} + +async function deleteSourcePaths( + deps: HostTransferDeps, + sourceSession: SSHSessionLike, + sourcePaths: string[], +): Promise { + const sourceSftp = await deps.getSessionSftp(sourceSession); + for (const path of sourcePaths) { + try { + await deletePathSftp(sourceSftp, path); + } catch { + /* best effort */ + } + } +} + +async function transferViaTar( + deps: HostTransferDeps, + transferId: string, + sourceSession: SSHSessionLike, + destSession: SSHSessionLike, + sourcePaths: string[], + destPath: string, + move: boolean, + reconnectMeta: TransferReconnectMeta, +): Promise { + const sourceSftp = await deps.getSessionSftp(sourceSession); + const destSftp = await deps.getSessionSftp(destSession); + const reconnect = buildTransferReconnectContext( + deps, + transferId, + reconnectMeta, + ); + const archiveId = randomUUID(); + const tempArchive = `/tmp/termix-transfer-${archiveId}.tar.gz`; + updateTransfer(transferId, { tempArchivePath: tempArchive }); + trackDestArtifact(transferId, tempArchive); + const workingDir = getWorkingDir(sourcePaths); + const basenames = sourcePaths.map((p) => basename(p)); + const escapedNames = basenames.map((n) => `'${escapeShell(n)}'`).join(" "); + const escapedDir = escapeShell(workingDir); + const escapedArchive = escapeShell(tempArchive); + const escapedDest = escapeShell(destPath); + + await ensureDestDirectory(deps, destSession, destPath); + + updateTransfer(transferId, { + method: "tar", + phase: "compressing", + }); + + throwIfCancelled(transferId); + const compressStart = Date.now(); + const compressCmd = `cd '${escapedDir}' && tar -czf '${escapedArchive}' ${escapedNames}`; + const compressResult = await execCommand(deps, sourceSession, compressCmd); + const compressMs = elapsedMs(compressStart); + updateTransfer(transferId, { timings: { compressMs } }); + if (compressResult.code !== 0) { + throw new Error( + compressResult.stderr || "Failed to compress on source host", + ); + } + + throwIfCancelled(transferId); + let archiveSize = 0; + try { + const archiveStats = await promisifySftpStat(sourceSftp, tempArchive); + archiveSize = archiveStats.size; + } catch { + /* size unknown */ + } + + updateTransfer(transferId, { + phase: "transferring", + totalBytes: archiveSize, + bytesTransferred: 0, + }); + + let bytesTransferred = 0; + const transferStart = Date.now(); + const xferStats = createEmptyXferStats(); + const syncProgress = (absoluteOffset: number) => { + bytesTransferred = absoluteOffset; + updateTransfer(transferId, { bytesTransferred: absoluteOffset }); + }; + try { + const fileStats = await transferFileData( + sourceSftp, + destSftp, + destSession, + tempArchive, + tempArchive, + archiveSize, + (n) => { + bytesTransferred += n; + updateTransfer(transferId, { bytesTransferred }); + }, + () => cancelRequestedTransfers.has(transferId), + transferId, + reconnect, + syncProgress, + ); + mergeXferStats(xferStats, fileStats); + await verifyTransferredFile( + deps, + transferId, + reconnectMeta, + tempArchive, + tempArchive, + ); + } catch (err) { + if (!(err instanceof TransferCancelledError)) { + await promisifySftpUnlink(destSftp, tempArchive).catch(() => {}); + await cleanupDestItems(deps, destSession, destPath, basenames); + } + throw err; + } finally { + await promisifySftpUnlink(sourceSftp, tempArchive); + } + throwIfCancelled(transferId); + const transferMs = elapsedMs(transferStart); + const hopTimings = buildTransferHopTimings(xferStats, transferMs); + updateTransfer(transferId, { + timings: { + ...activeTransfers.get(transferId)?.timings, + transferMs, + ...hopTimings, + }, + }); + + updateTransfer(transferId, { phase: "extracting" }); + + throwIfCancelled(transferId); + const extractStart = Date.now(); + const extractCmd = `tar -xzf '${escapedArchive}' -C '${escapedDest}'`; + const extractResult = await execCommand(deps, destSession, extractCmd); + const extractMs = elapsedMs(extractStart); + + await promisifySftpUnlink(destSftp, tempArchive); + + if (extractResult.code !== 0) { + await cleanupDestItems(deps, destSession, destPath, basenames); + throw new Error( + extractResult.stderr || "Failed to extract on destination host", + ); + } + + if (move) { + await deleteSourcePathsAfterSuccess( + deps, + transferId, + sourceSession, + sourcePaths, + ); + } + + return finalizeTransfer(transferId, { + status: "success", + phase: "extracting", + method: "tar", + bytesTransferred, + totalBytes: archiveSize, + sourcePaths, + destPath, + sourceDeleted: move, + moveRequested: move, + timings: { + ...activeTransfers.get(transferId)?.timings, + extractMs, + ...hopTimings, + }, + }); +} + +async function transferViaItemSftp( + deps: HostTransferDeps, + transferId: string, + sourceSession: SSHSessionLike, + destSession: SSHSessionLike, + sourcePaths: string[], + destPath: string, + move: boolean, + destPlatform: TransferPlatform, + reconnectMeta: TransferReconnectMeta, +): Promise { + const sourceSftp = await deps.getSessionSftp(sourceSession); + const destSftp = await deps.getSessionSftp(destSession); + const reconnect = buildTransferReconnectContext( + deps, + transferId, + reconnectMeta, + ); + + const createdDirs = new Set(); + await ensureDirectoryTreeSftp(destSftp, destPath, createdDirs); + + const allWork: FileWorkItem[] = []; + for (const sourcePath of sourcePaths) { + const items = await collectFileWorkItems(sourceSftp, sourcePath, destPath); + allWork.push(...items); + } + + allWork.sort((a, b) => b.size - a.size); + + const totalBytes = allWork.reduce((sum, item) => sum + item.size, 0); + + updateTransfer(transferId, { + method: "item_sftp", + phase: "transferring", + totalItems: allWork.length, + itemsCompleted: 0, + totalBytes, + bytesTransferred: 0, + }); + + const failedPaths: string[] = []; + const createdFiles: string[] = []; + let itemsCompleted = 0; + let bytesTransferred = 0; + const transferStart = Date.now(); + const xferStats = createEmptyXferStats(); + + for (const item of allWork) { + throwIfCancelled(transferId); + trackDestArtifact(transferId, item.destPath); + const bytesBeforeItem = bytesTransferred; + try { + const parentDir = dirname(item.destPath); + if (parentDir && !isRootOnlyPath(parentDir)) { + await ensureDirectoryTreeSftp(destSftp, parentDir, createdDirs); + } + const itemStats = await promisifySftpStat(sourceSftp, item.sourcePath); + const itemXferStats = await transferFileData( + sourceSftp, + destSftp, + destSession, + item.sourcePath, + item.destPath, + itemStats.size, + (n) => { + bytesTransferred += n; + updateTransfer(transferId, { bytesTransferred }); + }, + () => cancelRequestedTransfers.has(transferId), + transferId, + reconnect, + (absoluteOffset) => { + bytesTransferred = bytesBeforeItem + absoluteOffset; + updateTransfer(transferId, { bytesTransferred }); + }, + ); + mergeXferStats(xferStats, itemXferStats); + await verifyTransferredFile( + deps, + transferId, + reconnectMeta, + item.sourcePath, + item.destPath, + ); + updateTransfer(transferId, { phase: "transferring" }); + if (destPlatform !== "windows") { + await promisifySftpChmod(destSftp, item.destPath, item.mode); + } + createdFiles.push(item.destPath); + itemsCompleted++; + updateTransfer(transferId, { itemsCompleted, bytesTransferred }); + } catch (err) { + if (isPermissionError(err as Error)) { + failedPaths.push(item.sourcePath); + itemsCompleted++; + updateTransfer(transferId, { itemsCompleted, bytesTransferred }); + continue; + } + if (!(err instanceof TransferCancelledError)) { + await deletePathSftp(destSftp, item.destPath).catch(() => {}); + for (const created of [...createdFiles].reverse()) { + await deletePathSftp(destSftp, created).catch(() => {}); + } + } + throw err; + } + } + + const transferMs = elapsedMs(transferStart); + const hopTimings = buildTransferHopTimings(xferStats, transferMs); + + const status: TransferStatus = failedPaths.length > 0 ? "partial" : "success"; + + // Move only after every file succeeded — never delete source on partial transfer + if (status === "success" && move) { + await deleteSourcePathsAfterSuccess( + deps, + transferId, + sourceSession, + sourcePaths, + ); + } + + return finalizeTransfer(transferId, { + status, + phase: "transferring", + method: "item_sftp", + itemsCompleted, + totalItems: allWork.length, + failedPaths: failedPaths.length > 0 ? failedPaths : undefined, + sourcePaths, + destPath, + sourceDeleted: status === "success" && move, + moveRequested: move, + integrityVerified: status === "success", + timings: { + ...activeTransfers.get(transferId)?.timings, + transferMs, + ...hopTimings, + }, + }); +} + +async function runTransfer( + deps: HostTransferDeps, + transferId: string, + request: TransferRequest, +): Promise { + await initializeTransferProfiles(); + const { + sourceSessionId: browseSourceSessionId, + sourcePaths, + destSessionId: browseDestSessionId, + destPath, + move = false, + userId, + methodPreference = "auto", + parallelSegmentCount: requestParallelSegments, + } = request; + + const parallelSegmentCount = requestParallelSegments; + + const dedicatedSourceSessionId = `xfer:${transferId}:src`; + const dedicatedDestSessionId = `xfer:${transferId}:dst`; + const runStart = Date.now(); + const reconnectMeta: TransferReconnectMeta = { + userId, + browseSourceSessionId, + browseDestSessionId, + dedicatedSourceSessionId, + dedicatedDestSessionId, + }; + + try { + const sourceSession = await deps.openDedicatedTransferSession( + browseSourceSessionId, + dedicatedSourceSessionId, + userId, + transferId, + ); + const destSession = await deps.openDedicatedTransferSession( + browseDestSessionId, + dedicatedDestSessionId, + userId, + transferId, + ); + + reconnectMeta.profileKey = transferProfileKey( + sourceSession, + destSession, + userId, + ); + + sourceSession.lastActive = Date.now(); + destSession.lastActive = Date.now(); + + updateTransfer(transferId, { + parallelSegmentCount: + parallelSegmentCount ?? DEFAULT_PARALLEL_SEGMENT_COUNT, + dedicatedSourceSessionId, + dedicatedDestSessionId, + }); + + const pathHints = [destPath, ...sourcePaths]; + const sourcePlatform = await detectTransferPlatform( + deps, + sourceSession, + pathHints, + ); + const destPlatform = await detectTransferPlatform( + deps, + destSession, + pathHints, + ); + + const sourceSftp = await deps.getSessionSftp(sourceSession); + + for (const path of sourcePaths) { + try { + await promisifySftpStat(sourceSftp, path); + } catch { + throw new Error(`Source not found: ${path}`); + } + } + + if (browseSourceSessionId === browseDestSessionId) { + for (const src of sourcePaths) { + if (pathsOverlap(src, destPath)) { + throw new Error("Source and destination paths overlap"); + } + } + } + + const directResult = + methodPreference === "auto" + ? await tryAdaptiveDirectTransfer( + deps, + transferId, + sourceSession, + destSession, + sourcePaths, + destPath, + move, + reconnectMeta, + ) + : null; + if (directResult) { + updateTransfer(transferId, { + timings: { + ...directResult.timings, + totalMs: elapsedMs(runStart), + }, + }); + return; + } + + let useArchive = sourcePaths.length > 1; + + if (sourcePaths.length === 1) { + const stats = await promisifySftpStat(sourceSftp, sourcePaths[0]); + useArchive = stats.isDirectory(); + } + + if (!useArchive) { + await transferSingleFile( + deps, + transferId, + sourceSession, + destSession, + sourcePaths[0], + destPath, + move, + reconnectMeta, + parallelSegmentCount, + ); + } else { + const prepareStart = Date.now(); + await ensureDestDirectory(deps, destSession, destPath); + updateTransfer(transferId, { + timings: { prepareDestMs: elapsedMs(prepareStart) }, + }); + + const sourceHasTar = + sourcePlatform === "unix" && + (await checkTarAvailable(deps, sourceSession)); + const destHasTar = + destPlatform === "unix" && (await checkTarAvailable(deps, destSession)); + + throwIfCancelled(transferId); + const scanSummary = await scanSourcePathsForRouting( + sourceSftp, + sourcePaths, + transferId, + ); + const archiveMethod = resolveArchiveTransferMethod( + methodPreference, + scanSummary, + sourcePlatform, + destPlatform, + sourceHasTar, + destHasTar, + ); + + pluginCtx().log.info( + `Resolved archive transfer method: ${transferId} (methodPreference=${methodPreference}, archiveMethod=${archiveMethod}, fileCount=${scanSummary.fileCount}, totalBytes=${scanSummary.totalBytes}, incompressibleRatio=${scanSummary.incompressibleRatio})`, + ); + + if (archiveMethod === "tar") { + await transferViaTar( + deps, + transferId, + sourceSession, + destSession, + sourcePaths, + destPath, + move, + reconnectMeta, + ); + } else { + await transferViaItemSftp( + deps, + transferId, + sourceSession, + destSession, + sourcePaths, + destPath, + move, + destPlatform, + reconnectMeta, + ); + } + } + + pluginCtx().log.info( + `Host transfer completed: ${transferId} (sourcePaths=${sourcePaths.join(", ")}, destPath=${destPath}, move=${move}, totalMs=${elapsedMs(runStart)})`, + ); + updateTransfer(transferId, { + moveRequested: move, + timings: { + ...activeTransfers.get(transferId)?.timings, + totalMs: elapsedMs(runStart), + }, + }); + } catch (err) { + if (err instanceof TransferCancelledError) { + const current = activeTransfers.get(transferId); + finalizeTransfer( + transferId, + cancelledProgressPatch(current, { + status: "cancelled", + phase: current?.phase ?? "transferring", + message: "Transfer cancelled by user", + method: current?.method, + sourcePaths, + destPath, + bytesTransferred: current?.bytesTransferred, + totalBytes: current?.totalBytes, + itemsCompleted: current?.itemsCompleted, + totalItems: current?.totalItems, + moveRequested: move, + sourceDeleted: false, + integrityVerified: false, + timings: { + ...current?.timings, + totalMs: elapsedMs(runStart), + }, + }), + ); + pluginCtx().log.info( + `Host transfer cancelled: ${transferId} (sourcePaths=${sourcePaths.join(", ")}, destPath=${destPath})`, + ); + return; + } + + const current = activeTransfers.get(transferId); + const message = getErrorMessage(err, "Transfer failed"); + + if ( + sourcePaths.length === 1 && + current?.method === "stream" && + current.destPath && + current.totalBytes + ) { + try { + const destSession = await deps.openDedicatedTransferSession( + browseDestSessionId, + dedicatedDestSessionId, + userId, + transferId, + { allowBrowseDisconnected: true }, + ); + const finalized = await tryFinalizeStreamTransferIfDestComplete( + deps, + transferId, + destSession, + current.destPath, + current.totalBytes, + { + sourcePaths, + moveRequested: move, + sourceDeleted: current.sourceDeleted, + }, + () => + verifyTransferredFile( + deps, + transferId, + reconnectMeta, + sourcePaths[0], + current.destPath!, + ), + ); + if (finalized) { + pluginCtx().log.info( + `Host transfer succeeded after destination verify: ${transferId} (sourcePaths=${sourcePaths.join(", ")}, destPath=${destPath})`, + ); + return; + } + } catch { + /* fall through to error */ + } + } + + finalizeTransfer( + transferId, + failedProgressPatch(current, { + status: "error", + phase: current?.phase ?? "transferring", + message, + method: current?.method, + sourcePaths, + destPath, + bytesTransferred: current?.bytesTransferred, + totalBytes: current?.totalBytes, + itemsCompleted: current?.itemsCompleted, + totalItems: current?.totalItems, + moveRequested: move, + integrityVerified: false, + timings: { + ...current?.timings, + totalMs: elapsedMs(runStart), + }, + }), + ); + pluginCtx().log.error( + `${err instanceof TransferStalledError ? "Host transfer stalled" : "Host transfer failed"}: ${transferId} (sourcePaths=${sourcePaths.join(", ")}, destPath=${destPath})`, + err as Error, + ); + } finally { + deps.closeDedicatedTransferSession(dedicatedSourceSessionId); + deps.closeDedicatedTransferSession(dedicatedDestSessionId); + } +} + +export function getTransferStatus( + transferId: string, + userId: string, +): TransferProgress | null { + checkHungTransfers(); + const progress = activeTransfers.get(transferId); + if (!progress || progress.userId !== userId) return null; + return progress; +} + +export function listActiveTransfers(userId: string): TransferProgress[] { + return Array.from(activeTransfers.values()).filter( + (progress) => progress.userId === userId && progress.status === "running", + ); +} + +export async function cleanupCancelledTransfer( + deps: HostTransferDeps, + transferId: string, + userId: string, +): Promise<{ removedPaths: string[]; failedPaths: string[] }> { + const progress = activeTransfers.get(transferId); + if (!progress || progress.userId !== userId) { + throw new Error("Transfer not found"); + } + if (progress.status !== "cancelled") { + throw new Error("Transfer is not cancelled"); + } + if (progress.cleanupCompleted) { + return { removedPaths: [], failedPaths: [] }; + } + if (!progress.destSessionId) { + throw new Error("Transfer has no destination session"); + } + + const pathsToRemove = buildCleanupPaths(progress); + if (pathsToRemove.length === 0) { + updateTransfer(transferId, { cleanupCompleted: true }); + return { removedPaths: [], failedPaths: [] }; + } + + const cleanupSessionId = `xfer:cleanup:${transferId}:dst`; + const removedPaths: string[] = []; + const failedPaths: string[] = []; + + try { + const destSession = await deps.openDedicatedTransferSession( + progress.destSessionId, + cleanupSessionId, + userId, + transferId, + { allowBrowseDisconnected: true }, + ); + const destSftp = await deps.getSessionSftp(destSession); + + for (const path of pathsToRemove) { + try { + await deletePathSftp(destSftp, path); + removedPaths.push(path); + } catch { + failedPaths.push(path); + } + } + } finally { + deps.closeDedicatedTransferSession(cleanupSessionId); + } + + updateTransfer(transferId, { cleanupCompleted: true }); + return { removedPaths, failedPaths }; +} + +export function retryHostTransfer( + deps: HostTransferDeps, + transferId: string, + userId: string, +): boolean { + const progress = activeTransfers.get(transferId); + if ( + !progress || + progress.userId !== userId || + progress.status !== "error" || + !progress.retryable || + !progress.requestSnapshot + ) { + return false; + } + + void (async () => { + if ( + progress.method === "stream" && + progress.destPath && + progress.totalBytes && + progress.destSessionId + ) { + const destId = + progress.dedicatedDestSessionId ?? `xfer:${transferId}:dst`; + try { + const destSession = await deps.openDedicatedTransferSession( + progress.destSessionId, + destId, + userId, + transferId, + { allowBrowseDisconnected: true }, + ); + const done = await tryFinalizeStreamTransferIfDestComplete( + deps, + transferId, + destSession, + progress.destPath, + progress.totalBytes, + { + sourcePaths: progress.sourcePaths, + moveRequested: progress.moveRequested, + }, + ); + if (done) { + return; + } + } catch { + /* destination not ready — restart transfer below */ + } + } + + const latest = activeTransfers.get(transferId); + if (!latest || latest.status !== "error" || !latest.requestSnapshot) { + return; + } + + updateTransfer(transferId, { + status: "running", + phase: "transferring", + message: undefined, + retryable: false, + parallelSegmentCount: + latest.requestSnapshot?.parallelSegmentCount ?? + latest.parallelSegmentCount, + }); + + await runTransfer(deps, transferId, { + ...latest.requestSnapshot, + userId, + parallelSegmentCount: latest.requestSnapshot.parallelSegmentCount, + }); + })(); + + return true; +} + +export async function previewArchiveTransferMethod( + deps: HostTransferDeps, + request: { + sourceSessionId: string; + destSessionId: string; + sourcePaths: string[]; + destPath: string; + methodPreference?: TransferMethodPreference; + userId: string; + }, +): Promise { + const { + sourceSessionId, + destSessionId, + sourcePaths, + destPath, + methodPreference = "auto", + userId, + } = request; + + const sourceSession = deps.sshSessions[sourceSessionId]; + const destSession = deps.sshSessions[destSessionId]; + + if (!sourceSession?.isConnected || !destSession?.isConnected) { + throw new Error("SSH session not connected"); + } + + if ( + !deps.verifySessionOwnership(sourceSession, userId) || + !deps.verifySessionOwnership(destSession, userId) + ) { + throw new Error("Session access denied"); + } + + const pathHints = [destPath, ...sourcePaths]; + const sourcePlatform = await detectTransferPlatform( + deps, + sourceSession, + pathHints, + ); + const destPlatform = await detectTransferPlatform( + deps, + destSession, + pathHints, + ); + + const sourceSftp = await deps.getSessionSftp(sourceSession); + const scanSummary = await scanSourcePathsForRouting( + sourceSftp, + sourcePaths, + "preview", + ); + + const sourceHasTar = + sourcePlatform === "unix" && (await checkTarAvailable(deps, sourceSession)); + const destHasTar = + destPlatform === "unix" && (await checkTarAvailable(deps, destSession)); + + const resolvedMethod = resolveArchiveTransferMethod( + methodPreference, + scanSummary, + sourcePlatform, + destPlatform, + sourceHasTar, + destHasTar, + ); + + const reasonKey = getArchiveTransferReasonKey( + methodPreference, + resolvedMethod, + scanSummary, + sourcePlatform, + destPlatform, + sourceHasTar, + destHasTar, + ); + + return { + methodPreference, + resolvedMethod, + reasonKey, + sourcePlatform, + destPlatform, + sourceHasTar, + destHasTar, + summary: scanSummary, + }; +} + +export function startHostTransfer( + deps: HostTransferDeps, + request: TransferRequest, +): { transferId: string; syncResult?: TransferProgress } { + const transferId = randomUUID(); + + activeTransfers.set(transferId, { + transferId, + status: "running", + phase: "transferring", + sourcePaths: request.sourcePaths, + destPath: request.destPath, + userId: request.userId, + sourceSessionId: request.sourceSessionId, + destSessionId: request.destSessionId, + startedAt: Date.now(), + moveRequested: request.move ?? false, + methodPreference: request.methodPreference ?? "auto", + parallelSegmentCount: clampParallelSegmentCount( + request.parallelSegmentCount, + ), + requestSnapshot: { + sourceSessionId: request.sourceSessionId, + sourcePaths: request.sourcePaths, + destSessionId: request.destSessionId, + destPath: request.destPath, + move: request.move, + methodPreference: request.methodPreference, + parallelSegmentCount: request.parallelSegmentCount, + }, + }); + + const isSingleSmallFile = request.sourcePaths.length === 1 && !request.move; + + if (isSingleSmallFile) { + const sourceSession = deps.sshSessions[request.sourceSessionId]; + if (sourceSession?.sftp || sourceSession?.isConnected) { + void (async () => { + try { + const sftp = await deps.getSessionSftp(sourceSession); + const stats = await promisifySftpStat(sftp, request.sourcePaths[0]); + if (stats.isFile() && stats.size <= SMALL_FILE_SYNC_THRESHOLD) { + await runTransfer(deps, transferId, request); + return; + } + } catch { + /* fall through to async */ + } + void runTransfer(deps, transferId, request); + })(); + return { transferId }; + } + } + + void runTransfer(deps, transferId, request); + return { transferId }; +} + +export function cleanupOldTransfers(maxAgeMs = 60 * 60 * 1000): void { + const now = Date.now(); + for (const [id, progress] of activeTransfers.entries()) { + if ( + progress.status !== "running" && + now - (progress as TransferProgress & { _ts?: number })._ts! > maxAgeMs + ) { + activeTransfers.delete(id); + } + } +} diff --git a/plugins/file-manager/src/backend/transfer-errors.ts b/plugins/file-manager/src/backend/transfer-errors.ts new file mode 100644 index 000000000..2180e1ff7 --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-errors.ts @@ -0,0 +1,52 @@ +export class TransferCancelledError extends Error { + constructor() { + super("Transfer cancelled"); + this.name = "TransferCancelledError"; + } +} + +export class TransferStalledError extends Error { + readonly byteOffset?: number; + readonly segmentIndex?: number; + + constructor(byteOffset?: number, segmentIndex?: number) { + const pos = byteOffset !== undefined ? ` at byte offset ${byteOffset}` : ""; + const seg = segmentIndex !== undefined ? ` (segment ${segmentIndex})` : ""; + super(`Transfer stalled — no data moved for 45 seconds${pos}${seg}`); + this.name = "TransferStalledError"; + this.byteOffset = byteOffset; + this.segmentIndex = segmentIndex; + } +} + +export class TransferConnectionLostError extends Error { + constructor(message = "Transfer SSH connection lost") { + super(message); + this.name = "TransferConnectionLostError"; + } +} + +export function isRecoverableTransferConnectionError(err: unknown): boolean { + if (!(err instanceof Error)) return false; + const msg = err.message.toLowerCase(); + return ( + msg.includes("no response from server") || + msg.includes("connection lost") || + msg.includes("not connected") || + msg.includes("econnreset") || + msg.includes("econnrefused") || + msg.includes("etimedout") || + msg.includes("socket hang up") || + msg.includes("protocol error") || + msg.includes("connection closed") || + msg.includes("channel open failure") + ); +} + +export function isRecoverableTransferError(err: unknown): boolean { + return ( + err instanceof TransferStalledError || + err instanceof TransferConnectionLostError || + isRecoverableTransferConnectionError(err) + ); +} diff --git a/plugins/file-manager/src/backend/transfer-host-utils.ts b/plugins/file-manager/src/backend/transfer-host-utils.ts new file mode 100644 index 000000000..ef90e475d --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-host-utils.ts @@ -0,0 +1,59 @@ +import { networkInterfaces } from "os"; +import { normalizeSftpPath } from "./transfer-paths.js"; + +let cachedLocalAddresses: Set | null = null; + +export function normalizeHostAddress(host: string): string { + const trimmed = host.trim().toLowerCase(); + if (trimmed.startsWith("[") && trimmed.endsWith("]")) { + return trimmed.slice(1, -1); + } + return trimmed.split(":")[0] ?? trimmed; +} + +function getLocalAddresses(): Set { + if (cachedLocalAddresses) return cachedLocalAddresses; + + const addresses = new Set(["127.0.0.1", "::1", "localhost"]); + for (const ifaces of Object.values(networkInterfaces())) { + if (!ifaces) continue; + for (const iface of ifaces) { + if (!iface.internal && iface.family === "IPv4") { + addresses.add(iface.address.toLowerCase()); + } + } + } + cachedLocalAddresses = addresses; + return addresses; +} + +export function isLocalSshEndpoint(ip?: string): boolean { + if (!ip) return false; + const bare = normalizeHostAddress(ip); + if (bare === "localhost" || bare === "127.0.0.1" || bare === "::1") { + return true; + } + return getLocalAddresses().has(bare); +} + +export function escapeShell(s: string): string { + return s.replace(/'/g, "'\"'\"'"); +} + +export function isRootOnlyPath(path: string): boolean { + const normalized = normalizeSftpPath(path); + return ( + normalized === "/" || + /^\/[A-Za-z]:$/.test(normalized) || + /^[A-Za-z]:$/.test(normalized) + ); +} + +export function isPermissionError(err: Error): boolean { + const msg = err.message.toLowerCase(); + return ( + msg.includes("permission denied") || + msg.includes("eacces") || + msg.includes("access denied") + ); +} diff --git a/plugins/file-manager/src/backend/transfer-integrity.ts b/plugins/file-manager/src/backend/transfer-integrity.ts new file mode 100644 index 000000000..0dc7052a2 --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-integrity.ts @@ -0,0 +1,54 @@ +import { createHash } from "node:crypto"; + +type SFTPWrapper = import("ssh2").SFTPWrapper; + +export interface TransferIntegrityResult { + algorithm: "sha256"; + digest: string; +} + +export async function hashSftpFile( + sftp: SFTPWrapper, + path: string, + shouldAbort: () => boolean = () => false, + createAbortError: () => Error = () => new Error("Transfer cancelled"), +): Promise { + const stream = sftp.createReadStream(path); + const hash = createHash("sha256"); + + try { + for await (const chunk of stream) { + if (shouldAbort()) { + const error = createAbortError(); + stream.destroy(error); + throw error; + } + hash.update(chunk); + } + } finally { + stream.destroy(); + } + + if (shouldAbort()) throw createAbortError(); + return hash.digest("hex"); +} + +export async function verifySftpFileIntegrity( + sourceSftp: SFTPWrapper, + destSftp: SFTPWrapper, + sourcePath: string, + destPath: string, + shouldAbort: () => boolean = () => false, + createAbortError: () => Error = () => new Error("Transfer cancelled"), +): Promise { + const [sourceDigest, destDigest] = await Promise.all([ + hashSftpFile(sourceSftp, sourcePath, shouldAbort, createAbortError), + hashSftpFile(destSftp, destPath, shouldAbort, createAbortError), + ]); + + if (sourceDigest !== destDigest) { + throw new Error(`SHA-256 verification failed for ${sourcePath}`); + } + + return { algorithm: "sha256", digest: sourceDigest }; +} diff --git a/plugins/file-manager/src/backend/transfer-paths.ts b/plugins/file-manager/src/backend/transfer-paths.ts new file mode 100644 index 000000000..694a9f5b7 --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-paths.ts @@ -0,0 +1,151 @@ +export type TransferPlatform = "windows" | "unix"; + +/** OpenSSH SFTP on Windows commonly uses `/C:/...` or `C:/...` style paths. */ +export function isWindowsSftpPath(path: string): boolean { + const normalized = path.replace(/\\/g, "/"); + return /^[A-Za-z]:\//.test(normalized) || /^\/[A-Za-z]:\//.test(normalized); +} + +export function inferPlatformFromPath(path: string): TransferPlatform | null { + if (isWindowsSftpPath(path)) return "windows"; + if (path.startsWith("/") && !/^\/[A-Za-z]:/.test(path.replace(/\\/g, "/"))) { + return "unix"; + } + return null; +} + +export function normalizeSftpPath(path: string): string { + const normalized = path.replace(/\\/g, "/").replace(/\/+$/, ""); + if (!normalized) return path.startsWith("/") ? "/" : "."; + return normalized.replace(/([^:])\/+/g, "$1/"); +} + +export function basename(path: string): string { + const normalized = path.replace(/\\/g, "/").replace(/\/+$/, ""); + const idx = normalized.lastIndexOf("/"); + if (idx < 0) return normalized; + return normalized.substring(idx + 1) || normalized; +} + +export function dirname(path: string): string { + const normalized = path.replace(/\\/g, "/").replace(/\/+$/, ""); + if (!normalized) return normalized; + + const idx = normalized.lastIndexOf("/"); + if (idx < 0) return normalized; + + const parent = normalized.substring(0, idx); + if (!parent) return "/"; + if (/^\/[A-Za-z]:$/.test(parent) || /^[A-Za-z]:$/.test(parent)) { + return parent; + } + return parent || "/"; +} + +export function joinPath(base: string, ...parts: string[]): string { + let result = base.replace(/\\/g, "/"); + for (const part of parts) { + if (!part) continue; + const segment = part.replace(/\\/g, "/").replace(/^\/+/, ""); + if (!segment) continue; + if (result.endsWith("/")) { + result += segment; + } else { + result = `${result}/${segment}`; + } + } + return normalizeSftpPath(result); +} + +export interface PathSegments { + /** Drive root prefix, e.g. `/C:` or `C:`; empty for Unix absolute paths. */ + root: string; + /** Path segments below the root (may be empty). */ + segments: string[]; +} + +export function splitPathSegments(path: string): PathSegments { + const normalized = normalizeSftpPath(path); + + const posixDrive = normalized.match(/^(\/[A-Za-z]:)(?:\/(.*))?$/); + if (posixDrive) { + return { + root: posixDrive[1], + segments: (posixDrive[2] ?? "").split("/").filter(Boolean), + }; + } + + const drive = normalized.match(/^([A-Za-z]:)(?:\/(.*))?$/); + if (drive) { + return { + root: drive[1], + segments: (drive[2] ?? "").split("/").filter(Boolean), + }; + } + + if (normalized.startsWith("/")) { + return { + root: "/", + segments: normalized.split("/").filter(Boolean), + }; + } + + return { + root: "", + segments: normalized.split("/").filter(Boolean), + }; +} + +export function buildPathFromSegments( + root: string, + segments: string[], + count: number, +): string { + const slice = segments.slice(0, count); + if (/^\/[A-Za-z]:$/.test(root)) { + return slice.length ? `${root}/${slice.join("/")}` : root; + } + if (/^[A-Za-z]:$/.test(root)) { + return slice.length ? `${root}/${slice.join("/")}` : root; + } + if (root === "/") { + return slice.length ? `/${slice.join("/")}` : "/"; + } + if (root === "") { + return slice.join("/"); + } + return slice.length ? `${root}/${slice.join("/")}` : root; +} + +export function pathsOverlap(source: string, dest: string): boolean { + const norm = (p: string) => { + const n = normalizeSftpPath(p).toLowerCase(); + return n || (isWindowsSftpPath(p) ? p : "/"); + }; + const s = norm(source); + const d = norm(dest); + const sep = "/"; + return s === d || s.startsWith(`${d}${sep}`) || d.startsWith(`${s}${sep}`); +} + +export function getWorkingDir(paths: string[]): string { + if (paths.length === 0) return "/"; + const parents = paths.map((p) => dirname(p)); + const first = parents[0]; + if (parents.every((p) => p === first)) { + return first; + } + return first; +} + +/** Convert an SFTP path to a local filesystem path when Termix runs on the dest host. */ +export function sftpPathToLocalPath(sftpPath: string): string { + const normalized = sftpPath.replace(/\\/g, "/"); + if (/^\/[A-Za-z]:\//.test(normalized)) { + return normalized.slice(1).replace(/\//g, "\\"); + } + if (/^[A-Za-z]:\//.test(normalized)) { + return normalized.replace(/\//g, "\\"); + } + return sftpPath; +} diff --git a/plugins/file-manager/src/backend/transfer-routing.ts b/plugins/file-manager/src/backend/transfer-routing.ts new file mode 100644 index 000000000..5d9d9aac2 --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-routing.ts @@ -0,0 +1,205 @@ +import type { TransferPlatform } from "./transfer-paths.js"; +import { deflateRawSync } from "node:zlib"; + +export type TransferMethodPreference = "auto" | "tar" | "item_sftp"; + +export interface TransferScanSummary { + fileCount: number; + totalBytes: number; + largestFileBytes: number; + /** Share of total bytes in likely incompressible file types (0–1). */ + incompressibleRatio: number; + /** Share inferred from bounded content samples, when available. */ + sampledIncompressibleRatio?: number; +} + +const INCOMPRESSIBLE_EXT = + /\.(zip|gz|bz2|xz|7z|rar|tar|tgz|jpg|jpeg|png|gif|webp|mp3|mp4|mkv|avi|mov|wmv|iso|dmg|deb|rpm|pdf|db|sqlite|wasm|vmdk|qcow2)$/i; + +const GB = 1024 * 1024 * 1024; +const MB = 1024 * 1024; + +export function isLikelyIncompressiblePath(path: string): boolean { + return INCOMPRESSIBLE_EXT.test(path); +} + +export function buildTransferScanSummary( + items: Array<{ sourcePath: string; size: number }>, +): TransferScanSummary { + let totalBytes = 0; + let largestFileBytes = 0; + let incompressibleBytes = 0; + + for (const item of items) { + totalBytes += item.size; + largestFileBytes = Math.max(largestFileBytes, item.size); + if (isLikelyIncompressiblePath(item.sourcePath)) { + incompressibleBytes += item.size; + } + } + + return { + fileCount: items.length, + totalBytes, + largestFileBytes, + incompressibleRatio: totalBytes > 0 ? incompressibleBytes / totalBytes : 0, + }; +} + +export function estimateIncompressibleSample(data: Buffer): boolean { + if (data.length === 0) return false; + return deflateRawSync(data, { level: 1 }).length / data.length >= 0.9; +} + +function effectiveIncompressibleRatio(summary: TransferScanSummary): number { + return summary.sampledIncompressibleRatio ?? summary.incompressibleRatio; +} + +/** + * Choose tar vs per-item SFTP for directory / multi-file transfers. + * Single-file stream transfers bypass this entirely. + */ +export function resolveArchiveTransferMethod( + preference: TransferMethodPreference, + summary: TransferScanSummary, + sourcePlatform: TransferPlatform, + destPlatform: TransferPlatform, + sourceHasTar: boolean, + destHasTar: boolean, +): "tar" | "item_sftp" { + if (sourcePlatform === "windows" || destPlatform === "windows") { + return "item_sftp"; + } + + if (preference === "item_sftp") { + return "item_sftp"; + } + + if (preference === "tar") { + return sourceHasTar && destHasTar ? "tar" : "item_sftp"; + } + + // auto + if (!sourceHasTar || !destHasTar) { + return "item_sftp"; + } + + const { fileCount, totalBytes, largestFileBytes } = summary; + const incompressibleRatio = effectiveIncompressibleRatio(summary); + + if (fileCount === 0) { + return "item_sftp"; + } + + if (fileCount === 1 && largestFileBytes >= 2 * GB) { + return "item_sftp"; + } + + // Multi-item sets with a large file: tar only when data is likely compressible + // (mostly video/zip → per-file SFTP is simpler and avoids pack/unpack time). + if ( + fileCount > 1 && + largestFileBytes >= 2 * GB && + totalBytes >= 500 * MB && + incompressibleRatio < 0.7 + ) { + return "tar"; + } + + if (fileCount === 1 && largestFileBytes >= 5 * GB) { + return "item_sftp"; + } + + if (totalBytes >= 10 * GB && incompressibleRatio >= 0.5) { + return "item_sftp"; + } + + if (incompressibleRatio >= 0.85) { + return "item_sftp"; + } + + if (fileCount >= 100) { + return "tar"; + } + + if (fileCount >= 20 && totalBytes >= 50 * MB && incompressibleRatio < 0.5) { + return "tar"; + } + + if (fileCount > 5 && incompressibleRatio < 0.7) { + return "tar"; + } + + return "item_sftp"; +} + +export type ArchiveTransferReasonKey = + | "user_item_sftp" + | "user_tar" + | "tar_unavailable" + | "windows_host" + | "auto_multi_large" + | "auto_single_large_in_archive" + | "auto_many_incompressible" + | "auto_many_files" + | "auto_default"; + +/** i18n key suffix under transfer.methodReason.* */ +export function getArchiveTransferReasonKey( + preference: TransferMethodPreference, + resolvedMethod: "tar" | "item_sftp", + summary: TransferScanSummary, + sourcePlatform: TransferPlatform, + destPlatform: TransferPlatform, + sourceHasTar: boolean, + destHasTar: boolean, +): ArchiveTransferReasonKey { + if (preference === "item_sftp") return "user_item_sftp"; + if (preference === "tar") { + return sourceHasTar && destHasTar ? "user_tar" : "tar_unavailable"; + } + + if (sourcePlatform === "windows" || destPlatform === "windows") { + return "windows_host"; + } + if (!sourceHasTar || !destHasTar) { + return "tar_unavailable"; + } + + const { fileCount, totalBytes, largestFileBytes } = summary; + const incompressibleRatio = effectiveIncompressibleRatio(summary); + + if ( + fileCount > 1 && + largestFileBytes >= 2 * GB && + totalBytes >= 500 * MB && + incompressibleRatio < 0.7 + ) { + return "auto_multi_large"; + } + + if (fileCount === 1 && largestFileBytes >= 2 * GB) { + return "auto_single_large_in_archive"; + } + + if (totalBytes >= 10 * GB && incompressibleRatio >= 0.5) { + return "auto_many_incompressible"; + } + if (incompressibleRatio >= 0.85) { + return "auto_many_incompressible"; + } + + if ( + fileCount >= 100 || + (fileCount >= 20 && totalBytes >= 50 * MB && incompressibleRatio < 0.5) || + (fileCount > 5 && incompressibleRatio < 0.7) + ) { + return "auto_many_files"; + } + + if (resolvedMethod === "tar") { + return "auto_many_files"; + } + + return "auto_default"; +} diff --git a/plugins/file-manager/src/backend/transfer-scan.ts b/plugins/file-manager/src/backend/transfer-scan.ts new file mode 100644 index 000000000..1d790cedc --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-scan.ts @@ -0,0 +1,89 @@ +import { basename, joinPath } from "./transfer-paths.js"; +import { + SFTP_OPEN_READ, + promisifySftpClose, + promisifySftpOpen, + promisifySftpReaddir, + promisifySftpStat, +} from "./sftp-promisify.js"; + +type SFTPWrapper = import("ssh2").SFTPWrapper; + +export interface FileWorkItem { + sourcePath: string; + destPath: string; + mode: number; + size: number; +} + +export async function collectFileWorkItems( + sftp: SFTPWrapper, + sourcePath: string, + destRoot: string, + destBaseName?: string, +): Promise { + const stats = await promisifySftpStat(sftp, sourcePath); + const name = destBaseName ?? basename(sourcePath); + const destPath = joinPath(destRoot, name); + + if (stats.isFile()) { + return [ + { + sourcePath, + destPath, + mode: stats.mode & 0o7777, + size: stats.size, + }, + ]; + } + + if (!stats.isDirectory()) { + return []; + } + + const items: FileWorkItem[] = []; + const walk = async (srcDir: string, dstDir: string) => { + const entries = await promisifySftpReaddir(sftp, srcDir); + for (const entry of entries) { + if (entry.filename === "." || entry.filename === "..") continue; + const srcChild = joinPath(srcDir, entry.filename); + const dstChild = joinPath(dstDir, entry.filename); + + if (entry.attrs.isDirectory()) { + await walk(srcChild, dstChild); + } else if (entry.attrs.isFile()) { + items.push({ + sourcePath: srcChild, + destPath: dstChild, + mode: entry.attrs.mode & 0o7777, + size: entry.attrs.size, + }); + } + } + }; + + await walk(sourcePath, destPath); + return items; +} + +export async function readSftpSample( + sftp: SFTPWrapper, + path: string, + fileSize: number, +): Promise { + const sampleSize = Math.min(64 * 1024, fileSize); + const position = Math.max(0, Math.floor((fileSize - sampleSize) / 2)); + const handle = await promisifySftpOpen(sftp, path, SFTP_OPEN_READ, 0o666); + try { + const buffer = Buffer.alloc(sampleSize); + const bytesRead = await new Promise((resolve, reject) => { + sftp.read(handle, buffer, 0, sampleSize, position, (err, count) => { + if (err) reject(err); + else resolve(count); + }); + }); + return buffer.subarray(0, bytesRead); + } finally { + await promisifySftpClose(sftp, handle).catch(() => {}); + } +} diff --git a/plugins/file-manager/src/backend/transfer-segment-copy.ts b/plugins/file-manager/src/backend/transfer-segment-copy.ts new file mode 100644 index 000000000..36a494db2 --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-segment-copy.ts @@ -0,0 +1,40 @@ +export const SFTP_XFER_SEGMENT_SIZE = 256 * 1024 * 1024; +export const DEFAULT_PARALLEL_SEGMENT_COUNT = 2; +export const MAX_PARALLEL_SEGMENT_COUNT = 8; + +export interface SegmentCopyJob { + offset: number; + length: number; + segmentIndex: number; +} + +export function clampParallelSegmentCount(value?: number): number { + const n = value ?? DEFAULT_PARALLEL_SEGMENT_COUNT; + return Math.max(1, Math.min(MAX_PARALLEL_SEGMENT_COUNT, Math.floor(n))); +} + +export function buildSegmentCopyJobs( + fileSize: number, + initialOffset: number, + destResumeSize: number, +): SegmentCopyJob[] { + const jobs: SegmentCopyJob[] = []; + for ( + let offset = initialOffset; + offset < fileSize; + offset += SFTP_XFER_SEGMENT_SIZE + ) { + const length = Math.min(SFTP_XFER_SEGMENT_SIZE, fileSize - offset); + const segmentIndex = Math.floor(offset / SFTP_XFER_SEGMENT_SIZE); + if (destResumeSize >= offset + length) { + continue; + } + const start = destResumeSize > offset ? destResumeSize : offset; + jobs.push({ + offset: start, + length: offset + length - start, + segmentIndex, + }); + } + return jobs; +} diff --git a/plugins/file-manager/src/backend/transfer-sftp-dir.ts b/plugins/file-manager/src/backend/transfer-sftp-dir.ts new file mode 100644 index 000000000..8e9236bff --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-sftp-dir.ts @@ -0,0 +1,74 @@ +import { + buildPathFromSegments, + joinPath, + normalizeSftpPath, + splitPathSegments, +} from "./transfer-paths.js"; +import { isRootOnlyPath } from "./transfer-host-utils.js"; +import { + promisifySftpMkdir, + promisifySftpReaddir, + promisifySftpRmdir, + promisifySftpStat, + promisifySftpUnlink, +} from "./sftp-promisify.js"; + +type SFTPWrapper = import("ssh2").SFTPWrapper; + +export async function ensureDirectoryTreeSftp( + sftp: SFTPWrapper, + dirPath: string, + created: Set = new Set(), +): Promise { + const normalized = normalizeSftpPath(dirPath); + if (!normalized || isRootOnlyPath(normalized)) return; + + const { root, segments } = splitPathSegments(normalized); + if (segments.length === 0) return; + + for (let i = 0; i < segments.length; i++) { + const current = buildPathFromSegments(root, segments, i + 1); + if (created.has(current)) continue; + + try { + await promisifySftpMkdir(sftp, current, 0o755); + } catch (err) { + const code = (err as NodeJS.ErrnoException).code; + if (code !== "EEXIST") { + try { + const stats = await promisifySftpStat(sftp, current); + if (!stats.isDirectory()) throw err; + } catch { + throw err; + } + } + } + created.add(current); + } +} + +export async function deletePathSftp( + sftp: SFTPWrapper, + path: string, +): Promise { + let stats: import("ssh2").Stats; + try { + stats = await promisifySftpStat(sftp, path); + } catch { + return; + } + + if (stats.isDirectory()) { + const entries = await promisifySftpReaddir(sftp, path); + for (const entry of entries) { + if (entry.filename === "." || entry.filename === "..") continue; + await deletePathSftp(sftp, joinPath(path, entry.filename)); + } + await promisifySftpRmdir(sftp, path); + return; + } + + if (stats.isFile()) { + await promisifySftpUnlink(sftp, path); + } +} diff --git a/plugins/file-manager/src/backend/transfer-stats.ts b/plugins/file-manager/src/backend/transfer-stats.ts new file mode 100644 index 000000000..14e43fe28 --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-stats.ts @@ -0,0 +1,157 @@ +import { performance } from "node:perf_hooks"; + +const TRANSFER_PROGRESS_INTERVAL_MS = 200; + +export type TransferHopId = + "source_read" | "dest_sftp_write" | "dest_local_write"; + +export interface TransferHopMetrics { + id: TransferHopId; + bytes: number; + /** Wall-clock span from first I/O on this hop to last I/O complete. */ + spanMs: number; + mbPerSec: number; +} + +export interface TransferTimings { + prepareDestMs?: number; + compressMs?: number; + transferMs?: number; + extractMs?: number; + verifyMs?: number; + directBenchmarkMs?: number; + relayBenchmarkMs?: number; + sourceDeleteMs?: number; + totalMs?: number; + transferBytes?: number; + endToEndMbPerSec?: number; + hops?: TransferHopMetrics[]; +} + +export function elapsedMs(start: number): number { + return Date.now() - start; +} + +export function computeTransferMbPerSec( + bytes: number, + ms: number, +): number | undefined { + if (ms <= 0 || bytes <= 0) return undefined; + return ((bytes / ms) * 1000) / (1024 * 1024); +} + +export interface HopWallClock { + firstAt: number | null; + lastAt: number | null; +} + +export function createHopWallClock(): HopWallClock { + return { firstAt: null, lastAt: null }; +} + +export function noteHopStart( + clock: HopWallClock, + t: number = performance.now(), +): void { + if (clock.firstAt === null) clock.firstAt = t; +} + +export function noteHopEnd( + clock: HopWallClock, + t: number = performance.now(), +): void { + clock.lastAt = t; +} + +export function hopSpanMs(clock: HopWallClock): number { + if (clock.firstAt === null || clock.lastAt === null) return 0; + return Math.max(0, clock.lastAt - clock.firstAt); +} + +export function createThrottledProgress(onProgress?: (bytes: number) => void) { + let pending = 0; + let lastFlush = 0; + + const flush = () => { + if (pending > 0) { + onProgress?.(pending); + pending = 0; + lastFlush = Date.now(); + } + }; + + return { + add(bytes: number) { + pending += bytes; + const now = Date.now(); + if (now - lastFlush >= TRANSFER_PROGRESS_INTERVAL_MS) { + flush(); + } + }, + flush, + }; +} + +export interface PipelinedXferStats { + bytes: number; + sourceReadSpanMs: number; + destWriteSpanMs: number; + destWriteKind: "sftp" | "local"; +} + +export function createEmptyXferStats(): PipelinedXferStats { + return { + bytes: 0, + sourceReadSpanMs: 0, + destWriteSpanMs: 0, + destWriteKind: "sftp", + }; +} + +export function mergeXferStats( + target: PipelinedXferStats, + source: PipelinedXferStats, +): void { + target.bytes += source.bytes; + target.sourceReadSpanMs += source.sourceReadSpanMs; + target.destWriteSpanMs += source.destWriteSpanMs; + target.destWriteKind = source.destWriteKind; +} + +export function buildTransferHopTimings( + stats: PipelinedXferStats, + transferMs: number, +): Pick { + const hops: TransferHopMetrics[] = []; + + const sourceRate = computeTransferMbPerSec( + stats.bytes, + stats.sourceReadSpanMs, + ); + if (sourceRate !== undefined) { + hops.push({ + id: "source_read", + bytes: stats.bytes, + spanMs: stats.sourceReadSpanMs, + mbPerSec: sourceRate, + }); + } + + const destHopId: TransferHopId = + stats.destWriteKind === "local" ? "dest_local_write" : "dest_sftp_write"; + const destRate = computeTransferMbPerSec(stats.bytes, stats.destWriteSpanMs); + if (destRate !== undefined) { + hops.push({ + id: destHopId, + bytes: stats.bytes, + spanMs: stats.destWriteSpanMs, + mbPerSec: destRate, + }); + } + + return { + transferBytes: stats.bytes, + endToEndMbPerSec: computeTransferMbPerSec(stats.bytes, transferMs), + hops, + }; +} diff --git a/plugins/file-manager/src/backend/transfer-tuning.ts b/plugins/file-manager/src/backend/transfer-tuning.ts new file mode 100644 index 000000000..39a32265f --- /dev/null +++ b/plugins/file-manager/src/backend/transfer-tuning.ts @@ -0,0 +1,388 @@ +// TODO(file-manager): move this to ctx.kv, see FINISH-LIST.md +import { createHash } from "node:crypto"; +import { promises as fs } from "node:fs"; +import path from "node:path"; + +export interface TransferPerformanceProfile { + throughputBps: number; + failureRate: number; + samples: number; + preferredLanes: number; + pipelineConcurrency: number; + updatedAt: number; +} + +export interface TransferTuning { + parallelSegmentCount: number; + pipelineConcurrency: number; +} + +export interface DirectRouteProfile { + directMs: number; + relayMs: number; + failureRate: number; + benchmarkSamples: number; + outcomeSamples: number; + benchmarkedAt: number; + cooldownUntil?: number; + updatedAt: number; +} + +const MB = 1024 * 1024; +const PROFILE_TTL_MS = 7 * 24 * 60 * 60 * 1000; +const MAX_PROFILES = 128; +const STORE_VERSION = 1; +const STORE_FILENAME = "adaptive-transfer-profiles.json"; +const profiles = new Map(); +const directRoutes = new Map(); +let loadedPath: string | undefined; +let loadPromise: Promise | undefined; +let persistTimer: ReturnType | undefined; +let persistPromise = Promise.resolve(); + +interface PersistedProfiles { + version: typeof STORE_VERSION; + profiles: Record; + directRoutes: Record; +} + +function validDirectRoute(value: unknown): value is DirectRouteProfile { + if (!value || typeof value !== "object") return false; + const profile = value as Partial; + return ( + Number.isFinite(profile.directMs) && + Number(profile.directMs) > 0 && + Number.isFinite(profile.relayMs) && + Number(profile.relayMs) > 0 && + Number.isFinite(profile.failureRate) && + Number(profile.failureRate) >= 0 && + Number(profile.failureRate) <= 1 && + Number.isFinite(profile.benchmarkSamples) && + Number(profile.benchmarkSamples) > 0 && + Number.isFinite(profile.outcomeSamples) && + Number(profile.outcomeSamples) >= 0 && + Number.isFinite(profile.benchmarkedAt) && + (profile.cooldownUntil === undefined || + Number.isFinite(profile.cooldownUntil)) && + Number.isFinite(profile.updatedAt) + ); +} + +function storePath(): string { + const dataDir = + process.env.DATA_DIR || path.join(process.cwd(), "db", "data"); + return path.join(dataDir, STORE_FILENAME); +} + +function profileId(key: string): string { + return createHash("sha256").update(key).digest("hex"); +} + +function validProfile(value: unknown): value is TransferPerformanceProfile { + if (!value || typeof value !== "object") return false; + const profile = value as Partial; + return ( + Number.isFinite(profile.throughputBps) && + Number(profile.throughputBps) >= 0 && + Number.isFinite(profile.failureRate) && + Number(profile.failureRate) >= 0 && + Number(profile.failureRate) <= 1 && + Number.isFinite(profile.samples) && + Number(profile.samples) > 0 && + Number.isFinite(profile.preferredLanes) && + Number.isFinite(profile.pipelineConcurrency) && + Number.isFinite(profile.updatedAt) + ); +} + +function trimProfiles(now = Date.now()): void { + for (const [key, profile] of profiles) { + if (now - profile.updatedAt > PROFILE_TTL_MS) profiles.delete(key); + } + const recent = [...profiles.entries()] + .sort(([, a], [, b]) => b.updatedAt - a.updatedAt) + .slice(0, MAX_PROFILES); + profiles.clear(); + for (const [key, profile] of recent) profiles.set(key, profile); + + for (const [key, profile] of directRoutes) { + if (now - profile.updatedAt > PROFILE_TTL_MS) directRoutes.delete(key); + } + const recentRoutes = [...directRoutes.entries()] + .sort(([, a], [, b]) => b.updatedAt - a.updatedAt) + .slice(0, MAX_PROFILES); + directRoutes.clear(); + for (const [key, profile] of recentRoutes) directRoutes.set(key, profile); +} + +async function persistProfiles(): Promise { + trimProfiles(); + const target = storePath(); + const temporary = `${target}.${process.pid}.tmp`; + const payload: PersistedProfiles = { + version: STORE_VERSION, + profiles: Object.fromEntries(profiles), + directRoutes: Object.fromEntries(directRoutes), + }; + try { + await fs.mkdir(path.dirname(target), { recursive: true }); + await fs.writeFile(temporary, JSON.stringify(payload), { + encoding: "utf8", + mode: 0o600, + }); + await fs.rename(temporary, target); + } catch { + await fs.rm(temporary, { force: true }).catch(() => {}); + } +} + +function queuePersist(): void { + if (persistTimer) clearTimeout(persistTimer); + persistTimer = setTimeout(() => { + persistTimer = undefined; + persistPromise = persistPromise.then(persistProfiles); + }, 250); + persistTimer.unref?.(); +} + +export async function initializeTransferProfiles(): Promise { + const target = storePath(); + if (loadedPath === target) return loadPromise; + loadedPath = target; + loadPromise = (async () => { + profiles.clear(); + directRoutes.clear(); + try { + const parsed = JSON.parse( + await fs.readFile(target, "utf8"), + ) as Partial; + if (parsed.version !== STORE_VERSION || !parsed.profiles) return; + for (const [key, profile] of Object.entries(parsed.profiles)) { + if (/^[a-f0-9]{64}$/.test(key) && validProfile(profile)) { + profiles.set(key, profile); + } + } + for (const [key, profile] of Object.entries(parsed.directRoutes ?? {})) { + if (/^[a-f0-9]{64}$/.test(key) && validDirectRoute(profile)) { + directRoutes.set(key, profile); + } + } + trimProfiles(); + } catch { + // Missing or malformed local learning data must not affect transfers. + } + })(); + return loadPromise; +} + +export async function flushTransferProfiles(): Promise { + if (persistTimer) { + clearTimeout(persistTimer); + persistTimer = undefined; + } + persistPromise = persistPromise.then(persistProfiles); + await persistPromise; +} + +function clamp(value: number, min: number, max: number): number { + return Math.max(min, Math.min(max, Math.floor(value))); +} + +export function selectTransferTuning( + fileSize: number, + profile?: TransferPerformanceProfile, + requestedLanes?: number, +): TransferTuning { + if (fileSize < 32 * MB) { + return { parallelSegmentCount: 1, pipelineConcurrency: 8 }; + } + + let lanes = fileSize >= 1024 * MB ? 4 : 2; + let pipelineConcurrency = fileSize >= 256 * MB ? 32 : 16; + + if (profile) { + lanes = profile.preferredLanes; + pipelineConcurrency = profile.pipelineConcurrency; + if (profile.failureRate >= 0.2) { + lanes = Math.min(lanes, 2); + pipelineConcurrency = Math.min(pipelineConcurrency, 16); + } + } + + if (requestedLanes !== undefined) lanes = requestedLanes; + + const segmentCapacity = Math.max(1, Math.ceil(fileSize / (256 * MB))); + return { + parallelSegmentCount: clamp(lanes, 1, Math.min(8, segmentCapacity)), + pipelineConcurrency: clamp(pipelineConcurrency, 8, 64), + }; +} + +export function updateTransferProfile( + previous: TransferPerformanceProfile | undefined, + observation: { + bytes: number; + durationMs: number; + lanes: number; + pipelineConcurrency: number; + failed: boolean; + now?: number; + }, +): TransferPerformanceProfile { + const now = observation.now ?? Date.now(); + const throughputBps = + observation.durationMs > 0 + ? (observation.bytes * 1000) / observation.durationMs + : 0; + const weight = previous ? 0.25 : 1; + const smoothedThroughput = previous + ? previous.throughputBps * (1 - weight) + throughputBps * weight + : throughputBps; + const failure = observation.failed ? 1 : 0; + const failureRate = previous + ? previous.failureRate * (1 - weight) + failure * weight + : failure; + + let preferredLanes = observation.lanes; + let pipelineConcurrency = observation.pipelineConcurrency; + if (failureRate >= 0.2) { + preferredLanes = Math.max(1, Math.floor(preferredLanes / 2)); + pipelineConcurrency = Math.max(8, Math.floor(pipelineConcurrency / 2)); + } else if ( + !observation.failed && + previous && + previous.samples >= 2 && + throughputBps > previous.throughputBps * 1.25 + ) { + preferredLanes = Math.min(8, preferredLanes + 1); + pipelineConcurrency = Math.min(64, pipelineConcurrency + 8); + } + + return { + throughputBps: smoothedThroughput, + failureRate, + samples: (previous?.samples ?? 0) + 1, + preferredLanes, + pipelineConcurrency, + updatedAt: now, + }; +} + +export function getTransferProfile( + key: string, + now = Date.now(), +): TransferPerformanceProfile | undefined { + const id = profileId(key); + const profile = profiles.get(id); + if (!profile) return undefined; + if (now - profile.updatedAt <= PROFILE_TTL_MS) return profile; + profiles.delete(id); + queuePersist(); + return undefined; +} + +export function recordTransferProfile( + key: string, + observation: Parameters[1], +): TransferPerformanceProfile { + const profile = updateTransferProfile(getTransferProfile(key), observation); + profiles.set(profileId(key), profile); + trimProfiles(observation.now); + queuePersist(); + return profile; +} + +export function getDirectRouteProfile( + key: string, + now = Date.now(), +): DirectRouteProfile | undefined { + const id = profileId(key); + const profile = directRoutes.get(id); + if (!profile) return undefined; + if (now - profile.updatedAt <= PROFILE_TTL_MS) return profile; + directRoutes.delete(id); + queuePersist(); + return undefined; +} + +export function recordDirectRouteBenchmark( + key: string, + directMs: number, + relayMs: number, + now = Date.now(), +): DirectRouteProfile { + const previous = getDirectRouteProfile(key, now); + const weight = previous ? 0.25 : 1; + const profile: DirectRouteProfile = { + directMs: previous + ? previous.directMs * (1 - weight) + directMs * weight + : directMs, + relayMs: previous + ? previous.relayMs * (1 - weight) + relayMs * weight + : relayMs, + failureRate: previous?.failureRate ?? 0, + benchmarkSamples: (previous?.benchmarkSamples ?? 0) + 1, + outcomeSamples: previous?.outcomeSamples ?? 0, + benchmarkedAt: now, + cooldownUntil: previous?.cooldownUntil, + updatedAt: now, + }; + directRoutes.set(profileId(key), profile); + trimProfiles(now); + queuePersist(); + return profile; +} + +export function recordDirectRouteOutcome( + key: string, + failed: boolean, + now = Date.now(), + cooldownMs = 10 * 60 * 1000, +): DirectRouteProfile | undefined { + const previous = getDirectRouteProfile(key, now); + if (!previous) return undefined; + const failure = failed ? 1 : 0; + const weight = previous.outcomeSamples > 0 ? 0.25 : 1; + const profile = { + ...previous, + failureRate: previous.failureRate * (1 - weight) + failure * weight, + outcomeSamples: previous.outcomeSamples + 1, + cooldownUntil: failed ? now + cooldownMs : undefined, + updatedAt: now, + }; + directRoutes.set(profileId(key), profile); + queuePersist(); + return profile; +} + +export function getRecentDirectRouteDecision( + key: string, + maxAgeMs: number, + now = Date.now(), +): { useDirect: boolean; directMs: number; relayMs: number } | undefined { + const profile = getDirectRouteProfile(key, now); + if (!profile) return undefined; + if ((profile.cooldownUntil ?? 0) > now) { + return { + useDirect: false, + directMs: profile.directMs, + relayMs: profile.relayMs, + }; + } + if (now - profile.benchmarkedAt > maxAgeMs) return undefined; + return { + useDirect: + profile.failureRate < 0.2 && profile.directMs <= profile.relayMs * 0.8, + directMs: profile.directMs, + relayMs: profile.relayMs, + }; +} + +export function clearTransferProfiles(): void { + if (persistTimer) clearTimeout(persistTimer); + persistTimer = undefined; + loadedPath = undefined; + loadPromise = undefined; + profiles.clear(); + directRoutes.clear(); +} diff --git a/plugins/file-manager/src/backend/trash-service.ts b/plugins/file-manager/src/backend/trash-service.ts new file mode 100644 index 000000000..6b0705df4 --- /dev/null +++ b/plugins/file-manager/src/backend/trash-service.ts @@ -0,0 +1,234 @@ +import crypto from "node:crypto"; +import path from "node:path"; +import type { SFTPWrapper, Stats } from "ssh2"; + +export interface TrashItem { + id: string; + name: string; + originalPath: string; + isDirectory: boolean; + deletedAt: string; + size: number; +} + +type StoredTrashItem = TrashItem & { trashPath: string }; + +const TRASH_DIR = ".termix-trash"; +const ID_PATTERN = /^[0-9a-f-]{36}$/i; + +function call( + run: (done: (error: Error | undefined, value: T) => void) => void, +) { + return new Promise((resolve, reject) => { + run((error, value) => (error ? reject(error) : resolve(value))); + }); +} + +function stat(sftp: SFTPWrapper, target: string) { + return call((done) => sftp.stat(target, done)); +} + +function lstat(sftp: SFTPWrapper, target: string) { + return call((done) => sftp.lstat(target, done)); +} + +function readdir(sftp: SFTPWrapper, target: string) { + return call((done) => sftp.readdir(target, done)); +} + +function readFile(sftp: SFTPWrapper, target: string) { + return call((done) => sftp.readFile(target, done)); +} + +function writeFile(sftp: SFTPWrapper, target: string, data: string) { + return call((done) => sftp.writeFile(target, data, done)); +} + +function rename(sftp: SFTPWrapper, from: string, to: string) { + return call((done) => sftp.rename(from, to, done)); +} + +function unlink(sftp: SFTPWrapper, target: string) { + return call((done) => sftp.unlink(target, done)); +} + +function mkdir(sftp: SFTPWrapper, target: string) { + return call((done) => sftp.mkdir(target, done)); +} + +function rmdir(sftp: SFTPWrapper, target: string) { + return call((done) => sftp.rmdir(target, done)); +} + +async function exists(sftp: SFTPWrapper, target: string) { + try { + await stat(sftp, target); + return true; + } catch { + return false; + } +} + +async function ensureDirectory(sftp: SFTPWrapper, target: string) { + const normalized = target.replace(/\\/g, "/"); + const root = normalized.startsWith("/") ? "/" : ""; + const parts = normalized.split("/").filter(Boolean); + let current = root; + for (const part of parts) { + current = + current === "/" ? `/${part}` : current ? `${current}/${part}` : part; + if (await exists(sftp, current)) continue; + await mkdir(sftp, current); + } +} + +async function removeTree(sftp: SFTPWrapper, target: string): Promise { + const targetStat = await lstat(sftp, target); + if (!targetStat.isDirectory()) { + await unlink(sftp, target); + return; + } + for (const entry of await readdir(sftp, target)) { + if (entry.filename === "." || entry.filename === "..") continue; + await removeTree(sftp, path.posix.join(target, entry.filename)); + } + await rmdir(sftp, target); +} + +async function trashPaths(sftp: SFTPWrapper) { + const home = await call((done) => sftp.realpath(".", done)); + const root = path.posix.join(home.replace(/\\/g, "/"), TRASH_DIR); + const files = path.posix.join(root, "files"); + const info = path.posix.join(root, "info"); + await ensureDirectory(sftp, files); + await ensureDirectory(sftp, info); + return { root, files, info }; +} + +export function isSafeTrashSource(itemPath: string, trashRoot: string) { + const normalized = path.posix.normalize(itemPath.replace(/\\/g, "/")); + const root = path.posix.normalize(trashRoot); + return ( + normalized !== "." && + normalized !== "/" && + !/^[A-Za-z]:\/?$/.test(normalized) && + normalized !== root && + !normalized.startsWith(`${root}/`) + ); +} + +function publicItem(item: StoredTrashItem): TrashItem { + const { trashPath: _trashPath, ...result } = item; + return result; +} + +async function readStoredItem( + sftp: SFTPWrapper, + dirs: { root: string; files: string; info: string }, + id: string, +): Promise { + if (!ID_PATTERN.test(id)) throw new Error("Invalid trash item id"); + const parsed = JSON.parse( + (await readFile(sftp, path.posix.join(dirs.info, `${id}.json`))).toString( + "utf8", + ), + ) as StoredTrashItem; + if ( + parsed.id !== id || + parsed.trashPath !== path.posix.join(dirs.files, id) || + !isSafeTrashSource(parsed.originalPath, dirs.root) + ) { + throw new Error("Invalid trash metadata"); + } + return parsed; +} + +export async function moveToTrash( + sftp: SFTPWrapper, + itemPath: string, +): Promise { + const dirs = await trashPaths(sftp); + if (!isSafeTrashSource(itemPath, dirs.root)) { + throw new Error("This path cannot be moved to trash"); + } + const itemStat = await lstat(sftp, itemPath); + const id = crypto.randomUUID(); + const trashPath = path.posix.join(dirs.files, id); + const item: StoredTrashItem = { + id, + name: path.posix.basename(itemPath.replace(/\\/g, "/")), + originalPath: itemPath, + trashPath, + isDirectory: itemStat.isDirectory(), + deletedAt: new Date().toISOString(), + size: itemStat.size, + }; + + await rename(sftp, itemPath, trashPath); + try { + await writeFile( + sftp, + path.posix.join(dirs.info, `${id}.json`), + JSON.stringify(item), + ); + } catch (error) { + await rename(sftp, trashPath, itemPath).catch(() => {}); + throw error; + } + return publicItem(item); +} + +export async function listTrash( + sftp: SFTPWrapper, + retentionDays: number, +): Promise { + const dirs = await trashPaths(sftp); + const cutoff = Date.now() - retentionDays * 86_400_000; + const items: TrashItem[] = []; + for (const entry of await readdir(sftp, dirs.info)) { + if (!entry.filename.endsWith(".json")) continue; + const id = entry.filename.slice(0, -5); + try { + const item = await readStoredItem(sftp, dirs, id); + if (new Date(item.deletedAt).getTime() < cutoff) { + if (await exists(sftp, item.trashPath)) + await removeTree(sftp, item.trashPath); + await unlink(sftp, path.posix.join(dirs.info, entry.filename)); + continue; + } + if (await exists(sftp, item.trashPath)) items.push(publicItem(item)); + else await unlink(sftp, path.posix.join(dirs.info, entry.filename)); + } catch { + // Ignore corrupt metadata without exposing arbitrary paths to deletion. + } + } + return items.sort((a, b) => b.deletedAt.localeCompare(a.deletedAt)); +} + +export async function restoreTrashItem(sftp: SFTPWrapper, id: string) { + const dirs = await trashPaths(sftp); + const item = await readStoredItem(sftp, dirs, id); + if (await exists(sftp, item.originalPath)) { + throw new Error("A file already exists at the original path"); + } + await rename(sftp, item.trashPath, item.originalPath); + await unlink(sftp, path.posix.join(dirs.info, `${id}.json`)); + return publicItem(item); +} + +export async function permanentlyDeleteTrashItem( + sftp: SFTPWrapper, + id: string, +) { + const dirs = await trashPaths(sftp); + const item = await readStoredItem(sftp, dirs, id); + if (await exists(sftp, item.trashPath)) + await removeTree(sftp, item.trashPath); + await unlink(sftp, path.posix.join(dirs.info, `${id}.json`)); +} + +export async function emptyTrash(sftp: SFTPWrapper) { + const items = await listTrash(sftp, 365_000); + for (const item of items) await permanentlyDeleteTrashItem(sftp, item.id); + return items.length; +} diff --git a/plugins/file-manager/src/backend/utils.ts b/plugins/file-manager/src/backend/utils.ts new file mode 100644 index 000000000..5a0cf569f --- /dev/null +++ b/plugins/file-manager/src/backend/utils.ts @@ -0,0 +1,164 @@ +export function isExecutableFile( + permissions: string, + fileName: string, +): boolean { + const hasExecutePermission = + permissions[3] === "x" || permissions[6] === "x" || permissions[9] === "x"; + + const scriptExtensions = [ + ".sh", + ".py", + ".pl", + ".rb", + ".js", + ".php", + ".bash", + ".zsh", + ".fish", + ]; + const hasScriptExtension = scriptExtensions.some((ext) => + fileName.toLowerCase().endsWith(ext), + ); + + const executableExtensions = [".bin", ".exe", ".out"]; + const hasExecutableExtension = executableExtensions.some((ext) => + fileName.toLowerCase().endsWith(ext), + ); + + const hasNoExtension = !fileName.includes(".") && hasExecutePermission; + + return ( + hasExecutePermission && + (hasScriptExtension || hasExecutableExtension || hasNoExtension) + ); +} + +export function modeToPermissions(mode: number): string { + const S_IFDIR = 0o040000; + const S_IFLNK = 0o120000; + const S_IFMT = 0o170000; + + const type = mode & S_IFMT; + const prefix = type === S_IFDIR ? "d" : type === S_IFLNK ? "l" : "-"; + + const perms = [ + mode & 0o400 ? "r" : "-", + mode & 0o200 ? "w" : "-", + mode & 0o100 ? "x" : "-", + mode & 0o040 ? "r" : "-", + mode & 0o020 ? "w" : "-", + mode & 0o010 ? "x" : "-", + mode & 0o004 ? "r" : "-", + mode & 0o002 ? "w" : "-", + mode & 0o001 ? "x" : "-", + ].join(""); + + return prefix + perms; +} + +export function parseLsDateToTimestamp(dateStr: string): number { + const parts = dateStr.trim().split(/\s+/); + if (parts.length < 3) return 0; + + const [month, day, timeOrYear] = parts; + const monthIndex = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ].indexOf(month); + if (monthIndex === -1) return 0; + + const dayNum = parseInt(day, 10); + const now = new Date(); + + if (timeOrYear.includes(":")) { + const [hours, minutes] = timeOrYear.split(":").map(Number); + let year = now.getFullYear(); + const candidate = new Date(year, monthIndex, dayNum, hours, minutes); + if (candidate > now) year -= 1; + return new Date(year, monthIndex, dayNum, hours, minutes).getTime() / 1000; + } + + const year = parseInt(timeOrYear, 10); + if (isNaN(year)) return 0; + return new Date(year, monthIndex, dayNum).getTime() / 1000; +} + +export function formatMtime(mtime: number): string { + const date = new Date(mtime * 1000); + const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + const month = months[date.getMonth()]; + const day = date.getDate().toString().padStart(2, " "); + const now = new Date(); + const sixMonthsAgo = new Date(now.getTime() - 180 * 24 * 60 * 60 * 1000); + + if (date > sixMonthsAgo) { + const hours = date.getHours().toString().padStart(2, "0"); + const minutes = date.getMinutes().toString().padStart(2, "0"); + return `${month} ${day} ${hours}:${minutes}`; + } + return `${month} ${day} ${date.getFullYear()}`; +} + +export function getMimeType(fileName: string): string { + const ext = fileName.split(".").pop()?.toLowerCase(); + const mimeTypes: Record = { + txt: "text/plain", + json: "application/json", + js: "text/javascript", + html: "text/html", + css: "text/css", + png: "image/png", + jpg: "image/jpeg", + jpeg: "image/jpeg", + gif: "image/gif", + pdf: "application/pdf", + zip: "application/zip", + tar: "application/x-tar", + gz: "application/gzip", + }; + return mimeTypes[ext || ""] || "application/octet-stream"; +} + +export function detectBinary(buffer: Buffer): boolean { + if (buffer.length === 0) return false; + + const sampleSize = Math.min(buffer.length, 8192); + let nullBytes = 0; + + for (let i = 0; i < sampleSize; i++) { + const byte = buffer[i]; + + if (byte === 0) { + nullBytes++; + } + + if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) { + if (++nullBytes > 1) return true; + } + } + + return nullBytes / sampleSize > 0.01; +} diff --git a/src/ui/features/file-manager/FileManager.tsx b/plugins/file-manager/src/frontend/FileManager.tsx similarity index 99% rename from src/ui/features/file-manager/FileManager.tsx rename to plugins/file-manager/src/frontend/FileManager.tsx index 00a8d8910..b7bdf9748 100644 --- a/src/ui/features/file-manager/FileManager.tsx +++ b/plugins/file-manager/src/frontend/FileManager.tsx @@ -1,4 +1,4 @@ -import { getErrorMessage } from "../../lib/error-message.js"; +import { getErrorMessage } from "@/lib/error-message.js"; /* eslint-disable react-hooks/exhaustive-deps */ import React, { useState, @@ -21,8 +21,8 @@ import { import { FileWindow } from "./components/FileWindow.tsx"; import { DownloadProgressToast } from "./components/DownloadProgressToast.tsx"; import { DiffWindow } from "./components/DiffWindow.tsx"; -import { useDragToDesktop } from "@/features/file-manager/hooks/useDragToDesktop"; -import { useDragToSystemDesktop } from "@/features/file-manager/hooks/useDragToSystemDesktop"; +import { useDragToDesktop } from "./hooks/useDragToDesktop"; +import { useDragToSystemDesktop } from "./hooks/useDragToSystemDesktop"; import { useConfirmation } from "@/hooks/use-confirmation.ts"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; @@ -41,10 +41,10 @@ import type { SSHHost, FileItem } from "@/types/index"; import { ConnectionLogProvider, useConnectionLog, -} from "@/ssh/connection-log/ConnectionLogContext.tsx"; -import { ConnectionScreen } from "@/components/connection/ConnectionScreen.tsx"; + ConnectionScreen, +} from "@termix/plugin-sdk/ui"; import { ConnectionLogPanel } from "@/components/connection/ConnectionLogPanel.tsx"; -import { useConnectionRetry } from "@/lib/useConnectionRetry.ts"; +import { useConnectionRetry } from "@termix/plugin-sdk/frontend"; import { copyToClipboard } from "@/lib/clipboard.ts"; import { listSSHFiles, @@ -63,17 +63,21 @@ import { getSSHStatus, keepSSHAlive, identifySSHSymlink, + changeSSHPermissions, + extractSSHArchive, + compressSSHFiles, + setSudoPassword, +} from "./api/ssh-file-operations-api"; +import { addRecentFile, addPinnedFile, removePinnedFile, removeRecentFile, addFolderShortcut, getPinnedFiles, +} from "./api/file-manager-data-api"; +import { logActivity, - changeSSHPermissions, - extractSSHArchive, - compressSSHFiles, - setSudoPassword, getServerMetricsById, transferToHost, addTransferRecent, @@ -85,7 +89,7 @@ import { markAdaptiveResourceUsed, runAdaptiveBackgroundTask, } from "@/lib/adaptive-resource-budget"; -import { shouldPrefetchFileContent } from "@/lib/file-content-request-cache"; +import { shouldPrefetchFileContent } from "./lib/file-content-request-cache"; import { markFilePreviewUsed, preloadFilePreview, @@ -103,7 +107,7 @@ import { invalidateCachedFileList, peekCachedFileList, updateCachedFileList, -} from "@/lib/file-list-request-cache"; +} from "./lib/file-list-request-cache"; import { addOptimisticItem, childPath, @@ -1337,7 +1341,8 @@ function FileManagerContent({ try { await ensureSSHConnection(); - const { downloadSSHFileStream } = await import("@/main-axios.ts"); + const { downloadSSHFileStream } = + await import("./api/ssh-file-operations-api"); toast.loading(, { id: toastId, @@ -3003,7 +3008,6 @@ function FileManagerContent({ const offsetX = 100; const offsetY = 80; - const windowId = `diff-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const createWindowComponent = (windowId: string) => ( ( key: K, diff --git a/src/ui/features/file-manager/LocalFileContextMenu.tsx b/plugins/file-manager/src/frontend/LocalFileContextMenu.tsx similarity index 100% rename from src/ui/features/file-manager/LocalFileContextMenu.tsx rename to plugins/file-manager/src/frontend/LocalFileContextMenu.tsx diff --git a/src/ui/features/file-manager/LocalFilePane.tsx b/plugins/file-manager/src/frontend/LocalFilePane.tsx similarity index 99% rename from src/ui/features/file-manager/LocalFilePane.tsx rename to plugins/file-manager/src/frontend/LocalFilePane.tsx index ac3a7dfbb..daf5468b1 100644 --- a/src/ui/features/file-manager/LocalFilePane.tsx +++ b/plugins/file-manager/src/frontend/LocalFilePane.tsx @@ -27,8 +27,7 @@ import { X, } from "lucide-react"; import { cn } from "@/lib/utils.ts"; -import { Button } from "@/components/button.tsx"; -import { Input } from "@/components/input.tsx"; +import { Button, Input } from "@termix/plugin-sdk/ui"; import type { LocalFileEntry } from "@/types/electron"; import { createLocalFile, diff --git a/src/ui/features/sftp/SftpTransferTab.tsx b/plugins/file-manager/src/frontend/SftpTransferTab.tsx similarity index 98% rename from src/ui/features/sftp/SftpTransferTab.tsx rename to plugins/file-manager/src/frontend/SftpTransferTab.tsx index 010ec0c26..6332880e3 100644 --- a/src/ui/features/sftp/SftpTransferTab.tsx +++ b/plugins/file-manager/src/frontend/SftpTransferTab.tsx @@ -25,41 +25,41 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/alert-dialog"; -import { Button } from "@/components/button"; -import { + Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, -} from "@/components/dialog"; -import { Input } from "@/components/input"; -import { Label } from "@/components/label"; + Input, + Label, + Select2, +} from "@termix/plugin-sdk/ui"; import { addTransferRecent, + getSSHHosts, + transferToHost, + type TransferProgressResponse, +} from "@/main-axios"; +import { browseSSHDirectory, changeSSHPermissions, createSSHFolder, deleteSSHItem, ensureSSHSessionForHost, - getSSHHosts, renameSSHItem, - transferToHost, type HostConnectionState, - type TransferProgressResponse, -} from "@/main-axios"; +} from "./api/ssh-file-operations-api"; import type { SSHHost } from "@/types"; -import { PermissionsDialog } from "@/features/file-manager/components/PermissionsDialog"; -import { formatFileSize } from "@/features/file-manager/file-manager-utils"; -import { beginTransferProgressMonitoring } from "@/features/file-manager/transferProgressMonitor"; -import { createFormatTransferMetrics } from "@/features/file-manager/transferMetricsFormat"; +import { PermissionsDialog } from "./components/PermissionsDialog"; +import { formatFileSize } from "./file-manager-utils"; +import { beginTransferProgressMonitoring } from "./transferProgressMonitor"; +import { createFormatTransferMetrics } from "./transferMetricsFormat"; import { hasSameHostTransferConflict, joinRemotePath, normalizeRemoteDir, } from "./sftp-transfer-utils"; -import { Select2 } from "@/components/select2"; type EntryType = "file" | "directory" | "link" | "other"; type PaneId = "source" | "dest"; diff --git a/src/ui/features/file-manager/SudoPasswordDialog.tsx b/plugins/file-manager/src/frontend/SudoPasswordDialog.tsx similarity index 95% rename from src/ui/features/file-manager/SudoPasswordDialog.tsx rename to plugins/file-manager/src/frontend/SudoPasswordDialog.tsx index 0554cf006..27fc83539 100644 --- a/src/ui/features/file-manager/SudoPasswordDialog.tsx +++ b/plugins/file-manager/src/frontend/SudoPasswordDialog.tsx @@ -6,9 +6,9 @@ import { DialogHeader, DialogTitle, DialogFooter, -} from "@/components/dialog.tsx"; -import { Button } from "@/components/button.tsx"; -import { PasswordInput } from "@/components/password-input.tsx"; + Button, + PasswordInput, +} from "@termix/plugin-sdk/ui"; import { Shield } from "lucide-react"; import { useTranslation } from "react-i18next"; diff --git a/plugins/file-manager/src/frontend/TransferMonitor.tsx b/plugins/file-manager/src/frontend/TransferMonitor.tsx new file mode 100644 index 000000000..6dfa568b3 --- /dev/null +++ b/plugins/file-manager/src/frontend/TransferMonitor.tsx @@ -0,0 +1,91 @@ +import type { TranslateFn } from "@termix/plugin-sdk/frontend"; +import type { TFunction } from "i18next"; +import { getTransferStatus, listActiveTransfers } from "@/main-axios.ts"; +import { createFormatTransferMetrics } from "./transferMetricsFormat.ts"; +import { + beginTransferProgressMonitoring, + isTransferBeingMonitored, + showTransferCompletionToast, +} from "./transferProgressMonitor.tsx"; +import { + clearStalePendingTransfer, + getPendingTransferIds, + isTransferNotified, +} from "./transferNotificationStore.ts"; +import { runAdaptivePolling } from "@/lib/adaptive-polling.ts"; + +const POLL_INTERVAL_MS = 2000; + +/** + * Resumes progress toasts for in-flight host-to-host transfers across app + * reloads and tab switches. No UI of its own, so it runs as plain background + * polling from activate() rather than a mounted headless component - core + * cannot mount a plugin component outside a tab/panel/slot, and this needs + * nothing from one. + */ +export function startTransferMonitor(translate: TranslateFn): () => void { + // Every call here is a plain t("key") / t("key", {options}) - the calls + // these helpers make everywhere else, so the SDK's simpler translate + // function satisfies the same shape they're already typed against. + const t = translate as unknown as TFunction; + const formatTransferMetrics = createFormatTransferMetrics(t); + + const reconcileTransfers = async () => { + let hasWork = false; + try { + const { transfers } = await listActiveTransfers(); + hasWork = transfers.length > 0; + for (const transfer of transfers) { + if (isTransferBeingMonitored(transfer.transferId)) continue; + beginTransferProgressMonitoring(transfer.transferId, t, { + resumed: true, + initialStatus: transfer, + formatTransferMetrics, + }); + } + } catch { + // Non-fatal: file-manager service may be unavailable briefly + } + + const pendingTransferIds = getPendingTransferIds(); + for (const transferId of pendingTransferIds) { + if ( + isTransferBeingMonitored(transferId) || + isTransferNotified(transferId) + ) { + continue; + } + hasWork = true; + + try { + const status = await getTransferStatus(transferId); + if (status.status === "running") { + if (!isTransferBeingMonitored(transferId)) { + beginTransferProgressMonitoring(transferId, t, { + resumed: true, + initialStatus: status, + formatTransferMetrics, + }); + } + continue; + } + + showTransferCompletionToast( + status, + t, + undefined, + formatTransferMetrics, + ); + } catch { + clearStalePendingTransfer(transferId); + } + } + return hasWork; + }; + + return runAdaptivePolling(reconcileTransfers, { + minIntervalMs: POLL_INTERVAL_MS, + maxIntervalMs: 30_000, + stablePollsPerStep: 1, + }); +} diff --git a/src/ui/api/file-manager-data-api.ts b/plugins/file-manager/src/frontend/api/file-manager-data-api.ts similarity index 80% rename from src/ui/api/file-manager-data-api.ts rename to plugins/file-manager/src/frontend/api/file-manager-data-api.ts index 513785382..0b75526db 100644 --- a/src/ui/api/file-manager-data-api.ts +++ b/plugins/file-manager/src/frontend/api/file-manager-data-api.ts @@ -1,4 +1,4 @@ -import { authApi, handleApiError } from "@/main-axios"; +import { fileManagerApi, handleApiError } from "@/main-axios"; /** Row shape shared by the recent / pinned / shortcut list endpoints. */ export interface FileManagerEntry { @@ -16,7 +16,7 @@ export async function getRecentFiles( hostId: number, ): Promise { try { - const response = await authApi.get("/host/file_manager/recent", { + const response = await fileManagerApi.get("/recent", { params: { hostId }, }); return response.data; @@ -32,7 +32,7 @@ export async function addRecentFile( name?: string, ): Promise> { try { - const response = await authApi.post("/host/file_manager/recent", { + const response = await fileManagerApi.post("/recent", { hostId, path, name, @@ -49,7 +49,7 @@ export async function removeRecentFile( path: string, ): Promise> { try { - const response = await authApi.delete("/host/file_manager/recent", { + const response = await fileManagerApi.delete("/recent", { data: { hostId, path }, }); return response.data; @@ -63,7 +63,7 @@ export async function getPinnedFiles( hostId: number, ): Promise { try { - const response = await authApi.get("/host/file_manager/pinned", { + const response = await fileManagerApi.get("/pinned", { params: { hostId }, }); return response.data; @@ -79,7 +79,7 @@ export async function addPinnedFile( name?: string, ): Promise> { try { - const response = await authApi.post("/host/file_manager/pinned", { + const response = await fileManagerApi.post("/pinned", { hostId, path, name, @@ -96,7 +96,7 @@ export async function removePinnedFile( path: string, ): Promise> { try { - const response = await authApi.delete("/host/file_manager/pinned", { + const response = await fileManagerApi.delete("/pinned", { data: { hostId, path }, }); return response.data; @@ -110,7 +110,7 @@ export async function getFolderShortcuts( hostId: number, ): Promise { try { - const response = await authApi.get("/host/file_manager/shortcuts", { + const response = await fileManagerApi.get("/shortcuts", { params: { hostId }, }); return response.data; @@ -126,7 +126,7 @@ export async function addFolderShortcut( name?: string, ): Promise> { try { - const response = await authApi.post("/host/file_manager/shortcuts", { + const response = await fileManagerApi.post("/shortcuts", { hostId, path, name, @@ -143,7 +143,7 @@ export async function removeFolderShortcut( path: string, ): Promise> { try { - const response = await authApi.delete("/host/file_manager/shortcuts", { + const response = await fileManagerApi.delete("/shortcuts", { data: { hostId, path }, }); return response.data; diff --git a/src/ui/api/local-transfer-api.ts b/plugins/file-manager/src/frontend/api/local-transfer-api.ts similarity index 100% rename from src/ui/api/local-transfer-api.ts rename to plugins/file-manager/src/frontend/api/local-transfer-api.ts diff --git a/src/ui/api/ssh-file-operations-api.ts b/plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts similarity index 98% rename from src/ui/api/ssh-file-operations-api.ts rename to plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts index 1c688338a..256eb17f8 100644 --- a/src/ui/api/ssh-file-operations-api.ts +++ b/plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts @@ -1,9 +1,8 @@ -import { getErrorMessage } from "../lib/error-message.js"; +import { getErrorMessage } from "@/lib/error-message.js"; import axios from "axios"; import { asHttpError } from "@/lib/http-error"; import { authApi, - fileManagerApi, handleApiError, getFileManagerApiForSession, setSessionOrigin, @@ -12,12 +11,12 @@ import { import { resolveConnectionOrigin } from "@/lib/connection-origin"; import { fileLogger } from "@/lib/frontend-logger"; import type { FileItem, SSHHost } from "@/types/index"; -import { getCachedFileList } from "@/lib/file-list-request-cache"; +import { getCachedFileList } from "../lib/file-list-request-cache"; import { getCachedFileContent, invalidateCachedFileContent, type FileContentResult, -} from "@/lib/file-content-request-cache"; +} from "../lib/file-content-request-cache"; type ApiConnectionLog = { type: "info" | "success" | "warning" | "error"; @@ -56,11 +55,6 @@ export interface SSHConnectResult { securityKey?: string; } -function buildFileManagerUrl(path: string): string { - const baseURL = String(fileManagerApi.defaults.baseURL || ""); - return `${baseURL.replace(/\/$/, "")}${path}`; -} - function triggerBlobDownload(blob: Blob, fileName: string): void { const url = URL.createObjectURL(blob); const link = document.createElement("a"); diff --git a/src/ui/features/file-manager/components/AudioPreview.tsx b/plugins/file-manager/src/frontend/components/AudioPreview.tsx similarity index 100% rename from src/ui/features/file-manager/components/AudioPreview.tsx rename to plugins/file-manager/src/frontend/components/AudioPreview.tsx diff --git a/src/ui/features/file-manager/components/CodeEditor.tsx b/plugins/file-manager/src/frontend/components/CodeEditor.tsx similarity index 100% rename from src/ui/features/file-manager/components/CodeEditor.tsx rename to plugins/file-manager/src/frontend/components/CodeEditor.tsx diff --git a/src/ui/features/file-manager/components/ColumnResizeHandle.tsx b/plugins/file-manager/src/frontend/components/ColumnResizeHandle.tsx similarity index 100% rename from src/ui/features/file-manager/components/ColumnResizeHandle.tsx rename to plugins/file-manager/src/frontend/components/ColumnResizeHandle.tsx diff --git a/src/ui/features/file-manager/components/ColumnVisibilityMenu.tsx b/plugins/file-manager/src/frontend/components/ColumnVisibilityMenu.tsx similarity index 100% rename from src/ui/features/file-manager/components/ColumnVisibilityMenu.tsx rename to plugins/file-manager/src/frontend/components/ColumnVisibilityMenu.tsx diff --git a/src/ui/features/file-manager/components/CompressDialog.tsx b/plugins/file-manager/src/frontend/components/CompressDialog.tsx similarity index 96% rename from src/ui/features/file-manager/components/CompressDialog.tsx rename to plugins/file-manager/src/frontend/components/CompressDialog.tsx index 4e4a879d4..55ecb55c3 100644 --- a/src/ui/features/file-manager/components/CompressDialog.tsx +++ b/plugins/file-manager/src/frontend/components/CompressDialog.tsx @@ -6,10 +6,10 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@/components/dialog.tsx"; -import { Button } from "@/components/button.tsx"; -import { Input } from "@/components/input.tsx"; -import { Label } from "@/components/label.tsx"; + Button, + Input, + Label, +} from "@termix/plugin-sdk/ui"; import { Package } from "lucide-react"; import { useTranslation } from "react-i18next"; diff --git a/src/ui/features/file-manager/components/DiffViewer.tsx b/plugins/file-manager/src/frontend/components/DiffViewer.tsx similarity index 99% rename from src/ui/features/file-manager/components/DiffViewer.tsx rename to plugins/file-manager/src/frontend/components/DiffViewer.tsx index 74031e875..5b1979078 100644 --- a/src/ui/features/file-manager/components/DiffViewer.tsx +++ b/plugins/file-manager/src/frontend/components/DiffViewer.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import React, { useState, useEffect } from "react"; import { DiffEditor } from "@monaco-editor/react"; -import { Button } from "@/components/button.tsx"; +import { Button } from "@termix/plugin-sdk/ui"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { @@ -17,7 +17,7 @@ import { downloadSSHFile, getSSHStatus, connectSSH, -} from "@/main-axios.ts"; +} from "../api/ssh-file-operations-api"; import type { FileItem, SSHHost } from "@/types"; interface DiffViewerProps { diff --git a/src/ui/features/file-manager/components/DiffWindow.tsx b/plugins/file-manager/src/frontend/components/DiffWindow.tsx similarity index 100% rename from src/ui/features/file-manager/components/DiffWindow.tsx rename to plugins/file-manager/src/frontend/components/DiffWindow.tsx diff --git a/src/ui/features/file-manager/components/DownloadProgressToast.tsx b/plugins/file-manager/src/frontend/components/DownloadProgressToast.tsx similarity index 100% rename from src/ui/features/file-manager/components/DownloadProgressToast.tsx rename to plugins/file-manager/src/frontend/components/DownloadProgressToast.tsx diff --git a/src/ui/features/file-manager/components/DraggableWindow.tsx b/plugins/file-manager/src/frontend/components/DraggableWindow.tsx similarity index 100% rename from src/ui/features/file-manager/components/DraggableWindow.tsx rename to plugins/file-manager/src/frontend/components/DraggableWindow.tsx diff --git a/src/ui/features/file-manager/components/FileViewer.tsx b/plugins/file-manager/src/frontend/components/FileViewer.tsx similarity index 99% rename from src/ui/features/file-manager/components/FileViewer.tsx rename to plugins/file-manager/src/frontend/components/FileViewer.tsx index 5b8b1431d..449be13b8 100644 --- a/src/ui/features/file-manager/components/FileViewer.tsx +++ b/plugins/file-manager/src/frontend/components/FileViewer.tsx @@ -42,7 +42,7 @@ import { SiMysql, SiDocker, } from "react-icons/si"; -import { Button } from "@/components/button.tsx"; +import { Button } from "@termix/plugin-sdk/ui"; import { Kbd, KbdKey } from "@/components/kbd.tsx"; import type { CodeEditorHandle } from "./CodeEditor.tsx"; import { diff --git a/src/ui/features/file-manager/components/FileWindow.tsx b/plugins/file-manager/src/frontend/components/FileWindow.tsx similarity index 99% rename from src/ui/features/file-manager/components/FileWindow.tsx rename to plugins/file-manager/src/frontend/components/FileWindow.tsx index 0fd9a431e..1535a581c 100644 --- a/src/ui/features/file-manager/components/FileWindow.tsx +++ b/plugins/file-manager/src/frontend/components/FileWindow.tsx @@ -9,7 +9,7 @@ import { writeSSHFile, getSSHStatus, connectSSH, -} from "@/main-axios.ts"; +} from "../api/ssh-file-operations-api"; import { toast } from "sonner"; import type { SSHHost } from "@/types/index"; import { useTranslation } from "react-i18next"; diff --git a/src/ui/features/file-manager/components/ImagePreview.tsx b/plugins/file-manager/src/frontend/components/ImagePreview.tsx similarity index 98% rename from src/ui/features/file-manager/components/ImagePreview.tsx rename to plugins/file-manager/src/frontend/components/ImagePreview.tsx index 0bd0cb0b0..d8cb1bcf5 100644 --- a/src/ui/features/file-manager/components/ImagePreview.tsx +++ b/plugins/file-manager/src/frontend/components/ImagePreview.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { PhotoProvider, PhotoView } from "react-photo-view"; import "react-photo-view/dist/react-photo-view.css"; import { AlertCircle, Download } from "lucide-react"; -import { Button } from "@/components/button.tsx"; +import { Button } from "@termix/plugin-sdk/ui"; import { useTranslation } from "react-i18next"; interface ImagePreviewProps { diff --git a/src/ui/features/file-manager/components/LocalTransferProgressToast.tsx b/plugins/file-manager/src/frontend/components/LocalTransferProgressToast.tsx similarity index 98% rename from src/ui/features/file-manager/components/LocalTransferProgressToast.tsx rename to plugins/file-manager/src/frontend/components/LocalTransferProgressToast.tsx index 87042a699..fde2c77e5 100644 --- a/src/ui/features/file-manager/components/LocalTransferProgressToast.tsx +++ b/plugins/file-manager/src/frontend/components/LocalTransferProgressToast.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/button.tsx"; +import { Button } from "@termix/plugin-sdk/ui"; import { useTranslation } from "react-i18next"; import { formatFileSize } from "../file-manager-utils.ts"; diff --git a/src/ui/features/file-manager/components/MarkdownRenderer.tsx b/plugins/file-manager/src/frontend/components/MarkdownRenderer.tsx similarity index 100% rename from src/ui/features/file-manager/components/MarkdownRenderer.tsx rename to plugins/file-manager/src/frontend/components/MarkdownRenderer.tsx diff --git a/src/ui/features/file-manager/components/PaneResizeHandle.tsx b/plugins/file-manager/src/frontend/components/PaneResizeHandle.tsx similarity index 100% rename from src/ui/features/file-manager/components/PaneResizeHandle.tsx rename to plugins/file-manager/src/frontend/components/PaneResizeHandle.tsx diff --git a/src/ui/features/file-manager/components/PdfPreview.tsx b/plugins/file-manager/src/frontend/components/PdfPreview.tsx similarity index 91% rename from src/ui/features/file-manager/components/PdfPreview.tsx rename to plugins/file-manager/src/frontend/components/PdfPreview.tsx index 47cfe458a..92bda7454 100644 --- a/src/ui/features/file-manager/components/PdfPreview.tsx +++ b/plugins/file-manager/src/frontend/components/PdfPreview.tsx @@ -1,11 +1,14 @@ import React, { useState } from "react"; -import { Document, Page, pdfjs } from "react-pdf"; +import { Document, Page } from "react-pdf"; import { AlertCircle, Download } from "lucide-react"; -import { Button } from "@/components/button.tsx"; +import { Button } from "@termix/plugin-sdk/ui"; import { useTranslation } from "react-i18next"; -import pdfjsWorkerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url"; -pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorkerUrl; +// No workerSrc: the termix-plugin CLI's esbuild step has no asset-copy +// pipeline for a worker file the way core's Vite build does with `?url`, so +// pdfjs falls back to parsing on the main thread instead. Fine for the +// config/log-sized files this preview usually opens; a real worker needs the +// CLI to gain static-asset output first (see FINISH-LIST.md). interface PdfPreviewProps { content: string; diff --git a/src/ui/features/file-manager/components/PermissionsDialog.tsx b/plugins/file-manager/src/frontend/components/PermissionsDialog.tsx similarity index 98% rename from src/ui/features/file-manager/components/PermissionsDialog.tsx rename to plugins/file-manager/src/frontend/components/PermissionsDialog.tsx index 4a9ef3865..5eb7b858c 100644 --- a/src/ui/features/file-manager/components/PermissionsDialog.tsx +++ b/plugins/file-manager/src/frontend/components/PermissionsDialog.tsx @@ -6,9 +6,9 @@ import { DialogHeader, DialogTitle, DialogFooter, -} from "@/components/dialog.tsx"; -import { Button } from "@/components/button.tsx"; -import { Input } from "@/components/input.tsx"; + Button, + Input, +} from "@termix/plugin-sdk/ui"; import { useTranslation } from "react-i18next"; import { Lock } from "lucide-react"; diff --git a/src/ui/features/file-manager/components/TerminalWindow.tsx b/plugins/file-manager/src/frontend/components/TerminalWindow.tsx similarity index 100% rename from src/ui/features/file-manager/components/TerminalWindow.tsx rename to plugins/file-manager/src/frontend/components/TerminalWindow.tsx diff --git a/src/ui/features/file-manager/components/TransferProgressBar.tsx b/plugins/file-manager/src/frontend/components/TransferProgressBar.tsx similarity index 100% rename from src/ui/features/file-manager/components/TransferProgressBar.tsx rename to plugins/file-manager/src/frontend/components/TransferProgressBar.tsx diff --git a/src/ui/features/file-manager/components/TransferProgressToast.tsx b/plugins/file-manager/src/frontend/components/TransferProgressToast.tsx similarity index 98% rename from src/ui/features/file-manager/components/TransferProgressToast.tsx rename to plugins/file-manager/src/frontend/components/TransferProgressToast.tsx index ebb09e833..ac7747bd2 100644 --- a/src/ui/features/file-manager/components/TransferProgressToast.tsx +++ b/plugins/file-manager/src/frontend/components/TransferProgressToast.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/button.tsx"; +import { Button } from "@termix/plugin-sdk/ui"; import { formatTransferMbPerSec, getTransferProgressPercent, diff --git a/src/ui/features/file-manager/components/TransferToHostDialog.tsx b/plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx similarity index 99% rename from src/ui/features/file-manager/components/TransferToHostDialog.tsx rename to plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx index dc35c5aeb..fbb8f4319 100644 --- a/src/ui/features/file-manager/components/TransferToHostDialog.tsx +++ b/plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx @@ -12,10 +12,11 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@/components/dialog.tsx"; -import { Button } from "@/components/button.tsx"; -import { Input } from "@/components/input.tsx"; -import { Label } from "@/components/label.tsx"; + Button, + Input, + Label, + Select2, +} from "@termix/plugin-sdk/ui"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { @@ -29,22 +30,25 @@ import { Trash2, } from "lucide-react"; import { - getSSHStatus, getSSHHosts, getTransferRecent, - getFolderShortcuts, - addFolderShortcut, - removeFolderShortcut, - browseSSHDirectory, - ensureSSHSessionForHost, getTransferMethodPreview, type TransferDestination, - type HostConnectionState, type TransferMethodPreference, type TransferMethodPreview, } from "@/main-axios.ts"; +import { + getSSHStatus, + browseSSHDirectory, + ensureSSHSessionForHost, + type HostConnectionState, +} from "../api/ssh-file-operations-api"; +import { + getFolderShortcuts, + addFolderShortcut, + removeFolderShortcut, +} from "../api/file-manager-data-api"; import type { SSHHost } from "@/types"; -import { Select2 } from "@/components/select2"; interface FileItem { name: string; diff --git a/src/ui/features/file-manager/components/WindowManager.tsx b/plugins/file-manager/src/frontend/components/WindowManager.tsx similarity index 100% rename from src/ui/features/file-manager/components/WindowManager.tsx rename to plugins/file-manager/src/frontend/components/WindowManager.tsx diff --git a/src/ui/features/file-manager/file-manager-types.ts b/plugins/file-manager/src/frontend/file-manager-types.ts similarity index 100% rename from src/ui/features/file-manager/file-manager-types.ts rename to plugins/file-manager/src/frontend/file-manager-types.ts diff --git a/src/ui/features/file-manager/file-manager-utils.ts b/plugins/file-manager/src/frontend/file-manager-utils.ts similarity index 100% rename from src/ui/features/file-manager/file-manager-utils.ts rename to plugins/file-manager/src/frontend/file-manager-utils.ts diff --git a/src/ui/features/file-manager/file-preview-preload.ts b/plugins/file-manager/src/frontend/file-preview-preload.ts similarity index 100% rename from src/ui/features/file-manager/file-preview-preload.ts rename to plugins/file-manager/src/frontend/file-preview-preload.ts diff --git a/src/ui/features/homepage/widgets/FileManagerWidget.tsx b/plugins/file-manager/src/frontend/homepage/FileManagerWidget.tsx similarity index 75% rename from src/ui/features/homepage/widgets/FileManagerWidget.tsx rename to plugins/file-manager/src/frontend/homepage/FileManagerWidget.tsx index f5a708309..960c60ecf 100644 --- a/src/ui/features/homepage/widgets/FileManagerWidget.tsx +++ b/plugins/file-manager/src/frontend/homepage/FileManagerWidget.tsx @@ -1,19 +1,17 @@ import { useState, useEffect } from "react"; import { FolderSearch } from "lucide-react"; import { useTranslation } from "react-i18next"; -import { registerWidget } from "./WidgetRegistry"; import type { FileManagerWidgetConfig, WidgetComponentProps, } from "@/types/homepage-types"; -import { GRID_SIZE } from "@/types/homepage-types"; import { getSSHHosts } from "@/api/ssh-host-management-api"; import type { SSHHostWithStatus } from "@/main-axios"; -import { FileManager } from "@/features/file-manager/FileManager"; import type { SSHHost } from "@/types/index"; -import { WidgetTitle } from "./WidgetTitle"; +import { WidgetTitle } from "@/features/homepage/widgets/WidgetTitle"; +import { FileManager } from "../FileManager"; -function FileManagerWidget({ +export function FileManagerWidget({ widget, config, }: WidgetComponentProps) { @@ -72,15 +70,3 @@ function FileManagerWidget({ ); } - -registerWidget({ - id: "file_manager_widget", - name: "File Manager", - description: "Embedded SFTP file manager for a configured host", - category: "system", - icon: , - defaultConfig: { hostId: 0 }, - defaultSize: { w: GRID_SIZE * 20, h: GRID_SIZE * 14 }, - minSize: { w: GRID_SIZE * 10, h: GRID_SIZE * 8 }, - component: FileManagerWidget, -}); diff --git a/src/ui/features/homepage/dialogs/FileManagerWidgetEditForm.tsx b/plugins/file-manager/src/frontend/homepage/FileManagerWidgetEditForm.tsx similarity index 83% rename from src/ui/features/homepage/dialogs/FileManagerWidgetEditForm.tsx rename to plugins/file-manager/src/frontend/homepage/FileManagerWidgetEditForm.tsx index 2028981e9..af9616b89 100644 --- a/src/ui/features/homepage/dialogs/FileManagerWidgetEditForm.tsx +++ b/plugins/file-manager/src/frontend/homepage/FileManagerWidgetEditForm.tsx @@ -2,7 +2,7 @@ import type { FileManagerWidgetConfig, WidgetEditFormProps, } from "@/types/homepage-types"; -import { SingleHostEditForm } from "./SingleHostEditForm"; +import { SingleHostEditForm } from "@/features/homepage/dialogs/SingleHostEditForm"; export function FileManagerWidgetEditForm({ config, diff --git a/src/ui/features/file-manager/hooks/useDragAndDrop.ts b/plugins/file-manager/src/frontend/hooks/useDragAndDrop.ts similarity index 100% rename from src/ui/features/file-manager/hooks/useDragAndDrop.ts rename to plugins/file-manager/src/frontend/hooks/useDragAndDrop.ts diff --git a/src/ui/features/file-manager/hooks/useDragToDesktop.ts b/plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts similarity index 99% rename from src/ui/features/file-manager/hooks/useDragToDesktop.ts rename to plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts index c5b9db5df..54692c457 100644 --- a/src/ui/features/file-manager/hooks/useDragToDesktop.ts +++ b/plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts @@ -1,6 +1,6 @@ import { useState, useCallback } from "react"; import { toast } from "sonner"; -import { downloadSSHFile } from "@/main-axios"; +import { downloadSSHFile } from "../api/ssh-file-operations-api"; import type { FileItem, SSHHost } from "@/types/index.js"; interface DragToDesktopState { diff --git a/src/ui/features/file-manager/hooks/useDragToSystemDesktop.ts b/plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts similarity index 99% rename from src/ui/features/file-manager/hooks/useDragToSystemDesktop.ts rename to plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts index 8e84bcddb..8820f1cd5 100644 --- a/src/ui/features/file-manager/hooks/useDragToSystemDesktop.ts +++ b/plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { useState, useCallback, useRef } from "react"; import { toast } from "sonner"; -import { downloadSSHFile } from "@/main-axios"; +import { downloadSSHFile } from "../api/ssh-file-operations-api"; import type { FileItem, SSHHost } from "@/types/index.js"; interface DragToSystemState { diff --git a/src/ui/features/file-manager/hooks/useFileSelection.ts b/plugins/file-manager/src/frontend/hooks/useFileSelection.ts similarity index 100% rename from src/ui/features/file-manager/hooks/useFileSelection.ts rename to plugins/file-manager/src/frontend/hooks/useFileSelection.ts diff --git a/src/ui/features/file-manager/hooks/useLocalTransfers.ts b/plugins/file-manager/src/frontend/hooks/useLocalTransfers.ts similarity index 99% rename from src/ui/features/file-manager/hooks/useLocalTransfers.ts rename to plugins/file-manager/src/frontend/hooks/useLocalTransfers.ts index 8cb5bb19c..3171eb7ad 100644 --- a/src/ui/features/file-manager/hooks/useLocalTransfers.ts +++ b/plugins/file-manager/src/frontend/hooks/useLocalTransfers.ts @@ -7,11 +7,10 @@ import type { LocalWalkResult } from "@/types/electron"; import { cancelLocalTransfer, createLocalTransferId, - createSSHFolder, downloadSessionFileToLocal, - listSSHFiles, uploadLocalFileToSession, -} from "@/main-axios.ts"; +} from "../api/local-transfer-api"; +import { createSSHFolder, listSSHFiles } from "../api/ssh-file-operations-api"; import { ensureLocalDirectory, getLocalHome, diff --git a/src/ui/features/file-manager/hooks/usePaneWidth.ts b/plugins/file-manager/src/frontend/hooks/usePaneWidth.ts similarity index 100% rename from src/ui/features/file-manager/hooks/usePaneWidth.ts rename to plugins/file-manager/src/frontend/hooks/usePaneWidth.ts diff --git a/src/ui/features/file-manager/hooks/useResizableColumns.ts b/plugins/file-manager/src/frontend/hooks/useResizableColumns.ts similarity index 100% rename from src/ui/features/file-manager/hooks/useResizableColumns.ts rename to plugins/file-manager/src/frontend/hooks/useResizableColumns.ts diff --git a/plugins/file-manager/src/frontend/index.tsx b/plugins/file-manager/src/frontend/index.tsx new file mode 100644 index 000000000..8a077c3f3 --- /dev/null +++ b/plugins/file-manager/src/frontend/index.tsx @@ -0,0 +1,147 @@ +import type { ComponentType } from "react"; +import { FolderSearch, ArrowLeftRight } from "lucide-react"; +import type { + PluginHostRecord, + StandaloneViewProps, + TabProps, + TermixApp, +} from "@termix/plugin-sdk/frontend"; +import { GRID_SIZE } from "@/types/homepage-types"; +import { FileManager } from "./FileManager.tsx"; +import FileManagerApp from "./FileManagerApp.tsx"; +import { SftpTransferTab } from "./SftpTransferTab.tsx"; +import { HostFilesTab } from "./HostFilesTab.tsx"; +import { FileManagerWidget } from "./homepage/FileManagerWidget.tsx"; +import { FileManagerWidgetEditForm } from "./homepage/FileManagerWidgetEditForm.tsx"; +import { startTransferMonitor } from "./TransferMonitor.tsx"; + +function FilesTab({ tab, host, sshHost, isVisible, shell }: TabProps) { + const data = tab.data as + { initialFilePath?: string; initialPath?: string } | undefined; + return ( + shell.openTerminalTab(host, path) : undefined + } + /> + ); +} + +function FilesStandalone({ hostId, params }: StandaloneViewProps) { + return ( + + ); +} + +function SftpTab() { + return ; +} + +function openHostAction( + app: TermixApp, + host: PluginHostRecord | null, + path: string | undefined, +): void { + if (!host) return; + app.tabs.openTab(host, "files", { data: { initialPath: path } }); +} + +function openEditorAction( + app: TermixApp, + host: PluginHostRecord | null, + filePath: string, +): void { + if (!host) return; + app.tabs.openTab(host, "files", { data: { initialFilePath: filePath } }); +} + +export function activate(app: TermixApp): void { + app.registerTab("files", FilesTab as unknown as ComponentType, { + icon: FolderSearch, + titleKey: "nav.files", + requiresHost: true, + noHostMessageKey: "fileManager.noHostSelected", + persistent: true, + standalone: FilesStandalone, + // The pre-conversion `?view=file-manager` link keeps resolving. + standaloneViews: ["file-manager"], + // The activity log still records "file_manager" (matching the backend's + // stored string), so recent-activity entries resolve to this tab. + activityTypes: ["file_manager"], + }); + + app.registerTab("sftp", SftpTab as unknown as ComponentType, { + icon: ArrowLeftRight, + titleKey: "nav.sftp", + hostless: true, + inLayouts: false, + }); + + app.registerRailItem({ + id: "sftp", + icon: ArrowLeftRight, + titleKey: "nav.sftp", + kind: "tab", + after: "ssh-tools", + permission: "use", + }); + + app.registerHostAction({ + id: "files", + titleKey: "nav.files", + icon: FolderSearch, + kind: "open", + order: 20, + tabType: "files", + copyUrlView: "file-manager", + when: (host) => !!host.enableSsh && host.enableFileManager !== false, + }); + + app.registerHostEditorSection({ + id: "files", + group: "ssh", + titleKey: "hosts.tabFiles", + icon: FolderSearch, + order: 60, + component: HostFilesTab as never, + }); + + app.registerHomepageWidget({ + id: "file_manager_widget", + name: "File Manager", + description: "Embedded SFTP file manager for a configured host", + category: "system", + icon: , + defaultConfig: { hostId: 0 }, + defaultSize: { w: GRID_SIZE * 20, h: GRID_SIZE * 14 }, + minSize: { w: GRID_SIZE * 10, h: GRID_SIZE * 8 }, + component: FileManagerWidget as never, + editFormComponent: FileManagerWidgetEditForm as never, + }); + + app.registerAction("files.openHost", (( + host: PluginHostRecord | null, + path?: string, + ) => openHostAction(app, host, path)) as never); + app.registerAction("files.openEditor", (( + host: PluginHostRecord | null, + filePath: string, + ) => openEditorAction(app, host, filePath)) as never); + + app.registerSlotContribution("onboarding.features", { + actionId: "file-manager.feature", + titleKey: "onboarding.feature_files", + descriptionKey: "onboarding.feature_files_desc", + icon: FolderSearch as ComponentType<{ className?: string }>, + }); + + const stopTransferMonitor = startTransferMonitor(app.t); + app.onDispose(stopTransferMonitor); +} diff --git a/src/ui/lib/file-content-request-cache.ts b/plugins/file-manager/src/frontend/lib/file-content-request-cache.ts similarity index 92% rename from src/ui/lib/file-content-request-cache.ts rename to plugins/file-manager/src/frontend/lib/file-content-request-cache.ts index 6462e941b..e17c88022 100644 --- a/src/ui/lib/file-content-request-cache.ts +++ b/plugins/file-manager/src/frontend/lib/file-content-request-cache.ts @@ -1,5 +1,5 @@ import type { FileItem } from "@/types/index"; -import { createKeyedRequestCache } from "./keyed-request-cache"; +import { createKeyedRequestCache } from "@/lib/keyed-request-cache"; export interface FileContentResult { content: string; diff --git a/src/ui/lib/file-list-request-cache.ts b/plugins/file-manager/src/frontend/lib/file-list-request-cache.ts similarity index 93% rename from src/ui/lib/file-list-request-cache.ts rename to plugins/file-manager/src/frontend/lib/file-list-request-cache.ts index f6f53f485..85905f678 100644 --- a/src/ui/lib/file-list-request-cache.ts +++ b/plugins/file-manager/src/frontend/lib/file-list-request-cache.ts @@ -1,5 +1,5 @@ import type { FileItem } from "@/types/index"; -import { createKeyedRequestCache } from "./keyed-request-cache"; +import { createKeyedRequestCache } from "@/lib/keyed-request-cache"; export interface FileListResult { files: FileItem[]; diff --git a/src/ui/features/file-manager/local-transfer-utils.ts b/plugins/file-manager/src/frontend/local-transfer-utils.ts similarity index 100% rename from src/ui/features/file-manager/local-transfer-utils.ts rename to plugins/file-manager/src/frontend/local-transfer-utils.ts diff --git a/src/ui/features/file-manager/optimistic-file-list.ts b/plugins/file-manager/src/frontend/optimistic-file-list.ts similarity index 100% rename from src/ui/features/file-manager/optimistic-file-list.ts rename to plugins/file-manager/src/frontend/optimistic-file-list.ts diff --git a/src/ui/features/sftp/sftp-transfer-utils.ts b/plugins/file-manager/src/frontend/sftp-transfer-utils.ts similarity index 100% rename from src/ui/features/sftp/sftp-transfer-utils.ts rename to plugins/file-manager/src/frontend/sftp-transfer-utils.ts diff --git a/src/ui/features/file-manager/transferMetricsFormat.ts b/plugins/file-manager/src/frontend/transferMetricsFormat.ts similarity index 100% rename from src/ui/features/file-manager/transferMetricsFormat.ts rename to plugins/file-manager/src/frontend/transferMetricsFormat.ts diff --git a/src/ui/features/file-manager/transferNotificationStore.ts b/plugins/file-manager/src/frontend/transferNotificationStore.ts similarity index 100% rename from src/ui/features/file-manager/transferNotificationStore.ts rename to plugins/file-manager/src/frontend/transferNotificationStore.ts diff --git a/src/ui/features/file-manager/transferProgressMonitor.tsx b/plugins/file-manager/src/frontend/transferProgressMonitor.tsx similarity index 99% rename from src/ui/features/file-manager/transferProgressMonitor.tsx rename to plugins/file-manager/src/frontend/transferProgressMonitor.tsx index d09436056..6edc8e945 100644 --- a/src/ui/features/file-manager/transferProgressMonitor.tsx +++ b/plugins/file-manager/src/frontend/transferProgressMonitor.tsx @@ -1,4 +1,4 @@ -import { getErrorMessage } from "../../lib/error-message.js"; +import { getErrorMessage } from "@/lib/error-message.js"; import { toast } from "sonner"; import type { TFunction } from "i18next"; import { diff --git a/plugins/file-manager/tests/backend/helpers.ts b/plugins/file-manager/tests/backend/helpers.ts new file mode 100644 index 000000000..e2cfc6a5d --- /dev/null +++ b/plugins/file-manager/tests/backend/helpers.ts @@ -0,0 +1,91 @@ +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import { fileURLToPath } from "node:url"; +import express, { type Router } from "express"; +import { + createMockCtx, + createTestDb, + type MockPluginContext, + type TestDb, +} from "@termix/plugin-sdk/testing"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import manifestJson from "../../manifest.json"; + +export const pluginDir = fileURLToPath(new URL("../..", import.meta.url)); +export const manifest = manifestJson as unknown as PluginManifest; + +export interface TestServer { + db: TestDb; + mock: MockPluginContext; + request: ( + method: string, + path: string, + options?: { user?: string; body?: unknown }, + // Route bodies vary per test; asserting on them is the point. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) => Promise<{ status: number; body: any }>; + close: () => Promise; +} + +/** + * The plugin activated against a real in-memory database, its router mounted + * on an express app the way core mounts it: JSON parsed, the acting user set + * from the request, everything else left to the plugin. + */ +export async function startServer( + options: { permissions?: string[]; users?: string[] } = {}, +): 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); + } + + 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 ?? ["file-manager.use"], + }); + + const { activate } = await import("../../src/backend/index.js"); + await activate(mock.ctx); + + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + mock.setActor(req.header("x-test-user") ?? undefined); + next(); + }); + app.use((req, res, next) => router!(req, res, next)); + + const server = http.createServer(app); + await new Promise((resolve) => server.listen(0, resolve)); + const { port } = server.address() as AddressInfo; + + return { + db, + mock, + async request(method, path, { user = "user-1", body: given } = {}) { + const body = method === "GET" ? undefined : given; + const response = await fetch(`http://127.0.0.1:${port}${path}`, { + method, + headers: { + "x-test-user": user, + ...(body !== undefined ? { "content-type": "application/json" } : {}), + }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + const text = await response.text(); + return { status: response.status, body: text ? JSON.parse(text) : null }; + }, + async close() { + await new Promise((resolve) => server.close(() => resolve())); + db.close(); + }, + }; +} diff --git a/plugins/file-manager/tests/backend/migrations.test.ts b/plugins/file-manager/tests/backend/migrations.test.ts new file mode 100644 index 000000000..6d24ddf00 --- /dev/null +++ b/plugins/file-manager/tests/backend/migrations.test.ts @@ -0,0 +1,167 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing"; +import { pluginDir } from "./helpers.js"; + +// file_manager_recent, file_manager_pinned, file_manager_shortcuts and +// transfer_recent as core's SQLite bootstrap created them before 2.9.0. +const LEGACY_DDL = ` + CREATE TABLE file_manager_recent ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, + name TEXT NOT NULL, + path TEXT NOT NULL, + last_opened TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE INDEX idx_file_manager_recent_user ON file_manager_recent (user_id, host_id); + + CREATE TABLE file_manager_pinned ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, + name TEXT NOT NULL, + path TEXT NOT NULL, + pinned_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE INDEX idx_file_manager_pinned_user ON file_manager_pinned (user_id, host_id); + + CREATE TABLE file_manager_shortcuts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, + name TEXT NOT NULL, + path TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE INDEX idx_file_manager_shortcuts_user ON file_manager_shortcuts (user_id, host_id); + + CREATE TABLE transfer_recent ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + source_host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, + dest_host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE, + dest_path TEXT NOT NULL, + dest_path_label TEXT NOT NULL, + last_used TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + CREATE INDEX idx_transfer_recent_user ON transfer_recent (user_id); +`; + +let db: TestDb | null = null; + +afterEach(() => { + db?.close(); + db = null; +}); + +const tableExists = (name: string) => + !!db!.sqlite + .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?") + .get(name); + +const indexOn = (table: string) => + ( + db!.sqlite + .prepare( + "SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND name NOT LIKE 'sqlite_%'", + ) + .all(table) as { name: string }[] + ).map((row) => row.name); + +describe("adopting the four file-manager tables", () => { + it("keeps every existing row when the legacy tables are there", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec( + "INSERT INTO users (id, username) VALUES ('user-1', 'user-1')", + ); + sqlite.exec("INSERT INTO ssh_data (id) VALUES (1), (2)"); + sqlite.exec(LEGACY_DDL); + sqlite + .prepare( + "INSERT INTO file_manager_recent (user_id, host_id, name, path) VALUES (?, ?, ?, ?)", + ) + .run("user-1", 1, "notes.txt", "/home/user/notes.txt"); + sqlite + .prepare( + "INSERT INTO file_manager_pinned (user_id, host_id, name, path) VALUES (?, ?, ?, ?)", + ) + .run("user-1", 1, "logs", "/var/log"); + sqlite + .prepare( + "INSERT INTO file_manager_shortcuts (user_id, host_id, name, path) VALUES (?, ?, ?, ?)", + ) + .run("user-1", 1, "Home", "/home/user"); + sqlite + .prepare( + "INSERT INTO transfer_recent (user_id, source_host_id, dest_host_id, dest_path, dest_path_label) VALUES (?, ?, ?, ?, ?)", + ) + .run("user-1", 1, 2, "/backups", "Backups"); + }, + }); + + expect(db.applied).toEqual(["0001_adopt_file_manager_tables"]); + + for (const legacy of [ + "file_manager_recent", + "file_manager_pinned", + "file_manager_shortcuts", + "transfer_recent", + ]) { + expect(tableExists(legacy)).toBe(false); + } + for (const adopted of [ + "p_file_manager_recent", + "p_file_manager_pinned", + "p_file_manager_shortcuts", + "p_file_manager_transfer_recent", + ]) { + expect(tableExists(adopted)).toBe(true); + } + + expect( + db.sqlite.prepare("SELECT name, path FROM p_file_manager_recent").all(), + ).toEqual([{ name: "notes.txt", path: "/home/user/notes.txt" }]); + expect( + db.sqlite.prepare("SELECT name, path FROM p_file_manager_pinned").all(), + ).toEqual([{ name: "logs", path: "/var/log" }]); + expect( + db.sqlite + .prepare("SELECT name, path FROM p_file_manager_shortcuts") + .all(), + ).toEqual([{ name: "Home", path: "/home/user" }]); + expect( + db.sqlite + .prepare( + "SELECT dest_path, dest_path_label FROM p_file_manager_transfer_recent", + ) + .all(), + ).toEqual([{ dest_path: "/backups", dest_path_label: "Backups" }]); + + expect(indexOn("p_file_manager_recent")).toEqual([ + "idx_file_manager_recent_user", + ]); + expect(indexOn("p_file_manager_pinned")).toEqual([ + "idx_file_manager_pinned_user", + ]); + expect(indexOn("p_file_manager_shortcuts")).toEqual([ + "idx_file_manager_shortcuts_user", + ]); + expect(indexOn("p_file_manager_transfer_recent")).toEqual([ + "idx_transfer_recent_user", + ]); + }); + + it("creates the tables fresh when there is no legacy one", async () => { + db = await createTestDb(pluginDir); + + expect(tableExists("file_manager_recent")).toBe(false); + expect(tableExists("p_file_manager_recent")).toBe(true); + expect(tableExists("p_file_manager_pinned")).toBe(true); + expect(tableExists("p_file_manager_shortcuts")).toBe(true); + expect(tableExists("p_file_manager_transfer_recent")).toBe(true); + expect( + db.sqlite.prepare("SELECT * FROM p_file_manager_recent").all(), + ).toEqual([]); + }); +}); diff --git a/plugins/file-manager/tests/backend/routes.test.ts b/plugins/file-manager/tests/backend/routes.test.ts new file mode 100644 index 000000000..7ef9a49e2 --- /dev/null +++ b/plugins/file-manager/tests/backend/routes.test.ts @@ -0,0 +1,68 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { startServer, type TestServer } from "./helpers.js"; + +let server: TestServer; + +beforeEach(async () => { + server = await startServer(); +}); + +afterEach(async () => { + await server.close(); +}); + +describe("recent/pinned/shortcuts", () => { + it("round-trips a recent file", async () => { + server.db.sqlite.exec("INSERT INTO ssh_data (id) VALUES (1)"); + + const add = await server.request("POST", "/recent", { + body: { hostId: 1, path: "/etc/hosts", name: "hosts" }, + }); + expect(add.status).toBe(200); + + const list = await server.request("GET", "/recent?hostId=1"); + expect(list.status).toBe(200); + expect(list.body).toEqual([ + expect.objectContaining({ name: "hosts", path: "/etc/hosts" }), + ]); + }); + + it("refuses to pin the same path twice", async () => { + server.db.sqlite.exec("INSERT INTO ssh_data (id) VALUES (1)"); + + const first = await server.request("POST", "/pinned", { + body: { hostId: 1, path: "/var/log", name: "logs" }, + }); + expect(first.status).toBe(200); + + const second = await server.request("POST", "/pinned", { + body: { hostId: 1, path: "/var/log", name: "logs" }, + }); + expect(second.status).toBe(409); + }); +}); + +describe("file-manager.use", () => { + it("refuses every route without it", async () => { + await server.close(); + server = await startServer({ permissions: [] }); + + for (const [method, path] of [ + ["GET", "/status?sessionId=x"], + ["POST", "/connect"], + ["GET", "/recent?hostId=1"], + ["POST", "/recent"], + ["GET", "/pinned?hostId=1"], + ["POST", "/pinned"], + ["GET", "/shortcuts?hostId=1"], + ["POST", "/shortcuts"], + ["GET", "/activeTransfers"], + ] as const) { + const res = await server.request(method, path, { + body: { hostId: 1, path: "/tmp", name: "tmp" }, + }); + expect(res.status, `${method} ${path}`).toBe(403); + expect(res.body.required).toBe("file-manager.use"); + } + }); +}); diff --git a/src/ui/tests/features/file-manager/LocalFilePane.test.tsx b/plugins/file-manager/tests/frontend/LocalFilePane.test.tsx similarity index 98% rename from src/ui/tests/features/file-manager/LocalFilePane.test.tsx rename to plugins/file-manager/tests/frontend/LocalFilePane.test.tsx index 349adf145..353782eb0 100644 --- a/src/ui/tests/features/file-manager/LocalFilePane.test.tsx +++ b/plugins/file-manager/tests/frontend/LocalFilePane.test.tsx @@ -1,11 +1,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom/vitest"; -import { LocalFilePane } from "@/features/file-manager/LocalFilePane"; +import { LocalFilePane } from "../../src/frontend/LocalFilePane"; import { LOCAL_FILES_DRAG_MIME, REMOTE_FILES_DRAG_MIME, -} from "@/features/file-manager/local-transfer-utils"; +} from "../../src/frontend/local-transfer-utils"; import type { LocalFileEntry } from "@/types/electron"; // jsdom has no layout, so render every row instead of a virtual window. @@ -40,6 +40,7 @@ vi.mock("react-i18next", () => ({ t: (key: string, opts?: Record) => opts?.count !== undefined ? `${key}:${opts.count}` : key, }), + initReactI18next: { type: "3rdParty", init: () => {} }, })); const HOME = "/Users/max"; diff --git a/src/ui/tests/features/sftp/SftpTransferTab.test.tsx b/plugins/file-manager/tests/frontend/SftpTransferTab.test.tsx similarity index 95% rename from src/ui/tests/features/sftp/SftpTransferTab.test.tsx rename to plugins/file-manager/tests/frontend/SftpTransferTab.test.tsx index 07c91a6a7..7c5d982ed 100644 --- a/src/ui/tests/features/sftp/SftpTransferTab.test.tsx +++ b/plugins/file-manager/tests/frontend/SftpTransferTab.test.tsx @@ -3,7 +3,7 @@ import { toast } from "sonner"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { SftpTransferTab } from "@/features/sftp/SftpTransferTab"; +import { SftpTransferTab } from "../../src/frontend/SftpTransferTab"; const api = vi.hoisted(() => ({ addTransferRecent: vi.fn(), @@ -21,27 +21,31 @@ const api = vi.hoisted(() => ({ vi.mock("@/main-axios", () => ({ addTransferRecent: api.addTransferRecent, + getSSHHosts: api.getSSHHosts, + getTransferProgressPercent: api.getTransferProgressPercent, + transferToHost: api.transferToHost, +})); + +vi.mock("../../src/frontend/api/ssh-file-operations-api", () => ({ browseSSHDirectory: api.browseSSHDirectory, changeSSHPermissions: api.changeSSHPermissions, createSSHFolder: api.createSSHFolder, deleteSSHItem: api.deleteSSHItem, ensureSSHSessionForHost: api.ensureSSHSessionForHost, - getSSHHosts: api.getSSHHosts, - getTransferProgressPercent: api.getTransferProgressPercent, renameSSHItem: api.renameSSHItem, - transferToHost: api.transferToHost, })); -vi.mock("@/features/file-manager/transferProgressMonitor", () => ({ +vi.mock("../../src/frontend/transferProgressMonitor", () => ({ beginTransferProgressMonitoring: api.beginTransferProgressMonitoring, })); -vi.mock("@/features/file-manager/transferMetricsFormat", () => ({ +vi.mock("../../src/frontend/transferMetricsFormat", () => ({ createFormatTransferMetrics: () => () => "", })); vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (key: string) => key }), + initReactI18next: { type: "3rdParty", init: () => {} }, })); vi.mock("sonner", () => ({ diff --git a/plugins/file-manager/tests/frontend/activate.test.tsx b/plugins/file-manager/tests/frontend/activate.test.tsx new file mode 100644 index 000000000..0840390a5 --- /dev/null +++ b/plugins/file-manager/tests/frontend/activate.test.tsx @@ -0,0 +1,80 @@ +import { afterEach, describe, expect, it } from "vitest"; +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; + +const declared = (views?: Array<{ id: string }>) => + (views ?? []).map((view) => view.id).sort(); + +let rendered: RenderedPluginApp | null = null; + +afterEach(async () => { + await rendered?.deactivate(); + rendered = null; +}); + +describe(`${manifest.id} activate`, () => { + it("activates and registers only views its manifest declares", async () => { + rendered = await renderWithApp(plugin, { manifest, locales }); + const contributes = manifest.contributes as unknown as Record< + string, + Array<{ id: string }> | undefined + >; + const tabs = declared(contributes.tabs); + const panels = [...declared(contributes.panels), ...tabs]; + for (const id of rendered.registered.tabs()) expect(tabs).toContain(id); + for (const id of rendered.registered.panels()) expect(panels).toContain(id); + }); + + it("registers the files and sftp tabs", async () => { + rendered = await renderWithApp(plugin, { manifest, locales }); + expect(rendered.registered.tabs().sort()).toEqual(["files", "sftp"]); + }); + + it("registers the sftp rail item pointing at the sftp tab", async () => { + rendered = await renderWithApp(plugin, { manifest, locales }); + const railItems = rendered.registered.railItems(); + expect(railItems.map((item) => item.id)).toContain("sftp"); + }); + + it("registers a files host action opening the files tab", async () => { + rendered = await renderWithApp(plugin, { manifest, locales }); + const hostActions = rendered.registered.hostActions(); + expect(hostActions).toContainEqual( + expect.objectContaining({ id: "files", tabType: "files" }), + ); + }); + + it("registers the files host editor section", async () => { + rendered = await renderWithApp(plugin, { manifest, locales }); + expect(rendered.registered.hostEditorSections()).toContain("files"); + }); + + it("registers files.openHost and files.openEditor actions", async () => { + rendered = await renderWithApp(plugin, { manifest, locales }); + expect(rendered.registered.actions().sort()).toEqual([ + "files.openEditor", + "files.openHost", + ]); + }); + + it("removes everything it registered on deactivate", async () => { + const app = await renderWithApp(plugin, { manifest, locales }); + await app.deactivate(); + expect(app.registered.tabs()).toEqual([]); + expect(app.registered.panels()).toEqual([]); + expect(app.registered.railItems()).toEqual([]); + expect(app.registered.hostActions()).toEqual([]); + expect(app.registered.hostEditorSections()).toEqual([]); + expect(app.registered.dashboardCards()).toEqual([]); + expect(app.registered.settingsComponents()).toEqual([]); + expect(app.registered.actions()).toEqual([]); + }); +}); diff --git a/src/ui/tests/api/ssh-file-operations-api.test.ts b/plugins/file-manager/tests/frontend/api/ssh-file-operations-api.test.ts similarity index 96% rename from src/ui/tests/api/ssh-file-operations-api.test.ts rename to plugins/file-manager/tests/frontend/api/ssh-file-operations-api.test.ts index 5e8c4bcd7..9a42cccf6 100644 --- a/src/ui/tests/api/ssh-file-operations-api.test.ts +++ b/plugins/file-manager/tests/frontend/api/ssh-file-operations-api.test.ts @@ -31,7 +31,7 @@ vi.mock("@/lib/file-content-request-cache", () => ({ invalidateCachedFileContent: vi.fn(), })); -import { uploadSSHFile } from "../../api/ssh-file-operations-api"; +import { uploadSSHFile } from "../../../src/frontend/api/ssh-file-operations-api"; describe("chunked SSH file uploads", () => { beforeEach(() => { diff --git a/src/ui/tests/features/file-manager/TransferProgressBar.test.tsx b/plugins/file-manager/tests/frontend/components/TransferProgressBar.test.tsx similarity index 95% rename from src/ui/tests/features/file-manager/TransferProgressBar.test.tsx rename to plugins/file-manager/tests/frontend/components/TransferProgressBar.test.tsx index dcb06959d..60dd9a366 100644 --- a/src/ui/tests/features/file-manager/TransferProgressBar.test.tsx +++ b/plugins/file-manager/tests/frontend/components/TransferProgressBar.test.tsx @@ -1,7 +1,7 @@ import { cleanup, render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; -import { TransferProgressBar } from "../../../features/file-manager/components/TransferProgressBar"; +import { TransferProgressBar } from "../../../src/frontend/components/TransferProgressBar"; afterEach(cleanup); diff --git a/src/ui/tests/features/file-manager/file-manager-utils.test.ts b/plugins/file-manager/tests/frontend/file-manager-utils.test.ts similarity index 96% rename from src/ui/tests/features/file-manager/file-manager-utils.test.ts rename to plugins/file-manager/tests/frontend/file-manager-utils.test.ts index 03fb9a94c..827949d68 100644 --- a/src/ui/tests/features/file-manager/file-manager-utils.test.ts +++ b/plugins/file-manager/tests/frontend/file-manager-utils.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { formatFileSize, runWithConcurrency, -} from "../../../features/file-manager/file-manager-utils.js"; +} from "../../src/frontend/file-manager-utils.js"; describe("formatFileSize", () => { it("returns a dash for undefined or null", () => { diff --git a/src/ui/tests/features/file-manager/file-preview-preload.test.ts b/plugins/file-manager/tests/frontend/file-preview-preload.test.ts similarity index 83% rename from src/ui/tests/features/file-manager/file-preview-preload.test.ts rename to plugins/file-manager/tests/frontend/file-preview-preload.test.ts index ee38c28de..8cd4cd0a6 100644 --- a/src/ui/tests/features/file-manager/file-preview-preload.test.ts +++ b/plugins/file-manager/tests/frontend/file-preview-preload.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { resolveFilePreviewKind } from "../../../features/file-manager/file-preview-preload"; +import { resolveFilePreviewKind } from "../../src/frontend/file-preview-preload"; describe("resolveFilePreviewKind", () => { it.each([ diff --git a/src/ui/tests/features/file-manager/hooks/useDragAndDrop.test.ts b/plugins/file-manager/tests/frontend/hooks/useDragAndDrop.test.ts similarity index 96% rename from src/ui/tests/features/file-manager/hooks/useDragAndDrop.test.ts rename to plugins/file-manager/tests/frontend/hooks/useDragAndDrop.test.ts index d78e64ee3..75fe92249 100644 --- a/src/ui/tests/features/file-manager/hooks/useDragAndDrop.test.ts +++ b/plugins/file-manager/tests/frontend/hooks/useDragAndDrop.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi } from "vitest"; import { act, renderHook } from "@testing-library/react"; -import { useDragAndDrop } from "../../../../features/file-manager/hooks/useDragAndDrop.js"; +import { useDragAndDrop } from "../../../src/frontend/hooks/useDragAndDrop.js"; function makeEntry(name: string, isDirectory: boolean) { return { name, isDirectory, isFile: !isDirectory } as FileSystemEntry; diff --git a/src/ui/tests/features/file-manager/hooks/useFileSelection.test.ts b/plugins/file-manager/tests/frontend/hooks/useFileSelection.test.ts similarity index 96% rename from src/ui/tests/features/file-manager/hooks/useFileSelection.test.ts rename to plugins/file-manager/tests/frontend/hooks/useFileSelection.test.ts index 268cb94a4..0176c1088 100644 --- a/src/ui/tests/features/file-manager/hooks/useFileSelection.test.ts +++ b/plugins/file-manager/tests/frontend/hooks/useFileSelection.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; import { act, renderHook } from "@testing-library/react"; -import { useFileSelection } from "../../../../features/file-manager/hooks/useFileSelection.js"; +import { useFileSelection } from "../../../src/frontend/hooks/useFileSelection.js"; type FileItem = { name: string; diff --git a/src/ui/tests/features/file-manager/hooks/usePaneWidth.test.ts b/plugins/file-manager/tests/frontend/hooks/usePaneWidth.test.ts similarity index 96% rename from src/ui/tests/features/file-manager/hooks/usePaneWidth.test.ts rename to plugins/file-manager/tests/frontend/hooks/usePaneWidth.test.ts index ffb1e0637..4289fc332 100644 --- a/src/ui/tests/features/file-manager/hooks/usePaneWidth.test.ts +++ b/plugins/file-manager/tests/frontend/hooks/usePaneWidth.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach } from "vitest"; import { act, renderHook } from "@testing-library/react"; -import { usePaneWidth } from "@/features/file-manager/hooks/usePaneWidth"; +import { usePaneWidth } from "../../../src/frontend/hooks/usePaneWidth"; const KEY = "test:pane-width"; diff --git a/src/ui/tests/features/file-manager/hooks/useResizableColumns.test.ts b/plugins/file-manager/tests/frontend/hooks/useResizableColumns.test.ts similarity index 97% rename from src/ui/tests/features/file-manager/hooks/useResizableColumns.test.ts rename to plugins/file-manager/tests/frontend/hooks/useResizableColumns.test.ts index 273937ce8..460339c6a 100644 --- a/src/ui/tests/features/file-manager/hooks/useResizableColumns.test.ts +++ b/plugins/file-manager/tests/frontend/hooks/useResizableColumns.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach } from "vitest"; import { act, renderHook } from "@testing-library/react"; -import { useResizableColumns } from "@/features/file-manager/hooks/useResizableColumns"; +import { useResizableColumns } from "../../../src/frontend/hooks/useResizableColumns"; const columns = [ { key: "modified", labelKey: "m", defaultWidth: 120, minWidth: 70 }, diff --git a/src/ui/tests/lib/file-content-request-cache.test.ts b/plugins/file-manager/tests/frontend/lib/file-content-request-cache.test.ts similarity index 95% rename from src/ui/tests/lib/file-content-request-cache.test.ts rename to plugins/file-manager/tests/frontend/lib/file-content-request-cache.test.ts index f080b2dc9..f3b43c638 100644 --- a/src/ui/tests/lib/file-content-request-cache.test.ts +++ b/plugins/file-manager/tests/frontend/lib/file-content-request-cache.test.ts @@ -3,7 +3,7 @@ import { getCachedFileContent, invalidateCachedFileContent, shouldPrefetchFileContent, -} from "../../lib/file-content-request-cache"; +} from "../../../src/frontend/lib/file-content-request-cache"; describe("file content request cache", () => { it("coalesces reads and reloads after invalidation", async () => { diff --git a/src/ui/tests/features/file-manager/local-transfer-utils.test.ts b/plugins/file-manager/tests/frontend/local-transfer-utils.test.ts similarity index 99% rename from src/ui/tests/features/file-manager/local-transfer-utils.test.ts rename to plugins/file-manager/tests/frontend/local-transfer-utils.test.ts index 38edea7a9..793168316 100644 --- a/src/ui/tests/features/file-manager/local-transfer-utils.test.ts +++ b/plugins/file-manager/tests/frontend/local-transfer-utils.test.ts @@ -25,7 +25,7 @@ import { remoteDirForRelativePath, serializeLocalFilesDragPayload, sortLocalEntries, -} from "@/features/file-manager/local-transfer-utils"; +} from "../../src/frontend/local-transfer-utils"; import type { LocalFileEntry } from "@/types/electron"; describe("drag payloads", () => { diff --git a/src/ui/tests/features/file-manager/optimistic-file-list.test.ts b/plugins/file-manager/tests/frontend/optimistic-file-list.test.ts similarity index 96% rename from src/ui/tests/features/file-manager/optimistic-file-list.test.ts rename to plugins/file-manager/tests/frontend/optimistic-file-list.test.ts index ba582cc29..a5fa3f2dc 100644 --- a/src/ui/tests/features/file-manager/optimistic-file-list.test.ts +++ b/plugins/file-manager/tests/frontend/optimistic-file-list.test.ts @@ -6,7 +6,7 @@ import { removePaths, renameOptimisticItem, restoreItems, -} from "../../../features/file-manager/optimistic-file-list"; +} from "../../src/frontend/optimistic-file-list"; const files: FileItem[] = [ { name: "alpha.txt", path: "/work/alpha.txt", type: "file", size: 10 }, diff --git a/src/ui/tests/features/sftp/sftp-transfer-utils.test.ts b/plugins/file-manager/tests/frontend/sftp-transfer-utils.test.ts similarity index 94% rename from src/ui/tests/features/sftp/sftp-transfer-utils.test.ts rename to plugins/file-manager/tests/frontend/sftp-transfer-utils.test.ts index b37165286..cc7895cc0 100644 --- a/src/ui/tests/features/sftp/sftp-transfer-utils.test.ts +++ b/plugins/file-manager/tests/frontend/sftp-transfer-utils.test.ts @@ -3,7 +3,7 @@ import { hasSameHostTransferConflict, joinRemotePath, normalizeRemoteDir, -} from "@/features/sftp/sftp-transfer-utils"; +} from "../../src/frontend/sftp-transfer-utils"; describe("sftp transfer utilities", () => { it("normalizes and joins remote paths", () => { diff --git a/src/ui/tests/features/file-manager/transfer-progress-toast.test.ts b/plugins/file-manager/tests/frontend/transfer-progress-toast.test.ts similarity index 90% rename from src/ui/tests/features/file-manager/transfer-progress-toast.test.ts rename to plugins/file-manager/tests/frontend/transfer-progress-toast.test.ts index f96d6cceb..28164fe06 100644 --- a/src/ui/tests/features/file-manager/transfer-progress-toast.test.ts +++ b/plugins/file-manager/tests/frontend/transfer-progress-toast.test.ts @@ -12,7 +12,7 @@ vi.mock("../../../features/file-manager/transferNotificationStore.ts", () => ({ markTransferNotified: vi.fn(), })); -import { showTransferCompletionToast } from "../../../features/file-manager/transferProgressMonitor.tsx"; +import { showTransferCompletionToast } from "../../src/frontend/transferProgressMonitor.tsx"; describe("transfer completion toast", () => { beforeEach(() => { diff --git a/plugins/file-manager/tsconfig.json b/plugins/file-manager/tsconfig.json new file mode 100644 index 000000000..1d3e42542 --- /dev/null +++ b/plugins/file-manager/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@termix/plugin-sdk/tsconfig.plugin.json", + "compilerOptions": { + "paths": { + "@/types/*": ["../../src/types/*"], + "@/*": ["../../src/ui/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx"] +} diff --git a/plugins/file-manager/vitest.config.ts b/plugins/file-manager/vitest.config.ts new file mode 100644 index 000000000..31ba004fc --- /dev/null +++ b/plugins/file-manager/vitest.config.ts @@ -0,0 +1,3 @@ +import { pluginVitestConfig } from "@termix/plugin-sdk/vitest-preset"; + +export default pluginVitestConfig(import.meta.url); diff --git a/plugins/host-metrics/src/frontend/HostMetricsTab.tsx b/plugins/host-metrics/src/frontend/HostMetricsTab.tsx index af531f0db..1523f42b0 100644 --- a/plugins/host-metrics/src/frontend/HostMetricsTab.tsx +++ b/plugins/host-metrics/src/frontend/HostMetricsTab.tsx @@ -35,6 +35,7 @@ import { useTabsSafe } from "@/shell/TabContext.tsx"; import { useTranslation, useSlotContributions, + useConnectionRetry, } from "@termix/plugin-sdk/frontend"; import { toast } from "sonner"; import { @@ -51,7 +52,6 @@ import { useConnectionLog, } from "@/ssh/connection-log/ConnectionLogContext.tsx"; import { ConnectionScreen } from "@/components/connection/ConnectionScreen.tsx"; -import { useConnectionRetry } from "@/lib/useConnectionRetry.ts"; import { runAdaptivePolling } from "@/lib/adaptive-polling.ts"; import type { LogEntry } from "@/types/connection-log.ts"; import { diff --git a/plugins/proxmox/src/frontend/stats/ProxmoxStatsTab.tsx b/plugins/proxmox/src/frontend/stats/ProxmoxStatsTab.tsx index 14ac42c4a..1b10cdeaa 100644 --- a/plugins/proxmox/src/frontend/stats/ProxmoxStatsTab.tsx +++ b/plugins/proxmox/src/frontend/stats/ProxmoxStatsTab.tsx @@ -19,7 +19,7 @@ import { GuestTable } from "./GuestTable"; import { NodeNetworkCard } from "./cards/NodeNetworkCard"; import { StoragePoolsCard } from "./cards/StoragePoolsCard"; import { ClusterHealthCard } from "./cards/ClusterHealthCard"; -import { useConnectionRetry } from "./useConnectionRetry"; +import { useConnectionRetry } from "@termix/plugin-sdk/frontend"; import { runAdaptivePolling } from "./adaptive-polling"; const HISTORY_LEN = 30; diff --git a/plugins/proxmox/src/frontend/stats/useConnectionRetry.ts b/plugins/proxmox/src/frontend/stats/useConnectionRetry.ts deleted file mode 100644 index 38dc0d4e1..000000000 --- a/plugins/proxmox/src/frontend/stats/useConnectionRetry.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; - -type ConnectionStatus = "connecting" | "connected" | "error" | "disconnected"; - -interface UseConnectionRetryOptions { - connect: () => void | Promise; - maxAttempts?: number; - baseDelayMs?: number; - maxDelayMs?: number; - enabled?: boolean; - autoStart?: boolean; -} - -interface UseConnectionRetryResult { - status: ConnectionStatus; - attempt: number; - maxAttempts: number; - nextRetryInMs: number | null; - markConnected: () => void; - markFailed: () => void; - retryNow: () => void; - reset: () => void; -} - -const COUNTDOWN_TICK_MS = 250; -const RETRY_JITTER_RATIO = 0.1; - -export function computeReconnectDelay( - attempt: number, - baseDelayMs: number, - maxDelayMs: number, - random = Math.random, -): number { - const base = Math.min( - baseDelayMs * Math.pow(2, Math.max(0, attempt - 1)), - maxDelayMs, - ); - const jitter = base * RETRY_JITTER_RATIO * (random() * 2 - 1); - return Math.max(baseDelayMs, Math.min(maxDelayMs, Math.round(base + jitter))); -} - -// Copy of src/ui/lib/useConnectionRetry.ts. Duplicated here rather than -// exposed through the SDK because only this plugin's Proxmox stats tab uses -// it today; promote to @termix/plugin-sdk/ui once a second plugin needs it. -export function useConnectionRetry({ - connect, - maxAttempts = 8, - baseDelayMs = 2000, - maxDelayMs = 8000, - enabled = true, - autoStart = true, -}: UseConnectionRetryOptions): UseConnectionRetryResult { - const [status, setStatus] = useState("connecting"); - const [attempt, setAttempt] = useState(0); - const [nextRetryInMs, setNextRetryInMs] = useState(null); - - const connectRef = useRef(connect); - connectRef.current = connect; - - const enabledRef = useRef(enabled); - enabledRef.current = enabled; - - const attemptRef = useRef(0); - const retryTimeoutRef = useRef | null>(null); - const countdownIntervalRef = useRef | null>( - null, - ); - const isMountedRef = useRef(true); - const markFailedRef = useRef<() => void>(() => {}); - - const clearTimers = useCallback(() => { - if (retryTimeoutRef.current) { - clearTimeout(retryTimeoutRef.current); - retryTimeoutRef.current = null; - } - if (countdownIntervalRef.current) { - clearInterval(countdownIntervalRef.current); - countdownIntervalRef.current = null; - } - }, []); - - const runConnect = useCallback(() => { - if (!isMountedRef.current) return; - setStatus("connecting"); - try { - void Promise.resolve(connectRef.current()).catch(() => { - markFailedRef.current(); - }); - } catch { - markFailedRef.current(); - } - }, []); - - const scheduleRetry = useCallback(() => { - if (attemptRef.current >= maxAttempts) { - setStatus("disconnected"); - setNextRetryInMs(null); - return; - } - - attemptRef.current += 1; - setAttempt(attemptRef.current); - - const delay = computeReconnectDelay( - attemptRef.current, - baseDelayMs, - maxDelayMs, - ); - - let remaining = delay; - setNextRetryInMs(remaining); - countdownIntervalRef.current = setInterval(() => { - remaining = Math.max(0, remaining - COUNTDOWN_TICK_MS); - setNextRetryInMs(remaining); - }, COUNTDOWN_TICK_MS); - - retryTimeoutRef.current = setTimeout(() => { - clearTimers(); - if (!isMountedRef.current || !enabledRef.current) return; - runConnect(); - }, delay); - }, [baseDelayMs, maxDelayMs, maxAttempts, clearTimers, runConnect]); - - const markConnected = useCallback(() => { - clearTimers(); - attemptRef.current = 0; - setAttempt(0); - setNextRetryInMs(null); - if (isMountedRef.current) setStatus("connected"); - }, [clearTimers]); - - const markFailed = useCallback(() => { - clearTimers(); - if (!isMountedRef.current) return; - setStatus("error"); - if (enabledRef.current) { - scheduleRetry(); - } else { - setNextRetryInMs(null); - } - }, [clearTimers, scheduleRetry]); - markFailedRef.current = markFailed; - - const retryNow = useCallback(() => { - clearTimers(); - attemptRef.current = 0; - setAttempt(0); - setNextRetryInMs(null); - runConnect(); - }, [clearTimers, runConnect]); - - const reset = useCallback(() => { - clearTimers(); - attemptRef.current = 0; - setAttempt(0); - setNextRetryInMs(null); - if (isMountedRef.current) setStatus("connecting"); - }, [clearTimers]); - - useEffect(() => { - isMountedRef.current = true; - if (autoStart) { - runConnect(); - } - return () => { - isMountedRef.current = false; - clearTimers(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (enabled && status === "error" && !retryTimeoutRef.current) { - scheduleRetry(); - } - if (!enabled) { - clearTimers(); - setNextRetryInMs(null); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [enabled]); - - return { - status, - attempt, - maxAttempts, - nextRetryInMs, - markConnected, - markFailed, - retryNow, - reset, - }; -} diff --git a/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx b/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx index ddc422eba..d7c699f6a 100644 --- a/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx +++ b/plugins/remote-desktop/src/frontend/GuacamoleApp.tsx @@ -22,7 +22,10 @@ import { resolveConnectionOrigin, type ConnectionOrigin, } from "@/lib/connection-origin.ts"; -import { useTranslation } from "@termix/plugin-sdk/frontend"; +import { + useTranslation, + useConnectionRetry, +} from "@termix/plugin-sdk/frontend"; import { GuacamoleToolbar } from "./GuacamoleToolbar.tsx"; import { GuacamoleFileBrowser } from "./GuacamoleFileBrowser.tsx"; import { describeUploadError } from "./guacamole-filesystem.ts"; @@ -42,7 +45,6 @@ import { ConnectionLogProvider, useConnectionLog, } from "@/ssh/connection-log/ConnectionLogContext.tsx"; -import { useConnectionRetry } from "@/lib/useConnectionRetry.ts"; import { ShareSessionModal } from "@/features/session-sharing/ShareSessionModal.tsx"; import type { SSHHost } from "@/types"; import { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext"; diff --git a/scripts/plugin-boundary-allowlist.json b/scripts/plugin-boundary-allowlist.json index aa0fa1862..3d4703038 100644 --- a/scripts/plugin-boundary-allowlist.json +++ b/scripts/plugin-boundary-allowlist.json @@ -31,6 +31,48 @@ "plugins/docker/src/frontend/components/LogViewer.tsx", "plugins/docker/src/frontend/docker-api.ts", "plugins/docker/src/frontend/index.tsx", + "plugins/file-manager/src/frontend/FileManager.tsx", + "plugins/file-manager/src/frontend/FileManagerContextMenu.tsx", + "plugins/file-manager/src/frontend/FileManagerDialogs.tsx", + "plugins/file-manager/src/frontend/FileManagerGrid.tsx", + "plugins/file-manager/src/frontend/FileManagerSidebar.tsx", + "plugins/file-manager/src/frontend/FileManagerToolbar.tsx", + "plugins/file-manager/src/frontend/HostFilesTab.tsx", + "plugins/file-manager/src/frontend/LocalFileContextMenu.tsx", + "plugins/file-manager/src/frontend/LocalFilePane.tsx", + "plugins/file-manager/src/frontend/SftpTransferTab.tsx", + "plugins/file-manager/src/frontend/TransferMonitor.tsx", + "plugins/file-manager/src/frontend/api/file-manager-data-api.ts", + "plugins/file-manager/src/frontend/api/local-transfer-api.ts", + "plugins/file-manager/src/frontend/api/ssh-file-operations-api.ts", + "plugins/file-manager/src/frontend/components/AudioPreview.tsx", + "plugins/file-manager/src/frontend/components/ColumnResizeHandle.tsx", + "plugins/file-manager/src/frontend/components/ColumnVisibilityMenu.tsx", + "plugins/file-manager/src/frontend/components/DiffViewer.tsx", + "plugins/file-manager/src/frontend/components/DiffWindow.tsx", + "plugins/file-manager/src/frontend/components/DownloadProgressToast.tsx", + "plugins/file-manager/src/frontend/components/DraggableWindow.tsx", + "plugins/file-manager/src/frontend/components/FileViewer.tsx", + "plugins/file-manager/src/frontend/components/FileWindow.tsx", + "plugins/file-manager/src/frontend/components/PaneResizeHandle.tsx", + "plugins/file-manager/src/frontend/components/TerminalWindow.tsx", + "plugins/file-manager/src/frontend/components/TransferProgressBar.tsx", + "plugins/file-manager/src/frontend/components/TransferProgressToast.tsx", + "plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx", + "plugins/file-manager/src/frontend/file-manager-types.ts", + "plugins/file-manager/src/frontend/file-preview-preload.ts", + "plugins/file-manager/src/frontend/homepage/FileManagerWidget.tsx", + "plugins/file-manager/src/frontend/homepage/FileManagerWidgetEditForm.tsx", + "plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts", + "plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts", + "plugins/file-manager/src/frontend/hooks/useLocalTransfers.ts", + "plugins/file-manager/src/frontend/index.tsx", + "plugins/file-manager/src/frontend/lib/file-content-request-cache.ts", + "plugins/file-manager/src/frontend/lib/file-list-request-cache.ts", + "plugins/file-manager/src/frontend/local-transfer-utils.ts", + "plugins/file-manager/src/frontend/optimistic-file-list.ts", + "plugins/file-manager/src/frontend/transferMetricsFormat.ts", + "plugins/file-manager/src/frontend/transferProgressMonitor.tsx", "plugins/host-metrics/src/frontend/HostEditorStatsTab.tsx", "plugins/host-metrics/src/frontend/HostMetricsApp.tsx", "plugins/host-metrics/src/frontend/HostMetricsTab.tsx", @@ -124,6 +166,8 @@ "plugins/docker/src/backend/index.ts", "plugins/docker/src/backend/routes.ts", "plugins/docker/src/backend/session-manager.ts", + "plugins/file-manager/src/backend/index.ts", + "plugins/file-manager/src/backend/operation-routes.ts", "plugins/host-metrics/src/backend/automation-bridge.ts", "plugins/host-metrics/src/backend/history-routes.ts", "plugins/host-metrics/src/backend/managers/cron.ts", diff --git a/scripts/shell-plugin-id-allowlist.json b/scripts/shell-plugin-id-allowlist.json index 7d71263dd..9d7c1ae91 100644 --- a/scripts/shell-plugin-id-allowlist.json +++ b/scripts/shell-plugin-id-allowlist.json @@ -25,22 +25,14 @@ "literals": ["rdp", "ssh-terminal", "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. The core terminal opens its own websocket, served by the ssh-terminal plugin, until the terminal moves into it in Phase B." }, - "src/ui/features/file-manager/components/TransferToHostDialog.tsx": { - "literals": ["rdp", "vnc"], - "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it." - }, "src/ui/features/homepage/widgets/QuickConnectWidget.tsx": { - "literals": ["terminal"], - "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." + "literals": ["files", "terminal"], + "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered. A default widget config value (connectionTypes), not a code branch on the plugin: quick connect offers terminal and files by default regardless of which plugins are enabled." }, "src/ui/features/session-sharing/SharedSessionView.tsx": { "literals": ["ssh-terminal"], "reason": "The core terminal opens its own websocket, served by the ssh-terminal plugin, until the terminal moves into it in Phase B." }, - "src/ui/features/sftp/SftpTransferTab.tsx": { - "literals": ["rdp", "vnc"], - "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it." - }, "src/ui/features/terminal/Terminal.tsx": { "literals": ["ssh-terminal", "terminal"], "reason": "The core terminal opens its own websocket, served by the ssh-terminal plugin, until the terminal moves into it in Phase B. The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." @@ -50,8 +42,8 @@ "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." }, "src/ui/features/terminal/TerminalToolbar.tsx": { - "literals": ["terminal"], - "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." + "literals": ["files", "terminal"], + "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered. The toolbar's \"open file manager here\" button needs the terminal's current working directory before opening the tab, so it special-cases the files host action instead of using the generic opener; folds into the terminal's own Phase B step." }, "src/ui/features/tmux-monitor/TmuxMonitor.tsx": { "literals": ["terminal"], @@ -66,8 +58,8 @@ "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." }, "src/ui/onboarding/steps/WelcomeStep.tsx": { - "literals": ["terminal"], - "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." + "literals": ["files", "terminal"], + "reason": "The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered. The fixed 3-item first-run welcome card (hosts, terminal, files) is core promotional copy, not an extensible list a plugin contributes to." }, "src/ui/shell/TabBar.tsx": { "literals": ["terminal"], @@ -106,8 +98,8 @@ "reason": "Host data: the enableDocker column filter and the container runtime name, until Phase B moves them. Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableTelnet, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it. The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." }, "src/ui/sidebar/QuickConnectPanel.tsx": { - "literals": ["rdp", "terminal", "vnc"], - "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it. The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered." + "literals": ["files", "rdp", "terminal", "vnc"], + "reason": "Host protocol value (connectionType, enableRdp, session protocol), a host column until Phase B moves it. Host protocol value (connectionType, enableVnc, session protocol), a host column until Phase B moves it. The terminal tab type. The terminal component, split view and file-manager hooks stay in core until the terminal's Phase B step; the ssh-terminal plugin only decides whether it is offered. The quick-connect-for-an-unsaved-host flow offers a fixed terminal/files button pair, not an extensible list a plugin contributes to." }, "src/ui/sidebar/quick-connect-host.ts": { "literals": ["rdp", "vnc"], diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index ab78fd94b..37d64b501 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -48,7 +48,6 @@ import { AutoSSLSetup } from "../utils/auto-ssl-setup.js"; import { createCurrentCredentialRepository, createCurrentDismissedAlertRepository, - createCurrentFileManagerBookmarkRepository, createCurrentHostRepository, createCurrentSettingsRepository, createCurrentSshCredentialUsageRepository, @@ -63,7 +62,8 @@ import type { GitHubAPIResponse, AuthenticatedRequest, } from "../../types/index.js"; -import { DatabaseSaveTrigger } from "./db/index.js"; +import { DatabaseSaveTrigger, getDb } from "./db/index.js"; +import { sql } from "drizzle-orm"; import Database from "better-sqlite3"; import { fileURLToPath } from "url"; @@ -855,7 +855,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => { updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); - CREATE TABLE file_manager_recent ( + CREATE TABLE p_file_manager_recent ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, host_id INTEGER NOT NULL, @@ -864,7 +864,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => { last_opened TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); - CREATE TABLE file_manager_pinned ( + CREATE TABLE p_file_manager_pinned ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, host_id INTEGER NOT NULL, @@ -873,7 +873,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => { pinned_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); - CREATE TABLE file_manager_shortcuts ( + CREATE TABLE p_file_manager_shortcuts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, host_id INTEGER NOT NULL, @@ -882,7 +882,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => { created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); - CREATE TABLE transfer_recent ( + CREATE TABLE p_file_manager_transfer_recent ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, source_host_id INTEGER NOT NULL, @@ -1031,56 +1031,84 @@ app.post("/database/export", authenticateJWT, async (req, res) => { ); } - const fileManagerRepository = - createCurrentFileManagerBookmarkRepository(); + // The file manager plugin owns these tables now (p_file_manager_*); + // read them by raw SQL rather than importing plugin code into core. + const drizzleDb = getDb(); const [recentFiles, pinnedFiles, shortcuts] = await Promise.all([ - fileManagerRepository.listRecentByUserId(userId), - fileManagerRepository.listPinnedByUserId(userId), - fileManagerRepository.listShortcutsByUserId(userId), + drizzleDb.all<{ + id: number; + user_id: string; + host_id: number; + name: string; + path: string; + last_opened: string; + }>( + sql`SELECT id, user_id, host_id, name, path, last_opened FROM p_file_manager_recent WHERE user_id = ${userId}`, + ), + drizzleDb.all<{ + id: number; + user_id: string; + host_id: number; + name: string; + path: string; + pinned_at: string; + }>( + sql`SELECT id, user_id, host_id, name, path, pinned_at FROM p_file_manager_pinned WHERE user_id = ${userId}`, + ), + drizzleDb.all<{ + id: number; + user_id: string; + host_id: number; + name: string; + path: string; + created_at: string; + }>( + sql`SELECT id, user_id, host_id, name, path, created_at FROM p_file_manager_shortcuts WHERE user_id = ${userId}`, + ), ]); const insertRecent = exportDb.prepare(` - INSERT INTO file_manager_recent (id, user_id, host_id, name, path, last_opened) + INSERT INTO p_file_manager_recent (id, user_id, host_id, name, path, last_opened) VALUES (?, ?, ?, ?, ?, ?) `); for (const item of recentFiles) { insertRecent.run( item.id, - item.userId, - item.hostId, + item.user_id, + item.host_id, item.name, item.path, - item.lastOpened, + item.last_opened, ); } const insertPinned = exportDb.prepare(` - INSERT INTO file_manager_pinned (id, user_id, host_id, name, path, pinned_at) + INSERT INTO p_file_manager_pinned (id, user_id, host_id, name, path, pinned_at) VALUES (?, ?, ?, ?, ?, ?) `); for (const item of pinnedFiles) { insertPinned.run( item.id, - item.userId, - item.hostId, + item.user_id, + item.host_id, item.name, item.path, - item.pinnedAt, + item.pinned_at, ); } const insertShortcut = exportDb.prepare(` - INSERT INTO file_manager_shortcuts (id, user_id, host_id, name, path, created_at) + INSERT INTO p_file_manager_shortcuts (id, user_id, host_id, name, path, created_at) VALUES (?, ?, ?, ?, ?, ?) `); for (const item of shortcuts) { insertShortcut.run( item.id, - item.userId, - item.hostId, + item.user_id, + item.host_id, item.name, item.path, - item.createdAt, + item.created_at, ); } @@ -1445,70 +1473,88 @@ app.post( ); } + // The file manager plugin owns these tables now (p_file_manager_*). + // Older export files still have the legacy name, so try that too. const fileManagerTables = [ { - table: "file_manager_recent", + tableNames: ["p_file_manager_recent", "file_manager_recent"], + physicalTable: "p_file_manager_recent", + dateColumn: "last_opened", key: "fileManagerItemsImported", }, { - table: "file_manager_pinned", + tableNames: ["p_file_manager_pinned", "file_manager_pinned"], + physicalTable: "p_file_manager_pinned", + dateColumn: "pinned_at", key: "fileManagerItemsImported", }, { - table: "file_manager_shortcuts", + tableNames: [ + "p_file_manager_shortcuts", + "file_manager_shortcuts", + ], + physicalTable: "p_file_manager_shortcuts", + dateColumn: "created_at", key: "fileManagerItemsImported", }, ]; - const fileManagerRepository = - createCurrentFileManagerBookmarkRepository(); + const importDrizzleDb = getDb(); - for (const { table, key } of fileManagerTables) { - try { - const importedItems = importDb - .prepare(`SELECT * FROM ${table}`) - .all(); - for (const item of importedItems) { - try { - const bookmark = { - hostId: item.host_id, - name: item.name, - path: item.path, - }; - const created = - table === "file_manager_recent" - ? await fileManagerRepository.createRecentForImport( - userId, - bookmark, - item.last_opened, - ) - : table === "file_manager_pinned" - ? await fileManagerRepository.createPinnedForImport( - userId, - bookmark, - item.pinned_at, - ) - : await fileManagerRepository.createShortcutForImport( - userId, - bookmark, - item.created_at, - ); - - if (created) { - result.summary[key]++; - } else { - result.summary.skippedItems++; - } - } catch (itemError) { - result.summary.errors.push( - `${table} import error: ${itemError.message}`, - ); - } + for (const { + tableNames, + physicalTable, + dateColumn, + key, + } of fileManagerTables) { + let importedItems: Array> | null = null; + let sourceTable = ""; + for (const tableName of tableNames) { + try { + importedItems = importDb + .prepare(`SELECT * FROM ${tableName}`) + .all() as Array>; + sourceTable = tableName; + break; + } catch { + // try the next name } - } catch { + } + + if (importedItems === null) { apiLogger.info( - `${table} table not found in import file, skipping`, + `${physicalTable} table not found in import file, skipping`, ); + continue; + } + + for (const item of importedItems) { + try { + const hostId = item.host_id as number; + const path = item.path as string; + const name = + (item.name as string) || path.split("/").pop() || "Unknown"; + const dateValue = + (item[dateColumn] as string) || new Date().toISOString(); + + const existing = await importDrizzleDb.all<{ id: number }>( + sql`SELECT id FROM ${sql.raw(physicalTable)} WHERE user_id = ${userId} AND host_id = ${hostId} AND path = ${path}`, + ); + + if (existing.length > 0) { + result.summary.skippedItems++; + continue; + } + + await importDrizzleDb.run( + sql`INSERT INTO ${sql.raw(physicalTable)} (user_id, host_id, path, name, ${sql.raw(dateColumn)}) VALUES (${userId}, ${hostId}, ${path}, ${name}, ${dateValue})`, + ); + result.summary[key]++; + } catch (itemError) { + result.summary.errors.push( + `${sourceTable} import error: ${itemError.message}`, + ); + } } } diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index bb4c0b9d7..eeb38d5cb 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -350,51 +350,10 @@ async function initializeCompleteDatabase(): Promise { FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ); - CREATE TABLE IF NOT EXISTS file_manager_recent ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL, - host_id INTEGER NOT NULL, - name TEXT NOT NULL, - path TEXT NOT NULL, - last_opened TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, - FOREIGN KEY (host_id) REFERENCES ssh_data (id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS file_manager_pinned ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL, - host_id INTEGER NOT NULL, - name TEXT NOT NULL, - path TEXT NOT NULL, - pinned_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, - FOREIGN KEY (host_id) REFERENCES ssh_data (id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS file_manager_shortcuts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL, - host_id INTEGER NOT NULL, - name TEXT NOT NULL, - path TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, - FOREIGN KEY (host_id) REFERENCES ssh_data (id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS transfer_recent ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL, - source_host_id INTEGER NOT NULL, - dest_host_id INTEGER NOT NULL, - dest_path TEXT NOT NULL, - dest_path_label TEXT NOT NULL, - last_used TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, - FOREIGN KEY (source_host_id) REFERENCES ssh_data (id) ON DELETE CASCADE, - FOREIGN KEY (dest_host_id) REFERENCES ssh_data (id) ON DELETE CASCADE - ); + -- file_manager_recent, file_manager_pinned, file_manager_shortcuts and + -- transfer_recent are owned by the file-manager plugin now. Its own + -- migration creates p_file_manager_* on a fresh install and adopts the + -- legacy tables by rename on an upgrade. CREATE TABLE IF NOT EXISTS dismissed_alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/src/backend/database/db/performance-indexes.ts b/src/backend/database/db/performance-indexes.ts index a3c27fdfd..7e8269b32 100644 --- a/src/backend/database/db/performance-indexes.ts +++ b/src/backend/database/db/performance-indexes.ts @@ -110,26 +110,9 @@ export const PERFORMANCE_INDEXES: PerformanceIndex[] = [ table: "command_history", columns: "user_id, host_id", }, - { - name: "idx_file_manager_recent_user", - table: "file_manager_recent", - columns: "user_id, host_id", - }, - { - name: "idx_file_manager_pinned_user", - table: "file_manager_pinned", - columns: "user_id, host_id", - }, - { - name: "idx_file_manager_shortcuts_user", - table: "file_manager_shortcuts", - columns: "user_id, host_id", - }, - { - name: "idx_transfer_recent_user", - table: "transfer_recent", - columns: "user_id", - }, + // file_manager_recent, file_manager_pinned, file_manager_shortcuts and + // transfer_recent are owned by the file-manager plugin now; its adopted + // table definitions carry these same indexes across the rename. { name: "idx_user_open_tabs_user_id", table: "user_open_tabs", diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts index a8744bd20..f7ea75c41 100644 --- a/src/backend/database/db/schema.mysql.ts +++ b/src/backend/database/db/schema.mysql.ts @@ -383,91 +383,8 @@ export const hosts = mysqlTable( ], ); -export const fileManagerRecent = mysqlTable( - "file_manager_recent", - { - id: int("id").autoincrement().primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: int("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - path: text("path").notNull(), - lastOpened: text("last_opened") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - }, - // Every file manager surface is read for one user on one host at a time. - (table) => [ - index("idx_file_manager_recent_user").on(table.userId, table.hostId), - ], -); - -export const fileManagerPinned = mysqlTable( - "file_manager_pinned", - { - id: int("id").autoincrement().primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: int("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - path: text("path").notNull(), - pinnedAt: text("pinned_at") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - }, - (table) => [ - index("idx_file_manager_pinned_user").on(table.userId, table.hostId), - ], -); - -export const fileManagerShortcuts = mysqlTable( - "file_manager_shortcuts", - { - id: int("id").autoincrement().primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: int("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - path: text("path").notNull(), - createdAt: varchar("created_at", { length: 255 }) - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - }, - (table) => [ - index("idx_file_manager_shortcuts_user").on(table.userId, table.hostId), - ], -); - -export const transferRecent = mysqlTable( - "transfer_recent", - { - id: int("id").autoincrement().primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - sourceHostId: int("source_host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - destHostId: int("dest_host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - destPath: text("dest_path").notNull(), - destPathLabel: text("dest_path_label").notNull(), - lastUsed: text("last_used") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - }, - (table) => [index("idx_transfer_recent_user").on(table.userId)], -); +// file_manager_recent, file_manager_pinned, file_manager_shortcuts and +// transfer_recent moved to the file-manager plugin (p_file_manager_*). export const dismissedAlerts = mysqlTable( "dismissed_alerts", diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts index 5ecc3f943..a97f91d96 100644 --- a/src/backend/database/db/schema.pg.ts +++ b/src/backend/database/db/schema.pg.ts @@ -384,91 +384,8 @@ export const hosts = pgTable( ], ); -export const fileManagerRecent = pgTable( - "file_manager_recent", - { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - path: text("path").notNull(), - lastOpened: text("last_opened") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - // Every file manager surface is read for one user on one host at a time. - (table) => [ - index("idx_file_manager_recent_user").on(table.userId, table.hostId), - ], -); - -export const fileManagerPinned = pgTable( - "file_manager_pinned", - { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - path: text("path").notNull(), - pinnedAt: text("pinned_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - (table) => [ - index("idx_file_manager_pinned_user").on(table.userId, table.hostId), - ], -); - -export const fileManagerShortcuts = pgTable( - "file_manager_shortcuts", - { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - path: text("path").notNull(), - createdAt: varchar("created_at", { length: 255 }) - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - (table) => [ - index("idx_file_manager_shortcuts_user").on(table.userId, table.hostId), - ], -); - -export const transferRecent = pgTable( - "transfer_recent", - { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - sourceHostId: integer("source_host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - destHostId: integer("dest_host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - destPath: text("dest_path").notNull(), - destPathLabel: text("dest_path_label").notNull(), - lastUsed: text("last_used") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - (table) => [index("idx_transfer_recent_user").on(table.userId)], -); +// file_manager_recent, file_manager_pinned, file_manager_shortcuts and +// transfer_recent moved to the file-manager plugin (p_file_manager_*). export const dismissedAlerts = pgTable( "dismissed_alerts", diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index d47404263..4e456a01a 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -376,91 +376,8 @@ export const hosts = sqliteTable( ], ); -export const fileManagerRecent = sqliteTable( - "file_manager_recent", - { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: text("name").notNull(), - path: text("path").notNull(), - lastOpened: text("last_opened") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - // Every file manager surface is read for one user on one host at a time. - (table) => [ - index("idx_file_manager_recent_user").on(table.userId, table.hostId), - ], -); - -export const fileManagerPinned = sqliteTable( - "file_manager_pinned", - { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: text("name").notNull(), - path: text("path").notNull(), - pinnedAt: text("pinned_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - (table) => [ - index("idx_file_manager_pinned_user").on(table.userId, table.hostId), - ], -); - -export const fileManagerShortcuts = sqliteTable( - "file_manager_shortcuts", - { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - hostId: integer("host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - name: text("name").notNull(), - path: text("path").notNull(), - createdAt: text("created_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - (table) => [ - index("idx_file_manager_shortcuts_user").on(table.userId, table.hostId), - ], -); - -export const transferRecent = sqliteTable( - "transfer_recent", - { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - sourceHostId: integer("source_host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - destHostId: integer("dest_host_id") - .notNull() - .references(() => hosts.id, { onDelete: "cascade" }), - destPath: text("dest_path").notNull(), - destPathLabel: text("dest_path_label").notNull(), - lastUsed: text("last_used") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - }, - (table) => [index("idx_transfer_recent_user").on(table.userId)], -); +// file_manager_recent, file_manager_pinned, file_manager_shortcuts and +// transfer_recent moved to the file-manager plugin (p_file_manager_*). export const dismissedAlerts = sqliteTable( "dismissed_alerts", diff --git a/src/backend/database/repositories/factory.ts b/src/backend/database/repositories/factory.ts index b72e1ce31..eeb2fb4f9 100644 --- a/src/backend/database/repositories/factory.ts +++ b/src/backend/database/repositories/factory.ts @@ -14,7 +14,6 @@ import { CommandHistoryRepository } from "./command-history-repository.js"; import { CredentialRepository } from "./credential-repository.js"; import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js"; import { DismissedAlertRepository } from "./dismissed-alert-repository.js"; -import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js"; import { HomepageItemRepository } from "./homepage-item-repository.js"; import { HomepageLayoutRepository } from "./homepage-layout-repository.js"; import { HostFolderRepository } from "./host-folder-repository.js"; @@ -57,7 +56,6 @@ import { SsoProviderRepository } from "./sso-provider-repository.js"; import { TermixIdentityCaRepository } from "./termix-identity-ca-repository.js"; import { TermixIdentityRepository } from "./termix-identity-repository.js"; import { TmuxSessionTagRepository } from "./tmux-session-tag-repository.js"; -import { TransferRecentRepository } from "./transfer-recent-repository.js"; import { TrustedDeviceRepository } from "./trusted-device-repository.js"; import { UserDataExportRepository } from "./user-data-export-repository.js"; import { UserPreferenceRepository } from "./user-preference-repository.js"; @@ -229,12 +227,7 @@ export function createCurrentDismissedAlertRepository(): DismissedAlertRepositor ); } -export function createCurrentFileManagerBookmarkRepository(): FileManagerBookmarkRepository { - return new FileManagerBookmarkRepository( - createCurrentRepositoryContext(), - createCurrentRepositoryWriteHook("file_manager_bookmarks_repository_write"), - ); -} +// File manager recent/pinned/shortcuts moved to the file-manager plugin. export function createCurrentHomepageItemRepository(): HomepageItemRepository { return new HomepageItemRepository( @@ -512,12 +505,7 @@ export function createCurrentTmuxSessionTagRepository(): TmuxSessionTagRepositor ); } -export function createCurrentTransferRecentRepository(): TransferRecentRepository { - return new TransferRecentRepository( - createCurrentRepositoryContext(), - createCurrentRepositoryWriteHook("transfer_recent_repository_write"), - ); -} +// transfer_recent moved to the file-manager plugin. export function createCurrentTrustedDeviceRepository(): TrustedDeviceRepository { return new TrustedDeviceRepository( diff --git a/src/backend/database/repositories/file-manager-bookmark-repository.ts b/src/backend/database/repositories/file-manager-bookmark-repository.ts deleted file mode 100644 index 351b2af7c..000000000 --- a/src/backend/database/repositories/file-manager-bookmark-repository.ts +++ /dev/null @@ -1,522 +0,0 @@ -import { and, desc, eq, inArray } from "drizzle-orm"; -import { - fileManagerPinned, - fileManagerRecent, - fileManagerShortcuts, -} from "../db/schema.js"; -import type { DatabaseContext } from "./database-context.js"; -import { rowsAffected } from "./mutation-result.js"; - -export type FileManagerRecentRecord = typeof fileManagerRecent.$inferSelect; -export type FileManagerPinnedRecord = typeof fileManagerPinned.$inferSelect; -export type FileManagerShortcutRecord = - typeof fileManagerShortcuts.$inferSelect; - -export interface FileManagerBookmarkInput { - hostId: number; - path: string; - name?: string | null; -} - -function resolveBookmarkName(path: string, name?: string | null): string { - return name || path.split("/").pop() || "Unknown"; -} - -export class FileManagerBookmarkRepository { - constructor( - private readonly context: DatabaseContext, - private readonly onWrite?: () => void | Promise, - ) {} - - async listRecentForHost( - userId: string, - hostId: number, - limit = 20, - ): Promise { - return this.context.drizzle - .select() - .from(fileManagerRecent) - .where( - and( - eq(fileManagerRecent.userId, userId), - eq(fileManagerRecent.hostId, hostId), - ), - ) - .orderBy(desc(fileManagerRecent.lastOpened)) - .limit(limit); - } - - async listRecentByUserId(userId: string): Promise { - return this.context.drizzle - .select() - .from(fileManagerRecent) - .where(eq(fileManagerRecent.userId, userId)); - } - - async upsertRecent( - userId: string, - input: FileManagerBookmarkInput, - lastOpened = new Date().toISOString(), - ): Promise { - const [existing] = await this.context.drizzle - .select({ id: fileManagerRecent.id }) - .from(fileManagerRecent) - .where( - and( - eq(fileManagerRecent.userId, userId), - eq(fileManagerRecent.hostId, input.hostId), - eq(fileManagerRecent.path, input.path), - ), - ) - .limit(1); - - if (existing) { - await this.context.drizzle - .update(fileManagerRecent) - .set({ lastOpened }) - .where(eq(fileManagerRecent.id, existing.id)); - } else { - await this.context.drizzle.insert(fileManagerRecent).values({ - userId, - hostId: input.hostId, - path: input.path, - name: resolveBookmarkName(input.path, input.name), - lastOpened, - }); - } - - await this.afterWrite(); - } - - async createRecentForImport( - userId: string, - input: FileManagerBookmarkInput, - lastOpened = new Date().toISOString(), - ): Promise { - const exists = await this.existsRecentImportItem(userId, input); - if (exists) { - return false; - } - - await this.context.drizzle.insert(fileManagerRecent).values({ - userId, - hostId: input.hostId, - path: input.path, - name: resolveBookmarkName(input.path, input.name), - lastOpened, - }); - await this.afterWrite(); - return true; - } - - async deleteRecentForHostPath( - userId: string, - input: Pick, - ): Promise { - const result = await this.context.drizzle - .delete(fileManagerRecent) - .where( - and( - eq(fileManagerRecent.userId, userId), - eq(fileManagerRecent.hostId, input.hostId), - eq(fileManagerRecent.path, input.path), - ), - ); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async listPinnedForHost( - userId: string, - hostId: number, - ): Promise { - return this.context.drizzle - .select() - .from(fileManagerPinned) - .where( - and( - eq(fileManagerPinned.userId, userId), - eq(fileManagerPinned.hostId, hostId), - ), - ) - .orderBy(desc(fileManagerPinned.pinnedAt)); - } - - async listPinnedByUserId(userId: string): Promise { - return this.context.drizzle - .select() - .from(fileManagerPinned) - .where(eq(fileManagerPinned.userId, userId)); - } - - async createPinned( - userId: string, - input: FileManagerBookmarkInput, - pinnedAt = new Date().toISOString(), - ): Promise { - const exists = await this.existsPinned(userId, input.hostId, input.path); - if (exists) { - return false; - } - - await this.context.drizzle.insert(fileManagerPinned).values({ - userId, - hostId: input.hostId, - path: input.path, - name: resolveBookmarkName(input.path, input.name), - pinnedAt, - }); - await this.afterWrite(); - return true; - } - - async createPinnedForImport( - userId: string, - input: FileManagerBookmarkInput, - pinnedAt = new Date().toISOString(), - ): Promise { - const exists = await this.existsPinnedImportItem(userId, input); - if (exists) { - return false; - } - - await this.context.drizzle.insert(fileManagerPinned).values({ - userId, - hostId: input.hostId, - path: input.path, - name: resolveBookmarkName(input.path, input.name), - pinnedAt, - }); - await this.afterWrite(); - return true; - } - - async deletePinnedForHostPath( - userId: string, - input: Pick, - ): Promise { - const result = await this.context.drizzle - .delete(fileManagerPinned) - .where( - and( - eq(fileManagerPinned.userId, userId), - eq(fileManagerPinned.hostId, input.hostId), - eq(fileManagerPinned.path, input.path), - ), - ); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async listShortcutsForHost( - userId: string, - hostId: number, - ): Promise { - return this.context.drizzle - .select() - .from(fileManagerShortcuts) - .where( - and( - eq(fileManagerShortcuts.userId, userId), - eq(fileManagerShortcuts.hostId, hostId), - ), - ) - .orderBy(desc(fileManagerShortcuts.createdAt)); - } - - async listShortcutsByUserId( - userId: string, - ): Promise { - return this.context.drizzle - .select() - .from(fileManagerShortcuts) - .where(eq(fileManagerShortcuts.userId, userId)); - } - - async createShortcut( - userId: string, - input: FileManagerBookmarkInput, - createdAt = new Date().toISOString(), - ): Promise { - const exists = await this.existsShortcut(userId, input.hostId, input.path); - if (exists) { - return false; - } - - await this.context.drizzle.insert(fileManagerShortcuts).values({ - userId, - hostId: input.hostId, - path: input.path, - name: resolveBookmarkName(input.path, input.name), - createdAt, - }); - await this.afterWrite(); - return true; - } - - async createShortcutForImport( - userId: string, - input: FileManagerBookmarkInput, - createdAt = new Date().toISOString(), - ): Promise { - const exists = await this.existsShortcutImportItem(userId, input); - if (exists) { - return false; - } - - await this.context.drizzle.insert(fileManagerShortcuts).values({ - userId, - hostId: input.hostId, - path: input.path, - name: resolveBookmarkName(input.path, input.name), - createdAt, - }); - await this.afterWrite(); - return true; - } - - async deleteShortcutForHostPath( - userId: string, - input: Pick, - ): Promise { - const result = await this.context.drizzle - .delete(fileManagerShortcuts) - .where( - and( - eq(fileManagerShortcuts.userId, userId), - eq(fileManagerShortcuts.hostId, input.hostId), - eq(fileManagerShortcuts.path, input.path), - ), - ); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async deleteByUserId(userId: string): Promise { - const total = - (await this.deleteRecentByUserId(userId)) + - (await this.deletePinnedByUserId(userId)) + - (await this.deleteShortcutsByUserId(userId)); - - if (total > 0) { - await this.afterWrite(); - } - - return total; - } - - async deleteByHostId(hostId: number): Promise { - const total = - (await this.deleteRecentByHostId(hostId)) + - (await this.deletePinnedByHostId(hostId)) + - (await this.deleteShortcutsByHostId(hostId)); - - if (total > 0) { - await this.afterWrite(); - } - - return total; - } - - async deleteByHostIds(hostIds: number[]): Promise { - if (hostIds.length === 0) { - return 0; - } - - const total = - (await this.deleteRecentByHostIds(hostIds)) + - (await this.deletePinnedByHostIds(hostIds)) + - (await this.deleteShortcutsByHostIds(hostIds)); - - if (total > 0) { - await this.afterWrite(); - } - - return total; - } - - private async existsPinned( - userId: string, - hostId: number, - path: string, - ): Promise { - const rows = await this.context.drizzle - .select({ id: fileManagerPinned.id }) - .from(fileManagerPinned) - .where( - and( - eq(fileManagerPinned.userId, userId), - eq(fileManagerPinned.hostId, hostId), - eq(fileManagerPinned.path, path), - ), - ) - .limit(1); - - return rows.length > 0; - } - - private async existsRecentImportItem( - userId: string, - input: FileManagerBookmarkInput, - ): Promise { - const rows = await this.context.drizzle - .select({ id: fileManagerRecent.id }) - .from(fileManagerRecent) - .where( - and( - eq(fileManagerRecent.userId, userId), - eq(fileManagerRecent.path, input.path), - eq( - fileManagerRecent.name, - resolveBookmarkName(input.path, input.name), - ), - ), - ) - .limit(1); - - return rows.length > 0; - } - - private async existsPinnedImportItem( - userId: string, - input: FileManagerBookmarkInput, - ): Promise { - const rows = await this.context.drizzle - .select({ id: fileManagerPinned.id }) - .from(fileManagerPinned) - .where( - and( - eq(fileManagerPinned.userId, userId), - eq(fileManagerPinned.path, input.path), - eq( - fileManagerPinned.name, - resolveBookmarkName(input.path, input.name), - ), - ), - ) - .limit(1); - - return rows.length > 0; - } - - private async existsShortcutImportItem( - userId: string, - input: FileManagerBookmarkInput, - ): Promise { - const rows = await this.context.drizzle - .select({ id: fileManagerShortcuts.id }) - .from(fileManagerShortcuts) - .where( - and( - eq(fileManagerShortcuts.userId, userId), - eq(fileManagerShortcuts.path, input.path), - eq( - fileManagerShortcuts.name, - resolveBookmarkName(input.path, input.name), - ), - ), - ) - .limit(1); - - return rows.length > 0; - } - - private async existsShortcut( - userId: string, - hostId: number, - path: string, - ): Promise { - const rows = await this.context.drizzle - .select({ id: fileManagerShortcuts.id }) - .from(fileManagerShortcuts) - .where( - and( - eq(fileManagerShortcuts.userId, userId), - eq(fileManagerShortcuts.hostId, hostId), - eq(fileManagerShortcuts.path, path), - ), - ) - .limit(1); - - return rows.length > 0; - } - - private async deleteRecentByUserId(userId: string): Promise { - const result = await this.context.drizzle - .delete(fileManagerRecent) - .where(eq(fileManagerRecent.userId, userId)); - return rowsAffected(result); - } - - private async deletePinnedByUserId(userId: string): Promise { - const result = await this.context.drizzle - .delete(fileManagerPinned) - .where(eq(fileManagerPinned.userId, userId)); - return rowsAffected(result); - } - - private async deleteShortcutsByUserId(userId: string): Promise { - const result = await this.context.drizzle - .delete(fileManagerShortcuts) - .where(eq(fileManagerShortcuts.userId, userId)); - return rowsAffected(result); - } - - private async deleteRecentByHostId(hostId: number): Promise { - const result = await this.context.drizzle - .delete(fileManagerRecent) - .where(eq(fileManagerRecent.hostId, hostId)); - return rowsAffected(result); - } - - private async deletePinnedByHostId(hostId: number): Promise { - const result = await this.context.drizzle - .delete(fileManagerPinned) - .where(eq(fileManagerPinned.hostId, hostId)); - return rowsAffected(result); - } - - private async deleteShortcutsByHostId(hostId: number): Promise { - const result = await this.context.drizzle - .delete(fileManagerShortcuts) - .where(eq(fileManagerShortcuts.hostId, hostId)); - return rowsAffected(result); - } - - private async deleteRecentByHostIds(hostIds: number[]): Promise { - const result = await this.context.drizzle - .delete(fileManagerRecent) - .where(inArray(fileManagerRecent.hostId, hostIds)); - return rowsAffected(result); - } - - private async deletePinnedByHostIds(hostIds: number[]): Promise { - const result = await this.context.drizzle - .delete(fileManagerPinned) - .where(inArray(fileManagerPinned.hostId, hostIds)); - return rowsAffected(result); - } - - private async deleteShortcutsByHostIds(hostIds: number[]): Promise { - const result = await this.context.drizzle - .delete(fileManagerShortcuts) - .where(inArray(fileManagerShortcuts.hostId, hostIds)); - return rowsAffected(result); - } - - private async afterWrite(): Promise { - await this.onWrite?.(); - } -} diff --git a/src/backend/database/repositories/transfer-recent-repository.ts b/src/backend/database/repositories/transfer-recent-repository.ts deleted file mode 100644 index de1266ea6..000000000 --- a/src/backend/database/repositories/transfer-recent-repository.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { and, desc, eq, inArray, or } from "drizzle-orm"; -import { transferRecent } from "../db/schema.js"; -import type { DatabaseContext } from "./database-context.js"; -import { rowsAffected } from "./mutation-result.js"; - -export type TransferRecentRecord = typeof transferRecent.$inferSelect; - -export interface TransferRecentDestinationInput { - sourceHostId: number; - destHostId: number; - destPath: string; - destPathLabel?: string | null; -} - -export class TransferRecentRepository { - constructor( - private readonly context: DatabaseContext, - private readonly onWrite?: () => void | Promise, - ) {} - - async listByUserId(userId: string): Promise { - return this.context.drizzle - .select() - .from(transferRecent) - .where(eq(transferRecent.userId, userId)); - } - - async listBySourceHost( - userId: string, - sourceHostId: number, - limit = 10, - ): Promise { - return this.context.drizzle - .select() - .from(transferRecent) - .where( - and( - eq(transferRecent.userId, userId), - eq(transferRecent.sourceHostId, sourceHostId), - ), - ) - .orderBy(desc(transferRecent.lastUsed)) - .limit(limit); - } - - async upsertForDestination( - userId: string, - input: TransferRecentDestinationInput, - lastUsed = new Date().toISOString(), - ): Promise { - const [existing] = await this.context.drizzle - .select({ id: transferRecent.id }) - .from(transferRecent) - .where( - and( - eq(transferRecent.userId, userId), - eq(transferRecent.sourceHostId, input.sourceHostId), - eq(transferRecent.destHostId, input.destHostId), - eq(transferRecent.destPath, input.destPath), - ), - ) - .limit(1); - - if (existing) { - await this.context.drizzle - .update(transferRecent) - .set({ lastUsed }) - .where(eq(transferRecent.id, existing.id)); - } else { - await this.context.drizzle.insert(transferRecent).values({ - userId, - sourceHostId: input.sourceHostId, - destHostId: input.destHostId, - destPath: input.destPath, - destPathLabel: input.destPathLabel || input.destPath, - lastUsed, - }); - } - - await this.afterWrite(); - } - - async pruneSourceHost( - userId: string, - sourceHostId: number, - keep = 10, - ): Promise { - const rows = await this.context.drizzle - .select({ id: transferRecent.id }) - .from(transferRecent) - .where( - and( - eq(transferRecent.userId, userId), - eq(transferRecent.sourceHostId, sourceHostId), - ), - ) - .orderBy(desc(transferRecent.lastUsed)); - - const idsToDelete = rows.slice(keep).map((row) => row.id); - if (idsToDelete.length === 0) { - return 0; - } - - const result = await this.context.drizzle - .delete(transferRecent) - .where(inArray(transferRecent.id, idsToDelete)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async deleteByUserId(userId: string): Promise { - const result = await this.context.drizzle - .delete(transferRecent) - .where(eq(transferRecent.userId, userId)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - async deleteByHostId(hostId: number): Promise { - const result = await this.context.drizzle - .delete(transferRecent) - .where( - or( - eq(transferRecent.sourceHostId, hostId), - eq(transferRecent.destHostId, 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(transferRecent) - .where( - or( - inArray(transferRecent.sourceHostId, hostIds), - inArray(transferRecent.destHostId, 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 951b7e114..345adc2b2 100644 --- a/src/backend/database/routes/delete-user-data.ts +++ b/src/backend/database/routes/delete-user-data.ts @@ -9,7 +9,6 @@ import { createCurrentCredentialRepository, createCurrentDashboardServiceLinkRepository, createCurrentDismissedAlertRepository, - createCurrentFileManagerBookmarkRepository, createCurrentHomepageItemRepository, createCurrentHomepageLayoutRepository, createCurrentHostHealthRepository, @@ -35,7 +34,6 @@ import { createCurrentTrustedDeviceRepository, createCurrentUserPreferenceRepository, createCurrentUserRepository, - createCurrentTransferRecentRepository, createCurrentVaultProfileRepository, createCurrentSecretSourceRepository, createCurrentSharedCredentialSecretsRepository, @@ -85,9 +83,9 @@ export async function deleteUserAndRelatedData( await createCurrentSshCredentialUsageRepository().deleteByUserId(userId); - await createCurrentFileManagerBookmarkRepository().deleteByUserId(userId); - - await createCurrentTransferRecentRepository().deleteByUserId(userId); + // file manager recent/pinned/shortcuts and transfer_recent cascade on the + // user's refUser() foreign key, as the file-manager plugin's adopted + // tables. await createCurrentRecentActivityRepository().deleteByUserId(userId); await createCurrentDismissedAlertRepository().deleteByUserId(userId); diff --git a/src/backend/database/routes/host-file-manager-bookmark-routes.ts b/src/backend/database/routes/host-file-manager-bookmark-routes.ts deleted file mode 100644 index d8d470315..000000000 --- a/src/backend/database/routes/host-file-manager-bookmark-routes.ts +++ /dev/null @@ -1,544 +0,0 @@ -import type { AuthenticatedRequest } from "../../../types/index.js"; -import type { Request, RequestHandler, Response, Router } from "express"; -import { sshLogger } from "../../utils/logger.js"; -import { createCurrentFileManagerBookmarkRepository } from "../repositories/factory.js"; -import { isNonEmptyString } from "./host-normalizers.js"; - -export function registerHostFileManagerBookmarkRoutes( - router: Router, - authenticateJWT: RequestHandler, - requireViewPermission: RequestHandler, - requireDataAccess: RequestHandler, -): void { - /** - * @openapi - * /host/file_manager/recent: - * get: - * summary: Get recent files - * description: Retrieves a list of recent files for a specific host. - * tags: - * - SSH - * parameters: - * - in: query - * name: hostId - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: A list of recent files. - * 400: - * description: Invalid userId or hostId. - * 500: - * description: Failed to fetch recent files. - */ - router.get( - "/file_manager/recent", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const hostIdQuery = Array.isArray(req.query.hostId) - ? req.query.hostId[0] - : req.query.hostId; - const hostId = hostIdQuery ? parseInt(hostIdQuery as string) : null; - - if (!isNonEmptyString(userId)) { - sshLogger.warn("Invalid userId for recent files fetch"); - return res.status(400).json({ error: "Invalid userId" }); - } - - if (!hostId) { - sshLogger.warn("Host ID is required for recent files fetch"); - return res.status(400).json({ error: "Host ID is required" }); - } - - try { - const recentFiles = - await createCurrentFileManagerBookmarkRepository().listRecentForHost( - userId, - hostId, - 20, - ); - - res.json(recentFiles); - } catch (err) { - sshLogger.error("Failed to fetch recent files", err); - res.status(500).json({ error: "Failed to fetch recent files" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/recent: - * post: - * summary: Add recent file - * description: Adds a file to the list of recent files for a host. - * tags: - * - SSH - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * hostId: - * type: integer - * path: - * type: string - * name: - * type: string - * responses: - * 200: - * description: Recent file added. - * 400: - * description: Invalid data. - * 500: - * description: Failed to add recent file. - */ - router.post( - "/file_manager/recent", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { hostId, path, name } = req.body; - - if (!isNonEmptyString(userId) || !hostId || !path) { - sshLogger.warn("Invalid data for recent file addition"); - return res.status(400).json({ error: "Invalid data" }); - } - - try { - await createCurrentFileManagerBookmarkRepository().upsertRecent( - userId, - { - hostId, - path, - name, - }, - ); - - res.json({ message: "Recent file added" }); - } catch (err) { - sshLogger.error("Failed to add recent file", err); - res.status(500).json({ error: "Failed to add recent file" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/recent: - * delete: - * summary: Remove recent file - * description: Removes a file from the list of recent files for a host. - * tags: - * - SSH - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * hostId: - * type: integer - * path: - * type: string - * responses: - * 200: - * description: Recent file removed. - * 400: - * description: Invalid data. - * 500: - * description: Failed to remove recent file. - */ - router.delete( - "/file_manager/recent", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { hostId, path } = req.body; - - if (!isNonEmptyString(userId) || !hostId || !path) { - sshLogger.warn("Invalid data for recent file deletion"); - return res.status(400).json({ error: "Invalid data" }); - } - - try { - await createCurrentFileManagerBookmarkRepository().deleteRecentForHostPath( - userId, - { hostId, path }, - ); - - res.json({ message: "Recent file removed" }); - } catch (err) { - sshLogger.error("Failed to remove recent file", err); - res.status(500).json({ error: "Failed to remove recent file" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/pinned: - * get: - * summary: Get pinned files - * description: Retrieves a list of pinned files for a specific host. - * tags: - * - SSH - * parameters: - * - in: query - * name: hostId - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: A list of pinned files. - * 400: - * description: Invalid userId or hostId. - * 500: - * description: Failed to fetch pinned files. - */ - router.get( - "/file_manager/pinned", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const hostIdQuery = Array.isArray(req.query.hostId) - ? req.query.hostId[0] - : req.query.hostId; - const hostId = hostIdQuery ? parseInt(hostIdQuery as string) : null; - - if (!isNonEmptyString(userId)) { - sshLogger.warn("Invalid userId for pinned files fetch"); - return res.status(400).json({ error: "Invalid userId" }); - } - - if (!hostId) { - sshLogger.warn("Host ID is required for pinned files fetch"); - return res.status(400).json({ error: "Host ID is required" }); - } - - try { - const pinnedFiles = - await createCurrentFileManagerBookmarkRepository().listPinnedForHost( - userId, - hostId, - ); - - res.json(pinnedFiles); - } catch (err) { - sshLogger.error("Failed to fetch pinned files", err); - res.status(500).json({ error: "Failed to fetch pinned files" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/pinned: - * post: - * summary: Add pinned file - * description: Adds a file to the list of pinned files for a host. - * tags: - * - SSH - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * hostId: - * type: integer - * path: - * type: string - * name: - * type: string - * responses: - * 200: - * description: File pinned. - * 400: - * description: Invalid data. - * 409: - * description: File already pinned. - * 500: - * description: Failed to pin file. - */ - router.post( - "/file_manager/pinned", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { hostId, path, name } = req.body; - - if (!isNonEmptyString(userId) || !hostId || !path) { - sshLogger.warn("Invalid data for pinned file addition"); - return res.status(400).json({ error: "Invalid data" }); - } - - try { - const created = - await createCurrentFileManagerBookmarkRepository().createPinned( - userId, - { hostId, path, name }, - ); - - if (!created) { - return res.status(409).json({ error: "File already pinned" }); - } - - res.json({ message: "File pinned" }); - } catch (err) { - sshLogger.error("Failed to pin file", err); - res.status(500).json({ error: "Failed to pin file" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/pinned: - * delete: - * summary: Remove pinned file - * description: Removes a file from the list of pinned files for a host. - * tags: - * - SSH - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * hostId: - * type: integer - * path: - * type: string - * responses: - * 200: - * description: Pinned file removed. - * 400: - * description: Invalid data. - * 500: - * description: Failed to remove pinned file. - */ - router.delete( - "/file_manager/pinned", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { hostId, path } = req.body; - - if (!isNonEmptyString(userId) || !hostId || !path) { - sshLogger.warn("Invalid data for pinned file deletion"); - return res.status(400).json({ error: "Invalid data" }); - } - - try { - await createCurrentFileManagerBookmarkRepository().deletePinnedForHostPath( - userId, - { hostId, path }, - ); - - res.json({ message: "Pinned file removed" }); - } catch (err) { - sshLogger.error("Failed to remove pinned file", err); - res.status(500).json({ error: "Failed to remove pinned file" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/shortcuts: - * get: - * summary: Get shortcuts - * description: Retrieves a list of shortcuts for a specific host. - * tags: - * - SSH - * parameters: - * - in: query - * name: hostId - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: A list of shortcuts. - * 400: - * description: Invalid userId or hostId. - * 500: - * description: Failed to fetch shortcuts. - */ - router.get( - "/file_manager/shortcuts", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const hostIdQuery = Array.isArray(req.query.hostId) - ? req.query.hostId[0] - : req.query.hostId; - const hostId = hostIdQuery ? parseInt(hostIdQuery as string) : null; - - if (!isNonEmptyString(userId)) { - sshLogger.warn("Invalid userId for shortcuts fetch"); - return res.status(400).json({ error: "Invalid userId" }); - } - - if (!hostId) { - sshLogger.warn("Host ID is required for shortcuts fetch"); - return res.status(400).json({ error: "Host ID is required" }); - } - - try { - const shortcuts = - await createCurrentFileManagerBookmarkRepository().listShortcutsForHost( - userId, - hostId, - ); - - res.json(shortcuts); - } catch (err) { - sshLogger.error("Failed to fetch shortcuts", err); - res.status(500).json({ error: "Failed to fetch shortcuts" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/shortcuts: - * post: - * summary: Add shortcut - * description: Adds a shortcut for a specific host. - * tags: - * - SSH - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * hostId: - * type: integer - * path: - * type: string - * name: - * type: string - * responses: - * 200: - * description: Shortcut added. - * 400: - * description: Invalid data. - * 409: - * description: Shortcut already exists. - * 500: - * description: Failed to add shortcut. - */ - router.post( - "/file_manager/shortcuts", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { hostId, path, name } = req.body; - - if (!isNonEmptyString(userId) || !hostId || !path) { - sshLogger.warn("Invalid data for shortcut addition"); - return res.status(400).json({ error: "Invalid data" }); - } - - try { - const created = - await createCurrentFileManagerBookmarkRepository().createShortcut( - userId, - { hostId, path, name }, - ); - - if (!created) { - return res.status(409).json({ error: "Shortcut already exists" }); - } - - res.json({ message: "Shortcut added" }); - } catch (err) { - sshLogger.error("Failed to add shortcut", err); - res.status(500).json({ error: "Failed to add shortcut" }); - } - }, - ); - - /** - * @openapi - * /host/file_manager/shortcuts: - * delete: - * summary: Remove shortcut - * description: Removes a shortcut for a specific host. - * tags: - * - SSH - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * hostId: - * type: integer - * path: - * type: string - * responses: - * 200: - * description: Shortcut removed. - * 400: - * description: Invalid data. - * 500: - * description: Failed to remove shortcut. - */ - router.delete( - "/file_manager/shortcuts", - authenticateJWT, - requireViewPermission, - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { hostId, path } = req.body; - - if (!isNonEmptyString(userId) || !hostId || !path) { - sshLogger.warn("Invalid data for shortcut deletion"); - return res.status(400).json({ error: "Invalid data" }); - } - - try { - await createCurrentFileManagerBookmarkRepository().deleteShortcutForHostPath( - userId, - { hostId, path }, - ); - - res.json({ message: "Shortcut removed" }); - } catch (err) { - sshLogger.error("Failed to remove shortcut", err); - res.status(500).json({ error: "Failed to remove shortcut" }); - } - }, - ); -} diff --git a/src/backend/database/routes/host-folder-routes.ts b/src/backend/database/routes/host-folder-routes.ts index 42fd69674..3b0521568 100644 --- a/src/backend/database/routes/host-folder-routes.ts +++ b/src/backend/database/routes/host-folder-routes.ts @@ -5,14 +5,12 @@ import { pluginEvents, TOPICS } from "../../plugins/events.js"; import { createCurrentCommandHistoryRepository, createCurrentCredentialRepository, - createCurrentFileManagerBookmarkRepository, createCurrentHostFolderRepository, createCurrentFolderAccessRepository, createCurrentRecentActivityRepository, createCurrentRbacAccessRepository, createCurrentSshCredentialUsageRepository, createCurrentSessionRecordingRepository, - createCurrentTransferRecentRepository, createCurrentSyncTombstoneRepository, } from "../repositories/factory.js"; import { isNonEmptyString } from "./host-normalizers.js"; @@ -400,13 +398,9 @@ export function registerHostFolderRoutes( const hostIds = hostsToDelete.map((host) => host.id); if (hostIds.length > 0) { - await createCurrentFileManagerBookmarkRepository().deleteByHostIds( - hostIds, - ); - - await createCurrentTransferRecentRepository().deleteByHostIds( - hostIds, - ); + // file manager recent/pinned/shortcuts and transfer_recent cascade + // on the host's refHost() foreign key, as the file-manager + // plugin's adopted tables. await createCurrentCommandHistoryRepository().deleteByHostIds( hostIds, diff --git a/src/backend/database/routes/host.ts b/src/backend/database/routes/host.ts index 0a486349b..37a63d875 100644 --- a/src/backend/database/routes/host.ts +++ b/src/backend/database/routes/host.ts @@ -18,12 +18,10 @@ import { notifyAutomationInternalEvent } from "../../hosts/automation-events.js" import { createCurrentCommandHistoryRepository, createCurrentCredentialRepository, - createCurrentFileManagerBookmarkRepository, createCurrentOpksshTokenRepository, createCurrentRecentActivityRepository, createCurrentSshCredentialUsageRepository, createCurrentSessionRecordingRepository, - createCurrentTransferRecentRepository, createCurrentRbacAccessRepository, createCurrentRoleRepository, createCurrentHostResolutionRepository, @@ -55,7 +53,6 @@ import { validateParentHostId } from "./host-parent-validation.js"; import { registerHostOpksshRoutes } from "./host-opkssh-routes.js"; import { registerHostStepCaRoutes } from "./host-step-ca-routes.js"; import { registerHostFolderRoutes } from "./host-folder-routes.js"; -import { registerHostFileManagerBookmarkRoutes } from "./host-file-manager-bookmark-routes.js"; import { registerHostCommandHistoryRoutes } from "./host-command-history-routes.js"; import { registerHostAutostartRoutes } from "./host-autostart-routes.js"; import { registerHostInternalRoutes } from "./host-internal-routes.js"; @@ -2550,13 +2547,9 @@ router.delete( const numericHostId = Number(hostId); - await createCurrentFileManagerBookmarkRepository().deleteByHostId( - numericHostId, - ); - - await createCurrentTransferRecentRepository().deleteByHostId( - numericHostId, - ); + // file manager recent/pinned/shortcuts and transfer_recent cascade on + // the host's refHost() foreign key, as the file-manager plugin's + // adopted tables. await createCurrentCommandHistoryRepository().deleteByHostId( numericHostId, @@ -2635,87 +2628,8 @@ router.delete( }, ); -registerHostFileManagerBookmarkRoutes( - router, - authenticateJWT, - permissionManager.requirePermission("hosts.view"), - requireDataAccess, -); - -router.get( - "/transfer/recent", - authenticateJWT, - permissionManager.requirePermission("hosts.view"), - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const sourceHostIdQuery = Array.isArray(req.query.sourceHostId) - ? req.query.sourceHostId[0] - : req.query.sourceHostId; - const sourceHostId = sourceHostIdQuery - ? parseInt(sourceHostIdQuery as string) - : null; - - if (!isNonEmptyString(userId)) { - return res.status(400).json({ error: "Invalid userId" }); - } - - if (!sourceHostId) { - return res.status(400).json({ error: "Source host ID is required" }); - } - - try { - const recent = - await createCurrentTransferRecentRepository().listBySourceHost( - userId, - sourceHostId, - 10, - ); - - res.json(recent); - } catch (err) { - sshLogger.error("Failed to fetch transfer recent destinations", err); - res.status(500).json({ error: "Failed to fetch recent destinations" }); - } - }, -); - -router.post( - "/transfer/recent", - authenticateJWT, - permissionManager.requirePermission("hosts.view"), - requireDataAccess, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { sourceHostId, destHostId, destPath, destPathLabel } = req.body; - - if ( - !isNonEmptyString(userId) || - !sourceHostId || - !destHostId || - !destPath - ) { - return res.status(400).json({ error: "Invalid data" }); - } - - try { - const transferRecentRepository = createCurrentTransferRecentRepository(); - await transferRecentRepository.upsertForDestination(userId, { - sourceHostId, - destHostId, - destPath, - destPathLabel, - }); - - await transferRecentRepository.pruneSourceHost(userId, sourceHostId, 10); - - res.json({ message: "Recent destination saved" }); - } catch (err) { - sshLogger.error("Failed to save transfer recent destination", err); - res.status(500).json({ error: "Failed to save recent destination" }); - } - }, -); +// File manager recent/pinned/shortcuts and transfer/recent routes moved to +// the file-manager plugin, under /plugin-api/file-manager/. registerHostCommandHistoryRoutes( router, authenticateJWT, diff --git a/src/backend/database/routes/user-password-reset-routes.ts b/src/backend/database/routes/user-password-reset-routes.ts index 6d1f42573..30edcc63f 100644 --- a/src/backend/database/routes/user-password-reset-routes.ts +++ b/src/backend/database/routes/user-password-reset-routes.ts @@ -8,7 +8,6 @@ import { loginRateLimiter } from "../../utils/login-rate-limiter.js"; import { createCurrentCredentialRepository, createCurrentDismissedAlertRepository, - createCurrentFileManagerBookmarkRepository, createCurrentHostRepository, createCurrentRecentActivityRepository, createCurrentSettingsRepository, @@ -70,7 +69,6 @@ export async function resetUserPassword( await userRepository.update(userId, { passwordHash }); await createCurrentSshCredentialUsageRepository().deleteByUserId(userId); - await createCurrentFileManagerBookmarkRepository().deleteByUserId(userId); await createCurrentRecentActivityRepository().deleteByUserId(userId); await createCurrentDismissedAlertRepository().deleteByUserId(userId); await createCurrentHostRepository().deleteByUserId(userId); diff --git a/src/backend/plugins/ctx-ssh-auth.ts b/src/backend/plugins/ctx-ssh-auth.ts index 6fc37bab1..65630f02d 100644 --- a/src/backend/plugins/ctx-ssh-auth.ts +++ b/src/backend/plugins/ctx-ssh-auth.ts @@ -53,6 +53,8 @@ const PLUGIN_PURPOSES = new Set([ "fleet", "remote-desktop", "tunnel", + "file-manager", + "file-transfer", ]); function purposeOf(options?: PluginSshConnectOptions): SshConnectPurpose { diff --git a/src/backend/starter.ts b/src/backend/starter.ts index 76da05452..de0fbc0b8 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -302,10 +302,11 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { const { primeKnownPermissions } = await import("./utils/known-permissions.js"); await primeKnownPermissions(); - // Terminal, docker and host-metrics are deliberately absent: the - // ssh-terminal, docker and host-metrics plugins start their own servers, - // so disabling any of them stops its WS/HTTP server. See - // plugins/ssh-terminal, plugins/docker and plugins/host-metrics. + // Terminal, docker, host-metrics and file-manager are deliberately + // absent: the ssh-terminal, docker, host-metrics and file-manager plugins + // start their own servers, so disabling any of them stops its WS/HTTP + // server. See plugins/ssh-terminal, plugins/docker, plugins/host-metrics + // and plugins/file-manager. // Every other plugin (AI, Proxmox, Remote Desktop, Fleets, Automations, // Network Topology, Workspaces, Web Endpoint) is absent for the same // reason: each one serves its routes under /plugin-api// through @@ -313,7 +314,6 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { // dead import here. Automations' scheduler also starts from its own // activate() rather than here. await import("./hosts/tunnel/index.js"); - await import("./hosts/file-manager/index.js"); await import("./hosts/tmux/index.js"); await import("./hosts/serial.js"); await import("./services/dashboard.js"); @@ -350,6 +350,10 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { const { runProxmoxSettingsMigration } = await import("./utils/crypto-migration/proxmox-settings-migration.js"); await runProxmoxSettingsMigration(); + + const { runFileManagerSettingsMigration } = + await import("./utils/crypto-migration/file-manager-settings-migration.js"); + await runFileManagerSettingsMigration(); } catch (error) { systemLogger.warn("Plugin runtime failed to initialize", { operation: "plugin_init", diff --git a/src/backend/tests/database/repositories/file-manager-bookmark-repository.test.ts b/src/backend/tests/database/repositories/file-manager-bookmark-repository.test.ts deleted file mode 100644 index 71cf3df01..000000000 --- a/src/backend/tests/database/repositories/file-manager-bookmark-repository.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { TestSqliteDatabase } from "./test-support.js"; -import { FileManagerBookmarkRepository } from "../../../database/repositories/file-manager-bookmark-repository.js"; - -describe("FileManagerBookmarkRepository", () => { - 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.1', 22, 'root', 'password'), (3, 'user-2', 'other', '10.0.0.1', 22, 'root', 'password'); - `); - - return new FileManagerBookmarkRepository(context, onWrite); - } - - it("upserts and lists recent files by last-opened time", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - await repo.upsertRecent( - "user-1", - { hostId: 1, path: "/var/log/app.log" }, - "2026-01-01T00:00:00.000Z", - ); - await repo.upsertRecent( - "user-1", - { hostId: 1, path: "/opt/app/config.json", name: "Config" }, - "2026-01-02T00:00:00.000Z", - ); - await repo.upsertRecent( - "user-1", - { hostId: 1, path: "/var/log/app.log" }, - "2026-01-03T00:00:00.000Z", - ); - - const recent = await repo.listRecentForHost("user-1", 1); - - expect(recent.map((entry) => entry.path)).toEqual([ - "/var/log/app.log", - "/opt/app/config.json", - ]); - expect(recent[0].lastOpened).toBe("2026-01-03T00:00:00.000Z"); - expect(recent[0].name).toBe("app.log"); - expect(writeCount).toBe(3); - - expect( - await repo.deleteRecentForHostPath("user-1", { - hostId: 1, - path: "/var/log/app.log", - }), - ).toBe(1); - expect(writeCount).toBe(4); - }); - - it("creates pinned files and shortcuts without duplicating paths", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - expect( - await repo.createPinned( - "user-1", - { hostId: 1, path: "/srv/www", name: "Web" }, - "2026-01-01T00:00:00.000Z", - ), - ).toBe(true); - expect( - await repo.createPinned("user-1", { hostId: 1, path: "/srv/www" }), - ).toBe(false); - expect((await repo.listPinnedForHost("user-1", 1))[0]).toMatchObject({ - name: "Web", - path: "/srv/www", - }); - - expect( - await repo.createShortcut( - "user-1", - { hostId: 1, path: "/etc/nginx" }, - "2026-01-02T00:00:00.000Z", - ), - ).toBe(true); - expect( - await repo.createShortcut("user-1", { hostId: 1, path: "/etc/nginx" }), - ).toBe(false); - expect((await repo.listShortcutsForHost("user-1", 1))[0]).toMatchObject({ - name: "nginx", - path: "/etc/nginx", - }); - expect(writeCount).toBe(2); - - expect( - await repo.deletePinnedForHostPath("user-1", { - hostId: 1, - path: "/srv/www", - }), - ).toBe(1); - expect( - await repo.deleteShortcutForHostPath("user-1", { - hostId: 1, - path: "/etc/nginx", - }), - ).toBe(1); - expect(writeCount).toBe(4); - }); - - it("creates import bookmarks without duplicating user path/name pairs", async () => { - const repo = await createRepository(); - - await expect( - repo.createRecentForImport( - "user-1", - { hostId: 1, path: "/tmp/a.txt", name: "A" }, - "2026-01-01T00:00:00.000Z", - ), - ).resolves.toBe(true); - await expect( - repo.createRecentForImport("user-1", { - hostId: 2, - path: "/tmp/a.txt", - name: "A", - }), - ).resolves.toBe(false); - - await expect( - repo.createPinnedForImport("user-1", { - hostId: 1, - path: "/srv/www", - name: "Web", - }), - ).resolves.toBe(true); - await expect( - repo.createPinnedForImport("user-1", { - hostId: 2, - path: "/srv/www", - name: "Web", - }), - ).resolves.toBe(false); - - await expect( - repo.createShortcutForImport("user-1", { - hostId: 1, - path: "/etc/nginx", - name: "Nginx", - }), - ).resolves.toBe(true); - await expect( - repo.createShortcutForImport("user-1", { - hostId: 2, - path: "/etc/nginx", - name: "Nginx", - }), - ).resolves.toBe(false); - }); - - it("deletes bookmarks by user, host, and host list", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - await repo.upsertRecent("user-1", { hostId: 1, path: "/one" }); - await repo.createPinned("user-1", { hostId: 2, path: "/two" }); - await repo.createShortcut("user-2", { hostId: 3, path: "/three" }); - expect(writeCount).toBe(3); - - expect(await repo.deleteByHostId(1)).toBe(1); - expect(await repo.deleteByHostId(1)).toBe(0); - 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.deleteByUserId("user-2")).toBe(0); - expect(writeCount).toBe(6); - }); -}); diff --git a/src/backend/tests/database/repositories/transfer-recent-repository.test.ts b/src/backend/tests/database/repositories/transfer-recent-repository.test.ts deleted file mode 100644 index 65d2eccb1..000000000 --- a/src/backend/tests/database/repositories/transfer-recent-repository.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { TestSqliteDatabase } from "./test-support.js"; -import { TransferRecentRepository } from "../../../database/repositories/transfer-recent-repository.js"; - -describe("TransferRecentRepository", () => { - 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', 'source', '10.0.0.1', 22, 'root', 'password'), (2, 'user-1', 'dest-a', '10.0.0.1', 22, 'root', 'password'), (3, 'user-1', 'dest-b', '10.0.0.1', 22, 'root', 'password'), (4, 'user-2', 'other', '10.0.0.1', 22, 'root', 'password'); - `); - - return new TransferRecentRepository(context, onWrite); - } - - it("upserts, lists, and prunes recent transfer destinations", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - await repo.upsertForDestination( - "user-1", - { sourceHostId: 1, destHostId: 2, destPath: "/var/www" }, - "2026-01-01T00:00:00.000Z", - ); - await repo.upsertForDestination( - "user-1", - { - sourceHostId: 1, - destHostId: 3, - destPath: "/opt/app", - destPathLabel: "App", - }, - "2026-01-02T00:00:00.000Z", - ); - await repo.upsertForDestination( - "user-1", - { sourceHostId: 1, destHostId: 2, destPath: "/var/www" }, - "2026-01-03T00:00:00.000Z", - ); - - const recent = await repo.listBySourceHost("user-1", 1); - - expect(recent).toHaveLength(2); - expect(recent.map((entry) => entry.destPath)).toEqual([ - "/var/www", - "/opt/app", - ]); - expect(recent[0].lastUsed).toBe("2026-01-03T00:00:00.000Z"); - expect(recent[0].destPathLabel).toBe("/var/www"); - expect(writeCount).toBe(3); - - expect(await repo.pruneSourceHost("user-1", 1, 1)).toBe(1); - expect(await repo.pruneSourceHost("user-1", 1, 1)).toBe(0); - expect(await repo.listBySourceHost("user-1", 1)).toHaveLength(1); - expect(writeCount).toBe(4); - }); - - it("deletes recent transfer destinations by user and host references", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - await repo.upsertForDestination("user-1", { - sourceHostId: 1, - destHostId: 2, - destPath: "/one", - }); - await repo.upsertForDestination("user-1", { - sourceHostId: 2, - destHostId: 3, - destPath: "/two", - }); - await repo.upsertForDestination("user-2", { - sourceHostId: 4, - destHostId: 1, - destPath: "/other", - }); - expect(writeCount).toBe(3); - - expect(await repo.deleteByHostId(1)).toBe(2); - expect(await repo.deleteByHostId(1)).toBe(0); - expect(writeCount).toBe(4); - - await repo.upsertForDestination("user-1", { - sourceHostId: 1, - destHostId: 2, - destPath: "/three", - }); - expect(await repo.deleteByHostIds([])).toBe(0); - expect(await repo.deleteByHostIds([2, 3])).toBe(2); - expect(writeCount).toBe(6); - - await repo.upsertForDestination("user-2", { - sourceHostId: 4, - destHostId: 1, - destPath: "/last", - }); - expect(await repo.deleteByUserId("user-2")).toBe(1); - expect(await repo.deleteByUserId("user-2")).toBe(0); - expect(writeCount).toBe(8); - }); -}); diff --git a/src/backend/tests/database/routes/user-password-reset-routes.test.ts b/src/backend/tests/database/routes/user-password-reset-routes.test.ts index 2ff1791fa..02262c544 100644 --- a/src/backend/tests/database/routes/user-password-reset-routes.test.ts +++ b/src/backend/tests/database/routes/user-password-reset-routes.test.ts @@ -27,7 +27,6 @@ vi.mock("../../../database/repositories/factory.js", () => ({ }), createCurrentSettingsRepository: () => ({}), createCurrentSshCredentialUsageRepository: deletingRepo("usage"), - createCurrentFileManagerBookmarkRepository: deletingRepo("bookmarks"), createCurrentRecentActivityRepository: deletingRepo("activity"), createCurrentDismissedAlertRepository: deletingRepo("alerts"), createCurrentHostRepository: deletingRepo("hosts"), @@ -117,7 +116,6 @@ describe("resetUserPassword", () => { expect(outcome).toEqual({ status: "reset", dataWiped: true }); expect(calls.deletedFor).toEqual([ "usage:user-1", - "bookmarks:user-1", "activity:user-1", "alerts:user-1", "hosts:user-1", diff --git a/src/backend/tests/utils/file-manager-settings-migration.test.ts b/src/backend/tests/utils/file-manager-settings-migration.test.ts new file mode 100644 index 000000000..9154a87be --- /dev/null +++ b/src/backend/tests/utils/file-manager-settings-migration.test.ts @@ -0,0 +1,163 @@ +/** + * The file-manager host-columns migration. + * + * An upgrade must be lossless: a host that had file manager disabled, a + * custom default path, or SCP legacy mode on must keep that configuration + * once the ssh_data columns are dropped. Running it twice must not duplicate + * or clobber what it moved. + */ + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +interface HostRow { + id: number; + enable_file_manager: boolean; + default_path: string | null; + scp_legacy: boolean; +} + +interface SettingsRow { + pluginId: string; + scope: string; + scopeId: string | null; + key: string; + value: string | null; +} + +const hostRows: HostRow[] = []; +const settingsRows: SettingsRow[] = []; + +const pluginSettingsRepository = { + get: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + ) => + settingsRows.find( + (row) => + row.pluginId === pluginId && + row.scope === scope && + row.scopeId === scopeId && + row.key === key, + ) ?? null, + set: async ( + pluginId: string, + scope: string, + scopeId: string | null, + key: string, + value: string | null, + ) => { + const existing = settingsRows.find( + (row) => + row.pluginId === pluginId && + row.scope === scope && + row.scopeId === scopeId && + row.key === 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.filter( + (row) => + row.enable_file_manager === false || + row.default_path !== null || + row.scp_legacy, + ), + }), +})); + +const { runFileManagerSettingsMigration } = + await import("../../utils/crypto-migration/file-manager-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("runFileManagerSettingsMigration", () => { + it("moves a host with file manager disabled into plugin settings", async () => { + hostRows.push({ + id: 1, + enable_file_manager: false, + default_path: "/srv/app", + scp_legacy: true, + }); + + const result = await runFileManagerSettingsMigration(); + + expect(result.moved).toBe(1); + expect(storedValue(1, "enableFileManager")).toBe(false); + expect(storedValue(1, "defaultPath")).toBe("/srv/app"); + expect(storedValue(1, "scpLegacy")).toBe(true); + }); + + it("does nothing on a fresh install with no non-default hosts", async () => { + const result = await runFileManagerSettingsMigration(); + + expect(result.moved).toBe(0); + expect(settingsRows).toEqual([]); + }); + + it("is idempotent: a second run changes nothing", async () => { + hostRows.push({ + id: 2, + enable_file_manager: false, + default_path: null, + scp_legacy: false, + }); + + await runFileManagerSettingsMigration(); + const afterFirst = JSON.parse(JSON.stringify(settingsRows)); + + const second = await runFileManagerSettingsMigration(); + + expect(second.moved).toBe(0); + expect(second.skipped).toBe(1); + expect(settingsRows).toEqual(afterFirst); + }); + + it("moves each host independently", async () => { + hostRows.push( + { + id: 1, + enable_file_manager: false, + default_path: null, + scp_legacy: false, + }, + { + id: 2, + enable_file_manager: true, + default_path: "/data", + scp_legacy: false, + }, + ); + + const result = await runFileManagerSettingsMigration(); + + expect(result.moved).toBe(2); + expect(storedValue(1, "enableFileManager")).toBe(false); + expect(storedValue(2, "defaultPath")).toBe("/data"); + }); +}); diff --git a/src/backend/utils/crypto-migration/file-manager-settings-migration.ts b/src/backend/utils/crypto-migration/file-manager-settings-migration.ts new file mode 100644 index 000000000..dda2d52f3 --- /dev/null +++ b/src/backend/utils/crypto-migration/file-manager-settings-migration.ts @@ -0,0 +1,100 @@ +/** + * Moves the three file-manager host columns into the file-manager plugin's + * host-scope settings, before those columns are dropped from ssh_data. + * + * Idempotent: skips a host that already has a file-manager settings row, so + * it is safe to run on every boot. Lossless: reads straight off the + * still-present ssh_data columns, so it must run and finish before those + * columns are ever dropped from schema.ts. + */ + +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 FileManagerSettingsMigrationResult { + moved: number; + skipped: number; +} + +interface LegacyFileManagerRow { + id: number; + enable_file_manager: number | boolean | null; + default_path: string | null; + scp_legacy: number | boolean | null; +} + +export async function runFileManagerSettingsMigration(): Promise { + const result: FileManagerSettingsMigrationResult = { moved: 0, skipped: 0 }; + + try { + const drizzleDb = getDb(); + // Raw SQL, not the typed schema: this runs once schema.ts has already + // dropped these columns, so the query is the only thing left that still + // knows they used to exist. + const rows = await drizzleDb.all(sql` + SELECT id, enable_file_manager, default_path, scp_legacy + FROM ssh_data + WHERE enable_file_manager = false OR default_path IS NOT NULL + OR scp_legacy = true + `); + + if (rows.length === 0) return result; + + const pluginSettingsRepository = createCurrentPluginSettingsRepository(); + + for (const row of rows) { + const scopeId = String(row.id); + const existing = await pluginSettingsRepository.get( + "file-manager", + "host", + scopeId, + "enableFileManager", + ); + if (existing && existing.value !== null) { + result.skipped++; + continue; + } + + await pluginSettingsRepository.set( + "file-manager", + "host", + scopeId, + "enableFileManager", + JSON.stringify(row.enable_file_manager !== false), + ); + await pluginSettingsRepository.set( + "file-manager", + "host", + scopeId, + "defaultPath", + JSON.stringify(row.default_path ?? null), + ); + await pluginSettingsRepository.set( + "file-manager", + "host", + scopeId, + "scpLegacy", + JSON.stringify(!!row.scp_legacy), + ); + result.moved++; + } + + if (result.moved > 0) { + databaseLogger.info( + `Moved file manager settings for ${result.moved} host(s) into plugin settings`, + { operation: "file_manager_settings_migration", moved: result.moved }, + ); + } + } catch (error) { + // A failed migration must not stop the backend. Hosts simply keep + // whatever file-manager settings they already had (none, on a fresh run). + databaseLogger.warn("File manager settings migration failed", { + operation: "file_manager_settings_migration", + error: error instanceof Error ? error.message : String(error), + }); + } + + return result; +} diff --git a/src/backend/utils/user-data-export.ts b/src/backend/utils/user-data-export.ts index 42c39c798..3bcce49a7 100644 --- a/src/backend/utils/user-data-export.ts +++ b/src/backend/utils/user-data-export.ts @@ -1,13 +1,35 @@ +import { sql } from "drizzle-orm"; import { createCurrentDismissedAlertRepository, - createCurrentFileManagerBookmarkRepository, - createCurrentTransferRecentRepository, createCurrentUserDataExportRepository, createCurrentUserRepository, } from "../database/repositories/factory.js"; +import { getDb } from "../database/db/index.js"; import { DataCrypto } from "./data-crypto.js"; import { databaseLogger } from "./logger.js"; +// The file manager plugin owns these tables now (p_file_manager_*); read by +// raw SQL rather than importing plugin code into core. +async function listFileManagerExportData(userId: string): Promise<{ + recent: unknown[]; + pinned: unknown[]; + shortcuts: unknown[]; + transferRecent: unknown[]; +}> { + const db = getDb(); + const [recent, pinned, shortcuts, transferRecent] = await Promise.all([ + db.all(sql`SELECT * FROM p_file_manager_recent WHERE user_id = ${userId}`), + db.all(sql`SELECT * FROM p_file_manager_pinned WHERE user_id = ${userId}`), + db.all( + sql`SELECT * FROM p_file_manager_shortcuts WHERE user_id = ${userId}`, + ), + db.all( + sql`SELECT * FROM p_file_manager_transfer_recent WHERE user_id = ${userId}`, + ), + ]); + return { recent, pinned, shortcuts, transferRecent }; +} + interface UserExportData { version: string; exportedAt: string; @@ -90,19 +112,12 @@ class UserDataExport { : credentials; } - const [recentFiles, pinnedFiles, shortcuts, transferRecentData] = - await Promise.all([ - createCurrentFileManagerBookmarkRepository().listRecentByUserId( - userId, - ), - createCurrentFileManagerBookmarkRepository().listPinnedByUserId( - userId, - ), - createCurrentFileManagerBookmarkRepository().listShortcutsByUserId( - userId, - ), - createCurrentTransferRecentRepository().listByUserId(userId), - ]); + const { + recent: recentFiles, + pinned: pinnedFiles, + shortcuts, + transferRecent: transferRecentData, + } = await listFileManagerExportData(userId); const alerts = await createCurrentDismissedAlertRepository().listByUserId(userId); diff --git a/src/main.tsx b/src/main.tsx index c64b4c340..1f147a6f5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -34,11 +34,6 @@ const AppShell = lazy(() => // Full-screen apps opened via query params (e.g. from external links or // Electron). Plugin views register a standalone component with their tab. -const FileManagerApp = lazy(() => - import("@/features/file-manager/FileManagerApp").then((m) => ({ - default: m.default, - })), -); const TunnelApp = lazy(() => import("@/features/tunnel/TunnelApp").then((m) => ({ default: m.default })), ); @@ -80,16 +75,8 @@ function FullscreenApp() { const searchParams = new URLSearchParams(window.location.search); const view = searchParams.get("view"); const hostId = searchParams.get("hostId"); - const path = searchParams.get("path"); switch (view) { - case "file-manager": - return ( - - ); case "tunnel": return ; case "tmux-monitor": // --- tmux-monitor --- diff --git a/src/ui/AppShell.tsx b/src/ui/AppShell.tsx index 1d8d76ba9..e2babf8a3 100644 --- a/src/ui/AppShell.tsx +++ b/src/ui/AppShell.tsx @@ -173,7 +173,6 @@ import { RemoteSyncBanner } from "@/components/RemoteSyncBanner.tsx"; import { MigrationNoticeDialog } from "@/components/MigrationNoticeDialog.tsx"; import { dbHealthMonitor } from "@/lib/db-health-monitor"; import { ServerStatusProvider } from "@/lib/ServerStatusContext"; -import { TransferMonitor } from "@/features/file-manager/TransferMonitor.tsx"; import { sshHostToHost } from "@/sidebar/HostManagerData"; import { resolveHostTabType } from "@/lib/host-connection-tabs"; import { changeAppLanguage, consumeLoginLanguage } from "@/i18n/i18n"; @@ -195,6 +194,7 @@ import { type TabShellCallbacks, } from "@/shell/tab-registry"; import { getPanel, usePanels } from "@/shell/panel-registry"; +import { invokeAction } from "@/shell/action-registry"; import { startPluginRuntime, stopPluginRuntime } from "@/plugin-host/loader"; import { usePluginStore } from "@/plugin-host/plugin-store"; import { @@ -2207,12 +2207,6 @@ export function AppShell({ } function handleRailClick(view: RailView) { - if (view === "sftp") { - openSingletonTab("sftp"); - if (isMobile) setSidebarOpen(false); - return; - } - if (railView === view && sidebarOpen) { setSidebarOpen(false); } else { @@ -2435,18 +2429,6 @@ export function AppShell({ openSingletonTab(type, undefined, undefined, options?.data), closeTab: (tabId) => closeTab(tabId), renameTab: (tabId, label) => renameTab(tabId, label), - openFileInEditor: (host, filePath) => - openTab(host, "files", { - instanceId: newInstanceId(), - restoredSessionId: null, - initialFilePath: filePath, - }), - openFileManager: (host, path) => - openTab(host, "files", { - instanceId: newInstanceId(), - restoredSessionId: null, - initialPath: path, - }), openTerminalTab: (host, path) => openTab(host, "terminal", { instanceId: newInstanceId(), @@ -3032,7 +3014,9 @@ export function AppShell({ onRenameTab={renameTab} onOpenFileManager={(tabId) => { const targetTab = tabs.find((t) => t.id === tabId); - if (targetTab?.host) openTab(targetTab.host, "files"); + if (targetTab?.host) { + void invokeAction("files.openHost", targetTab.host); + } }} onOpenShare={openShareForTab} isAppFullscreen={isAppFullscreen} @@ -3195,7 +3179,6 @@ export function AppShell({ /> )} - diff --git a/src/ui/api/file-manager-metadata-api.ts b/src/ui/api/file-manager-metadata-api.ts deleted file mode 100644 index b2f0420d6..000000000 --- a/src/ui/api/file-manager-metadata-api.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { handleApiError, sshHostApi } from "@/main-axios"; -import type { FileManagerFile, FileManagerShortcut } from "@/types/index"; - -interface FileManagerOperation { - name: string; - path: string; - isSSH: boolean; - sshSessionId?: string; - hostId: number; -} - -// FILE MANAGER METADATA (Recent, Pinned, Shortcuts) -// ============================================================================ - -export async function getFileManagerRecent( - hostId: number, -): Promise { - try { - const response = await sshHostApi.get( - `/file_manager/recent?hostId=${hostId}`, - ); - return response.data || []; - } catch { - return []; - } -} - -export async function addFileManagerRecent( - file: FileManagerOperation, -): Promise> { - try { - const response = await sshHostApi.post("/file_manager/recent", file); - return response.data; - } catch (error) { - handleApiError(error, "add recent file"); - } -} - -export async function removeFileManagerRecent( - file: FileManagerOperation, -): Promise> { - try { - const response = await sshHostApi.delete("/file_manager/recent", { - data: file, - }); - return response.data; - } catch (error) { - handleApiError(error, "remove recent file"); - } -} - -export async function getFileManagerPinned( - hostId: number, -): Promise { - try { - const response = await sshHostApi.get( - `/file_manager/pinned?hostId=${hostId}`, - ); - return response.data || []; - } catch { - return []; - } -} - -export async function addFileManagerPinned( - file: FileManagerOperation, -): Promise> { - try { - const response = await sshHostApi.post("/file_manager/pinned", file); - return response.data; - } catch (error) { - handleApiError(error, "add pinned file"); - } -} - -export async function removeFileManagerPinned( - file: FileManagerOperation, -): Promise> { - try { - const response = await sshHostApi.delete("/file_manager/pinned", { - data: file, - }); - return response.data; - } catch (error) { - handleApiError(error, "remove pinned file"); - } -} - -export async function getFileManagerShortcuts( - hostId: number, -): Promise { - try { - const response = await sshHostApi.get( - `/file_manager/shortcuts?hostId=${hostId}`, - ); - return response.data || []; - } catch { - return []; - } -} - -export async function addFileManagerShortcut( - shortcut: FileManagerOperation, -): Promise> { - try { - const response = await sshHostApi.post("/file_manager/shortcuts", shortcut); - return response.data; - } catch (error) { - handleApiError(error, "add shortcut"); - } -} - -export async function removeFileManagerShortcut( - shortcut: FileManagerOperation, -): Promise> { - try { - const response = await sshHostApi.delete("/file_manager/shortcuts", { - data: shortcut, - }); - return response.data; - } catch (error) { - handleApiError(error, "remove shortcut"); - } -} - -// ============================================================================ diff --git a/src/ui/features/file-manager/TransferMonitor.tsx b/src/ui/features/file-manager/TransferMonitor.tsx deleted file mode 100644 index d01cada38..000000000 --- a/src/ui/features/file-manager/TransferMonitor.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { useEffect, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { getTransferStatus, listActiveTransfers } from "@/main-axios.ts"; -import { createFormatTransferMetrics } from "./transferMetricsFormat.ts"; -import { - beginTransferProgressMonitoring, - isTransferBeingMonitored, - showTransferCompletionToast, -} from "./transferProgressMonitor.tsx"; -import { - clearStalePendingTransfer, - getPendingTransferIds, - isTransferNotified, -} from "./transferNotificationStore.ts"; -import { runAdaptivePolling } from "@/lib/adaptive-polling.ts"; - -const POLL_INTERVAL_MS = 2000; - -export function TransferMonitor() { - const { t } = useTranslation(); - const formatTransferMetrics = useMemo( - () => createFormatTransferMetrics(t), - [t], - ); - - useEffect(() => { - const reconcileTransfers = async () => { - let hasWork = false; - try { - const { transfers } = await listActiveTransfers(); - hasWork = transfers.length > 0; - for (const transfer of transfers) { - if (isTransferBeingMonitored(transfer.transferId)) continue; - beginTransferProgressMonitoring(transfer.transferId, t, { - resumed: true, - initialStatus: transfer, - formatTransferMetrics, - }); - } - } catch { - // Non-fatal: file-manager service may be unavailable briefly - } - - const pendingTransferIds = getPendingTransferIds(); - for (const transferId of pendingTransferIds) { - if ( - isTransferBeingMonitored(transferId) || - isTransferNotified(transferId) - ) { - continue; - } - hasWork = true; - - try { - const status = await getTransferStatus(transferId); - if (status.status === "running") { - if (!isTransferBeingMonitored(transferId)) { - beginTransferProgressMonitoring(transferId, t, { - resumed: true, - initialStatus: status, - formatTransferMetrics, - }); - } - continue; - } - - showTransferCompletionToast( - status, - t, - undefined, - formatTransferMetrics, - ); - } catch { - clearStalePendingTransfer(transferId); - } - } - return hasWork; - }; - - return runAdaptivePolling(reconcileTransfers, { - minIntervalMs: POLL_INTERVAL_MS, - maxIntervalMs: 30_000, - stablePollsPerStep: 1, - }); - }, [t, formatTransferMetrics]); - - return null; -} diff --git a/src/ui/features/homepage/HomepageCanvas.tsx b/src/ui/features/homepage/HomepageCanvas.tsx index af0e9c0eb..5e14a8271 100644 --- a/src/ui/features/homepage/HomepageCanvas.tsx +++ b/src/ui/features/homepage/HomepageCanvas.tsx @@ -44,7 +44,6 @@ import "./widgets/TermixUptimeWidget"; import "./widgets/SystemOverviewWidget"; import "./widgets/SshTerminalWidget"; import "./widgets/QuickConnectWidget"; -import "./widgets/FileManagerWidget"; import "./widgets/TunnelWidget"; import "./widgets/CalendarWidget"; import "./widgets/CountdownWidget"; diff --git a/src/ui/features/homepage/dialogs/WidgetEditDialog.tsx b/src/ui/features/homepage/dialogs/WidgetEditDialog.tsx index ab74ce947..9b6f91ff3 100644 --- a/src/ui/features/homepage/dialogs/WidgetEditDialog.tsx +++ b/src/ui/features/homepage/dialogs/WidgetEditDialog.tsx @@ -28,7 +28,6 @@ import type { SystemOverviewConfig, SshTerminalConfig, QuickConnectConfig, - FileManagerWidgetConfig, TunnelWidgetConfig, CalendarConfig, CountdownConfig, @@ -58,7 +57,6 @@ import { TermixUptimeEditForm } from "./TermixUptimeEditForm"; import { SystemOverviewEditForm } from "./SystemOverviewEditForm"; import { SshTerminalEditForm } from "./SshTerminalEditForm"; import { QuickConnectEditForm } from "./QuickConnectEditForm"; -import { FileManagerWidgetEditForm } from "./FileManagerWidgetEditForm"; import { TunnelWidgetEditForm } from "./TunnelWidgetEditForm"; import { CalendarEditForm } from "./CalendarEditForm"; import { CountdownEditForm } from "./CountdownEditForm"; @@ -227,13 +225,6 @@ export function WidgetEditDialog({ onChange={(c) => setConfig(c as unknown as Record)} /> ); - case "file_manager_widget": - return ( - setConfig(c as unknown as Record)} - /> - ); case "tunnel_widget": return ( !!(h.enableSsh && h.enableFileManager), - }, { type: "tunnel", icon: Network, diff --git a/src/ui/features/terminal/TerminalTabContent.tsx b/src/ui/features/terminal/TerminalTabContent.tsx index 5910c74a8..c740bc286 100644 --- a/src/ui/features/terminal/TerminalTabContent.tsx +++ b/src/ui/features/terminal/TerminalTabContent.tsx @@ -5,6 +5,7 @@ import type { TerminalHandle, TerminalHostConfig } from "./Terminal"; import { useIsMobile } from "@/hooks/use-mobile"; import { useTabsSafe } from "@/shell/TabContext"; import { hostToSSHHost } from "@/lib/host-to-ssh-host"; +import { invokeAction } from "@/shell/action-registry"; import type { TabRenderProps } from "@/shell/tab-registry"; const CommandHistoryProvider = lazy(() => @@ -88,9 +89,11 @@ export function TerminalTabContent({ } previewTheme={previewTerminalTheme} onOpenFileInEditor={(filePath) => - shell.openFileInEditor(host, filePath) + void invokeAction("files.openEditor", host, filePath) + } + onOpenFileManager={(path) => + void invokeAction("files.openHost", host, path) } - onOpenFileManager={(path) => shell.openFileManager(host, path)} isQuickConnect={host.id.startsWith("quick-connect-")} onSaveQuickConnect={ shell.saveQuickConnect diff --git a/src/ui/lib/activity-types.ts b/src/ui/lib/activity-types.ts index 91bf10734..39ebe6465 100644 --- a/src/ui/lib/activity-types.ts +++ b/src/ui/lib/activity-types.ts @@ -1,5 +1,5 @@ import type { ComponentType } from "react"; -import { FolderOpen, Network } from "lucide-react"; +import { Network } from "lucide-react"; import { tabTypeForActivity } from "@/shell/tab-registry"; export interface ActivityTarget { @@ -11,11 +11,6 @@ export interface ActivityTarget { /** Recent-activity types core itself records. */ const CORE_ACTIVITY: Record = { - file_manager: { - icon: FolderOpen, - tab: "files", - labelKey: "dashboard.activityFileManager", - }, tunnel: { icon: Network, tab: "tunnel", diff --git a/src/ui/lib/useConnectionRetry.ts b/src/ui/lib/useConnectionRetry.ts deleted file mode 100644 index 1af7d0da7..000000000 --- a/src/ui/lib/useConnectionRetry.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import type { ConnectionStatus } from "@/components/connection/connection-status.ts"; - -interface UseConnectionRetryOptions { - connect: () => void | Promise; - maxAttempts?: number; - baseDelayMs?: number; - maxDelayMs?: number; - enabled?: boolean; - autoStart?: boolean; -} - -interface UseConnectionRetryResult { - status: ConnectionStatus; - attempt: number; - maxAttempts: number; - nextRetryInMs: number | null; - markConnected: () => void; - markFailed: () => void; - retryNow: () => void; - reset: () => void; -} - -const COUNTDOWN_TICK_MS = 250; -const RETRY_JITTER_RATIO = 0.1; - -export function computeReconnectDelay( - attempt: number, - baseDelayMs: number, - maxDelayMs: number, - random = Math.random, -): number { - const base = Math.min( - baseDelayMs * Math.pow(2, Math.max(0, attempt - 1)), - maxDelayMs, - ); - const jitter = base * RETRY_JITTER_RATIO * (random() * 2 - 1); - return Math.max(baseDelayMs, Math.min(maxDelayMs, Math.round(base + jitter))); -} - -// Backoff curve mirrors Terminal.tsx's proven attemptReconnection(): 2s, 4s, -// 8s, capped at 8s, up to 8 attempts before falling back to a manual retry. -export function useConnectionRetry({ - connect, - maxAttempts = 8, - baseDelayMs = 2000, - maxDelayMs = 8000, - enabled = true, - autoStart = true, -}: UseConnectionRetryOptions): UseConnectionRetryResult { - const [status, setStatus] = useState("connecting"); - const [attempt, setAttempt] = useState(0); - const [nextRetryInMs, setNextRetryInMs] = useState(null); - - const connectRef = useRef(connect); - connectRef.current = connect; - - const enabledRef = useRef(enabled); - enabledRef.current = enabled; - - const attemptRef = useRef(0); - const retryTimeoutRef = useRef | null>(null); - const countdownIntervalRef = useRef | null>( - null, - ); - const isMountedRef = useRef(true); - const markFailedRef = useRef<() => void>(() => {}); - - const clearTimers = useCallback(() => { - if (retryTimeoutRef.current) { - clearTimeout(retryTimeoutRef.current); - retryTimeoutRef.current = null; - } - if (countdownIntervalRef.current) { - clearInterval(countdownIntervalRef.current); - countdownIntervalRef.current = null; - } - }, []); - - const runConnect = useCallback(() => { - if (!isMountedRef.current) return; - setStatus("connecting"); - try { - void Promise.resolve(connectRef.current()).catch(() => { - markFailedRef.current(); - }); - } catch { - markFailedRef.current(); - } - }, []); - - const scheduleRetry = useCallback(() => { - if (attemptRef.current >= maxAttempts) { - setStatus("disconnected"); - setNextRetryInMs(null); - return; - } - - attemptRef.current += 1; - setAttempt(attemptRef.current); - - const delay = computeReconnectDelay( - attemptRef.current, - baseDelayMs, - maxDelayMs, - ); - - let remaining = delay; - setNextRetryInMs(remaining); - countdownIntervalRef.current = setInterval(() => { - remaining = Math.max(0, remaining - COUNTDOWN_TICK_MS); - setNextRetryInMs(remaining); - }, COUNTDOWN_TICK_MS); - - retryTimeoutRef.current = setTimeout(() => { - clearTimers(); - if (!isMountedRef.current || !enabledRef.current) return; - runConnect(); - }, delay); - }, [baseDelayMs, maxDelayMs, maxAttempts, clearTimers, runConnect]); - - const markConnected = useCallback(() => { - clearTimers(); - attemptRef.current = 0; - setAttempt(0); - setNextRetryInMs(null); - if (isMountedRef.current) setStatus("connected"); - }, [clearTimers]); - - const markFailed = useCallback(() => { - clearTimers(); - if (!isMountedRef.current) return; - setStatus("error"); - if (enabledRef.current) { - scheduleRetry(); - } else { - setNextRetryInMs(null); - } - }, [clearTimers, scheduleRetry]); - markFailedRef.current = markFailed; - - const retryNow = useCallback(() => { - clearTimers(); - attemptRef.current = 0; - setAttempt(0); - setNextRetryInMs(null); - runConnect(); - }, [clearTimers, runConnect]); - - const reset = useCallback(() => { - clearTimers(); - attemptRef.current = 0; - setAttempt(0); - setNextRetryInMs(null); - if (isMountedRef.current) setStatus("connecting"); - }, [clearTimers]); - - useEffect(() => { - isMountedRef.current = true; - if (autoStart) { - runConnect(); - } - return () => { - isMountedRef.current = false; - clearTimers(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (enabled && status === "error" && !retryTimeoutRef.current) { - scheduleRetry(); - } - if (!enabled) { - clearTimers(); - setNextRetryInMs(null); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [enabled]); - - return { - status, - attempt, - maxAttempts, - nextRetryInMs, - markConnected, - markFailed, - retryNow, - reset, - }; -} diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 93dde68d0..815aea3a8 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -712,7 +712,6 @@ "hostManager": "Host Manager", "credentials": "Credentials", "portForwarding": "Forwarding", - "sftp": "SFTP", "connections": "Connections", "serial": "Serial", "keepSideMenuOpen": "Keep Menu Open", @@ -764,14 +763,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tunnels", - "tabFiles": "Files", "tabStats": "Host Metrics", "tabTelnet": "Telnet", "tabSharing": "Sharing", "tabAuthentication": "Authentication", "terminal": "Terminal", "tunnel": "Tunnel", - "fileManager": "File Manager", "serverStats": "Host Metrics", "status": { "available": "Available", @@ -1115,12 +1112,6 @@ "proxmoxResultSkippedNoIp": "{{count}} skipped (no IP found)", "proxmoxStatsUrlCopied": "Proxmox Stats URL copied to clipboard", "copyProxmoxStatsUrlAction": "Copy Proxmox Stats URL", - "enableFileManagerMonitor": "Enable File Manager", - "enableFileManagerMonitorDesc": "Browse and manage files on this host over SFTP", - "scpLegacyLabel": "SCP Legacy Mode", - "scpLegacyDesc": "Use legacy file transfer for servers that do not support the SFTP subsystem (e.g. embedded or minimal SSH servers).", - "defaultPathLabel": "Default Path", - "fileManagerPathHint": "The directory to open when the file manager launches for this host.", "statusChecksLabel": "Status Checks", "enableStatusChecks": "Enable Status Checks", "enableStatusChecksDesc": "Periodically ping this host to verify availability", @@ -1280,7 +1271,6 @@ "deletedHost": "Deleted {{name}}", "copiedToClipboard": "Copied to clipboard", "terminalUrlCopied": "Terminal URL copied", - "fileManagerUrlCopied": "File Manager URL copied", "tunnelUrlCopied": "Tunnel URL copied", "dockerUrlCopied": "Docker URL copied", "hostMetricsUrlCopied": "Host Metrics URL copied", @@ -1298,7 +1288,6 @@ "copyAddress": "Copy Address", "copyLink": "Copy Link", "copyTerminalUrlAction": "Copy Terminal URL", - "copyFileManagerUrlAction": "Copy File Manager URL", "copyTunnelUrlAction": "Copy Tunnel URL", "copyDockerUrlAction": "Copy Docker URL", "copyHostMetricsUrlAction": "Copy Host Metrics URL", @@ -1868,336 +1857,6 @@ "removeFromSplit": "Remove from Split" } }, - "fileManager": { - "noHostSelected": "No host selected", - "initializingEditor": "Initializing editor...", - "file": "File", - "folder": "Folder", - "uploadFile": "Upload File", - "downloadFile": "Download", - "extractArchive": "Extract Archive", - "extractingArchive": "Extracting {{name}}...", - "archiveExtractedSuccessfully": "{{name}} extracted successfully", - "extractFailed": "Extract failed", - "compressFile": "Compress File", - "compressFiles": "Compress Files", - "compressFilesDesc": "Compress {{count}} items into an archive", - "archiveName": "Archive Name", - "enterArchiveName": "Enter archive name...", - "compressionFormat": "Compression Format", - "selectedFiles": "Selected files", - "andMoreFiles": "and {{count}} more...", - "compress": "Compress", - "compressingFiles": "Compressing {{count}} items into {{name}}...", - "filesCompressedSuccessfully": "{{name}} created successfully", - "compressFailed": "Compression failed", - "edit": "Edit", - "preview": "Preview", - "previous": "Previous", - "next": "Next", - "pageXOfY": "Page {{current}} of {{total}}", - "zoomOut": "Zoom Out", - "zoomIn": "Zoom In", - "newFile": "New File", - "newFolder": "New Folder", - "rename": "Rename", - "uploading": "Uploading...", - "uploadingFile": "Uploading {{name}}...", - "fileName": "File Name", - "folderName": "Folder Name", - "fileUploadedSuccessfully": "File \"{{name}}\" uploaded successfully", - "failedToUploadFile": "Failed to upload file", - "uploadingFolderFiles": "Uploading {{count}} file(s)...", - "uploadedFolderFiles": "Uploaded {{count}} file(s) successfully", - "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", - "fileDownloadedSuccessfully": "File \"{{name}}\" downloaded successfully", - "failedToDownloadFile": "Failed to download file", - "fileCreatedSuccessfully": "File \"{{name}}\" created successfully", - "folderCreatedSuccessfully": "Folder \"{{name}}\" created successfully", - "failedToCreateItem": "Failed to create item", - "operationFailed": "{{operation}} operation failed for {{name}}: {{error}}", - "failedToResolveSymlink": "Failed to resolve symlink", - "itemsDeletedSuccessfully": "{{count}} items deleted successfully", - "failedToDeleteItems": "Failed to delete items", - "sudoPasswordRequired": "Administrator Password Required", - "enterSudoPassword": "Enter sudo password to continue this operation", - "sudoPassword": "Sudo password", - "sudoOperationFailed": "Sudo operation failed", - "sudoAuthFailed": "Sudo authentication failed", - "dragFilesToUpload": "Drop files here to upload", - "dropToUploadHere": "Drop to upload here", - "dropToDownloadHere": "Drop to download here", - "localFiles": "Local", - "showLocalFiles": "Show local files (split view)", - "hideLocalFiles": "Hide local files", - "localHome": "Home folder", - "localPathPlaceholder": "Local folder path", - "localShowHidden": "Show hidden files", - "localHideHidden": "Hide hidden files", - "localRevealInFileManager": "Reveal in system file manager", - "localCannotOpenFolder": "Cannot open folder", - "localItemCount": "{{count}} items", - "localSelectedCount": "{{count}} selected", - "back": "Back", - "forward": "Forward", - "up": "Up", - "kind": "Kind", - "localUploadingProgress": "Uploading {{current}} of {{total}}", - "localDownloadingProgress": "Downloading {{current}} of {{total}}", - "localTransferInFlight": "{{count}} at once", - "localPreparingDownload": "Preparing download...", - "localTransferCancelled": "Transfer cancelled", - "localNothingToTransfer": "Nothing to transfer", - "localUploadComplete": "Uploaded {{count}} file(s)", - "localUploadFailed": "Upload failed", - "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", - "localDownloadComplete": "Downloaded {{count}} file(s)", - "localDownloadFailed": "Download failed", - "localDownloadPartial": "Downloaded {{done}} file(s), {{failed}} failed", - "localReplaceExistingPrompt": "{{count}} item(s) already exist in this folder. Replace them?", - "localReplace": "Replace", - "localSkipExisting": "Skip", - "localSkippedExisting": "Skipped {{count}} existing item(s)", - "localUnsafeNamesSkipped": "Skipped {{count}} item(s) whose names cannot be used on this computer", - "localOpen": "Open", - "localOpenFolder": "Open folder", - "localUploadToRemote": "Upload to server", - "localUploadToRemoteMany": "Upload {{count}} items to server", - "localMoveToTrash": "Move to Trash", - "localMoveToTrashMany": "Move {{count}} items to Trash", - "localTrashConfirmSingle": "Move \"{{name}}\" to the Trash?", - "localTrashConfirmFolder": "Move the folder \"{{name}}\" and all its contents to the Trash?", - "localTrashConfirmMany": "Move {{count}} items to the Trash?", - "localTrashed": "Moved {{count}} item(s) to the Trash", - "localTrashFailed": "Could not move to the Trash", - "localRenameFailed": "Rename failed", - "localCreateFailed": "Could not create item", - "goToParentFolder": "Go to parent folder", - "resizeSidebar": "Drag to resize the sidebar (double-click to reset)", - "resizeLocalPane": "Drag to resize the local pane (double-click to reset)", - "columns": "Columns", - "columnsHint": "Right-click to choose columns", - "emptyFolder": "This folder is empty", - "searchFiles": "Search files...", - "upload": "Upload", - "selectHostToStart": "Select a host to start file management", - "sshRequiredForFileManager": "File manager requires SSH. This host does not have SSH enabled.", - "failedToConnect": "Failed to connect to SSH", - "failedToLoadDirectory": "Failed to load directory", - "noSSHConnection": "No SSH connection available", - "copy": "Copy", - "cut": "Cut", - "paste": "Paste", - "copyPath": "Copy Path", - "copyPaths": "Copy Paths", - "delete": "Delete", - "properties": "Properties", - "refresh": "Refresh", - "downloadFiles": "Download {{count}} files to Browser", - "copyFiles": "Copy {{count}} items", - "cutFiles": "Cut {{count}} items", - "deleteFiles": "Delete {{count}} items", - "filesCopiedToClipboard": "{{count}} items copied to clipboard", - "filesCutToClipboard": "{{count}} items cut to clipboard", - "pathCopiedToClipboard": "Path copied to clipboard", - "pathsCopiedToClipboard": "{{count}} paths copied to clipboard", - "failedToCopyPath": "Failed to copy path to clipboard", - "copyFolderLink": "Copy Link to Folder", - "copyCurrentFolderLink": "Copy Link to Current Folder", - "folderLinkCopied": "Folder link copied to clipboard", - "failedToCopyFolderLink": "Failed to copy folder link", - "movedItems": "Moved {{count}} items", - "failedToDeleteItem": "Failed to delete item", - "itemRenamedSuccessfully": "{{type}} renamed successfully", - "failedToRenameItem": "Failed to rename item", - "download": "Download", - "openExternalEditor": "Open externally", - "chooseExternalEditor": "Choose external editor", - "externalEditorSelected": "External editor selected", - "externalEditorDesktopOnly": "External editor is only available in the desktop app", - "externalEditorOpened": "Opened in external editor. Saving there will upload changes back to the server.", - "failedToOpenExternalEditor": "Failed to open external editor", - "failedToSelectExternalEditor": "Failed to select external editor", - "permissions": "Permissions", - "size": "Size", - "modified": "Modified", - "path": "Path", - "confirmDelete": "Are you sure you want to delete {{name}}?", - "permissionDenied": "Permission denied", - "serverError": "Server Error", - "fileSavedSuccessfully": "File saved successfully", - "failedToSaveFile": "Failed to save file", - "confirmDeleteSingleItem": "Are you sure you want to permanently delete \"{{name}}\"?", - "confirmDeleteMultipleItems": "Are you sure you want to permanently delete {{count}} items?", - "confirmDeleteMultipleItemsWithFolders": "Are you sure you want to permanently delete {{count}} items? This includes folders and their contents.", - "confirmDeleteFolder": "Are you sure you want to permanently delete the folder \"{{name}}\" and all its contents?", - "permanentDeleteWarning": "This action cannot be undone. The item(s) will be permanently deleted from the server.", - "moveToTrashWarning": "The item(s) will be moved to the Termix Trash and can be restored before the retention period expires.", - "itemsMovedToTrash": "{{count}} item(s) moved to Trash", - "trash": "Trash", - "trashDescription": "Deleted items are retained for {{days}} day(s) on this host.", - "trashLoadFailed": "Failed to load Trash", - "trashEmpty": "Trash is empty", - "trashRestore": "Restore", - "trashRestored": "Restored {{name}}", - "trashRestoreFailed": "Failed to restore item. The original path may already exist.", - "trashDeletePermanently": "Delete permanently", - "trashDeleteConfirm": "Permanently delete {{name}}? This cannot be undone.", - "trashPermanentlyDeleted": "Permanently deleted {{name}}", - "trashDeleteFailed": "Failed to permanently delete item", - "trashEmptyAction": "Empty Trash", - "trashEmptyConfirm": "Permanently delete every item in Trash? This cannot be undone.", - "trashEmptied": "Trash emptied", - "trashRetention": "Automatic cleanup after (days)", - "trashRetentionSaved": "Trash retention updated", - "trashRetentionFailed": "Failed to update Trash retention", - "trashUnavailableConfirm": "This item cannot be moved to Trash, usually because it is on another filesystem or requires elevated permissions. Delete it permanently instead?", - "recent": "Recent", - "pinned": "Pinned", - "folderShortcuts": "Folder Shortcuts", - "failedToReconnectSSH": "Failed to reconnect SSH session", - "openTerminalHere": "Open Terminal Here", - "run": "Run", - "openTerminalInFolder": "Open Terminal in This Folder", - "openTerminalInFileLocation": "Open Terminal at File Location", - "runningFile": "Running - {{file}}", - "onlyRunExecutableFiles": "Can only run executable files", - "directories": "Directories", - "removedFromRecentFiles": "Removed \"{{name}}\" from recent files", - "removeFailed": "Remove failed", - "unpinnedSuccessfully": "Unpinned \"{{name}}\" successfully", - "unpinFailed": "Unpin failed", - "removedShortcut": "Removed shortcut \"{{name}}\"", - "removeShortcutFailed": "Remove shortcut failed", - "clearedAllRecentFiles": "Cleared all recent files", - "clearFailed": "Clear failed", - "removeFromRecentFiles": "Remove from recent files", - "clearAllRecentFiles": "Clear all recent files", - "unpinFile": "Unpin file", - "removeShortcut": "Remove shortcut", - "pinFile": "Pin file", - "addToShortcuts": "Add to shortcuts", - "pasteFailed": "Paste failed", - "noUndoableActions": "No undoable actions", - "undoCopySuccess": "Undid copy operation: Deleted {{count}} copied files", - "undoCopyFailedDelete": "Undo failed: Could not delete any copied files", - "undoCopyFailedNoInfo": "Undo failed: Could not find copied file information", - "undoMoveSuccess": "Undid move operation: Moved {{count}} files back to original location", - "undoMoveFailedMove": "Undo failed: Could not move any files back", - "undoMoveFailedNoInfo": "Undo failed: Could not find moved file information", - "undoDeleteNotSupported": "Delete operation cannot be undone: Files have been permanently deleted from server", - "undoTypeNotSupported": "Unsupported undo operation type", - "undoOperationFailed": "Undo operation failed", - "unknownError": "Unknown error", - "confirm": "Confirm", - "find": "Find...", - "replace": "Replace", - "downloadInstead": "Download Instead", - "keyboardShortcuts": "Keyboard Shortcuts", - "searchAndReplace": "Search & Replace", - "editing": "Editing", - "search": "Search", - "findNext": "Find Next", - "findPrevious": "Find Previous", - "save": "Save", - "selectAll": "Select All", - "undo": "Undo", - "redo": "Redo", - "moveLineUp": "Move Line Up", - "moveLineDown": "Move Line Down", - "toggleComment": "Toggle Comment", - "autoComplete": "Auto Complete", - "imageLoadError": "Failed to load image", - "startTyping": "Start typing...", - "unknownSize": "Unknown size", - "fileIsEmpty": "File is empty", - "largeFileWarning": "Large File Warning", - "largeFileWarningDesc": "This file is {{size}} in size, which may cause performance issues when opened as text.", - "fileNotFoundAndRemoved": "File \"{{name}}\" not found and has been removed from recent/pinned files", - "failedToLoadFile": "Failed to load file: {{error}}", - "serverErrorOccurred": "Server error occurred. Please try again later.", - "autoSaveFailed": "Auto-save failed", - "fileAutoSaved": "File auto-saved", - "moveFileFailed": "Failed to move {{name}}", - "moveOperationFailed": "Move operation failed", - "canOnlyCompareFiles": "Can only compare two files", - "comparingFiles": "Comparing files: {{file1}} and {{file2}}", - "dragFailed": "Drag operation failed", - "filePinnedSuccessfully": "File \"{{name}}\" pinned successfully", - "pinFileFailed": "Failed to pin file", - "fileUnpinnedSuccessfully": "File \"{{name}}\" unpinned successfully", - "unpinFileFailed": "Failed to unpin file", - "shortcutAddedSuccessfully": "Folder shortcut \"{{name}}\" added successfully", - "addShortcutFailed": "Failed to add shortcut", - "operationCompletedSuccessfully": "{{operation}} {{count}} items successfully", - "operationCompleted": "{{operation}} {{count}} items", - "downloadFileSuccess": "File {{name}} downloaded successfully", - "downloadFileFailed": "Download failed", - "downloadingFile": "Downloading {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Move to {{name}}", - "diffCompareWith": "Diff compare with {{name}}", - "dragOutsideToDownload": "Drag outside window to download ({{count}} files)", - "newFolderDefault": "NewFolder", - "newFileDefault": "NewFile.txt", - "successfullyMovedItems": "Successfully moved {{count}} items to {{target}}", - "move": "Move", - "searchInFile": "Search in file (Ctrl+F)", - "showKeyboardShortcuts": "Show keyboard shortcuts", - "decreaseFontSize": "Decrease font size", - "increaseFontSize": "Increase font size", - "startWritingMarkdown": "Start writing your markdown content...", - "loadingFileComparison": "Loading file comparison...", - "reload": "Reload", - "compare": "Compare", - "sideBySide": "Side by Side", - "inline": "Inline", - "fileComparison": "File Comparison: {{file1}} vs {{file2}}", - "fileTooLarge": "File too large: {{error}}", - "sshConnectionFailed": "SSH connection failed. Please check your connection to {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Failed to load file: {{error}}", - "connecting": "Connecting...", - "connectedSuccessfully": "Connected successfully", - "totpVerificationFailed": "TOTP verification failed", - "warpgateVerificationFailed": "Warpgate authentication failed", - "authenticationFailed": "Authentication failed", - "incorrectPassphrase": "Incorrect passphrase. Please try again.", - "verificationCodePrompt": "Verification code:", - "changePermissions": "Change Permissions", - "currentPermissions": "Current Permissions", - "owner": "Owner", - "group": "Group", - "others": "Others", - "read": "Read", - "write": "Write", - "execute": "Execute", - "permissionsChangedSuccessfully": "Permissions changed successfully", - "failedToChangePermissions": "Failed to change permissions", - "name": "Name", - "sortByName": "Name", - "sortByDate": "Date Modified", - "sortBySize": "Size", - "ascending": "Ascending", - "descending": "Descending", - "root": "Root", - "new": "New", - "sortBy": "Sort By", - "items": "Items", - "selected": "Selected", - "editor": "Editor", - "octal": "Octal", - "storage": "Storage", - "disk": "Disk", - "used": "Used", - "of": "of", - "toggleSidebar": "Toggle Sidebar", - "compactLayout": "Use compact layout", - "comfortableLayout": "Use comfortable layout", - "cannotLoadPdf": "Cannot load PDF", - "pdfLoadError": "There was an error loading this PDF file.", - "loadingPdf": "Loading PDF...", - "loadingPage": "Loading page..." - }, "transfer": { "copyToHost": "Copy to host…", "moveToHost": "Move to host…", @@ -2303,50 +1962,6 @@ "retryTransferError": "Retry failed", "transferFailedRetryHint": "Partial data was kept on the destination. Retry will resume when the connection is back." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Transfer files between two SSH hosts.", - "sourceServer": "Source Server", - "destinationServer": "Destination Server", - "selectHost": "Select host", - "noHostSelected": "No host selected", - "move": "Move", - "copy": "Copy", - "upload": "Upload", - "itemsSelected": "{{count}} item selected", - "itemsSelected_other": "{{count}} items selected", - "ready": "Ready", - "loadingHosts": "Loading hosts...", - "name": "Name", - "modified": "Modified", - "size": "Size", - "noFiles": "No files", - "open": "Open", - "up": "Up", - "refresh": "Refresh", - "copyToTarget": "Copy to Target Directory", - "rename": "Rename", - "delete": "Delete", - "editPermissions": "Edit Permissions", - "createNewFolder": "Create New Folder", - "deleteSelectedItems": "Delete Selected Items", - "deleteConfirm": "Delete {{count}} selected item?", - "deleteConfirm_other": "Delete {{count}} selected items?", - "cancel": "Cancel", - "save": "Save", - "folderCreated": "Folder created", - "itemRenamed": "Item renamed", - "actionFailed": "Action failed", - "itemsDeleted": "{{count}} item deleted", - "itemsDeleted_other": "{{count}} items deleted", - "deleteFailed": "Delete failed", - "permissionsUpdated": "Permissions updated", - "noRemoteSessionConnected": "No remote session connected", - "failedToListFiles": "Failed to list files", - "failedToRefresh": "Failed to refresh", - "destinationInsideSource": "Destination cannot be inside the selected source path.", - "sameHostNoOp": "Source and destination are the same folder." - }, "tunnels": { "noSshTunnels": "No SSH Tunnels", "createFirstTunnelMessage": "You haven't created any SSH tunnels yet. Configure tunnel connections in the Host Manager to get started.", diff --git a/src/ui/locales/translated/af_ZA.json b/src/ui/locales/translated/af_ZA.json index 1951aea6b..7aec07f7c 100644 --- a/src/ui/locales/translated/af_ZA.json +++ b/src/ui/locales/translated/af_ZA.json @@ -715,7 +715,6 @@ "hostManager": "Gasheerbestuurder", "credentials": "Geloofsbriewe", "portForwarding": "Aanstuur", - "sftp": "SFTP", "connections": "Verbindings", "serial": "Reeks", "keepSideMenuOpen": "Hou Kieslys Oop", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tonnels", - "tabFiles": "Lêers", "tabStats": "Gasheermetrieke", "tabTelnet": "Telnet", "tabSharing": "Deling", "tabAuthentication": "Verifikasie", "terminal": "Terminaal", "tunnel": "Tonnel", - "fileManager": "Lêerbestuurder", "serverStats": "Gasheermetrieke", "status": { "available": "Beskikbaar", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} oorgeslaan (geen IP gevind nie)", "proxmoxStatsUrlCopied": "Proxmox Statistiek URL na knipbord gekopieer", "copyProxmoxStatsUrlAction": "Kopieer Proxmox Statistiek URL", - "enableFileManagerMonitor": "Aktiveer Lêerbestuurder", - "enableFileManagerMonitorDesc": "Blaai deur en bestuur lêers op hierdie gasheer via SFTP", - "scpLegacyLabel": "SCP Legacy-modus", - "scpLegacyDesc": "Gebruik ouer lêeroordrag vir bedieners wat nie die SFTP-substelsel ondersteun nie (bv. ingebedde of minimale SSH-bedieners).", - "defaultPathLabel": "Standaardpad", - "fileManagerPathHint": "Die gids om oop te maak wanneer die lêerbestuurder vir hierdie gasheer begin.", "statusChecksLabel": "Statuskontroles", "enableStatusChecks": "Aktiveer statuskontroles", "enableStatusChecksDesc": "Ping hierdie gasheer gereeld om beskikbaarheid te verifieer", @@ -1278,7 +1269,6 @@ "deletedHost": "Verwyder {{name}}", "copiedToClipboard": "Na knipbord gekopieer", "terminalUrlCopied": "Terminaal-URL gekopieer", - "fileManagerUrlCopied": "Lêerbestuurder-URL gekopieer", "tunnelUrlCopied": "Tonnel-URL gekopieer", "dockerUrlCopied": "Docker-URL gekopieer", "hostMetricsUrlCopied": "Gasheermetrieke-URL gekopieer", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopieer Adres", "copyLink": "Kopieer skakel", "copyTerminalUrlAction": "Kopieer terminaal-URL", - "copyFileManagerUrlAction": "Kopieer Lêerbestuurder URL", "copyTunnelUrlAction": "Kopieer Tonnel URL", "copyDockerUrlAction": "Kopieer Docker URL", "copyHostMetricsUrlAction": "Kopieer gasheermetrieke-URL", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Verwyder van Split" } }, - "fileManager": { - "noHostSelected": "Geen gasheer gekies nie", - "initializingEditor": "Inisialiseer redigeerder...", - "file": "Lêer", - "folder": "Vouer", - "uploadFile": "Laai lêer op", - "downloadFile": "Laai af", - "extractArchive": "Uittrekselargief", - "extractingArchive": "Onttrek tans {{name}}...", - "archiveExtractedSuccessfully": "{{name}} suksesvol onttrek", - "extractFailed": "Uittreksel het misluk", - "compressFile": "Komprimeer lêer", - "compressFiles": "Kompresseer lêers", - "compressFilesDesc": "Kompresseer {{count}} items in 'n argief", - "archiveName": "Argiefnaam", - "enterArchiveName": "Voer argiefnaam in...", - "compressionFormat": "Kompressieformaat", - "selectedFiles": "Geselekteerde lêers", - "andMoreFiles": "en {{count}} meer...", - "compress": "Kompressie", - "compressingFiles": "Komprimering van {{count}} items in {{name}}...", - "filesCompressedSuccessfully": "{{name}} suksesvol geskep", - "compressFailed": "Kompressie het misluk", - "edit": "Wysig", - "preview": "Voorskou", - "previous": "Vorige", - "next": "Volgende", - "pageXOfY": "Bladsy {{current}} van {{total}}", - "zoomOut": "Uitzoom", - "zoomIn": "Inzoem", - "newFile": "Nuwe Lêer", - "newFolder": "Nuwe Vouer", - "rename": "Hernoem", - "uploading": "Laai tans op...", - "uploadingFile": "Laai tans {{name}} op ...", - "fileName": "Lêernaam", - "folderName": "Vouernaam", - "fileUploadedSuccessfully": "Lêer \"{{name}}\" is suksesvol opgelaai", - "failedToUploadFile": "Kon nie lêer oplaai nie", - "uploadingFolderFiles": "Laai tans {{count}} lêer(s) op...", - "uploadedFolderFiles": "{{count}} lêer(s) suksesvol opgelaai", - "uploadedFolderPartial": "Opgelaaide {{uploaded}} lêer(s), {{failed}} het misluk", - "fileDownloadedSuccessfully": "Lêer \"{{name}}\" is suksesvol afgelaai", - "failedToDownloadFile": "Kon nie lêer aflaai nie", - "fileCreatedSuccessfully": "Lêer \"{{name}}\" suksesvol geskep", - "folderCreatedSuccessfully": "Lêer \"{{name}}\" suksesvol geskep", - "failedToCreateItem": "Kon nie item skep nie", - "operationFailed": "{{operation}} -bewerking het misluk vir {{name}}: {{error}}", - "failedToResolveSymlink": "Kon nie simskakel oplos nie", - "itemsDeletedSuccessfully": "{{count}} items is suksesvol verwyder", - "failedToDeleteItems": "Kon nie items uitvee nie", - "sudoPasswordRequired": "Administrateurwagwoord benodig", - "enterSudoPassword": "Voer die sudo-wagwoord in om hierdie operasie voort te sit", - "sudoPassword": "Sudo-wagwoord", - "sudoOperationFailed": "Sudo-bewerking het misluk", - "sudoAuthFailed": "Sudo-verifikasie het misluk", - "dragFilesToUpload": "Plaas lêers hier om op te laai", - "dropToUploadHere": "Laat val om hier op te laai", - "dropToDownloadHere": "Los om hier af te laai", - "localFiles": "Plaaslik", - "showLocalFiles": "Wys plaaslike lêers (gesplete aansig)", - "hideLocalFiles": "Versteek plaaslike lêers", - "localHome": "Tuisbladmap", - "localPathPlaceholder": "Plaaslike vouerpad", - "localShowHidden": "Wys versteekte lêers", - "localHideHidden": "Versteek versteekte lêers", - "localRevealInFileManager": "Wys in stelsellêerbestuurder", - "localCannotOpenFolder": "Kan nie lêer oopmaak nie", - "localItemCount": "{{count}} items", - "localSelectedCount": "{{count}} gekies", - "back": "Terug", - "forward": "Vorentoe", - "up": "Op", - "kind": "Soortgelyk", - "localUploadingProgress": "Laai tans {{current}} van {{total}} op", - "localDownloadingProgress": "Laai tans {{current}} van {{total}} af", - "localTransferInFlight": "{{count}} gelyktydig", - "localPreparingDownload": "Berei aflaai voor...", - "localTransferCancelled": "Oordrag gekanselleer", - "localNothingToTransfer": "Niks om oor te dra nie", - "localUploadComplete": "Opgelaaide {{count}} lêer(s)", - "localUploadFailed": "Oplaai het misluk", - "localUploadPartial": "Opgelaaide {{done}} lêer(s), {{failed}} het misluk", - "localDownloadComplete": "Afgelaaide {{count}} lêer(s)", - "localDownloadFailed": "Aflaai het misluk", - "localDownloadPartial": "Aflaai van {{done}} lêer(s), {{failed}} het misluk", - "localReplaceExistingPrompt": "{{count}} item(s) bestaan reeds in hierdie vouer. Vervang hulle?", - "localReplace": "Vervang", - "localSkipExisting": "Slaan oor", - "localSkippedExisting": "Oorgeslaan {{count}} bestaande item(s)", - "localUnsafeNamesSkipped": "Oorgeslaan {{count}} item(s) waarvan die name nie op hierdie rekenaar gebruik kan word nie", - "localOpen": "Oop", - "localOpenFolder": "Maak vouer oop", - "localUploadToRemote": "Laai op na bediener", - "localUploadToRemoteMany": "Laai {{count}} items na bediener op", - "localMoveToTrash": "Skuif na asblik", - "localMoveToTrashMany": "Skuif {{count}} items na die asblik", - "localTrashConfirmSingle": "Skuif \"{{name}}\" na die asblik?", - "localTrashConfirmFolder": "Skuif die vouer \"{{name}}\" en al sy inhoud na die asblik?", - "localTrashConfirmMany": "Skuif {{count}} items na die asblik?", - "localTrashed": "Het {{count}} item(s) na die asblik geskuif", - "localTrashFailed": "Kon nie na die asblik skuif nie", - "localRenameFailed": "Hernoem het misluk", - "localCreateFailed": "Kon nie item skep nie", - "goToParentFolder": "Gaan na ouermap", - "resizeSidebar": "Sleep om die sybalk se grootte te verander (dubbelklik om terug te stel)", - "resizeLocalPane": "Sleep om die plaaslike paneel se grootte te verander (dubbelklik om terug te stel)", - "columns": "Kolomme", - "columnsHint": "Regskliek om kolomme te kies", - "emptyFolder": "Hierdie vouer is leeg", - "searchFiles": "Soek lêers...", - "upload": "Oplaai", - "selectHostToStart": "Kies 'n gasheer om lêerbestuur te begin", - "sshRequiredForFileManager": "Lêerbestuurder benodig SSH. Hierdie gasheer het nie SSH geaktiveer nie.", - "failedToConnect": "Kon nie aan SSH koppel nie", - "failedToLoadDirectory": "Kon nie gids laai nie", - "noSSHConnection": "Geen SSH-verbinding beskikbaar nie", - "copy": "Kopieer", - "cut": "Sny", - "paste": "Plak", - "copyPath": "Kopieer Pad", - "copyPaths": "Kopieer paaie", - "delete": "Vee uit", - "properties": "Eienskappe", - "refresh": "Verfris", - "downloadFiles": "Laai {{count}} lêers na die blaaier af", - "copyFiles": "Kopieer {{count}} items", - "cutFiles": "Sny {{count}} items", - "deleteFiles": "Vee {{count}} items uit", - "filesCopiedToClipboard": "{{count}} items gekopieer na knipbord", - "filesCutToClipboard": "{{count}} items is na knipbord gesny", - "pathCopiedToClipboard": "Pad gekopieer na knipbord", - "pathsCopiedToClipboard": "{{count}} paaie gekopieer na knipbord", - "failedToCopyPath": "Kon nie pad na knipbord kopieer nie", - "copyFolderLink": "Kopieer skakel na vouer", - "copyCurrentFolderLink": "Kopieer skakel na huidige vouer", - "folderLinkCopied": "Vouerskakel gekopieer na knipbord", - "failedToCopyFolderLink": "Kon nie vouerskakel kopieer nie", - "movedItems": "Het {{count}} items geskuif", - "failedToDeleteItem": "Kon nie item verwyder nie", - "itemRenamedSuccessfully": "{{type}} suksesvol hernoem", - "failedToRenameItem": "Kon nie item hernoem nie", - "download": "Laai af", - "openExternalEditor": "Maak ekstern oop", - "chooseExternalEditor": "Kies eksterne redigeerder", - "externalEditorSelected": "Eksterne redigeerder gekies", - "externalEditorDesktopOnly": "Eksterne redigeerder is slegs beskikbaar in die rekenaarprogram", - "externalEditorOpened": "Oopgemaak in eksterne redigeerder. As jy dit daar stoor, sal dit veranderinge terug na die bediener oplaai.", - "failedToOpenExternalEditor": "Kon nie eksterne redigeerder oopmaak nie", - "failedToSelectExternalEditor": "Kon nie eksterne redigeerder kies nie", - "permissions": "Toestemmings", - "size": "Grootte", - "modified": "Gewysig", - "path": "Pad", - "confirmDelete": "Is jy seker jy wil {{name}} verwyder?", - "permissionDenied": "Toestemming geweier", - "serverError": "Bedienerfout", - "fileSavedSuccessfully": "Lêer suksesvol gestoor", - "failedToSaveFile": "Kon nie lêer stoor nie", - "confirmDeleteSingleItem": "Is jy seker jy wil \"{{name}} \" permanent verwyder?", - "confirmDeleteMultipleItems": "Is jy seker jy wil {{count}} items permanent verwyder?", - "confirmDeleteMultipleItemsWithFolders": "Is jy seker jy wil {{count}} items permanent verwyder? Dit sluit gidse en hul inhoud in.", - "confirmDeleteFolder": "Is jy seker jy wil die vouer \"{{name}}\" en al sy inhoud permanent verwyder?", - "permanentDeleteWarning": "Hierdie aksie kan nie ongedaan gemaak word nie. Die item(s) sal permanent van die bediener verwyder word.", - "moveToTrashWarning": "Die item(s) sal na die Termix-asblik geskuif word en kan herstel word voordat die bewaringstydperk verstryk.", - "itemsMovedToTrash": "{{count}} item(s) na die asblik geskuif", - "trash": "Asblik", - "trashDescription": "Geskrapte items word vir {{days}} dag(e) op hierdie gasheer behou.", - "trashLoadFailed": "Kon nie die asblik laai nie", - "trashEmpty": "Asblik is leeg", - "trashRestore": "Herstel", - "trashRestored": "Herstel {{name}}", - "trashRestoreFailed": "Kon nie item herstel nie. Die oorspronklike pad bestaan dalk reeds.", - "trashDeletePermanently": "Vee permanent uit", - "trashDeleteConfirm": "Wil jy {{name}}permanent verwyder? Dit kan nie ongedaan gemaak word nie.", - "trashPermanentlyDeleted": "Permanent verwyder {{name}}", - "trashDeleteFailed": "Kon nie item permanent verwyder nie", - "trashEmptyAction": "Maak asblik leeg", - "trashEmptyConfirm": "Wil jy elke item in die asblik permanent verwyder? Dit kan nie ongedaan gemaak word nie.", - "trashEmptied": "Asblik leeggemaak", - "trashRetention": "Outomatiese skoonmaak na (dae)", - "trashRetentionSaved": "Asblikbewaring opgedateer", - "trashRetentionFailed": "Kon nie asblikbehoud opdateer nie", - "trashUnavailableConfirm": "Hierdie item kan nie na die asblik geskuif word nie, gewoonlik omdat dit op 'n ander lêerstelsel is of verhoogde toestemmings benodig. Verwyder dit eerder permanent?", - "recent": "Onlangs", - "pinned": "Vasgepen", - "folderShortcuts": "Vouerkortpaaie", - "failedToReconnectSSH": "Kon nie SSH-sessie herkoppel nie", - "openTerminalHere": "Maak Terminaal Hier Oop", - "run": "Hardloop", - "openTerminalInFolder": "Maak Terminaal in Hierdie Vouer Oop", - "openTerminalInFileLocation": "Maak Terminaal oop by Lêerligging", - "runningFile": "Hardloop - {{file}}", - "onlyRunExecutableFiles": "Kan slegs uitvoerbare lêers uitvoer", - "directories": "Gidse", - "removedFromRecentFiles": "\"{{name}}\" is uit onlangse lêers verwyder", - "removeFailed": "Verwydering het misluk", - "unpinnedSuccessfully": "\"{{name}}\" suksesvol ontspeld", - "unpinFailed": "Ontspeld het misluk", - "removedShortcut": "Kortpad \"{{name}} \" verwyder", - "removeShortcutFailed": "Verwydering van kortpad het misluk", - "clearedAllRecentFiles": "Alle onlangse lêers is uitgevee", - "clearFailed": "Vee uit misluk", - "removeFromRecentFiles": "Verwyder uit onlangse lêers", - "clearAllRecentFiles": "Vee alle onlangse lêers uit", - "unpinFile": "Ontspeld lêer", - "removeShortcut": "Verwyder kortpad", - "pinFile": "Speld lêer vas", - "addToShortcuts": "Voeg by kortpaaie", - "pasteFailed": "Plak het misluk", - "noUndoableActions": "Geen ongedaanbare aksies nie", - "undoCopySuccess": "Kopieerbewerking ongedaan gemaak: Gekopieerde lêers is verwyder {{count}}", - "undoCopyFailedDelete": "Ongedaan maak het misluk: Kon geen gekopieerde lêers verwyder nie", - "undoCopyFailedNoInfo": "Ongedaan maak het misluk: Kon nie gekopieerde lêerinligting vind nie", - "undoMoveSuccess": "Ongedaan gemaak met skuifbewerking: {{count}} lêers teruggeskuif na oorspronklike ligging", - "undoMoveFailedMove": "Ongedaan maak het misluk: Kon geen lêers terugskuif nie", - "undoMoveFailedNoInfo": "Ongedaan maak het misluk: Kon nie inligting oor die verskuifde lêer vind nie", - "undoDeleteNotSupported": "Verwyderingsbewerking kan nie ongedaan gemaak word nie: Lêers is permanent van die bediener verwyder", - "undoTypeNotSupported": "Nie-ondersteunde ongedaanmaak-bewerkingtipe", - "undoOperationFailed": "Ongedaanmaak-bewerking het misluk", - "unknownError": "Onbekende fout", - "confirm": "Bevestig", - "find": "Vind...", - "replace": "Vervang", - "downloadInstead": "Laai eerder af", - "keyboardShortcuts": "Sleutelbordkortpaaie", - "searchAndReplace": "Soek en vervang", - "editing": "Redigering", - "search": "Soek", - "findNext": "Vind Volgende", - "findPrevious": "Vind Vorige", - "save": "Stoor", - "selectAll": "Kies Alles", - "undo": "Ongedaan maak", - "redo": "Herdoen", - "moveLineUp": "Skuif lyn op", - "moveLineDown": "Skuif lyn af", - "toggleComment": "Wissel kommentaar", - "autoComplete": "Outomatiese Voltooiing", - "imageLoadError": "Kon nie beeld laai nie", - "startTyping": "Begin tik...", - "unknownSize": "Onbekende grootte", - "fileIsEmpty": "Lêer is leeg", - "largeFileWarning": "Waarskuwing oor groot lêers", - "largeFileWarningDesc": "Hierdie lêer is {{size}} groot, wat werkverrigtingsprobleme kan veroorsaak wanneer dit as teks oopgemaak word.", - "fileNotFoundAndRemoved": "Lêer \"{{name}}\" nie gevind nie en is verwyder uit onlangse/vasgespelde lêers", - "failedToLoadFile": "Kon nie lêer laai nie: {{error}}", - "serverErrorOccurred": "Bedienerfout het voorgekom. Probeer asseblief later weer.", - "autoSaveFailed": "Outomatiese stoor het misluk", - "fileAutoSaved": "Lêer outomaties gestoor", - "moveFileFailed": "Kon nie {{name}} skuif nie", - "moveOperationFailed": "Skuifbewerking het misluk", - "canOnlyCompareFiles": "Kan slegs twee lêers vergelyk", - "comparingFiles": "Vergelyk lêers: {{file1}} en {{file2}}", - "dragFailed": "Sleepbewerking het misluk", - "filePinnedSuccessfully": "Lêer \"{{name}}\" suksesvol vasgepen", - "pinFileFailed": "Kon nie lêer vaspen nie", - "fileUnpinnedSuccessfully": "Lêer \"{{name}}\" suksesvol ontspeld", - "unpinFileFailed": "Kon nie lêer ontspeld nie", - "shortcutAddedSuccessfully": "Vouerkortpad \"{{name}}\" suksesvol bygevoeg", - "addShortcutFailed": "Kon nie kortpad byvoeg nie", - "operationCompletedSuccessfully": "{{operation}} {{count}} items suksesvol", - "operationCompleted": "{{operation}} {{count}} items", - "downloadFileSuccess": "Lêer {{name}} suksesvol afgelaai", - "downloadFileFailed": "Aflaai het misluk", - "downloadingFile": "Laai tans af {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Skuif na {{name}}", - "diffCompareWith": "Verskil vergelyking met {{name}}", - "dragOutsideToDownload": "Sleep buite die venster om af te laai ({{count}} lêers)", - "newFolderDefault": "NuweVouer", - "newFileDefault": "NuweLêer.txt", - "successfullyMovedItems": "Suksesvol {{count}} items na {{target}} geskuif", - "move": "Beweeg", - "searchInFile": "Soek in lêer (Ctrl+F)", - "showKeyboardShortcuts": "Wys sleutelbordkortpaaie", - "decreaseFontSize": "Verklein lettergrootte", - "increaseFontSize": "Vergroot lettergrootte", - "startWritingMarkdown": "Begin om jou afslaginhoud te skryf...", - "loadingFileComparison": "Laai lêervergelyking...", - "reload": "Herlaai", - "compare": "Vergelyk", - "sideBySide": "Sy aan Sy", - "inline": "Inlyn", - "fileComparison": "Lêervergelyking: {{file1}} vs {{file2}}", - "fileTooLarge": "Lêer te groot: {{error}}", - "sshConnectionFailed": "SSH-verbinding het misluk. Kontroleer asseblief jou verbinding met {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Kon nie lêer laai nie: {{error}}", - "connecting": "Verbind...", - "connectedSuccessfully": "Suksesvol gekoppel", - "totpVerificationFailed": "TOTP-verifikasie het misluk", - "warpgateVerificationFailed": "Warpgate-verifikasie het misluk", - "authenticationFailed": "Verifikasie het misluk", - "incorrectPassphrase": "Verkeerde wagwoordfrase. Probeer asseblief weer.", - "verificationCodePrompt": "Verifikasiekode:", - "changePermissions": "Verander Toestemmings", - "currentPermissions": "Huidige Toestemmings", - "owner": "Eienaar", - "group": "Groep", - "others": "Ander", - "read": "Lees", - "write": "Skryf", - "execute": "Voer uit", - "permissionsChangedSuccessfully": "Toestemmings suksesvol verander", - "failedToChangePermissions": "Kon nie toestemmings verander nie", - "name": "Naam", - "sortByName": "Naam", - "sortByDate": "Datum Gewysig", - "sortBySize": "Grootte", - "ascending": "Stygend", - "descending": "Dalend", - "root": "Wortel", - "new": "Nuut", - "sortBy": "Sorteer volgens", - "items": "Items", - "selected": "Gekies", - "editor": "Redakteur", - "octal": "Oktaal", - "storage": "Berging", - "disk": "Skyf", - "used": "Gebruik", - "of": "van", - "toggleSidebar": "Wissel sybalk", - "compactLayout": "Gebruik kompakte uitleg", - "comfortableLayout": "Gebruik gemaklike uitleg", - "cannotLoadPdf": "Kan nie PDF laai nie", - "pdfLoadError": "Daar was 'n fout tydens die laai van hierdie PDF-lêer.", - "loadingPdf": "Laai PDF...", - "loadingPage": "Laai bladsy..." - }, "transfer": { "copyToHost": "Kopieer na gasheer…", "moveToHost": "Skuif na gasheer…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Herprobeer het misluk", "transferFailedRetryHint": "Gedeeltelike data is op die bestemming gehou. Herprobeer sal hervat word wanneer die verbinding terug is." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Dra lêers oor tussen twee SSH-gashere.", - "sourceServer": "Bronbediener", - "destinationServer": "Bestemmingsbediener", - "selectHost": "Kies gasheer", - "noHostSelected": "Geen gasheer gekies nie", - "move": "Beweeg", - "copy": "Kopieer", - "upload": "Oplaai", - "itemsSelected": "{{count}} item gekies", - "itemsSelected_other": "{{count}} items gekies", - "ready": "Gereed", - "loadingHosts": "Laai gashere...", - "name": "Naam", - "modified": "Gewysig", - "size": "Grootte", - "noFiles": "Geen lêers nie", - "open": "Oop", - "up": "Op", - "refresh": "Verfris", - "copyToTarget": "Kopieer na teikengids", - "rename": "Hernoem", - "delete": "Vee uit", - "editPermissions": "Wysig Toestemmings", - "createNewFolder": "Skep Nuwe Vouer", - "deleteSelectedItems": "Vee geselekteerde items uit", - "deleteConfirm": "Vee {{count}} gekose item uit?", - "deleteConfirm_other": "Vee {{count}} gekose items uit?", - "cancel": "Kanselleer", - "save": "Stoor", - "folderCreated": "Vouer geskep", - "itemRenamed": "Item hernoem", - "actionFailed": "Aksie het misluk", - "itemsDeleted": "{{count}} item is verwyder", - "itemsDeleted_other": "{{count}} items is uitgevee", - "deleteFailed": "Verwydering het misluk", - "permissionsUpdated": "Toestemmings opgedateer", - "noRemoteSessionConnected": "Geen afstandsessie gekoppel nie", - "failedToListFiles": "Kon nie lêers lys nie", - "failedToRefresh": "Kon nie herlaai nie", - "destinationInsideSource": "Bestemming kan nie binne die gekose bronpad wees nie.", - "sameHostNoOp": "Bron en bestemming is dieselfde lêergids." - }, "tunnels": { "noSshTunnels": "Geen SSH-tonnels nie", "createFirstTunnelMessage": "Jy het nog geen SSH-tonnels geskep nie. Konfigureer tonnelverbindings in die Gasheerbestuurder om te begin.", diff --git a/src/ui/locales/translated/ar_SA.json b/src/ui/locales/translated/ar_SA.json index 2698da22d..37d720154 100644 --- a/src/ui/locales/translated/ar_SA.json +++ b/src/ui/locales/translated/ar_SA.json @@ -715,7 +715,6 @@ "hostManager": "مدير المضيفين", "credentials": "بيانات الاعتماد", "portForwarding": "إعادة التوجيه", - "sftp": "SFTP", "connections": "الاتصالات", "serial": "تسلسلي", "keepSideMenuOpen": "أبقِ قائمة الطعام مفتوحة", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "الأنفاق", - "tabFiles": "الملفات", "tabStats": "مقاييس المضيف", "tabTelnet": "Telnet", "tabSharing": "المشاركة", "tabAuthentication": "المصادقة", "terminal": "الطرفية", "tunnel": "النفق", - "fileManager": "مدير الملفات", "serverStats": "مقاييس المضيف", "status": { "available": "متاح", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "تم تخطي {{count}} (لم يتم العثور على عنوان IP)", "proxmoxStatsUrlCopied": "تم نسخ رابط إحصائيات Proxmox إلى الحافظة", "copyProxmoxStatsUrlAction": "انسخ رابط إحصائيات Proxmox", - "enableFileManagerMonitor": "تمكين مدير الملفات", - "enableFileManagerMonitorDesc": "استعراض وإدارة الملفات على هذا المضيف عبر SFTP", - "scpLegacyLabel": "وضع SCP القديم", - "scpLegacyDesc": "استخدم وضع النقل القديم للملفات للخوادم التي لا تدعم النظام الفرعي SFTP (مثل خوادم SSH المدمجة أو ذات الإمكانيات المحدودة).", - "defaultPathLabel": "المسار الافتراضي", - "fileManagerPathHint": "الدليل الذي سيتم فتحه عند تشغيل مدير الملفات لهذا المضيف.", "statusChecksLabel": "فحوصات الحالة", "enableStatusChecks": "تمكين فحوصات الحالة", "enableStatusChecksDesc": "إرسال ping دوريًا لهذا المضيف للتحقق من توفره", @@ -1278,7 +1269,6 @@ "deletedHost": "تم حذف {{name}}", "copiedToClipboard": "تم النسخ إلى الحافظة", "terminalUrlCopied": "تم نسخ رابط الطرفية", - "fileManagerUrlCopied": "تم نسخ رابط مدير الملفات", "tunnelUrlCopied": "تم نسخ رابط النفق", "dockerUrlCopied": "تم نسخ رابط Docker", "hostMetricsUrlCopied": "تم نسخ رابط مقاييس المضيف", @@ -1296,7 +1286,6 @@ "copyAddress": "نسخ العنوان", "copyLink": "نسخ الرابط", "copyTerminalUrlAction": "نسخ رابط الطرفية", - "copyFileManagerUrlAction": "نسخ رابط مدير الملفات", "copyTunnelUrlAction": "نسخ رابط النفق", "copyDockerUrlAction": "نسخ رابط Docker", "copyHostMetricsUrlAction": "نسخ رابط مقاييس المضيف", @@ -1857,336 +1846,6 @@ "removeFromSplit": "إزالة من التقسيم" } }, - "fileManager": { - "noHostSelected": "لم يتم اختيار مضيف", - "initializingEditor": "جارٍ تهيئة المحرر...", - "file": "ملف", - "folder": "مجلد", - "uploadFile": "رفع ملف", - "downloadFile": "تنزيل", - "extractArchive": "استخراج الأرشيف", - "extractingArchive": "جارٍ استخراج {{name}}...", - "archiveExtractedSuccessfully": "تم استخراج {{name}} بنجاح", - "extractFailed": "فشل الاستخراج", - "compressFile": "ضغط ملف", - "compressFiles": "ضغط ملفات", - "compressFilesDesc": "ضغط {{count}} عنصر في أرشيف", - "archiveName": "اسم الأرشيف", - "enterArchiveName": "أدخل اسم الأرشيف...", - "compressionFormat": "صيغة الضغط", - "selectedFiles": "الملفات المحددة", - "andMoreFiles": "و {{count}} المزيد...", - "compress": "ضغط", - "compressingFiles": "جارٍ ضغط {{count}} عنصر في {{name}}...", - "filesCompressedSuccessfully": "تم إنشاء {{name}} بنجاح", - "compressFailed": "فشل الضغط", - "edit": "تحرير", - "preview": "معاينة", - "previous": "السابق", - "next": "التالي", - "pageXOfY": "صفحة {{current}} من {{total}}", - "zoomOut": "تصغير", - "zoomIn": "تكبير", - "newFile": "ملف جديد", - "newFolder": "مجلد جديد", - "rename": "إعادة تسمية", - "uploading": "جارٍ الرفع...", - "uploadingFile": "جارٍ رفع {{name}}...", - "fileName": "اسم الملف", - "folderName": "اسم المجلد", - "fileUploadedSuccessfully": "تم رفع الملف \"{{name}}\" بنجاح", - "failedToUploadFile": "فشل رفع الملف", - "uploadingFolderFiles": "جارٍ تحميل {{count}} ملف(ملفات)...", - "uploadedFolderFiles": "تم تحميل {{count}} ملف بنجاح", - "uploadedFolderPartial": "تم تحميل {{uploaded}} ملفًا، {{failed}} فشل", - "fileDownloadedSuccessfully": "تم تنزيل الملف \"{{name}}\" بنجاح", - "failedToDownloadFile": "فشل تنزيل الملف", - "fileCreatedSuccessfully": "تم إنشاء الملف \"{{name}}\" بنجاح", - "folderCreatedSuccessfully": "تم إنشاء المجلد \"{{name}}\" بنجاح", - "failedToCreateItem": "فشل إنشاء العنصر", - "operationFailed": "فشلت عملية {{operation}} لـ {{name}}: {{error}}", - "failedToResolveSymlink": "فشل حل الرابط الرمزي", - "itemsDeletedSuccessfully": "تم حذف {{count}} عناصر بنجاح", - "failedToDeleteItems": "فشل حذف العناصر", - "sudoPasswordRequired": "كلمة مرور المسؤول مطلوبة", - "enterSudoPassword": "أدخل كلمة مرور sudo لمتابعة هذه العملية", - "sudoPassword": "كلمة مرور sudo", - "sudoOperationFailed": "فشلت عملية sudo", - "sudoAuthFailed": "فشلت مصادقة sudo", - "dragFilesToUpload": "أفلت الملفات هنا لرفعها", - "dropToUploadHere": "قم بإسقاط الملف هنا للتحميل", - "dropToDownloadHere": "قم بالتنزيل من هنا", - "localFiles": "محلي", - "showLocalFiles": "عرض الملفات المحلية (عرض مقسم)", - "hideLocalFiles": "إخفاء الملفات المحلية", - "localHome": "المجلد الرئيسي", - "localPathPlaceholder": "مسار المجلد المحلي", - "localShowHidden": "إظهار الملفات المخفية", - "localHideHidden": "إخفاء الملفات المخفية", - "localRevealInFileManager": "إظهار في مدير ملفات النظام", - "localCannotOpenFolder": "لا يمكن فتح المجلد", - "localItemCount": "{{count}} عناصر", - "localSelectedCount": "{{count}} تم الاختيار", - "back": "خلف", - "forward": "إلى الأمام", - "up": "أعلى", - "kind": "عطوف", - "localUploadingProgress": "جارٍ تحميل {{current}} من {{total}}", - "localDownloadingProgress": "جارٍ تنزيل {{current}} من {{total}}", - "localTransferInFlight": "{{count}} دفعة واحدة", - "localPreparingDownload": "جارٍ تحضير التنزيل...", - "localTransferCancelled": "تم إلغاء التحويل", - "localNothingToTransfer": "لا يوجد شيء لنقله", - "localUploadComplete": "تم تحميل {{count}} ملف(ملفات)", - "localUploadFailed": "فشل التحميل", - "localUploadPartial": "تم تحميل {{done}} ملفًا، {{failed}} فشل", - "localDownloadComplete": "تم تنزيل {{count}} ملفًا(ملفات)", - "localDownloadFailed": "فشل التنزيل", - "localDownloadPartial": "تم تنزيل {{done}} ملفًا، {{failed}} فشل", - "localReplaceExistingPrompt": "{{count}} العنصر (العناصر) موجودة بالفعل في هذا المجلد. هل تريد استبدالها؟", - "localReplace": "يستبدل", - "localSkipExisting": "يتخطى", - "localSkippedExisting": "تم تخطي {{count}} عنصر (عناصر) موجودة", - "localUnsafeNamesSkipped": "تم تخطي {{count}} عنصرًا لا يمكن استخدام أسمائها على هذا الكمبيوتر", - "localOpen": "يفتح", - "localOpenFolder": "افتح المجلد", - "localUploadToRemote": "التحميل إلى الخادم", - "localUploadToRemoteMany": "قم بتحميل {{count}} عنصرًا إلى الخادم", - "localMoveToTrash": "انقل إلى سلة المهملات", - "localMoveToTrashMany": "انقل {{count}} عنصرًا إلى سلة المهملات", - "localTrashConfirmSingle": "هل تريد نقل \"{{name}}\" إلى سلة المهملات؟", - "localTrashConfirmFolder": "هل تريد نقل المجلد \"{{name}}\" وجميع محتوياته إلى سلة المهملات؟", - "localTrashConfirmMany": "هل تريد نقل العناصر {{count}} إلى سلة المهملات؟", - "localTrashed": "تم نقل {{count}} عنصرًا إلى سلة المهملات", - "localTrashFailed": "لم يتمكن من الانتقال إلى سلة المهملات", - "localRenameFailed": "فشلت عملية إعادة التسمية", - "localCreateFailed": "تعذر إنشاء العنصر", - "goToParentFolder": "انتقل إلى المجلد الرئيسي", - "resizeSidebar": "اسحب لتغيير حجم الشريط الجانبي (انقر نقراً مزدوجاً لإعادة التعيين)", - "resizeLocalPane": "اسحب لتغيير حجم اللوحة المحلية (انقر نقراً مزدوجاً لإعادة التعيين)", - "columns": "الأعمدة", - "columnsHint": "انقر بزر الماوس الأيمن لاختيار الأعمدة", - "emptyFolder": "هذا المجلد فارغ", - "searchFiles": "بحث في الملفات...", - "upload": "رفع", - "selectHostToStart": "حدد مضيفًا لبدء إدارة الملفات", - "sshRequiredForFileManager": "يتطلب مدير الملفات SSH. هذا المضيف لم يتم تمكين SSH عليه.", - "failedToConnect": "فشل الاتصال بـ SSH", - "failedToLoadDirectory": "فشل تحميل المجلد", - "noSSHConnection": "لا يوجد اتصال SSH متاح", - "copy": "نسخ", - "cut": "قص", - "paste": "لصق", - "copyPath": "نسخ المسار", - "copyPaths": "نسخ المسارات", - "delete": "حذف", - "properties": "خصائص", - "refresh": "تحديث", - "downloadFiles": "تنزيل {{count}} ملفات إلى المتصفح", - "copyFiles": "نسخ {{count}} عناصر", - "cutFiles": "قص {{count}} عناصر", - "deleteFiles": "حذف {{count}} عناصر", - "filesCopiedToClipboard": "تم نسخ {{count}} عناصر إلى الحافظة", - "filesCutToClipboard": "تم قص {{count}} عناصر إلى الحافظة", - "pathCopiedToClipboard": "تم نسخ المسار إلى الحافظة", - "pathsCopiedToClipboard": "تم نسخ {{count}} مسارات إلى الحافظة", - "failedToCopyPath": "فشل نسخ المسار إلى الحافظة", - "copyFolderLink": "نسخ رابط المجلد", - "copyCurrentFolderLink": "نسخ رابط المجلد الحالي", - "folderLinkCopied": "تم نسخ رابط المجلد إلى الحافظة", - "failedToCopyFolderLink": "فشل نسخ رابط المجلد", - "movedItems": "تم نقل {{count}} عناصر", - "failedToDeleteItem": "فشل حذف العنصر", - "itemRenamedSuccessfully": "تمت إعادة تسمية {{type}} بنجاح", - "failedToRenameItem": "فشلت إعادة تسمية العنصر", - "download": "تنزيل", - "openExternalEditor": "فتح خارجيًا", - "chooseExternalEditor": "اختيار محرر خارجي", - "externalEditorSelected": "تم اختيار المحرر الخارجي", - "externalEditorDesktopOnly": "المحرر الخارجي متاح فقط في تطبيق سطح المكتب", - "externalEditorOpened": "تم الفتح في المحرر الخارجي. سيؤدي الحفظ هناك إلى رفع التغييرات إلى الخادم.", - "failedToOpenExternalEditor": "فشل فتح المحرر الخارجي", - "failedToSelectExternalEditor": "فشل اختيار المحرر الخارجي", - "permissions": "الصلاحيات", - "size": "الحجم", - "modified": "تاريخ التعديل", - "path": "المسار", - "confirmDelete": "هل أنت متأكد أنك تريد حذف {{name}}؟", - "permissionDenied": "تم رفض الإذن", - "serverError": "خطأ في الخادم", - "fileSavedSuccessfully": "تم حفظ الملف بنجاح", - "failedToSaveFile": "فشل حفظ الملف", - "confirmDeleteSingleItem": "هل أنت متأكد أنك تريد حذف \"{{name}}\" نهائيًا؟", - "confirmDeleteMultipleItems": "هل أنت متأكد أنك تريد حذف {{count}} عناصر نهائيًا؟", - "confirmDeleteMultipleItemsWithFolders": "هل أنت متأكد أنك تريد حذف {{count}} عناصر نهائيًا؟ يتضمن ذلك المجلدات ومحتوياتها.", - "confirmDeleteFolder": "هل أنت متأكد أنك تريد حذف المجلد \"{{name}}\" وجميع محتوياته نهائيًا؟", - "permanentDeleteWarning": "لا يمكن التراجع عن هذا الإجراء. سيتم حذف العنصر (العناصر) نهائيًا من الخادم.", - "moveToTrashWarning": "سيتم نقل العنصر (العناصر) إلى سلة مهملات Termix ويمكن استعادته قبل انتهاء فترة الاحتفاظ.", - "itemsMovedToTrash": "تم نقل العنصر (العناصر) إلى سلة المهملات {{count}}", - "trash": "نفاية", - "trashDescription": "يتم الاحتفاظ بالعناصر المحذوفة لمدة {{days}} يوم (أيام) على هذا المضيف.", - "trashLoadFailed": "فشل تحميل سلة المهملات", - "trashEmpty": "سلة المهملات فارغة", - "trashRestore": "يعيد", - "trashRestored": "تمت الاستعادة {{name}}", - "trashRestoreFailed": "فشلت عملية استعادة العنصر. قد يكون المسار الأصلي موجودًا بالفعل.", - "trashDeletePermanently": "حذف نهائياً", - "trashDeleteConfirm": "حذف {{name}}نهائياً؟ لا يمكن التراجع عن هذا الإجراء.", - "trashPermanentlyDeleted": "تم الحذف نهائياً {{name}}", - "trashDeleteFailed": "فشل حذف العنصر نهائياً", - "trashEmptyAction": "قم بإفراغ سلة المهملات", - "trashEmptyConfirm": "هل تريد حذف جميع العناصر الموجودة في سلة المهملات نهائياً؟ لا يمكن التراجع عن هذا الإجراء.", - "trashEmptied": "تم إفراغ سلة المهملات", - "trashRetention": "التنظيف التلقائي بعد (أيام)", - "trashRetentionSaved": "تحديث نظام الاحتفاظ بالنفايات", - "trashRetentionFailed": "فشل تحديث الاحتفاظ بالنفايات", - "trashUnavailableConfirm": "لا يمكن نقل هذا العنصر إلى سلة المهملات، عادةً لأنه موجود على نظام ملفات آخر أو يتطلب صلاحيات إدارية. هل تريد حذفه نهائياً بدلاً من ذلك؟", - "recent": "الأخيرة", - "pinned": "المثبتة", - "folderShortcuts": "اختصارات المجلدات", - "failedToReconnectSSH": "فشل إعادة الاتصال بجلسة SSH", - "openTerminalHere": "افتح الطرفية هنا", - "run": "تشغيل", - "openTerminalInFolder": "افتح الطرفية في هذا المجلد", - "openTerminalInFileLocation": "افتح الطرفية في موقع الملف", - "runningFile": "قيد التشغيل - {{file}}", - "onlyRunExecutableFiles": "يمكن تشغيل الملفات التنفيذية فقط", - "directories": "المجلدات", - "removedFromRecentFiles": "تمت إزالة \"{{name}}\" من الملفات الحديثة", - "removeFailed": "فشلت الإزالة", - "unpinnedSuccessfully": "تم إلغاء تثبيت \"{{name}}\" بنجاح", - "unpinFailed": "فشل إلغاء التثبيت", - "removedShortcut": "تمت إزالة الاختصار \"{{name}}\"", - "removeShortcutFailed": "فشلت إزالة الاختصار", - "clearedAllRecentFiles": "تم مسح جميع الملفات الحديثة", - "clearFailed": "فشل المسح", - "removeFromRecentFiles": "إزالة من الملفات الحديثة", - "clearAllRecentFiles": "مسح جميع الملفات الحديثة", - "unpinFile": "إلغاء تثبيت الملف", - "removeShortcut": "إزالة الاختصار", - "pinFile": "تثبيت الملف", - "addToShortcuts": "إضافة إلى الاختصارات", - "pasteFailed": "فشل اللصق", - "noUndoableActions": "لا توجد إجراءات يمكن التراجع عنها", - "undoCopySuccess": "تم التراجع عن عملية النسخ: حُذف {{count}} ملفًا منسوخًا", - "undoCopyFailedDelete": "فشل التراجع: تعذر حذف أي ملفات منسوخة", - "undoCopyFailedNoInfo": "فشل التراجع: تعذر العثور على معلومات الملفات المنسوخة", - "undoMoveSuccess": "تم التراجع عن عملية النقل: تم نقل {{count}} ملفًا إلى الموقع الأصلي", - "undoMoveFailedMove": "فشل التراجع: تعذر نقل أي ملفات للخلف", - "undoMoveFailedNoInfo": "فشل التراجع: تعذر العثور على معلومات الملفات المنقولة", - "undoDeleteNotSupported": "لا يمكن التراجع عن عملية الحذف: تم حذف الملفات نهائيًا من الخادم", - "undoTypeNotSupported": "نوع عملية التراجع غير مدعوم", - "undoOperationFailed": "فشلت عملية التراجع", - "unknownError": "خطأ غير معروف", - "confirm": "تأكيد", - "find": "بحث...", - "replace": "استبدال", - "downloadInstead": "تنزيل بدلاً من ذلك", - "keyboardShortcuts": "اختصارات لوحة المفاتيح", - "searchAndReplace": "بحث واستبدال", - "editing": "تعديل", - "search": "بحث", - "findNext": "بحث عن التالي", - "findPrevious": "بحث عن السابق", - "save": "حفظ", - "selectAll": "تحديد الكل", - "undo": "تراجع", - "redo": "إعادة", - "moveLineUp": "نقل السطر لأعلى", - "moveLineDown": "نقل السطر لأسفل", - "toggleComment": "تبديل التعليق", - "autoComplete": "إكمال تلقائي", - "imageLoadError": "فشل تحميل الصورة", - "startTyping": "ابدأ الكتابة...", - "unknownSize": "حجم غير معروف", - "fileIsEmpty": "الملف فارغ", - "largeFileWarning": "تحذير ملف كبير", - "largeFileWarningDesc": "هذا الملف بحجم {{size}}، مما قد يسبب مشكلات في الأداء عند فتحه كنص.", - "fileNotFoundAndRemoved": "الملف \"{{name}}\" غير موجود وتمت إزالته من الملفات الأخيرة/المثبتة", - "failedToLoadFile": "فشل تحميل الملف: {{error}}", - "serverErrorOccurred": "حدث خطأ في الخادم. يرجى المحاولة لاحقًا.", - "autoSaveFailed": "فشل الحفظ التلقائي", - "fileAutoSaved": "تم حفظ الملف تلقائيًا", - "moveFileFailed": "فشل نقل {{name}}", - "moveOperationFailed": "فشلت عملية النقل", - "canOnlyCompareFiles": "يمكن مقارنة ملفين فقط", - "comparingFiles": "مقارنة الملفات: {{file1}} و {{file2}}", - "dragFailed": "فشلت عملية السحب", - "filePinnedSuccessfully": "تم تثبيت الملف \"{{name}}\" بنجاح", - "pinFileFailed": "فشل تثبيت الملف", - "fileUnpinnedSuccessfully": "تم إلغاء تثبيت الملف \"{{name}}\" بنجاح", - "unpinFileFailed": "فشل إلغاء تثبيت الملف", - "shortcutAddedSuccessfully": "تمت إضافة اختصار المجلد \"{{name}}\" بنجاح", - "addShortcutFailed": "فشلت إضافة الاختصار", - "operationCompletedSuccessfully": "تمت {{operation}} {{count}} عناصر بنجاح", - "operationCompleted": "{{operation}} {{count}} عناصر", - "downloadFileSuccess": "تم تنزيل الملف {{name}} بنجاح", - "downloadFileFailed": "فشل التنزيل", - "downloadingFile": "جارٍ التنزيل {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "نقل إلى {{name}}", - "diffCompareWith": "مقارنة الاختلافات مع {{name}}", - "dragOutsideToDownload": "اسحب خارج النافذة للتنزيل ({{count}} ملفات)", - "newFolderDefault": "مجلد جديد", - "newFileDefault": "ملف جديد.txt", - "successfullyMovedItems": "تم نقل {{count}} عناصر بنجاح إلى {{target}}", - "move": "نقل", - "searchInFile": "بحث في الملف (Ctrl+F)", - "showKeyboardShortcuts": "إظهار اختصارات لوحة المفاتيح", - "decreaseFontSize": "تصغير حجم الخط", - "increaseFontSize": "تكبير حجم الخط", - "startWritingMarkdown": "ابدأ كتابة محتوى Markdown...", - "loadingFileComparison": "جارٍ تحميل مقارنة الملفات...", - "reload": "إعادة تحميل", - "compare": "مقارنة", - "sideBySide": "جنباً إلى جنب", - "inline": "مضمن", - "fileComparison": "مقارنة الملفات: {{file1}} مقابل {{file2}}", - "fileTooLarge": "الملف كبير جداً: {{error}}", - "sshConnectionFailed": "فشل اتصال SSH. يرجى التحقق من الاتصال بـ {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "فشل تحميل الملف: {{error}}", - "connecting": "جارٍ الاتصال...", - "connectedSuccessfully": "تم الاتصال بنجاح", - "totpVerificationFailed": "فشل التحقق من TOTP", - "warpgateVerificationFailed": "فشلت مصادقة Warpgate", - "authenticationFailed": "فشلت المصادقة", - "incorrectPassphrase": "عبارة المرور غير صحيحة. يرجى المحاولة مرة أخرى.", - "verificationCodePrompt": "رمز التحقق:", - "changePermissions": "تغيير الصلاحيات", - "currentPermissions": "الصلاحيات الحالية", - "owner": "المالك", - "group": "المجموعة", - "others": "الآخرون", - "read": "قراءة", - "write": "كتابة", - "execute": "تنفيذ", - "permissionsChangedSuccessfully": "تم تغيير الصلاحيات بنجاح", - "failedToChangePermissions": "فشل تغيير الصلاحيات", - "name": "الاسم", - "sortByName": "الاسم", - "sortByDate": "تاريخ التعديل", - "sortBySize": "الحجم", - "ascending": "تصاعدي", - "descending": "تنازلي", - "root": "الجذر", - "new": "جديد", - "sortBy": "ترتيب حسب", - "items": "عناصر", - "selected": "محدد", - "editor": "محرر", - "octal": "ثماني", - "storage": "التخزين", - "disk": "القرص", - "used": "مستخدم", - "of": "من", - "toggleSidebar": "إظهار/إخفاء الشريط الجانبي", - "compactLayout": "استخدم تصميمًا مضغوطًا", - "comfortableLayout": "استخدم تصميمًا مريحًا", - "cannotLoadPdf": "لا يمكن تحميل PDF", - "pdfLoadError": "حدث خطأ أثناء تحميل ملف PDF هذا.", - "loadingPdf": "جارٍ تحميل PDF...", - "loadingPage": "جارٍ تحميل الصفحة..." - }, "transfer": { "copyToHost": "نسخ إلى المضيف…", "moveToHost": "نقل إلى المضيف…", @@ -2292,50 +1951,6 @@ "retryTransferError": "فشلت إعادة المحاولة", "transferFailedRetryHint": "تم الاحتفاظ بالبيانات الجزئية على الوجهة. ستُستأنف إعادة المحاولة عند عودة الاتصال." }, - "sftpTransfer": { - "title": "SFTP", - "description": "نقل الملفات بين مضيفين عبر بروتوكول SSH.", - "sourceServer": "خادم المصدر", - "destinationServer": "خادم الوجهة", - "selectHost": "حدد المضيف", - "noHostSelected": "لم يتم تحديد أي مضيف", - "move": "يتحرك", - "copy": "ينسخ", - "upload": "رفع", - "itemsSelected": "تم تحديد العنصر {{count}}", - "itemsSelected_other": "تم تحديد {{count}} عنصر", - "ready": "مستعد", - "loadingHosts": "جارٍ تحميل المضيفين...", - "name": "اسم", - "modified": "معدل", - "size": "مقاس", - "noFiles": "لا توجد ملفات", - "open": "يفتح", - "up": "أعلى", - "refresh": "ينعش", - "copyToTarget": "انسخ إلى الدليل الهدف", - "rename": "إعادة تسمية", - "delete": "يمسح", - "editPermissions": "تعديل الأذونات", - "createNewFolder": "إنشاء مجلد جديد", - "deleteSelectedItems": "حذف العناصر المحددة", - "deleteConfirm": "حذف العنصر المحدد {{count}}؟", - "deleteConfirm_other": "حذف {{count}} من العناصر المحددة؟", - "cancel": "يلغي", - "save": "يحفظ", - "folderCreated": "تم إنشاء المجلد", - "itemRenamed": "تمت إعادة تسمية العنصر", - "actionFailed": "فشلت العملية", - "itemsDeleted": "تم حذف العنصر {{count}}", - "itemsDeleted_other": "تم حذف {{count}} عنصر", - "deleteFailed": "فشلت عملية الحذف", - "permissionsUpdated": "تم تحديث الأذونات", - "noRemoteSessionConnected": "لم يتم الاتصال بأي جلسة عن بعد", - "failedToListFiles": "فشل في عرض قائمة الملفات", - "failedToRefresh": "فشل التحديث", - "destinationInsideSource": "لا يمكن أن تكون الوجهة داخل مسار المصدر المحدد.", - "sameHostNoOp": "المصدر والوجهة هما نفس المجلد." - }, "tunnels": { "noSshTunnels": "لا توجد أنفاق SSH", "createFirstTunnelMessage": "لم تُنشئ أي أنفاق SSH بعد. قم بتكوين اتصالات الأنفاق في مدير المضيف للبدء.", diff --git a/src/ui/locales/translated/bg_BG.json b/src/ui/locales/translated/bg_BG.json index 4d7bda24e..142d5a000 100644 --- a/src/ui/locales/translated/bg_BG.json +++ b/src/ui/locales/translated/bg_BG.json @@ -715,7 +715,6 @@ "hostManager": "Мениджър на домакини", "credentials": "Пълномощия", "portForwarding": "Препращане", - "sftp": "SFTP", "connections": "Връзки", "serial": "Сериен", "keepSideMenuOpen": "Запази менюто отворено", @@ -767,14 +766,12 @@ "tabRdp": "ПРСР", "tabVnc": "VNC", "tabTunnels": "Тунели", - "tabFiles": "Файлове", "tabStats": "Метрики на хоста", "tabTelnet": "Телнет", "tabSharing": "Споделяне", "tabAuthentication": "Удостоверяване", "terminal": "Терминал", "tunnel": "Тунел", - "fileManager": "Файлов мениджър", "serverStats": "Метрики на хоста", "status": { "available": "Налично", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} пропуснато (не е намерен IP адрес)", "proxmoxStatsUrlCopied": "URL адресът на статистиката на Proxmox е копиран в клипборда", "copyProxmoxStatsUrlAction": "Копиране на URL адреса на статистиката на Proxmox", - "enableFileManagerMonitor": "Активиране на файловия мениджър", - "enableFileManagerMonitorDesc": "Разглеждайте и управлявайте файлове на този хост през SFTP", - "scpLegacyLabel": "SCP Legacy режим", - "scpLegacyDesc": "Използвайте наследствен трансфер на файлове за сървъри, които не поддържат SFTP подсистемата (напр. вградени или минимални SSH сървъри).", - "defaultPathLabel": "Път по подразбиране", - "fileManagerPathHint": "Директорията, която да се отвори при стартиране на файловия мениджър за този хост.", "statusChecksLabel": "Проверки на състоянието", "enableStatusChecks": "Активиране на проверки на състоянието", "enableStatusChecksDesc": "Периодично проверявайте наличността на този хост чрез ping.", @@ -1278,7 +1269,6 @@ "deletedHost": "Изтрито {{name}}", "copiedToClipboard": "Копирано в буферната памет", "terminalUrlCopied": "URL адресът на терминала е копиран", - "fileManagerUrlCopied": "URL адресът на файловия мениджър е копиран", "tunnelUrlCopied": "URL адресът на тунела е копиран", "dockerUrlCopied": "URL адресът на Docker е копиран", "hostMetricsUrlCopied": "URL адресът на показателите за хоста е копиран", @@ -1296,7 +1286,6 @@ "copyAddress": "Копиране на адрес", "copyLink": "Копиране на връзката", "copyTerminalUrlAction": "Копиране на URL адреса на терминала", - "copyFileManagerUrlAction": "Копиране на URL адреса на файловия мениджър", "copyTunnelUrlAction": "Копиране на URL адреса на тунела", "copyDockerUrlAction": "Копиране на URL адреса на Docker", "copyHostMetricsUrlAction": "Копиране на URL адреса на показателите за хоста", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Премахване от Сплит" } }, - "fileManager": { - "noHostSelected": "Няма избран хост", - "initializingEditor": "Инициализиране на редактора...", - "file": "Файл", - "folder": "Папка", - "uploadFile": "Качване на файл", - "downloadFile": "Изтегляне", - "extractArchive": "Извличане на архив", - "extractingArchive": "Извличане на {{name}}...", - "archiveExtractedSuccessfully": "{{name}} е извлечено успешно", - "extractFailed": "Извличането не бе успешно", - "compressFile": "Компресиране на файл", - "compressFiles": "Компресиране на файлове", - "compressFilesDesc": "Компресиране на {{count}} елементи в архив", - "archiveName": "Име на архива", - "enterArchiveName": "Въведете име на архива...", - "compressionFormat": "Формат на компресия", - "selectedFiles": "Избрани файлове", - "andMoreFiles": "и още {{count}}...", - "compress": "Компресиране", - "compressingFiles": "Компресиране на {{count}} елементи в {{name}}...", - "filesCompressedSuccessfully": "{{name}} е създадено успешно", - "compressFailed": "Компресията не беше успешна", - "edit": "Редактиране", - "preview": "Преглед", - "previous": "Предишен", - "next": "Следващо", - "pageXOfY": "Страница {{current}} от {{total}}", - "zoomOut": "Намаляване на мащаба", - "zoomIn": "Увеличаване на мащаба", - "newFile": "Нов файл", - "newFolder": "Нова папка", - "rename": "Преименуване", - "uploading": "Качване...", - "uploadingFile": "Качване {{name}}...", - "fileName": "Име на файл", - "folderName": "Име на папката", - "fileUploadedSuccessfully": "Файлът „{{name}}“ е качен успешно", - "failedToUploadFile": "Качването на файла не бе успешно", - "uploadingFolderFiles": "Качване на {{count}} файл(а)...", - "uploadedFolderFiles": "Качени {{count}} файла(а) успешно", - "uploadedFolderPartial": "Качени {{uploaded}} файла(а), {{failed}} неуспешно", - "fileDownloadedSuccessfully": "Файлът „{{name}}“ е изтеглен успешно", - "failedToDownloadFile": "Изтеглянето на файла не бе успешно", - "fileCreatedSuccessfully": "Файлът „{{name}}“ е създаден успешно", - "folderCreatedSuccessfully": "Папка „{{name}}“ е създадена успешно", - "failedToCreateItem": "Създаването на елемент не бе успешно", - "operationFailed": "Операцията {{operation}} е неуспешна за {{name}}: {{error}}", - "failedToResolveSymlink": "Неуспешно разрешаване на символната връзка", - "itemsDeletedSuccessfully": "{{count}} елементи са изтрити успешно", - "failedToDeleteItems": "Изтриването на елементи не бе успешно", - "sudoPasswordRequired": "Изисква се администраторска парола", - "enterSudoPassword": "Въведете паролата за sudo, за да продължите тази операция", - "sudoPassword": "Парола за Sudo", - "sudoOperationFailed": "Sudo операцията не беше успешна", - "sudoAuthFailed": "Sudo удостоверяването не беше успешно", - "dragFilesToUpload": "Пуснете файлове тук, за да ги качите", - "dropToUploadHere": "Пуснете, за да качите тук", - "dropToDownloadHere": "Пуснете, за да изтеглите тук", - "localFiles": "Местно", - "showLocalFiles": "Показване на локални файлове (разделен изглед)", - "hideLocalFiles": "Скриване на локални файлове", - "localHome": "Начална папка", - "localPathPlaceholder": "Път към локалната папка", - "localShowHidden": "Показване на скритите файлове", - "localHideHidden": "Скриване на скрити файлове", - "localRevealInFileManager": "Разкриване в системния файлов мениджър", - "localCannotOpenFolder": "Не може да се отвори папка", - "localItemCount": "{{count}} артикули", - "localSelectedCount": "{{count}} избрано", - "back": "Обратно", - "forward": "Напред", - "up": "Нагоре", - "kind": "Вид", - "localUploadingProgress": "Качване на {{current}} от {{total}}", - "localDownloadingProgress": "Изтегляне на {{current}} от {{total}}", - "localTransferInFlight": "{{count}} наведнъж", - "localPreparingDownload": "Подготовка за изтегляне...", - "localTransferCancelled": "Прехвърлянето е анулирано", - "localNothingToTransfer": "Няма нищо за прехвърляне", - "localUploadComplete": "Качени {{count}} файла(а)", - "localUploadFailed": "Качването не бе успешно", - "localUploadPartial": "Качени {{done}} файла(а), {{failed}} неуспешно", - "localDownloadComplete": "Изтеглени {{count}} файлове", - "localDownloadFailed": "Изтеглянето не бе успешно", - "localDownloadPartial": "Изтеглени {{done}} файлове, {{failed}} неуспешно", - "localReplaceExistingPrompt": "{{count}} елемент(а) вече съществуват в тази папка. Да се заменят ли?", - "localReplace": "Замяна", - "localSkipExisting": "Пропускане", - "localSkippedExisting": "Пропуснати {{count}} съществуващи елементи", - "localUnsafeNamesSkipped": "Пропуснати {{count}} елементи, чиито имена не могат да се използват на този компютър", - "localOpen": "Отворено", - "localOpenFolder": "Отвори папка", - "localUploadToRemote": "Качване на сървъра", - "localUploadToRemoteMany": "Качване на {{count}} елементи на сървъра", - "localMoveToTrash": "Премести в кошчето", - "localMoveToTrashMany": "Преместване на {{count}} елемента в кошчето", - "localTrashConfirmSingle": "Да се премести ли „{{name}}“ в кошчето?", - "localTrashConfirmFolder": "Да се премести ли папката „{{name}}“ и цялото ѝ съдържание в кошчето?", - "localTrashConfirmMany": "Да се преместят ли {{count}} елементи в кошчето?", - "localTrashed": "Преместени {{count}} елемент(а) в кошчето", - "localTrashFailed": "Не можа да се премести в кошчето", - "localRenameFailed": "Преименуването не бе успешно", - "localCreateFailed": "Не можа да се създаде елемент", - "goToParentFolder": "Отиди до родителската папка", - "resizeSidebar": "Плъзнете, за да промените размера на страничната лента (щракнете двукратно за нулиране)", - "resizeLocalPane": "Плъзнете, за да промените размера на локалния панел (щракнете двукратно за нулиране)", - "columns": "Колони", - "columnsHint": "Щракнете с десния бутон, за да изберете колони", - "emptyFolder": "Тази папка е празна", - "searchFiles": "Търсене на файлове...", - "upload": "Качване", - "selectHostToStart": "Изберете хост, за да започнете управлението на файлове", - "sshRequiredForFileManager": "Файловият мениджър изисква SSH. Този хост не е активиран за SSH.", - "failedToConnect": "Неуспешно свързване към SSH", - "failedToLoadDirectory": "Неуспешно зареждане на директорията", - "noSSHConnection": "Няма налична SSH връзка", - "copy": "Копиране", - "cut": "Изрязване", - "paste": "Паста", - "copyPath": "Копиране на пътя", - "copyPaths": "Копиране на пътища", - "delete": "Изтриване", - "properties": "Имоти", - "refresh": "Обновяване", - "downloadFiles": "Изтегляне на {{count}} файлове в браузъра", - "copyFiles": "Копиране на {{count}} елементи", - "cutFiles": "Изрязване на {{count}} елементи", - "deleteFiles": "Изтриване на {{count}} елементи", - "filesCopiedToClipboard": "{{count}} елементи са копирани в клипборда", - "filesCutToClipboard": "{{count}} елементи, изрязани в клипборда", - "pathCopiedToClipboard": "Пътят е копиран в клипборда", - "pathsCopiedToClipboard": "{{count}} пътища, копирани в клипборда", - "failedToCopyPath": "Копирането на пътя в клипборда не бе успешно", - "copyFolderLink": "Копиране на връзката към папката", - "copyCurrentFolderLink": "Копиране на връзката към текущата папка", - "folderLinkCopied": "Връзката към папката е копирана в клипборда", - "failedToCopyFolderLink": "Копирането на връзката към папката не бе успешно", - "movedItems": "Преместени {{count}} елементи", - "failedToDeleteItem": "Изтриването на елемента не бе успешно", - "itemRenamedSuccessfully": "{{type}} преименуван успешно", - "failedToRenameItem": "Преименуването на елемента не бе успешно", - "download": "Изтегляне", - "openExternalEditor": "Отворено външно", - "chooseExternalEditor": "Изберете външен редактор", - "externalEditorSelected": "Избран външен редактор", - "externalEditorDesktopOnly": "Външният редактор е наличен само в настолното приложение", - "externalEditorOpened": "Отворено във външен редактор. Запазването там ще качи промените обратно на сървъра.", - "failedToOpenExternalEditor": "Неуспешно отваряне на външен редактор", - "failedToSelectExternalEditor": "Избирането на външен редактор не бе успешно", - "permissions": "Разрешения", - "size": "Размер", - "modified": "Модифицирано", - "path": "Път", - "confirmDelete": "Сигурни ли сте, че искате да изтриете {{name}}?", - "permissionDenied": "Разрешението е отказано", - "serverError": "Грешка на сървъра", - "fileSavedSuccessfully": "Файлът е запазен успешно", - "failedToSaveFile": "Запазването на файла не бе успешно", - "confirmDeleteSingleItem": "Сигурни ли сте, че искате да изтриете за постоянно „{{name}}“?", - "confirmDeleteMultipleItems": "Сигурни ли сте, че искате да изтриете за постоянно {{count}} елементи?", - "confirmDeleteMultipleItemsWithFolders": "Наистина ли искате да изтриете за постоянно {{count}} елементи? Това включва папки и тяхното съдържание.", - "confirmDeleteFolder": "Сигурни ли сте, че искате да изтриете окончателно папката „{{name}}“ и цялото ѝ съдържание?", - "permanentDeleteWarning": "Това действие не може да бъде отменено. Елементът(ите) ще бъдат изтрити за постоянно от сървъра.", - "moveToTrashWarning": "Елементът(ите) ще бъдат преместени в кошчето на Termix и могат да бъдат възстановени преди изтичане на срока на съхранение.", - "itemsMovedToTrash": "{{count}} елемент(а) са преместени в кошчето", - "trash": "Кошче", - "trashDescription": "Изтритите елементи се запазват за {{days}} дни на този хост.", - "trashLoadFailed": "Зареждането на кошчето не бе успешно", - "trashEmpty": "Кошчето е празно", - "trashRestore": "Възстановяване", - "trashRestored": "Възстановено {{name}}", - "trashRestoreFailed": "Възстановяването на елемента не бе успешно. Възможно е оригиналният път вече да съществува.", - "trashDeletePermanently": "Изтрий за постоянно", - "trashDeleteConfirm": "Да се изтрие ли за постоянно {{name}}? Това не може да се отмени.", - "trashPermanentlyDeleted": "За постоянно изтрито {{name}}", - "trashDeleteFailed": "Неуспешно трайно изтриване на елемента", - "trashEmptyAction": "Изпразване на кошчето", - "trashEmptyConfirm": "Да се изтрие ли за постоянно всеки елемент в кошчето? Това не може да се отмени.", - "trashEmptied": "Кошчето е изпразнено", - "trashRetention": "Автоматично почистване след (дни)", - "trashRetentionSaved": "Запазването на кошчето е актуализирано", - "trashRetentionFailed": "Актуализирането на задържането на кошчето не бе успешно", - "trashUnavailableConfirm": "Този елемент не може да бъде преместен в кошчето, обикновено защото е на друга файлова система или изисква повишени права. Искате ли да го изтриете за постоянно вместо това?", - "recent": "Скорошни", - "pinned": "Закачено", - "folderShortcuts": "Преки пътища към папки", - "failedToReconnectSSH": "Неуспешно повторно свързване на SSH сесията", - "openTerminalHere": "Отворете терминала тук", - "run": "Бягане", - "openTerminalInFolder": "Отвори терминала в тази папка", - "openTerminalInFileLocation": "Отваряне на терминала на местоположението на файла", - "runningFile": "Бягане - {{file}}", - "onlyRunExecutableFiles": "Може да изпълнява само изпълними файлове", - "directories": "Директории", - "removedFromRecentFiles": "Премахнато е „{{name}}“ от последните файлове", - "removeFailed": "Премахването не бе успешно", - "unpinnedSuccessfully": "Откачено е успешно „{{name}}“", - "unpinFailed": "Откачването не бе успешно", - "removedShortcut": "Премахнат е пряк път „{{name}}“", - "removeShortcutFailed": "Премахването на пряк път не бе успешно", - "clearedAllRecentFiles": "Изчистени са всички скорошни файлове", - "clearFailed": "Изчистването не бе успешно", - "removeFromRecentFiles": "Премахване от скорошни файлове", - "clearAllRecentFiles": "Изчистване на всички скорошни файлове", - "unpinFile": "Откачи файл", - "removeShortcut": "Премахване на пряк път", - "pinFile": "Закачи файл", - "addToShortcuts": "Добавяне към преките пътища", - "pasteFailed": "Поставянето не бе успешно", - "noUndoableActions": "Няма отменими действия", - "undoCopySuccess": "Отменено копиране: Изтрити {{count}} копирани файлове", - "undoCopyFailedDelete": "Отмяната не бе успешна: Не можаха да бъдат изтрити копирани файлове", - "undoCopyFailedNoInfo": "Отмяната не бе успешна: Не можа да бъде намерена информация за копирания файл", - "undoMoveSuccess": "Отменена операция по преместване: {{count}} файлове са преместени обратно в оригиналното местоположение", - "undoMoveFailedMove": "Отмяната не бе успешна: Не можах да преместя файловете обратно", - "undoMoveFailedNoInfo": "Отмяната не бе успешна: Не можа да бъде намерена информация за преместения файл", - "undoDeleteNotSupported": "Операцията по изтриване не може да бъде отменена: Файловете са изтрити за постоянно от сървъра", - "undoTypeNotSupported": "Неподдържан тип операция за отмяна", - "undoOperationFailed": "Отмяната не бе успешна", - "unknownError": "Неизвестна грешка", - "confirm": "Потвърди", - "find": "Намерете...", - "replace": "Замяна", - "downloadInstead": "Изтеглете вместо това", - "keyboardShortcuts": "Клавишни комбинации", - "searchAndReplace": "Търсене и замяна", - "editing": "Редактиране", - "search": "Търсене", - "findNext": "Намери следващото", - "findPrevious": "Намери предишното", - "save": "Запазване", - "selectAll": "Избери всички", - "undo": "Отмяна", - "redo": "Преработи", - "moveLineUp": "Премести линията нагоре", - "moveLineDown": "Премести реда надолу", - "toggleComment": "Превключване на коментара", - "autoComplete": "Автоматично завършване", - "imageLoadError": "Зареждането на изображението не бе успешно", - "startTyping": "Започнете да пишете...", - "unknownSize": "Неизвестен размер", - "fileIsEmpty": "Файлът е празен", - "largeFileWarning": "Предупреждение за голям файл", - "largeFileWarningDesc": "Размерът на този файл е {{size}} , което може да причини проблеми с производителността, когато се отвори като текст.", - "fileNotFoundAndRemoved": "Файлът „{{name}}“ не е намерен и е премахнат от скорошни/закачени файлове", - "failedToLoadFile": "Неуспешно зареждане на файл: {{error}}", - "serverErrorOccurred": "Възникна грешка в сървъра. Моля, опитайте отново по-късно.", - "autoSaveFailed": "Автоматичното запазване не бе успешно", - "fileAutoSaved": "Файлът е автоматично запазен", - "moveFileFailed": "Преместването не бе успешно {{name}}", - "moveOperationFailed": "Операцията по преместване не бе успешна", - "canOnlyCompareFiles": "Може да се сравняват само два файла", - "comparingFiles": "Сравняване на файлове: {{file1}} и {{file2}}", - "dragFailed": "Плъзгането не беше успешно", - "filePinnedSuccessfully": "Файлът „{{name}}“ е успешно закачен", - "pinFileFailed": "Закрепването на файла не бе успешно", - "fileUnpinnedSuccessfully": "Файлът „{{name}}“ е откачен успешно", - "unpinFileFailed": "Откачването на файла не бе успешно", - "shortcutAddedSuccessfully": "Прякият път към папка „{{name}}“ е добавен успешно", - "addShortcutFailed": "Добавянето на пряк път не бе успешно", - "operationCompletedSuccessfully": "{{operation}} {{count}} елементи успешно", - "operationCompleted": "{{operation}} {{count}} артикули", - "downloadFileSuccess": "Файлът {{name}} е изтеглен успешно", - "downloadFileFailed": "Изтеглянето не бе успешно", - "downloadingFile": "Изтегляне {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Премести се в {{name}}", - "diffCompareWith": "Разлика в сравнение с {{name}}", - "dragOutsideToDownload": "Плъзнете извън прозореца, за да изтеглите ({{count}} файлове)", - "newFolderDefault": "НоваПапка", - "newFileDefault": "NewFile.txt", - "successfullyMovedItems": "Успешно преместени {{count}} елементи в {{target}}", - "move": "Преместване", - "searchInFile": "Търсене във файл (Ctrl+F)", - "showKeyboardShortcuts": "Показване на клавишни комбинации", - "decreaseFontSize": "Намаляване на размера на шрифта", - "increaseFontSize": "Увеличаване на размера на шрифта", - "startWritingMarkdown": "Започнете да пишете съдържанието си за markdown...", - "loadingFileComparison": "Зареждане на сравнението на файлове...", - "reload": "Презареждане", - "compare": "Сравни", - "sideBySide": "Рамо до рамо", - "inline": "Вграден", - "fileComparison": "Сравнение на файлове: {{file1}} срещу {{file2}}", - "fileTooLarge": "Файлът е твърде голям: {{error}}", - "sshConnectionFailed": "SSH връзката не бе успешна. Моля, проверете връзката си с {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Неуспешно зареждане на файл: {{error}}", - "connecting": "Свързване...", - "connectedSuccessfully": "Свързаването е осъществено успешно", - "totpVerificationFailed": "Проверката на TOTP не бе успешна", - "warpgateVerificationFailed": "Удостоверяването на Warpgate не беше успешно", - "authenticationFailed": "Удостоверяването не бе успешно", - "incorrectPassphrase": "Неправилна парола. Моля, опитайте отново.", - "verificationCodePrompt": "Код за потвърждение:", - "changePermissions": "Промяна на разрешенията", - "currentPermissions": "Текущи разрешения", - "owner": "Собственик", - "group": "Група", - "others": "Други", - "read": "Прочетете", - "write": "Пишете", - "execute": "Изпълнение", - "permissionsChangedSuccessfully": "Разрешенията са променени успешно", - "failedToChangePermissions": "Промяната на разрешенията не бе успешна", - "name": "Име", - "sortByName": "Име", - "sortByDate": "Дата на промяна", - "sortBySize": "Размер", - "ascending": "Възходящ", - "descending": "Низходящо", - "root": "Корен", - "new": "Ново", - "sortBy": "Сортиране по", - "items": "Елементи", - "selected": "Избрано", - "editor": "Редактор", - "octal": "Осмичен", - "storage": "Съхранение", - "disk": "Диск", - "used": "Използван", - "of": "от", - "toggleSidebar": "Превключване на страничната лента", - "compactLayout": "Използвайте компактно оформление", - "comfortableLayout": "Използвайте удобно оформление", - "cannotLoadPdf": "Не може да се зареди PDF файл", - "pdfLoadError": "Възникна грешка при зареждането на този PDF файл.", - "loadingPdf": "Зареждане на PDF файл...", - "loadingPage": "Зареждане на страницата..." - }, "transfer": { "copyToHost": "Копиране към хост…", "moveToHost": "Преместване към хост…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Повторният опит не бе успешен", "transferFailedRetryHint": "В местоназначението са запазени частични данни. Повторният опит ще се възобнови, когато връзката се възстанови." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Прехвърляне на файлове между два SSH хоста.", - "sourceServer": "Източник на сървъра", - "destinationServer": "Целеви сървър", - "selectHost": "Изберете хост", - "noHostSelected": "Няма избран хост", - "move": "Преместване", - "copy": "Копиране", - "upload": "Качване", - "itemsSelected": "{{count}} избран елемент", - "itemsSelected_other": "{{count}} избрани елементи", - "ready": "Готов", - "loadingHosts": "Зареждане на хостове...", - "name": "Име", - "modified": "Модифицирано", - "size": "Размер", - "noFiles": "Няма файлове", - "open": "Отворено", - "up": "Нагоре", - "refresh": "Опресняване", - "copyToTarget": "Копиране в целевата директория", - "rename": "Преименуване", - "delete": "Изтриване", - "editPermissions": "Редактиране на разрешения", - "createNewFolder": "Създаване на нова папка", - "deleteSelectedItems": "Изтриване на избраните елементи", - "deleteConfirm": "Да се изтрие ли {{count}} избран елемент?", - "deleteConfirm_other": "Да се изтрият ли {{count}} избрани елементи?", - "cancel": "Отказ", - "save": "Запазване", - "folderCreated": "Папката е създадена", - "itemRenamed": "Елементът е преименуван", - "actionFailed": "Действието е неуспешно", - "itemsDeleted": "{{count}} елементът е изтрит", - "itemsDeleted_other": "{{count}} елементи са изтрити", - "deleteFailed": "Изтриването не бе успешно", - "permissionsUpdated": "Разрешенията са актуализирани", - "noRemoteSessionConnected": "Няма свързана отдалечена сесия", - "failedToListFiles": "Неуспешно изброяване на файловете", - "failedToRefresh": "Опресняването не бе успешно", - "destinationInsideSource": "Дестинацията не може да бъде в избрания път на източника.", - "sameHostNoOp": "Източникът и местоназначението са една и съща папка." - }, "tunnels": { "noSshTunnels": "Няма SSH тунели", "createFirstTunnelMessage": "Все още не сте създали SSH тунели. Конфигурирайте тунелни връзки в Host Manager, за да започнете.", diff --git a/src/ui/locales/translated/bn_BD.json b/src/ui/locales/translated/bn_BD.json index 10e3f16c7..18add8d43 100644 --- a/src/ui/locales/translated/bn_BD.json +++ b/src/ui/locales/translated/bn_BD.json @@ -715,7 +715,6 @@ "hostManager": "হোস্ট ম্যানেজার", "credentials": "যোগ্যতা", "portForwarding": "ফরোয়ার্ডিং", - "sftp": "এসএফটিপি", "connections": "সংযোগ", "serial": "সিরিয়াল", "keepSideMenuOpen": "মেনু খোলা রাখুন", @@ -767,14 +766,12 @@ "tabRdp": "আরডিপি", "tabVnc": "ভিএনসি", "tabTunnels": "টানেল", - "tabFiles": "ফাইল", "tabStats": "হোস্ট মেট্রিক্স", "tabTelnet": "টেলনেট", "tabSharing": "শেয়ারিং", "tabAuthentication": "প্রমাণীকরণ", "terminal": "টার্মিনাল", "tunnel": "টানেল", - "fileManager": "ফাইল ম্যানেজার", "serverStats": "হোস্ট মেট্রিক্স", "status": { "available": "উপলব্ধ", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} বাদ দেওয়া হয়েছে (কোন আইপি পাওয়া যায়নি)", "proxmoxStatsUrlCopied": "Proxmox Stats URL ক্লিপবোর্ডে কপি করা হয়েছে", "copyProxmoxStatsUrlAction": "Proxmox Stats URL কপি করুন", - "enableFileManagerMonitor": "ফাইল ম্যানেজার সক্রিয় করুন", - "enableFileManagerMonitorDesc": "SFTP-এর মাধ্যমে এই হোস্টের ফাইলগুলি ব্রাউজ ও পরিচালনা করুন।", - "scpLegacyLabel": "SCP লিগ্যাসি মোড", - "scpLegacyDesc": "যেসব সার্ভার SFTP সাবসিস্টেম সমর্থন করে না (যেমন এমবেডেড বা মিনিমাল SSH সার্ভার), সেগুলোর জন্য লিগ্যাসি ফাইল ট্রান্সফার ব্যবহার করুন।", - "defaultPathLabel": "ডিফল্ট পথ", - "fileManagerPathHint": "এই হোস্টের জন্য ফাইল ম্যানেজার চালু হলে যে ডিরেক্টরিটি খুলবে।", "statusChecksLabel": "অবস্থা যাচাই", "enableStatusChecks": "স্ট্যাটাস চেক সক্ষম করুন", "enableStatusChecksDesc": "প্রাপ্যতা যাচাই করতে পর্যায়ক্রমে এই হোস্টটিকে পিং করুন।", @@ -1278,7 +1269,6 @@ "deletedHost": "মুছে ফেলা হয়েছে {{name}}", "copiedToClipboard": "ক্লিপবোর্ডে কপি করা হয়েছে", "terminalUrlCopied": "টার্মিনাল ইউআরএল কপি করা হয়েছে", - "fileManagerUrlCopied": "ফাইল ম্যানেজার ইউআরএল কপি করা হয়েছে", "tunnelUrlCopied": "টানেল ইউআরএল কপি করা হয়েছে", "dockerUrlCopied": "ডকার ইউআরএল কপি করা হয়েছে", "hostMetricsUrlCopied": "হোস্ট মেট্রিক্স ইউআরএল কপি করা হয়েছে", @@ -1296,7 +1286,6 @@ "copyAddress": "ঠিকানা কপি করুন", "copyLink": "লিঙ্ক কপি করুন", "copyTerminalUrlAction": "টার্মিনাল ইউআরএল কপি করুন", - "copyFileManagerUrlAction": "ফাইল ম্যানেজার ইউআরএল কপি করুন", "copyTunnelUrlAction": "টানেল ইউআরএল কপি করুন", "copyDockerUrlAction": "ডকার ইউআরএল কপি করুন", "copyHostMetricsUrlAction": "হোস্ট মেট্রিক্স ইউআরএল কপি করুন", @@ -1857,336 +1846,6 @@ "removeFromSplit": "স্প্লিট থেকে সরান" } }, - "fileManager": { - "noHostSelected": "কোন হোস্ট নির্বাচন করা হয়নি", - "initializingEditor": "এডিটর চালু করা হচ্ছে...", - "file": "ফাইল", - "folder": "ফোল্ডার", - "uploadFile": "ফাইল আপলোড করুন", - "downloadFile": "ডাউনলোড করুন", - "extractArchive": "আর্কাইভ নিষ্কাশন করুন", - "extractingArchive": "{{name}} নিষ্কাশন করা হচ্ছে ...", - "archiveExtractedSuccessfully": "{{name}} সফলভাবে নিষ্কাশন করা হয়েছে", - "extractFailed": "নিষ্কাশন ব্যর্থ হয়েছে", - "compressFile": "ফাইল সংকুচিত করুন", - "compressFiles": "ফাইলগুলি সংকুচিত করুন", - "compressFilesDesc": "{{count}} টি আইটেমকে আর্কাইভে সংকুচিত করুন", - "archiveName": "আর্কাইভের নাম", - "enterArchiveName": "আর্কাইভের নাম লিখুন...", - "compressionFormat": "কম্প্রেশন ফরম্যাট", - "selectedFiles": "নির্বাচিত ফাইল", - "andMoreFiles": "এবং {{count}} আরও...", - "compress": "সংকুচিত করুন", - "compressingFiles": "{{count}} টি আইটেমকে {{name}} টি আইটেমে সংকুচিত করা হচ্ছে...", - "filesCompressedSuccessfully": "{{name}} সফলভাবে তৈরি হয়েছে", - "compressFailed": "সংকোচন ব্যর্থ হয়েছে", - "edit": "সম্পাদনা", - "preview": "প্রিভিউ", - "previous": "পূর্ববর্তী", - "next": "পরবর্তী", - "pageXOfY": "{{current}} এর {{total}} পৃষ্ঠা", - "zoomOut": "জুম আউট", - "zoomIn": "জুম ইন", - "newFile": "নতুন ফাইল", - "newFolder": "নতুন ফোল্ডার", - "rename": "পুনঃনামকরণ", - "uploading": "আপলোড হচ্ছে...", - "uploadingFile": "{{name}} আপলোড হচ্ছে ...", - "fileName": "ফাইলের নাম", - "folderName": "ফোল্ডারের নাম", - "fileUploadedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে আপলোড হয়েছে", - "failedToUploadFile": "ফাইল আপলোড করতে ব্যর্থ হয়েছে", - "uploadingFolderFiles": "{{count}} টি ফাইল আপলোড করা হচ্ছে...", - "uploadedFolderFiles": "{{count}} টি ফাইল সফলভাবে আপলোড করা হয়েছে", - "uploadedFolderPartial": "{{uploaded}} টি ফাইল আপলোড করা হয়েছে, {{failed}} টি ফাইল আপলোড ব্যর্থ হয়েছে", - "fileDownloadedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে ডাউনলোড হয়েছে", - "failedToDownloadFile": "ফাইল ডাউনলোড করতে ব্যর্থ হয়েছে", - "fileCreatedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে তৈরি হয়েছে", - "folderCreatedSuccessfully": "\"{{name}}\" ফোল্ডারটি সফলভাবে তৈরি করা হয়েছে", - "failedToCreateItem": "আইটেম তৈরি করতে ব্যর্থ হয়েছে", - "operationFailed": "{{operation}} অপারেশনটি {{name}}এর জন্য ব্যর্থ হয়েছে: {{error}}", - "failedToResolveSymlink": "সিমলিঙ্ক সমাধান করতে ব্যর্থ হয়েছে", - "itemsDeletedSuccessfully": "{{count}} আইটেম সফলভাবে মুছে ফেলা হয়েছে", - "failedToDeleteItems": "আইটেমগুলি মুছে ফেলতে ব্যর্থ হয়েছে", - "sudoPasswordRequired": "প্রশাসক পাসওয়ার্ড আবশ্যক", - "enterSudoPassword": "এই অপারেশনটি চালিয়ে যেতে sudo পাসওয়ার্ড দিন।", - "sudoPassword": "সুডো পাসওয়ার্ড", - "sudoOperationFailed": "Sudo অপারেশন ব্যর্থ হয়েছে", - "sudoAuthFailed": "Sudo প্রমাণীকরণ ব্যর্থ হয়েছে", - "dragFilesToUpload": "আপলোড করার জন্য ফাইলগুলো এখানে রাখুন।", - "dropToUploadHere": "এখানে আপলোড করতে ড্রপ করুন", - "dropToDownloadHere": "ডাউনলোড করতে এখানে ক্লিক করুন", - "localFiles": "স্থানীয়", - "showLocalFiles": "স্থানীয় ফাইলগুলি দেখান (বিভক্ত দৃশ্য)", - "hideLocalFiles": "স্থানীয় ফাইলগুলি লুকান", - "localHome": "হোম ফোল্ডার", - "localPathPlaceholder": "স্থানীয় ফোল্ডার পাথ", - "localShowHidden": "লুকানো ফাইলগুলি দেখান", - "localHideHidden": "লুকানো ফাইলগুলি লুকান", - "localRevealInFileManager": "সিস্টেম ফাইল ম্যানেজারে প্রকাশ করুন", - "localCannotOpenFolder": "ফোল্ডার খোলা যাচ্ছে না", - "localItemCount": "{{count}} আইটেম", - "localSelectedCount": "{{count}} নির্বাচিত", - "back": "ফিরে যান", - "forward": "ফরোয়ার্ড", - "up": "উপরে", - "kind": "ধরনের", - "localUploadingProgress": "{{total}} এর {{current}} আপলোড করা হচ্ছে", - "localDownloadingProgress": "{{total}} এর {{current}} ডাউনলোড হচ্ছে", - "localTransferInFlight": "{{count}} একবারে", - "localPreparingDownload": "ডাউনলোড প্রস্তুত হচ্ছে...", - "localTransferCancelled": "স্থানান্তর বাতিল করা হয়েছে", - "localNothingToTransfer": "স্থানান্তর করার কিছু নেই", - "localUploadComplete": "{{count}} টি ফাইল আপলোড করা হয়েছে", - "localUploadFailed": "আপলোড ব্যর্থ হয়েছে", - "localUploadPartial": "{{done}} টি ফাইল আপলোড করা হয়েছে, {{failed}} টি ফাইল আপলোড ব্যর্থ হয়েছে", - "localDownloadComplete": "{{count}} টি ফাইল ডাউনলোড করা হয়েছে", - "localDownloadFailed": "ডাউনলোড ব্যর্থ হয়েছে", - "localDownloadPartial": "{{done}} টি ফাইল ডাউনলোড করা হয়েছে, {{failed}} টি ফাইল ডাউনলোড ব্যর্থ হয়েছে", - "localReplaceExistingPrompt": "এই ফোল্ডারে {{count}} টি আইটেম ইতিমধ্যেই বিদ্যমান। এগুলো প্রতিস্থাপন করবেন?", - "localReplace": "প্রতিস্থাপন করুন", - "localSkipExisting": "এড়িয়ে যান", - "localSkippedExisting": "{{count}} টি বিদ্যমান আইটেম বাদ দেওয়া হয়েছে", - "localUnsafeNamesSkipped": "{{count}} টি আইটেম বাদ দেওয়া হয়েছে, যেগুলোর নাম এই কম্পিউটারে ব্যবহার করা যাবে না।", - "localOpen": "খোলা", - "localOpenFolder": "ফোল্ডার খুলুন", - "localUploadToRemote": "সার্ভারে আপলোড করুন", - "localUploadToRemoteMany": "সার্ভারে {{count}} টি আইটেম আপলোড করুন", - "localMoveToTrash": "ট্র্যাশে সরান", - "localMoveToTrashMany": "{{count}} টি আইটেম ট্র্যাশে সরান", - "localTrashConfirmSingle": "\"{{name}}\" ট্র্যাশে সরাবেন?", - "localTrashConfirmFolder": "\"{{name}}\" ফোল্ডারটি এবং এর সমস্ত বিষয়বস্তু ট্র্যাশে সরান?", - "localTrashConfirmMany": "{{count}} টি আইটেম ট্র্যাশে সরাবেন?", - "localTrashed": "ট্র্যাশে {{count}} টি আইটেম সরানো হয়েছে", - "localTrashFailed": "ট্র্যাশে সরানো যায়নি", - "localRenameFailed": "নাম পরিবর্তন ব্যর্থ হয়েছে", - "localCreateFailed": "আইটেমটি তৈরি করা যায়নি", - "goToParentFolder": "প্যারেন্ট ফোল্ডারে যান", - "resizeSidebar": "সাইডবারের আকার পরিবর্তন করতে ড্র্যাগ করুন (রিসেট করতে ডাবল-ক্লিক করুন)", - "resizeLocalPane": "স্থানীয় প্যানেলের আকার পরিবর্তন করতে ড্র্যাগ করুন (রিসেট করতে ডাবল-ক্লিক করুন)", - "columns": "কলাম", - "columnsHint": "কলাম বাছাই করতে ডান-ক্লিক করুন", - "emptyFolder": "এই ফোল্ডারটি খালি", - "searchFiles": "ফাইলগুলি অনুসন্ধান করুন...", - "upload": "আপলোড", - "selectHostToStart": "ফাইল ব্যবস্থাপনা শুরু করতে একটি হোস্ট নির্বাচন করুন", - "sshRequiredForFileManager": "ফাইল ম্যানেজারের জন্য SSH প্রয়োজন। এই হোস্টে SSH সক্রিয় করা নেই।", - "failedToConnect": "SSH-এর সাথে সংযোগ করতে ব্যর্থ হয়েছে", - "failedToLoadDirectory": "ডিরেক্টরি লোড করতে ব্যর্থ হয়েছে", - "noSSHConnection": "কোন SSH সংযোগ উপলব্ধ নেই", - "copy": "অনুলিপি", - "cut": "কাটা", - "paste": "পেস্ট", - "copyPath": "কপি পাথ", - "copyPaths": "কপি পাথ", - "delete": "মুছে ফেলুন", - "properties": "বৈশিষ্ট্য", - "refresh": "রিফ্রেশ", - "downloadFiles": "ব্রাউজারে {{count}} ফাইলগুলো ডাউনলোড করুন", - "copyFiles": "{{count}} আইটেম কপি করুন", - "cutFiles": "{{count}} টি আইটেম কাটুন", - "deleteFiles": "{{count}} টি আইটেম মুছুন", - "filesCopiedToClipboard": "{{count}} আইটেম ক্লিপবোর্ডে কপি করা হয়েছে", - "filesCutToClipboard": "{{count}} আইটেমগুলো ক্লিপবোর্ডে কাটা হয়েছে", - "pathCopiedToClipboard": "পাথটি ক্লিপবোর্ডে কপি করা হয়েছে", - "pathsCopiedToClipboard": "{{count}} পাথগুলো ক্লিপবোর্ডে কপি করা হয়েছে", - "failedToCopyPath": "ক্লিপবোর্ডে পাথ কপি করতে ব্যর্থ হয়েছে", - "copyFolderLink": "ফোল্ডারে লিঙ্ক কপি করুন", - "copyCurrentFolderLink": "বর্তমান ফোল্ডারে লিঙ্ক কপি করুন", - "folderLinkCopied": "ফোল্ডারের লিঙ্ক ক্লিপবোর্ডে কপি করা হয়েছে", - "failedToCopyFolderLink": "ফোল্ডার লিঙ্ক কপি করতে ব্যর্থ হয়েছে", - "movedItems": "{{count}} টি আইটেম সরানো হয়েছে", - "failedToDeleteItem": "আইটেমটি মুছে ফেলতে ব্যর্থ হয়েছে", - "itemRenamedSuccessfully": "{{type}} সফলভাবে নাম পরিবর্তন করা হয়েছে", - "failedToRenameItem": "আইটেমের নাম পরিবর্তন করতে ব্যর্থ হয়েছে", - "download": "ডাউনলোড করুন", - "openExternalEditor": "বাইরে খুলুন", - "chooseExternalEditor": "বাহ্যিক সম্পাদক নির্বাচন করুন", - "externalEditorSelected": "বাহ্যিক সম্পাদক নির্বাচিত", - "externalEditorDesktopOnly": "এক্সটার্নাল এডিটর শুধুমাত্র ডেস্কটপ অ্যাপে উপলব্ধ।", - "externalEditorOpened": "বাহ্যিক এডিটরে খোলা হয়েছে। সেখানে সেভ করলে পরিবর্তনগুলো সার্ভারে আপলোড হয়ে যাবে।", - "failedToOpenExternalEditor": "বাহ্যিক সম্পাদক খুলতে ব্যর্থ হয়েছে", - "failedToSelectExternalEditor": "বাহ্যিক সম্পাদক নির্বাচন করতে ব্যর্থ হয়েছে", - "permissions": "অনুমতি", - "size": "আকার", - "modified": "পরিবর্তিত", - "path": "পথ", - "confirmDelete": "আপনি কি {{name}} মুছে ফেলতে নিশ্চিত?", - "permissionDenied": "অনুমতি প্রত্যাখ্যান করা হয়েছে", - "serverError": "সার্ভার ত্রুটি", - "fileSavedSuccessfully": "ফাইল সফলভাবে সংরক্ষিত হয়েছে।", - "failedToSaveFile": "ফাইল সংরক্ষণ করতে ব্যর্থ হয়েছে", - "confirmDeleteSingleItem": "আপনি কি \"{{name}} \" স্থায়ীভাবে মুছে ফেলতে চান?", - "confirmDeleteMultipleItems": "আপনি কি {{count}} টি আইটেম স্থায়ীভাবে মুছে ফেলতে চান?", - "confirmDeleteMultipleItemsWithFolders": "আপনি কি {{count}} টি আইটেম স্থায়ীভাবে মুছে ফেলতে নিশ্চিত? এর মধ্যে ফোল্ডার এবং তার ভেতরের সবকিছু অন্তর্ভুক্ত।", - "confirmDeleteFolder": "আপনি কি \"{{name}}\" ফোল্ডারটি এবং এর ভেতরের সবকিছু স্থায়ীভাবে মুছে ফেলতে চান?", - "permanentDeleteWarning": "এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না। আইটেম(গুলি) সার্ভার থেকে স্থায়ীভাবে মুছে ফেলা হবে।", - "moveToTrashWarning": "আইটেমটি টারমিক্স ট্র্যাশে স্থানান্তরিত হবে এবং সংরক্ষণের সময়সীমা শেষ হওয়ার আগে তা পুনরুদ্ধার করা যাবে।", - "itemsMovedToTrash": "{{count}} আইটেম(গুলি) ট্র্যাশে সরানো হয়েছে", - "trash": "আবর্জনা", - "trashDescription": "এই হোস্টে মুছে ফেলা আইটেমগুলি {{days}} দিন পর্যন্ত সংরক্ষিত থাকে।", - "trashLoadFailed": "ট্র্যাশ লোড করতে ব্যর্থ হয়েছে", - "trashEmpty": "ময়লার ঝুড়ি খালি", - "trashRestore": "পুনরুদ্ধার করুন", - "trashRestored": "পুনরুদ্ধার করা হয়েছে {{name}}", - "trashRestoreFailed": "আইটেমটি পুনরুদ্ধার করা সম্ভব হয়নি। মূল পাথটি হয়তো ইতিমধ্যেই বিদ্যমান আছে।", - "trashDeletePermanently": "স্থায়ীভাবে মুছে ফেলুন", - "trashDeleteConfirm": "{{name}}স্থায়ীভাবে মুছে ফেলবেন? এটি আর পূর্বাবস্থায় ফেরানো যাবে না।", - "trashPermanentlyDeleted": "স্থায়ীভাবে মুছে ফেলা হয়েছে {{name}}", - "trashDeleteFailed": "আইটেমটি স্থায়ীভাবে মুছে ফেলতে ব্যর্থ হয়েছে", - "trashEmptyAction": "ময়লা খালি করুন", - "trashEmptyConfirm": "ট্র্যাশের প্রতিটি আইটেম স্থায়ীভাবে মুছে ফেলবেন? এটি আর পূর্বাবস্থায় ফেরানো যাবে না।", - "trashEmptied": "ময়লা খালি করা হয়েছে", - "trashRetention": "(দিন) পর স্বয়ংক্রিয়ভাবে পরিষ্কার করা হবে", - "trashRetentionSaved": "আবর্জনা সংরক্ষণের সময় আপডেট করা হয়েছে", - "trashRetentionFailed": "ট্র্যাশ রিটেনশন আপডেট করতে ব্যর্থ হয়েছে", - "trashUnavailableConfirm": "এই আইটেমটি ট্র্যাশে সরানো যাচ্ছে না, কারণ এটি সাধারণত অন্য ফাইলসিস্টেমে রয়েছে অথবা এর জন্য বিশেষ অনুমতির প্রয়োজন। এর পরিবর্তে এটি স্থায়ীভাবে মুছে ফেলবেন?", - "recent": "সাম্প্রতিক", - "pinned": "পিন করা", - "folderShortcuts": "ফোল্ডার শর্টকাট", - "failedToReconnectSSH": "SSH সেশন পুনরায় সংযোগ করতে ব্যর্থ হয়েছে", - "openTerminalHere": "এখানে টার্মিনাল খুলুন", - "run": "দৌড়", - "openTerminalInFolder": "এই ফোল্ডারে টার্মিনাল খুলুন", - "openTerminalInFileLocation": "ফাইল লোকেশনে টার্মিনাল খুলুন", - "runningFile": "দৌড়ানো - {{file}}", - "onlyRunExecutableFiles": "শুধুমাত্র এক্সিকিউটেবল ফাইল চালানো যাবে", - "directories": "ডিরেক্টরি", - "removedFromRecentFiles": "সাম্প্রতিক ফাইলগুলি থেকে \"{{name}}\" সরানো হয়েছে", - "removeFailed": "ব্যর্থ অপসারণ", - "unpinnedSuccessfully": "\"{{name}}\" সফলভাবে আনপিন করা হয়েছে", - "unpinFailed": "আনপিন ব্যর্থ হয়েছে", - "removedShortcut": "\"{{name}} \" শর্টকাটটি সরানো হয়েছে", - "removeShortcutFailed": "শর্টকাট অপসারণ ব্যর্থ হয়েছে", - "clearedAllRecentFiles": "সাম্প্রতিক সমস্ত ফাইল মুছে ফেলা হয়েছে", - "clearFailed": "পরিষ্কার করতে ব্যর্থ হয়েছে", - "removeFromRecentFiles": "সাম্প্রতিক ফাইলগুলি থেকে সরান", - "clearAllRecentFiles": "সাম্প্রতিক সমস্ত ফাইল মুছে ফেলুন", - "unpinFile": "ফাইলটি আনপিন করুন", - "removeShortcut": "শর্টকাট সরান", - "pinFile": "পিন ফাইল", - "addToShortcuts": "শর্টকাটে যোগ করুন", - "pasteFailed": "পেস্ট ব্যর্থ হয়েছে", - "noUndoableActions": "কোনো পূর্বাবস্থায় ফেরানো যায় না এমন কাজ নেই।", - "undoCopySuccess": "কপি অপারেশন বাতিল করা হয়েছে: {{count}} টি কপি করা ফাইল মুছে ফেলা হয়েছে", - "undoCopyFailedDelete": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: কপি করা কোনো ফাইল মুছে ফেলা সম্ভব হয়নি", - "undoCopyFailedNoInfo": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: কপি করা ফাইলের তথ্য খুঁজে পাওয়া যায়নি", - "undoMoveSuccess": "স্থানান্তর প্রক্রিয়া বাতিল করা হয়েছে: {{count}} টি ফাইল মূল অবস্থানে ফিরিয়ে আনা হয়েছে", - "undoMoveFailedMove": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: কোনো ফাইল আগের জায়গায় সরানো সম্ভব হয়নি", - "undoMoveFailedNoInfo": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে: স্থানান্তরিত ফাইলের তথ্য খুঁজে পাওয়া যায়নি", - "undoDeleteNotSupported": "মুছে ফেলার প্রক্রিয়াটি পূর্বাবস্থায় ফেরানো যাবে না: ফাইলগুলো সার্ভার থেকে স্থায়ীভাবে মুছে ফেলা হয়েছে।", - "undoTypeNotSupported": "অসমর্থিত পূর্বাবস্থায় ফেরানোর অপারেশনের ধরণ", - "undoOperationFailed": "পূর্বাবস্থায় ফেরানো ব্যর্থ হয়েছে", - "unknownError": "অজানা ত্রুটি", - "confirm": "নিশ্চিত করুন", - "find": "খুঁজুন...", - "replace": "প্রতিস্থাপন করুন", - "downloadInstead": "পরিবর্তে ডাউনলোড করুন", - "keyboardShortcuts": "কীবোর্ড শর্টকাট", - "searchAndReplace": "অনুসন্ধান ও প্রতিস্থাপন", - "editing": "সম্পাদনা", - "search": "অনুসন্ধান", - "findNext": "পরবর্তী খুঁজুন", - "findPrevious": "পূর্ববর্তী খুঁজুন", - "save": "সংরক্ষণ করুন", - "selectAll": "সব নির্বাচন করুন", - "undo": "পূর্বাবস্থায় ফেরান", - "redo": "পুনরায় করুন", - "moveLineUp": "লাইন আপ সরান", - "moveLineDown": "লাইন নিচে সরান", - "toggleComment": "মন্তব্য টগল করুন", - "autoComplete": "স্বয়ংক্রিয়ভাবে সম্পূর্ণ", - "imageLoadError": "ছবি লোড করতে ব্যর্থ হয়েছে", - "startTyping": "টাইপ করা শুরু করুন...", - "unknownSize": "অজানা আকার", - "fileIsEmpty": "ফাইলটি খালি", - "largeFileWarning": "বড় ফাইলের সতর্কতা", - "largeFileWarningDesc": "এই ফাইলটির আকার {{size}} , যা টেক্সট হিসেবে খুললে পারফরম্যান্সের সমস্যা সৃষ্টি করতে পারে।", - "fileNotFoundAndRemoved": "\"{{name}}\" ফাইলটি খুঁজে পাওয়া যায়নি এবং সাম্প্রতিক/পিন করা ফাইলগুলো থেকে সরিয়ে ফেলা হয়েছে।", - "failedToLoadFile": "ফাইল লোড করতে ব্যর্থ: {{error}}", - "serverErrorOccurred": "সার্ভার ত্রুটি ঘটেছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন।", - "autoSaveFailed": "স্বয়ংক্রিয় সংরক্ষণ ব্যর্থ হয়েছে", - "fileAutoSaved": "ফাইল স্বয়ংক্রিয়ভাবে সংরক্ষিত হয়েছে", - "moveFileFailed": "{{name}} সরাতে ব্যর্থ", - "moveOperationFailed": "স্থানান্তর প্রক্রিয়া ব্যর্থ হয়েছে", - "canOnlyCompareFiles": "শুধুমাত্র দুটি ফাইল তুলনা করা যাবে।", - "comparingFiles": "ফাইলগুলোর তুলনা: {{file1}} এবং {{file2}}", - "dragFailed": "ড্র্যাগ অপারেশন ব্যর্থ হয়েছে", - "filePinnedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে পিন করা হয়েছে", - "pinFileFailed": "ফাইল পিন করতে ব্যর্থ হয়েছে", - "fileUnpinnedSuccessfully": "ফাইল \"{{name}}\" সফলভাবে আনপিন করা হয়েছে", - "unpinFileFailed": "ফাইল আনপিন করতে ব্যর্থ হয়েছে", - "shortcutAddedSuccessfully": "ফোল্ডার শর্টকাট \"{{name}}\" সফলভাবে যোগ করা হয়েছে", - "addShortcutFailed": "শর্টকাট যোগ করতে ব্যর্থ হয়েছে", - "operationCompletedSuccessfully": "{{operation}} {{count}} আইটেম সফলভাবে", - "operationCompleted": "{{operation}} {{count}} আইটেম", - "downloadFileSuccess": "ফাইল {{name}} সফলভাবে ডাউনলোড হয়েছে", - "downloadFileFailed": "ডাউনলোড ব্যর্থ হয়েছে", - "downloadingFile": "ডাউনলোড হচ্ছে {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "{{name}} এ যান", - "diffCompareWith": "{{name}} এর সাথে পার্থক্য তুলনা করুন", - "dragOutsideToDownload": "ডাউনলোড করতে উইন্ডোর বাইরে টেনে আনুন ({{count}} ফাইলগুলো)", - "newFolderDefault": "নতুন ফোল্ডার", - "newFileDefault": "NewFile.txt", - "successfullyMovedItems": "{{count}} টি আইটেম সফলভাবে {{target}} টি আইটেমে সরানো হয়েছে", - "move": "স্থানান্তর", - "searchInFile": "ফাইলে অনুসন্ধান করুন (Ctrl+F)", - "showKeyboardShortcuts": "কিবোর্ড শর্টকাট দেখান", - "decreaseFontSize": "ফন্টের আকার কমান", - "increaseFontSize": "ফন্টের আকার বাড়ান", - "startWritingMarkdown": "আপনার মার্কডাউন কন্টেন্ট লেখা শুরু করুন...", - "loadingFileComparison": "ফাইল তুলনা লোড হচ্ছে...", - "reload": "পুনরায় লোড করুন", - "compare": "তুলনা করুন", - "sideBySide": "পাশাপাশি", - "inline": "ইনলাইন", - "fileComparison": "ফাইল তুলনা: {{file1}} বনাম {{file2}}", - "fileTooLarge": "ফাইলের আকার অনেক বড়: {{error}}", - "sshConnectionFailed": "SSH সংযোগ ব্যর্থ হয়েছে। অনুগ্রহ করে {{name}} ({{ip}}:{{port}} ) -এ আপনার সংযোগ পরীক্ষা করুন।", - "loadFileFailed": "ফাইল লোড করতে ব্যর্থ: {{error}}", - "connecting": "সংযোগ স্থাপন...", - "connectedSuccessfully": "সফলভাবে সংযুক্ত হয়েছে", - "totpVerificationFailed": "TOTP যাচাইকরণ ব্যর্থ হয়েছে", - "warpgateVerificationFailed": "ওয়ার্পগেট প্রমাণীকরণ ব্যর্থ হয়েছে", - "authenticationFailed": "প্রমাণীকরণ ব্যর্থ হয়েছে", - "incorrectPassphrase": "ভুল পাসফ্রেজ। অনুগ্রহ করে আবার চেষ্টা করুন।", - "verificationCodePrompt": "যাচাইকরণ কোড:", - "changePermissions": "অনুমতি পরিবর্তন করুন", - "currentPermissions": "বর্তমান অনুমতি", - "owner": "মালিক", - "group": "গ্রুপ", - "others": "অন্যান্য", - "read": "পড়ুন", - "write": "লিখুন", - "execute": "কার্যকর করুন", - "permissionsChangedSuccessfully": "অনুমতি সফলভাবে পরিবর্তন করা হয়েছে", - "failedToChangePermissions": "অনুমতি পরিবর্তন করতে ব্যর্থ হয়েছে", - "name": "নাম", - "sortByName": "নাম", - "sortByDate": "পরিবর্তিত তারিখ", - "sortBySize": "আকার", - "ascending": "আরোহী", - "descending": "অবতরণ", - "root": "মূল", - "new": "নতুন", - "sortBy": "অনুসারে সাজান", - "items": "আইটেম", - "selected": "নির্বাচিত", - "editor": "সম্পাদক", - "octal": "অক্টাল", - "storage": "স্টোরেজ", - "disk": "ডিস্ক", - "used": "ব্যবহৃত", - "of": "এর", - "toggleSidebar": "সাইডবার টগল করুন", - "compactLayout": "সংক্ষিপ্ত বিন্যাস ব্যবহার করুন", - "comfortableLayout": "আরামদায়ক বিন্যাস ব্যবহার করুন", - "cannotLoadPdf": "পিডিএফ লোড করা যাচ্ছে না", - "pdfLoadError": "এই পিডিএফ ফাইলটি লোড করতে ত্রুটি হয়েছে।", - "loadingPdf": "পিডিএফ লোড হচ্ছে...", - "loadingPage": "পৃষ্ঠা লোড হচ্ছে..." - }, "transfer": { "copyToHost": "হোস্টে কপি করুন…", "moveToHost": "হোস্টে যান…", @@ -2292,50 +1951,6 @@ "retryTransferError": "পুনরায় চেষ্টা ব্যর্থ হয়েছে", "transferFailedRetryHint": "গন্তব্যে আংশিক ডেটা সংরক্ষিত হয়েছে। সংযোগ ফিরে এলে পুনরায় চেষ্টা শুরু হবে।" }, - "sftpTransfer": { - "title": "এসএফটিপি", - "description": "দুটি SSH হোস্টের মধ্যে ফাইল স্থানান্তর করুন।", - "sourceServer": "উৎস সার্ভার", - "destinationServer": "গন্তব্য সার্ভার", - "selectHost": "হোস্ট নির্বাচন করুন", - "noHostSelected": "কোন হোস্ট নির্বাচন করা হয়নি", - "move": "স্থানান্তর", - "copy": "অনুলিপি", - "upload": "আপলোড", - "itemsSelected": "{{count}} আইটেম নির্বাচিত", - "itemsSelected_other": "{{count}} নির্বাচিত আইটেম", - "ready": "প্রস্তুত", - "loadingHosts": "হোস্ট লোড হচ্ছে...", - "name": "নাম", - "modified": "পরিবর্তিত", - "size": "আকার", - "noFiles": "কোন ফাইল নেই", - "open": "খোলা", - "up": "উপরে", - "refresh": "রিফ্রেশ", - "copyToTarget": "লক্ষ্য ডিরেক্টরিতে অনুলিপি করুন", - "rename": "নাম পরিবর্তন", - "delete": "মুছে ফেলুন", - "editPermissions": "সম্পাদনার অনুমতি", - "createNewFolder": "নতুন ফোল্ডার তৈরি করুন", - "deleteSelectedItems": "নির্বাচিত আইটেমগুলি মুছুন", - "deleteConfirm": "{{count}} নির্বাচিত আইটেমটি মুছবেন?", - "deleteConfirm_other": "{{count}} নির্বাচিত আইটেমগুলি মুছবেন?", - "cancel": "বাতিল করুন", - "save": "সংরক্ষণ করুন", - "folderCreated": "ফোল্ডার তৈরি করা হয়েছে", - "itemRenamed": "আইটেমের নাম পরিবর্তন করা হয়েছে", - "actionFailed": "পদক্ষেপ ব্যর্থ হয়েছে", - "itemsDeleted": "{{count}} আইটেমটি মুছে ফেলা হয়েছে", - "itemsDeleted_other": "{{count}} আইটেম মুছে ফেলা হয়েছে", - "deleteFailed": "মুছে ফেলা ব্যর্থ হয়েছে", - "permissionsUpdated": "অনুমতি আপডেট করা হয়েছে", - "noRemoteSessionConnected": "কোন রিমোট সেশন সংযুক্ত নেই", - "failedToListFiles": "ফাইলগুলি তালিকাভুক্ত করতে ব্যর্থ হয়েছে", - "failedToRefresh": "রিফ্রেশ করতে ব্যর্থ হয়েছে", - "destinationInsideSource": "গন্তব্য নির্বাচিত উৎস পথের মধ্যে থাকতে পারবে না।", - "sameHostNoOp": "উৎস এবং গন্তব্য একই ফোল্ডার।" - }, "tunnels": { "noSshTunnels": "কোন SSH টানেল নেই", "createFirstTunnelMessage": "আপনি এখনও কোনো SSH টানেল তৈরি করেননি। শুরু করার জন্য হোস্ট ম্যানেজারে টানেল সংযোগগুলি কনফিগার করুন।", diff --git a/src/ui/locales/translated/ca_ES.json b/src/ui/locales/translated/ca_ES.json index 766b116f8..fc9f90103 100644 --- a/src/ui/locales/translated/ca_ES.json +++ b/src/ui/locales/translated/ca_ES.json @@ -715,7 +715,6 @@ "hostManager": "Gestor d'amfitrions", "credentials": "Credencials", "portForwarding": "Reenviament", - "sftp": "SFTP", "connections": "Connexions", "serial": "Sèrie", "keepSideMenuOpen": "Mantenir el menú obert", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Túnels", - "tabFiles": "Fitxers", "tabStats": "Mètriques de l'amfitrió", "tabTelnet": "Telnet", "tabSharing": "Compartir", "tabAuthentication": "Autenticació", "terminal": "Terminal", "tunnel": "Túnel", - "fileManager": "Gestor de fitxers", "serverStats": "Mètriques de l'amfitrió", "status": { "available": "Disponible", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} omès (no s'ha trobat cap IP)", "proxmoxStatsUrlCopied": "URL de les estadístiques de Proxmox copiada al porta-retalls", "copyProxmoxStatsUrlAction": "Copia l'URL de les estadístiques de Proxmox", - "enableFileManagerMonitor": "Habilita el gestor de fitxers", - "enableFileManagerMonitorDesc": "Navega i gestiona fitxers en aquest amfitrió mitjançant SFTP", - "scpLegacyLabel": "Mode Llegat de SCP", - "scpLegacyDesc": "Feu servir la transferència de fitxers antiga per a servidors que no admeten el subsistema SFTP (per exemple, servidors SSH integrats o mínims).", - "defaultPathLabel": "Camí predeterminat", - "fileManagerPathHint": "El directori que s'obrirà quan s'iniciï el gestor de fitxers per a aquest amfitrió.", "statusChecksLabel": "Comprovacions d'estat", "enableStatusChecks": "Activa les comprovacions d'estat", "enableStatusChecksDesc": "Fes ping periòdicament a aquest host per verificar la disponibilitat", @@ -1278,7 +1269,6 @@ "deletedHost": "Suprimit {{name}}", "copiedToClipboard": "Copiat al porta-retalls", "terminalUrlCopied": "S'ha copiat l'URL del terminal", - "fileManagerUrlCopied": "S'ha copiat l'URL del gestor de fitxers", "tunnelUrlCopied": "S'ha copiat l'URL del túnel", "dockerUrlCopied": "S'ha copiat l'URL de Docker", "hostMetricsUrlCopied": "URL de mètriques de l'amfitrió copiada", @@ -1296,7 +1286,6 @@ "copyAddress": "Copiar adreça", "copyLink": "Copia l'enllaç", "copyTerminalUrlAction": "Copia l'URL del terminal", - "copyFileManagerUrlAction": "Copia l'URL del gestor de fitxers", "copyTunnelUrlAction": "Copia l'URL del túnel", "copyDockerUrlAction": "Copia l'URL de Docker", "copyHostMetricsUrlAction": "Copia l'URL de les mètriques de l'amfitrió", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Elimina de Split" } }, - "fileManager": { - "noHostSelected": "No s'ha seleccionat cap amfitrió", - "initializingEditor": "Inicialitzant l'editor...", - "file": "Fitxer", - "folder": "Carpeta", - "uploadFile": "Puja el fitxer", - "downloadFile": "Descarrega", - "extractArchive": "Extreure l'arxiu", - "extractingArchive": "Extraient {{name}}...", - "archiveExtractedSuccessfully": "{{name}} s'ha extret correctament", - "extractFailed": "L'extracció ha fallat", - "compressFile": "Comprimir fitxer", - "compressFiles": "Comprimir fitxers", - "compressFilesDesc": "Comprimir elements {{count}} en un arxiu", - "archiveName": "Nom de l'arxiu", - "enterArchiveName": "Introduïu el nom de l'arxiu...", - "compressionFormat": "Format de compressió", - "selectedFiles": "Fitxers seleccionats", - "andMoreFiles": "i {{count}} més...", - "compress": "Comprimir", - "compressingFiles": "Comprimint elements {{count}} en {{name}}...", - "filesCompressedSuccessfully": "{{name}} creat correctament", - "compressFailed": "La compressió ha fallat", - "edit": "Edita", - "preview": "Vista prèvia", - "previous": "Anterior", - "next": "Següent", - "pageXOfY": "Pàgina {{current}} de {{total}}", - "zoomOut": "Allunyar", - "zoomIn": "Ampliar zoom", - "newFile": "Fitxer nou", - "newFolder": "Nova carpeta", - "rename": "Canvia el nom", - "uploading": "S'està carregant...", - "uploadingFile": "S'està carregant {{name}}...", - "fileName": "Nom del fitxer", - "folderName": "Nom de la carpeta", - "fileUploadedSuccessfully": "El fitxer \"{{name}}\" s'ha carregat correctament", - "failedToUploadFile": "No s'ha pogut carregar el fitxer", - "uploadingFolderFiles": "S'estan pujant {{count}} fitxer(s)...", - "uploadedFolderFiles": "S'han carregat correctament els fitxers {{count}}", - "uploadedFolderPartial": "No s'ha pogut pujar el(s) fitxer(s) {{uploaded}} , {{failed}}", - "fileDownloadedSuccessfully": "Fitxer \"{{name}}\" descarregat correctament", - "failedToDownloadFile": "No s'ha pogut descarregar el fitxer", - "fileCreatedSuccessfully": "Fitxer \"{{name}}\" creat correctament", - "folderCreatedSuccessfully": "La carpeta \"{{name}}\" s'ha creat correctament", - "failedToCreateItem": "No s'ha pogut crear l'element", - "operationFailed": "L'operació {{operation}} ha fallat per a {{name}}: {{error}}", - "failedToResolveSymlink": "No s'ha pogut resoldre l'enllaç simbòlic", - "itemsDeletedSuccessfully": "{{count}} elements suprimits correctament", - "failedToDeleteItems": "No s'han pogut suprimir els elements", - "sudoPasswordRequired": "Contrasenya d'administrador requerida", - "enterSudoPassword": "Introduïu la contrasenya sudo per continuar amb aquesta operació", - "sudoPassword": "Contrasenya de Sudo", - "sudoOperationFailed": "L'operació Sudo ha fallat.", - "sudoAuthFailed": "L'autenticació de Sudo ha fallat.", - "dragFilesToUpload": "Deixa anar els fitxers aquí per pujar-los", - "dropToUploadHere": "Deixa anar per pujar aquí", - "dropToDownloadHere": "Deixa anar per descarregar aquí", - "localFiles": "Local", - "showLocalFiles": "Mostra els fitxers locals (vista dividida)", - "hideLocalFiles": "Amaga els fitxers locals", - "localHome": "Carpeta d'inici", - "localPathPlaceholder": "Ruta de la carpeta local", - "localShowHidden": "Mostra els fitxers ocults", - "localHideHidden": "Amaga els fitxers ocults", - "localRevealInFileManager": "Mostra al gestor de fitxers del sistema", - "localCannotOpenFolder": "No es pot obrir la carpeta", - "localItemCount": "{{count}} elements", - "localSelectedCount": "{{count}} seleccionat", - "back": "Enrere", - "forward": "Endavant", - "up": "Amunt", - "kind": "Amable", - "localUploadingProgress": "S'està carregant {{current}} de {{total}}", - "localDownloadingProgress": "Descarregant {{current}} de {{total}}", - "localTransferInFlight": "{{count}} alhora", - "localPreparingDownload": "Preparant la descàrrega...", - "localTransferCancelled": "Transferència cancel·lada", - "localNothingToTransfer": "Res a transferir", - "localUploadComplete": "Fitxer(s) {{count}} penjat(s)", - "localUploadFailed": "La pujada ha fallat", - "localUploadPartial": "No s'ha pogut pujar el(s) fitxer(s) {{done}} , {{failed}}", - "localDownloadComplete": "Fitxer(s) descarregat(s) {{count}}", - "localDownloadFailed": "La descàrrega ha fallat", - "localDownloadPartial": "No s'ha pogut descarregar el(s) fitxer(s) {{done}} , {{failed}}.", - "localReplaceExistingPrompt": "Ja existeixen elements {{count}} en aquesta carpeta. Voleu substituir-los?", - "localReplace": "Substitueix", - "localSkipExisting": "Omet", - "localSkippedExisting": "S'han omès {{count}} elements existents", - "localUnsafeNamesSkipped": "S'han omès {{count}} elements els noms dels quals no es poden utilitzar en aquest ordinador", - "localOpen": "Obre", - "localOpenFolder": "Obre la carpeta", - "localUploadToRemote": "Puja al servidor", - "localUploadToRemoteMany": "Puja {{count}} elements al servidor", - "localMoveToTrash": "Mou a la paperera", - "localMoveToTrashMany": "Mou {{count}} elements a la paperera", - "localTrashConfirmSingle": "Voleu moure \"{{name}}\" a la paperera?", - "localTrashConfirmFolder": "Voleu moure la carpeta \"{{name}}\" i tot el seu contingut a la paperera?", - "localTrashConfirmMany": "Voleu moure {{count}} elements a la paperera?", - "localTrashed": "S'han mogut {{count}} elements a la paperera.", - "localTrashFailed": "No s'ha pogut moure a la paperera", - "localRenameFailed": "No s'ha pogut canviar el nom", - "localCreateFailed": "No s'ha pogut crear l'element", - "goToParentFolder": "Anar a la carpeta principal", - "resizeSidebar": "Arrossegueu per canviar la mida de la barra lateral (feu doble clic per restablir-la)", - "resizeLocalPane": "Arrossegueu per canviar la mida del panell local (feu doble clic per restablir-lo)", - "columns": "Columnes", - "columnsHint": "Feu clic amb el botó dret per triar columnes", - "emptyFolder": "Aquesta carpeta està buida", - "searchFiles": "Cerca fitxers...", - "upload": "Pujada", - "selectHostToStart": "Seleccioneu un amfitrió per iniciar la gestió de fitxers", - "sshRequiredForFileManager": "El gestor de fitxers requereix SSH. Aquest amfitrió no té SSH habilitat.", - "failedToConnect": "No s'ha pogut connectar a SSH", - "failedToLoadDirectory": "No s'ha pogut carregar el directori", - "noSSHConnection": "No hi ha connexió SSH disponible", - "copy": "Còpia", - "cut": "Tallar", - "paste": "Enganxa", - "copyPath": "Copia la ruta", - "copyPaths": "Copiar camins", - "delete": "Suprimeix", - "properties": "Propietats", - "refresh": "Actualitza", - "downloadFiles": "Baixeu els fitxers {{count}} al navegador", - "copyFiles": "Copia elements {{count}}", - "cutFiles": "Retalla elements {{count}}", - "deleteFiles": "Suprimeix elements {{count}}", - "filesCopiedToClipboard": "{{count}} elements copiats al porta-retalls", - "filesCutToClipboard": "{{count}} elements retallats al porta-retalls", - "pathCopiedToClipboard": "La ruta s'ha copiat al porta-retalls", - "pathsCopiedToClipboard": "{{count}} camins copiats al porta-retalls", - "failedToCopyPath": "No s'ha pogut copiar la ruta al porta-retalls", - "copyFolderLink": "Copia l'enllaç a la carpeta", - "copyCurrentFolderLink": "Copia l'enllaç a la carpeta actual", - "folderLinkCopied": "Enllaç de carpeta copiat al porta-retalls", - "failedToCopyFolderLink": "No s'ha pogut copiar l'enllaç de la carpeta", - "movedItems": "Elements moguts {{count}}", - "failedToDeleteItem": "No s'ha pogut suprimir l'element", - "itemRenamedSuccessfully": "{{type}} s'ha canviat el nom correctament", - "failedToRenameItem": "No s'ha pogut canviar el nom de l'element", - "download": "Descarrega", - "openExternalEditor": "Obre externament", - "chooseExternalEditor": "Trieu un editor extern", - "externalEditorSelected": "Editor extern seleccionat", - "externalEditorDesktopOnly": "L'editor extern només està disponible a l'aplicació d'escriptori", - "externalEditorOpened": "Obert en un editor extern. Si deseu-ho, els canvis es tornaran a carregar al servidor.", - "failedToOpenExternalEditor": "No s'ha pogut obrir l'editor extern", - "failedToSelectExternalEditor": "No s'ha pogut seleccionar l'editor extern", - "permissions": "Permisos", - "size": "Mida", - "modified": "Modificat", - "path": "Camí", - "confirmDelete": "Esteu segur que voleu suprimir {{name}}?", - "permissionDenied": "Permís denegat", - "serverError": "Error del servidor", - "fileSavedSuccessfully": "Fitxer desat correctament", - "failedToSaveFile": "No s'ha pogut desar el fitxer", - "confirmDeleteSingleItem": "Esteu segur que voleu suprimir permanentment \"{{name}}\"?", - "confirmDeleteMultipleItems": "Esteu segur que voleu suprimir permanentment els elements {{count}}?", - "confirmDeleteMultipleItemsWithFolders": "Esteu segur que voleu suprimir permanentment els elements {{count}} ? Això inclou les carpetes i el seu contingut.", - "confirmDeleteFolder": "Esteu segur que voleu suprimir permanentment la carpeta \"{{name}}\" i tot el seu contingut?", - "permanentDeleteWarning": "Aquesta acció no es pot desfer. Els elements s'eliminaran permanentment del servidor.", - "moveToTrashWarning": "Els elements es mouran a la paperera de Termix i es podran restaurar abans que expiri el període de retenció.", - "itemsMovedToTrash": "{{count}} element(s) mogut(s) a la paperera", - "trash": "Escombraries", - "trashDescription": "Els elements suprimits es conserven durant {{days}} dies en aquest amfitrió.", - "trashLoadFailed": "No s'ha pogut carregar la paperera", - "trashEmpty": "La paperera és buida", - "trashRestore": "Restaurar", - "trashRestored": "Restaurat {{name}}", - "trashRestoreFailed": "No s'ha pogut restaurar l'element. És possible que la ruta original ja existeixi.", - "trashDeletePermanently": "Suprimeix permanentment", - "trashDeleteConfirm": "Voleu suprimir permanentment {{name}}? Això no es pot desfer.", - "trashPermanentlyDeleted": "Suprimit permanentment {{name}}", - "trashDeleteFailed": "No s'ha pogut suprimir l'element permanentment", - "trashEmptyAction": "Buida la paperera", - "trashEmptyConfirm": "Voleu suprimir permanentment tots els elements de la paperera? Això no es pot desfer.", - "trashEmptied": "Paperera buida", - "trashRetention": "Neteja automàtica després de (dies)", - "trashRetentionSaved": "Retenció de brossa actualitzada", - "trashRetentionFailed": "No s'ha pogut actualitzar la retenció de la paperera", - "trashUnavailableConfirm": "Aquest element no es pot moure a la paperera, normalment perquè es troba en un altre sistema de fitxers o requereix permisos elevats. Voleu suprimir-lo permanentment?", - "recent": "Recent", - "pinned": "Fixat", - "folderShortcuts": "Dreceres de carpeta", - "failedToReconnectSSH": "No s'ha pogut reconnectar la sessió SSH", - "openTerminalHere": "Obre el terminal aquí", - "run": "Corre", - "openTerminalInFolder": "Obre el terminal en aquesta carpeta", - "openTerminalInFileLocation": "Obre el terminal a la ubicació del fitxer", - "runningFile": "Córrer - {{file}}", - "onlyRunExecutableFiles": "Només pot executar fitxers executables", - "directories": "Directoris", - "removedFromRecentFiles": "S'ha eliminat \"{{name}}\" dels fitxers recents", - "removeFailed": "No s'ha pogut eliminar l'opció.", - "unpinnedSuccessfully": "S'ha desfixat \"{{name}}\" correctament", - "unpinFailed": "No s'ha pogut desancorar la fixació.", - "removedShortcut": "S'ha eliminat la drecera \"{{name}}\"", - "removeShortcutFailed": "No s'ha pogut eliminar la drecera.", - "clearedAllRecentFiles": "S'han esborrat tots els fitxers recents", - "clearFailed": "Error en esborrar", - "removeFromRecentFiles": "Elimina dels fitxers recents", - "clearAllRecentFiles": "Esborra tots els fitxers recents", - "unpinFile": "Desfixar fitxer", - "removeShortcut": "Elimina la drecera", - "pinFile": "Fitxer fixat", - "addToShortcuts": "Afegeix a les dreceres", - "pasteFailed": "No s'ha pogut enganxar", - "noUndoableActions": "Sense accions desfaibles", - "undoCopySuccess": "Operació de còpia desfeta: S'han suprimit {{count}} fitxers copiats", - "undoCopyFailedDelete": "Error en desfer: no s'han pogut suprimir els fitxers copiats.", - "undoCopyFailedNoInfo": "Error en desfer: no s'ha pogut trobar la informació del fitxer copiat", - "undoMoveSuccess": "Operació de moviment desfeta: S'han tornat a moure els fitxers {{count}} a la ubicació original.", - "undoMoveFailedMove": "Desfer ha fallat: no s'han pogut moure cap fitxer enrere", - "undoMoveFailedNoInfo": "Desfer ha fallat: no s'ha pogut trobar la informació del fitxer mogut", - "undoDeleteNotSupported": "L'operació de supressió no es pot desfer: els fitxers s'han suprimit permanentment del servidor.", - "undoTypeNotSupported": "Tipus d'operació de desfer no compatible", - "undoOperationFailed": "L'operació de desfer ha fallat", - "unknownError": "Error desconegut", - "confirm": "Confirma", - "find": "Troba...", - "replace": "Substitueix", - "downloadInstead": "Descarrega en comptes d'això", - "keyboardShortcuts": "Dreceres de teclat", - "searchAndReplace": "Cerca i substitueix", - "editing": "Edició", - "search": "Cerca", - "findNext": "Cerca el següent", - "findPrevious": "Cerca anterior", - "save": "Desa", - "selectAll": "Selecciona-ho tot", - "undo": "Desfer", - "redo": "Refer", - "moveLineUp": "Mou la línia cap amunt", - "moveLineDown": "Mou la línia cap avall", - "toggleComment": "Activa/desactiva el comentari", - "autoComplete": "Completar automàticament", - "imageLoadError": "No s'ha pogut carregar la imatge", - "startTyping": "Comença a escriure...", - "unknownSize": "Mida desconeguda", - "fileIsEmpty": "El fitxer està buit", - "largeFileWarning": "Avís de fitxer gran", - "largeFileWarningDesc": "Aquest fitxer té una mida de {{size}} , cosa que pot causar problemes de rendiment quan s'obre com a text.", - "fileNotFoundAndRemoved": "No s'ha trobat el fitxer \"{{name}}\" i s'ha eliminat dels fitxers recents/fixats.", - "failedToLoadFile": "No s'ha pogut carregar el fitxer: {{error}}", - "serverErrorOccurred": "S'ha produït un error del servidor. Torna-ho a provar més tard.", - "autoSaveFailed": "El desament automàtic ha fallat", - "fileAutoSaved": "Fitxer desat automàticament", - "moveFileFailed": "No s'ha pogut moure {{name}}", - "moveOperationFailed": "L'operació de moviment ha fallat", - "canOnlyCompareFiles": "Només es poden comparar dos fitxers", - "comparingFiles": "Comparació de fitxers: {{file1}} i {{file2}}", - "dragFailed": "L'operació d'arrossegament ha fallat", - "filePinnedSuccessfully": "Fitxer \"{{name}}\" fixat correctament", - "pinFileFailed": "No s'ha pogut fixar el fitxer", - "fileUnpinnedSuccessfully": "El fitxer \"{{name}}\" s'ha desanclat correctament", - "unpinFileFailed": "No s'ha pogut desancorar el fitxer", - "shortcutAddedSuccessfully": "Drecera de carpeta \"{{name}}\" afegida correctament", - "addShortcutFailed": "No s'ha pogut afegir la drecera", - "operationCompletedSuccessfully": "{{operation}} {{count}} elements correctament", - "operationCompleted": "{{operation}} {{count}} elements", - "downloadFileSuccess": "Fitxer {{name}} descarregat correctament", - "downloadFileFailed": "La descàrrega ha fallat", - "downloadingFile": "Descarregant {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Mou a {{name}}", - "diffCompareWith": "Comparació de diferències amb {{name}}", - "dragOutsideToDownload": "Arrossega fora de la finestra per descarregar (fitxers{{count}})", - "newFolderDefault": "NovaCarpeta", - "newFileDefault": "NouFitxer.txt", - "successfullyMovedItems": "S'han mogut correctament {{count}} elements a {{target}}", - "move": "Moure", - "searchInFile": "Cerca en un fitxer (Ctrl+F)", - "showKeyboardShortcuts": "Mostra les dreceres de teclat", - "decreaseFontSize": "Reduir la mida de la lletra", - "increaseFontSize": "Augmenta la mida de la lletra", - "startWritingMarkdown": "Comença a escriure el teu contingut de rebaixes...", - "loadingFileComparison": "S'està carregant la comparació de fitxers...", - "reload": "Torna a carregar", - "compare": "Comparar", - "sideBySide": "Costat al costat", - "inline": "En línia", - "fileComparison": "Comparació de fitxers: {{file1}} vs {{file2}}", - "fileTooLarge": "Fitxer massa gran: {{error}}", - "sshConnectionFailed": "La connexió SSH ha fallat. Si us plau, comproveu la connexió a {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "No s'ha pogut carregar el fitxer: {{error}}", - "connecting": "Connectant...", - "connectedSuccessfully": "Connexió correcta", - "totpVerificationFailed": "La verificació TOTP ha fallat", - "warpgateVerificationFailed": "L'autenticació de Warpgate ha fallat", - "authenticationFailed": "L'autenticació ha fallat", - "incorrectPassphrase": "Contrasenya incorrecta. Si us plau, torneu-ho a intentar.", - "verificationCodePrompt": "Codi de verificació:", - "changePermissions": "Canvia els permisos", - "currentPermissions": "Permisos actuals", - "owner": "propietari", - "group": "Grup", - "others": "Altres", - "read": "Llegir", - "write": "Escriure", - "execute": "Executar", - "permissionsChangedSuccessfully": "Els permisos s'han canviat correctament", - "failedToChangePermissions": "No s'han pogut canviar els permisos", - "name": "Nom", - "sortByName": "Nom", - "sortByDate": "Data de modificació", - "sortBySize": "Mida", - "ascending": "Ascendent", - "descending": "Descendent", - "root": "Arrel", - "new": "Nou", - "sortBy": "Ordena per", - "items": "Articles", - "selected": "Seleccionat", - "editor": "Editor", - "octal": "Octal", - "storage": "Emmagatzematge", - "disk": "Disc", - "used": "Usat", - "of": "de", - "toggleSidebar": "Obre/tanca la barra lateral", - "compactLayout": "Utilitza un disseny compacte", - "comfortableLayout": "Utilitza un disseny còmode", - "cannotLoadPdf": "No es pot carregar el PDF", - "pdfLoadError": "S'ha produït un error en carregar aquest fitxer PDF.", - "loadingPdf": "S'està carregant el PDF...", - "loadingPage": "S'està carregant la pàgina..." - }, "transfer": { "copyToHost": "Copia a l'amfitrió…", "moveToHost": "Moure a l'amfitrió…", @@ -2292,50 +1951,6 @@ "retryTransferError": "No s'ha pogut reintentar.", "transferFailedRetryHint": "S'han conservat dades parcials a la destinació. El reintent es reprendrà quan es restableixi la connexió." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Transferir fitxers entre dos hosts SSH.", - "sourceServer": "Servidor d'origen", - "destinationServer": "Servidor de destinació", - "selectHost": "Selecciona l'amfitrió", - "noHostSelected": "No s'ha seleccionat cap amfitrió", - "move": "Moure", - "copy": "Còpia", - "upload": "Pujada", - "itemsSelected": "{{count}} element seleccionat", - "itemsSelected_other": "{{count}} elements seleccionats", - "ready": "Llest", - "loadingHosts": "S'estan carregant els amfitrions...", - "name": "Nom", - "modified": "Modificat", - "size": "Mida", - "noFiles": "Sense fitxers", - "open": "Obre", - "up": "Amunt", - "refresh": "Actualitza", - "copyToTarget": "Copia al directori de destinació", - "rename": "Canvia el nom", - "delete": "Suprimeix", - "editPermissions": "Edita els permisos", - "createNewFolder": "Crea una carpeta nova", - "deleteSelectedItems": "Suprimeix els elements seleccionats", - "deleteConfirm": "Voleu suprimir l'element seleccionat {{count}}?", - "deleteConfirm_other": "Voleu suprimir els elements seleccionats {{count}}?", - "cancel": "Cancel·la", - "save": "Desa", - "folderCreated": "Carpeta creada", - "itemRenamed": "L'element s'ha rebatejat", - "actionFailed": "L'acció ha fallat", - "itemsDeleted": "{{count}} element suprimit", - "itemsDeleted_other": "{{count}} elements suprimits", - "deleteFailed": "No s'ha pogut eliminar l'opció.", - "permissionsUpdated": "Permisos actualitzats", - "noRemoteSessionConnected": "No hi ha cap sessió remota connectada", - "failedToListFiles": "No s'han pogut llistar els fitxers", - "failedToRefresh": "No s'ha pogut actualitzar", - "destinationInsideSource": "La destinació no pot ser dins de la ruta d'origen seleccionada.", - "sameHostNoOp": "L'origen i la destinació són la mateixa carpeta." - }, "tunnels": { "noSshTunnels": "Sense túnels SSH", "createFirstTunnelMessage": "Encara no has creat cap túnel SSH. Configureu les connexions del túnel al Gestor d'amfitrions per començar.", diff --git a/src/ui/locales/translated/cs_CZ.json b/src/ui/locales/translated/cs_CZ.json index 0ffb8ef54..ca0a98977 100644 --- a/src/ui/locales/translated/cs_CZ.json +++ b/src/ui/locales/translated/cs_CZ.json @@ -715,7 +715,6 @@ "hostManager": "Hostitelský manažer", "credentials": "Pověření", "portForwarding": "Přeposílání", - "sftp": "SFTP", "connections": "Připojení", "serial": "Seriál", "keepSideMenuOpen": "Ponechat nabídku otevřenou", @@ -767,14 +766,12 @@ "tabRdp": "PRV", "tabVnc": "VNC", "tabTunnels": "Tunely", - "tabFiles": "Soubory", "tabStats": "Metriky hostitele", "tabTelnet": "Telnet", "tabSharing": "Sdílení", "tabAuthentication": "Ověřování", "terminal": "Terminál", "tunnel": "Tunel", - "fileManager": "Správce souborů", "serverStats": "Metriky hostitele", "status": { "available": "K dispozici", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} přeskočeno (nenalezena žádná IP adresa)", "proxmoxStatsUrlCopied": "URL statistik Proxmoxu zkopírována do schránky", "copyProxmoxStatsUrlAction": "Kopírovat URL statistik Proxmoxu", - "enableFileManagerMonitor": "Povolit Správce souborů", - "enableFileManagerMonitorDesc": "Procházení a správa souborů na tomto hostiteli přes SFTP", - "scpLegacyLabel": "Starší režim SCP", - "scpLegacyDesc": "Pro servery, které nepodporují subsystém SFTP (např. vestavěné nebo minimální SSH servery), použijte starší přenos souborů.", - "defaultPathLabel": "Výchozí cesta", - "fileManagerPathHint": "Adresář, který se má otevřít při spuštění správce souborů pro tento hostitel.", "statusChecksLabel": "Kontroly stavu", "enableStatusChecks": "Povolit kontroly stavu", "enableStatusChecksDesc": "Pravidelně pingovat tento hostitel pro ověření dostupnosti", @@ -1278,7 +1269,6 @@ "deletedHost": "Smazáno {{name}}", "copiedToClipboard": "Zkopírováno do schránky", "terminalUrlCopied": "URL terminálu zkopírována", - "fileManagerUrlCopied": "URL adresa Správce souborů zkopírována", "tunnelUrlCopied": "URL tunelu zkopírována", "dockerUrlCopied": "URL adresa Dockeru zkopírována", "hostMetricsUrlCopied": "URL adresa metrik hostitele zkopírována", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopírovat adresu", "copyLink": "Kopírovat odkaz", "copyTerminalUrlAction": "Kopírovat URL terminálu", - "copyFileManagerUrlAction": "Kopírovat URL Správce souborů", "copyTunnelUrlAction": "Kopírovat URL tunelu", "copyDockerUrlAction": "Kopírovat URL Dockeru", "copyHostMetricsUrlAction": "Kopírovat URL metrik hostitele", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Odebrat ze Splitu" } }, - "fileManager": { - "noHostSelected": "Nebyl vybrán žádný hostitel", - "initializingEditor": "Inicializace editoru...", - "file": "Soubor", - "folder": "Složka", - "uploadFile": "Nahrát soubor", - "downloadFile": "Stáhnout", - "extractArchive": "Extrahovat archiv", - "extractingArchive": "Extrahování {{name}}...", - "archiveExtractedSuccessfully": "{{name}} byl úspěšně extrahován", - "extractFailed": "Extrakce se nezdařila", - "compressFile": "Komprimovat soubor", - "compressFiles": "Komprimovat soubory", - "compressFilesDesc": "Komprimovat {{count}} položek do archivu", - "archiveName": "Název archivu", - "enterArchiveName": "Zadejte název archivu...", - "compressionFormat": "Kompresní formát", - "selectedFiles": "Vybrané soubory", - "andMoreFiles": "a {{count}} dalších...", - "compress": "Komprimovat", - "compressingFiles": "Komprimace {{count}} položek do {{name}}...", - "filesCompressedSuccessfully": "{{name}} úspěšně vytvořeno", - "compressFailed": "Komprese selhala", - "edit": "Upravit", - "preview": "Náhled", - "previous": "Předchozí", - "next": "Další", - "pageXOfY": "Strana {{current}} z {{total}}", - "zoomOut": "Oddálit", - "zoomIn": "Přiblížit", - "newFile": "Nový soubor", - "newFolder": "Nová složka", - "rename": "Přejmenovat", - "uploading": "Nahrávání...", - "uploadingFile": "Nahrávání {{name}}...", - "fileName": "Název souboru", - "folderName": "Název složky", - "fileUploadedSuccessfully": "Soubor „{{name}}“ byl úspěšně nahrán", - "failedToUploadFile": "Nahrávání souboru se nezdařilo", - "uploadingFolderFiles": "Nahrávání {{count}} souborů...", - "uploadedFolderFiles": "Úspěšně nahráno {{count}} souborů", - "uploadedFolderPartial": "Nahrání souboru (souborů) {{uploaded}} se nezdařilo. {{failed}}", - "fileDownloadedSuccessfully": "Soubor „{{name}}“ byl úspěšně stažen", - "failedToDownloadFile": "Stažení souboru se nezdařilo", - "fileCreatedSuccessfully": "Soubor „{{name}}“ byl úspěšně vytvořen", - "folderCreatedSuccessfully": "Složka „{{name}}“ byla úspěšně vytvořena", - "failedToCreateItem": "Vytvoření položky se nezdařilo", - "operationFailed": "Operace {{operation}} se nezdařila pro {{name}}: {{error}}", - "failedToResolveSymlink": "Nepodařilo se vyřešit symbolický odkaz", - "itemsDeletedSuccessfully": "{{count}} položek bylo úspěšně smazáno", - "failedToDeleteItems": "Položky se nepodařilo smazat", - "sudoPasswordRequired": "Vyžadováno heslo správce", - "enterSudoPassword": "Pro pokračování v této operaci zadejte heslo sudo.", - "sudoPassword": "Heslo Sudo", - "sudoOperationFailed": "Operace Sudo selhala", - "sudoAuthFailed": "Ověření Sudo selhalo", - "dragFilesToUpload": "Přetáhněte sem soubory k nahrání", - "dropToUploadHere": "Přetáhněte sem pro nahrání", - "dropToDownloadHere": "Přetáhněte pro stažení sem", - "localFiles": "Místní", - "showLocalFiles": "Zobrazit lokální soubory (rozdělené zobrazení)", - "hideLocalFiles": "Skrýt lokální soubory", - "localHome": "Domovská složka", - "localPathPlaceholder": "Cesta k místní složce", - "localShowHidden": "Zobrazit skryté soubory", - "localHideHidden": "Skrýt skryté soubory", - "localRevealInFileManager": "Zobrazit ve správci systémových souborů", - "localCannotOpenFolder": "Nelze otevřít složku", - "localItemCount": "{{count}} položek", - "localSelectedCount": "{{count}} vybráno", - "back": "Zadní", - "forward": "Vpřed", - "up": "Nahoru", - "kind": "Druh", - "localUploadingProgress": "Nahrávání {{current}} z {{total}}", - "localDownloadingProgress": "Stahování {{current}} z {{total}}", - "localTransferInFlight": "{{count}} najednou", - "localPreparingDownload": "Příprava stahování...", - "localTransferCancelled": "Převod zrušen", - "localNothingToTransfer": "Nic k převodu", - "localUploadComplete": "Nahráno {{count}} souborů", - "localUploadFailed": "Nahrávání se nezdařilo", - "localUploadPartial": "Nahrání souboru (souborů) {{done}} se nezdařilo. {{failed}}", - "localDownloadComplete": "Staženo {{count}} souborů", - "localDownloadFailed": "Stahování se nezdařilo", - "localDownloadPartial": "Stažení souboru (souborů) {{done}} se nezdařilo. {{failed}}", - "localReplaceExistingPrompt": "V této složce již existuje {{count}} položek. Nahradit je?", - "localReplace": "Nahradit", - "localSkipExisting": "Přeskočit", - "localSkippedExisting": "Přeskočeno {{count}} existujících položek", - "localUnsafeNamesSkipped": "Přeskočeno {{count}} položek, jejichž názvy nelze na tomto počítači použít", - "localOpen": "OTEVŘENO", - "localOpenFolder": "Otevřít složku", - "localUploadToRemote": "Nahrát na server", - "localUploadToRemoteMany": "Nahrát {{count}} položek na server", - "localMoveToTrash": "Přesunout do koše", - "localMoveToTrashMany": "Přesunout {{count}} položek do koše", - "localTrashConfirmSingle": "Přesunout „{{name}}“ do koše?", - "localTrashConfirmFolder": "Přesunout složku „{{name}}“ a veškerý její obsah do koše?", - "localTrashConfirmMany": "Přesunout {{count}} položek do koše?", - "localTrashed": "Přesunuto {{count}} položek do koše", - "localTrashFailed": "Nepodařilo se přesunout do koše", - "localRenameFailed": "Přejmenování se nezdařilo", - "localCreateFailed": "Nepodařilo se vytvořit položku", - "goToParentFolder": "Přejít do nadřazené složky", - "resizeSidebar": "Přetažením změníte velikost postranního panelu (dvojitým kliknutím resetujete)", - "resizeLocalPane": "Přetažením změníte velikost lokálního panelu (dvojitým kliknutím resetujete).", - "columns": "Sloupce", - "columnsHint": "Kliknutím pravým tlačítkem myši vyberete sloupce", - "emptyFolder": "Tato složka je prázdná", - "searchFiles": "Hledat soubory...", - "upload": "Nahrát", - "selectHostToStart": "Vyberte hostitele pro spuštění správy souborů", - "sshRequiredForFileManager": "Správce souborů vyžaduje SSH. Tento hostitel nemá SSH povoleno.", - "failedToConnect": "Nepodařilo se připojit k SSH", - "failedToLoadDirectory": "Nepodařilo se načíst adresář", - "noSSHConnection": "Není k dispozici žádné SSH připojení", - "copy": "Kopie", - "cut": "Střih", - "paste": "Pasta", - "copyPath": "Kopírovat cestu", - "copyPaths": "Kopírovat cesty", - "delete": "Vymazat", - "properties": "Nemovitosti", - "refresh": "Obnovit", - "downloadFiles": "Stáhnout {{count}} souborů do prohlížeče", - "copyFiles": "Kopírovat {{count}} položek", - "cutFiles": "Vyjmout {{count}} položek", - "deleteFiles": "Smazat {{count}} položek", - "filesCopiedToClipboard": "{{count}} položek zkopírováno do schránky", - "filesCutToClipboard": "{{count}} položek vyjmutých do schránky", - "pathCopiedToClipboard": "Cesta zkopírována do schránky", - "pathsCopiedToClipboard": "{{count}} cest zkopírováno do schránky", - "failedToCopyPath": "Nepodařilo se zkopírovat cestu do schránky", - "copyFolderLink": "Kopírovat odkaz do složky", - "copyCurrentFolderLink": "Kopírovat odkaz do aktuální složky", - "folderLinkCopied": "Odkaz na složku byl zkopírován do schránky", - "failedToCopyFolderLink": "Nepodařilo se zkopírovat odkaz na složku", - "movedItems": "Přesunuto {{count}} položek", - "failedToDeleteItem": "Položku se nepodařilo smazat", - "itemRenamedSuccessfully": "{{type}} byl úspěšně přejmenován", - "failedToRenameItem": "Přejmenování položky se nezdařilo", - "download": "Stáhnout", - "openExternalEditor": "Otevřít externě", - "chooseExternalEditor": "Vyberte externího editora", - "externalEditorSelected": "Vybrán externí editor", - "externalEditorDesktopOnly": "Externí editor je k dispozici pouze v desktopové aplikaci", - "externalEditorOpened": "Otevřeno v externím editoru. Uložením tam se změny nahrají zpět na server.", - "failedToOpenExternalEditor": "Nepodařilo se otevřít externí editor", - "failedToSelectExternalEditor": "Nepodařilo se vybrat externí editor", - "permissions": "Oprávnění", - "size": "Velikost", - "modified": "Upraveno", - "path": "Cesta", - "confirmDelete": "Opravdu chcete smazat {{name}}?", - "permissionDenied": "Oprávnění zamítnuto", - "serverError": "Chyba serveru", - "fileSavedSuccessfully": "Soubor byl úspěšně uložen", - "failedToSaveFile": "Uložení souboru se nezdařilo", - "confirmDeleteSingleItem": "Opravdu chcete trvale smazat „{{name}}“?", - "confirmDeleteMultipleItems": "Opravdu chcete trvale smazat {{count}} položek?", - "confirmDeleteMultipleItemsWithFolders": "Opravdu chcete trvale smazat {{count}} položek? To zahrnuje i složky a jejich obsah.", - "confirmDeleteFolder": "Opravdu chcete trvale smazat složku „{{name}}“ a veškerý její obsah?", - "permanentDeleteWarning": "Tuto akci nelze vrátit zpět. Položka(y) bude(budou) trvale smazána(y) ze serveru.", - "moveToTrashWarning": "Položka(y) bude(budou) přesunuty(y) do koše Termix a bude(budou) možné je obnovit před uplynutím doby uchování.", - "itemsMovedToTrash": "{{count}} položek přesunutých do koše", - "trash": "Odpadky", - "trashDescription": "Smazané položky jsou na tomto hostiteli uchovávány po dobu {{days}} dní.", - "trashLoadFailed": "Načtení koše se nezdařilo", - "trashEmpty": "Koš je prázdný", - "trashRestore": "Obnovit", - "trashRestored": "Obnoveno {{name}}", - "trashRestoreFailed": "Obnovení položky se nezdařilo. Původní cesta již pravděpodobně existuje.", - "trashDeletePermanently": "Trvale smazat", - "trashDeleteConfirm": "Trvale smazat {{name}}? Tuto akci nelze vrátit zpět.", - "trashPermanentlyDeleted": "Trvale smazáno {{name}}", - "trashDeleteFailed": "Trvalé smazání položky se nezdařilo", - "trashEmptyAction": "Vysypat koš", - "trashEmptyConfirm": "Trvale smazat všechny položky v koši? Tuto akci nelze vrátit zpět.", - "trashEmptied": "Koš vyprázdněn", - "trashRetention": "Automatické čištění po (dnech)", - "trashRetentionSaved": "Uchovávání koše aktualizováno", - "trashRetentionFailed": "Aktualizace uchovávání v koši se nepodařilo", - "trashUnavailableConfirm": "Tuto položku nelze přesunout do koše, obvykle proto, že se nachází v jiném souborovém systému nebo vyžaduje zvýšená oprávnění. Chcete ji místo toho trvale smazat?", - "recent": "Nedávné", - "pinned": "Připnuto", - "folderShortcuts": "Zkratky složek", - "failedToReconnectSSH": "Nepodařilo se znovu připojit relaci SSH", - "openTerminalHere": "Otevřít terminál zde", - "run": "Běh", - "openTerminalInFolder": "Otevřít terminál v této složce", - "openTerminalInFileLocation": "Otevřít terminál v umístění souboru", - "runningFile": "Běh - {{file}}", - "onlyRunExecutableFiles": "Lze spustit pouze spustitelné soubory", - "directories": "Adresáře", - "removedFromRecentFiles": "Z posledních souborů odstraněno „{{name}}“", - "removeFailed": "Odebrání se nezdařilo", - "unpinnedSuccessfully": "Úspěšně odepnuto „{{name}}“", - "unpinFailed": "Odepnout se nepodařilo", - "removedShortcut": "Odstraněna zkratka „{{name}}“", - "removeShortcutFailed": "Odebrání zástupce se nezdařilo", - "clearedAllRecentFiles": "Vymazány všechny nedávné soubory", - "clearFailed": "Vymazání se nezdařilo", - "removeFromRecentFiles": "Odebrat z posledních souborů", - "clearAllRecentFiles": "Vymazat všechny nedávné soubory", - "unpinFile": "Odepnout soubor", - "removeShortcut": "Odebrat zkratku", - "pinFile": "Připnout soubor", - "addToShortcuts": "Přidat do zkratek", - "pasteFailed": "Vložení se nezdařilo", - "noUndoableActions": "Žádné akce, které lze vrátit zpět", - "undoCopySuccess": "Zrušená operace kopírování: Smazáno {{count}} zkopírovaných souborů", - "undoCopyFailedDelete": "Vrácení se nezdařilo: Nepodařilo se smazat žádné zkopírované soubory.", - "undoCopyFailedNoInfo": "Vrácení se nezdařilo: Nelze najít informace o zkopírovaném souboru.", - "undoMoveSuccess": "Zrušená operace přesunutí: {{count}} souborů bylo přesunuto zpět do původního umístění.", - "undoMoveFailedMove": "Vrácení se nezdařilo: Nepodařilo se přesunout žádné soubory zpět.", - "undoMoveFailedNoInfo": "Vrácení se nezdařilo: Nelze najít informace o přesunutém souboru.", - "undoDeleteNotSupported": "Operaci smazání nelze vrátit zpět: Soubory byly trvale smazány ze serveru.", - "undoTypeNotSupported": "Nepodporovaný typ operace vrácení zpět", - "undoOperationFailed": "Operace vrácení se nezdařila", - "unknownError": "Neznámá chyba", - "confirm": "Potvrdit", - "find": "Nalézt...", - "replace": "Nahradit", - "downloadInstead": "Stáhnout místo toho", - "keyboardShortcuts": "Klávesové zkratky", - "searchAndReplace": "Hledat a nahradit", - "editing": "Editace", - "search": "Vyhledávání", - "findNext": "Najít další", - "findPrevious": "Najít předchozí", - "save": "Uložit", - "selectAll": "Vybrat vše", - "undo": "Zpět", - "redo": "Předělat", - "moveLineUp": "Posunout seřadit nahoru", - "moveLineDown": "Posunout řádek dolů", - "toggleComment": "Přepnout komentář", - "autoComplete": "Automatické dokončení", - "imageLoadError": "Nepodařilo se načíst obrázek", - "startTyping": "Začněte psát...", - "unknownSize": "Neznámá velikost", - "fileIsEmpty": "Soubor je prázdný", - "largeFileWarning": "Varování před velkým souborem", - "largeFileWarningDesc": "Velikost tohoto souboru je {{size}} , což může při otevření jako text způsobit problémy s výkonem.", - "fileNotFoundAndRemoved": "Soubor „{{name}}“ nebyl nalezen a byl odstraněn z nedávných/připnutých souborů.", - "failedToLoadFile": "Nepodařilo se načíst soubor: {{error}}", - "serverErrorOccurred": "Došlo k chybě serveru. Zkuste to prosím znovu později.", - "autoSaveFailed": "Automatické ukládání se nezdařilo", - "fileAutoSaved": "Soubor se automaticky ukládá", - "moveFileFailed": "Přesun se nezdařilo {{name}}", - "moveOperationFailed": "Operace přesunu se nezdařila", - "canOnlyCompareFiles": "Lze porovnat pouze dva soubory", - "comparingFiles": "Porovnání souborů: {{file1}} a {{file2}}", - "dragFailed": "Operace přetažení se nezdařila", - "filePinnedSuccessfully": "Soubor „{{name}}“ byl úspěšně připnut", - "pinFileFailed": "Připnutí souboru se nezdařilo", - "fileUnpinnedSuccessfully": "Soubor „{{name}}“ byl úspěšně odepnut", - "unpinFileFailed": "Nepodařilo se odepnout soubor", - "shortcutAddedSuccessfully": "Zástupce složky „{{name}}“ byl úspěšně přidán", - "addShortcutFailed": "Přidání zástupce se nezdařilo", - "operationCompletedSuccessfully": "{{operation}} {{count}} položek úspěšně", - "operationCompleted": "{{operation}} {{count}} položek", - "downloadFileSuccess": "Soubor {{name}} byl úspěšně stažen", - "downloadFileFailed": "Stahování se nezdařilo", - "downloadingFile": "Stahování {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Přesunout na {{name}}", - "diffCompareWith": "Rozdíl ve srovnání s {{name}}", - "dragOutsideToDownload": "Přetáhněte mimo okno pro stažení ({{count}} souborů)", - "newFolderDefault": "Nová složka", - "newFileDefault": "NovýSoubor.txt", - "successfullyMovedItems": "Položky {{count}} byly úspěšně přesunuty do {{target}}", - "move": "Pohyb", - "searchInFile": "Hledat v souboru (Ctrl+F)", - "showKeyboardShortcuts": "Zobrazit klávesové zkratky", - "decreaseFontSize": "Zmenšit velikost písma", - "increaseFontSize": "Zvětšit velikost písma", - "startWritingMarkdown": "Začněte psát svůj obsah v Markdownu...", - "loadingFileComparison": "Načítání porovnání souborů...", - "reload": "Znovu načíst", - "compare": "Porovnat", - "sideBySide": "Bok po boku", - "inline": "Vložený", - "fileComparison": "Porovnání souborů: {{file1}} vs. {{file2}}", - "fileTooLarge": "Soubor je příliš velký: {{error}}", - "sshConnectionFailed": "SSH připojení selhalo. Zkontrolujte prosím připojení k {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Nepodařilo se načíst soubor: {{error}}", - "connecting": "Spojovací...", - "connectedSuccessfully": "Úspěšné připojení", - "totpVerificationFailed": "Ověření TOTP se nezdařilo", - "warpgateVerificationFailed": "Ověření Warpgate selhalo", - "authenticationFailed": "Ověření se nezdařilo", - "incorrectPassphrase": "Nesprávné heslo. Zkuste to prosím znovu.", - "verificationCodePrompt": "Ověřovací kód:", - "changePermissions": "Změnit oprávnění", - "currentPermissions": "Aktuální oprávnění", - "owner": "Majitel", - "group": "Skupina", - "others": "Ostatní", - "read": "Číst", - "write": "Napsat", - "execute": "Vykonat", - "permissionsChangedSuccessfully": "Oprávnění byla úspěšně změněna", - "failedToChangePermissions": "Nepodařilo se změnit oprávnění", - "name": "Jméno", - "sortByName": "Jméno", - "sortByDate": "Datum úpravy", - "sortBySize": "Velikost", - "ascending": "Vzestupně", - "descending": "Klesající", - "root": "Vykořenit", - "new": "Nový", - "sortBy": "Seřadit podle", - "items": "Položky", - "selected": "Vybraný", - "editor": "Editor", - "octal": "Osmičkové", - "storage": "Skladování", - "disk": "Disk", - "used": "Použitý", - "of": "z", - "toggleSidebar": "Přepnout postranní panel", - "compactLayout": "Použít kompaktní rozvržení", - "comfortableLayout": "Použijte pohodlné rozvržení", - "cannotLoadPdf": "Nelze načíst PDF", - "pdfLoadError": "Při načítání tohoto PDF souboru došlo k chybě.", - "loadingPdf": "Načítání PDF...", - "loadingPage": "Načítání stránky..." - }, "transfer": { "copyToHost": "Kopírovat na hostitele…", "moveToHost": "Přesunout na hostitele…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Opakovaný pokus se nezdařil", "transferFailedRetryHint": "V cíli byla uložena částečná data. Po obnovení připojení bude pokus pokračovat." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Přenos souborů mezi dvěma SSH hostiteli.", - "sourceServer": "Zdrojový server", - "destinationServer": "Cílový server", - "selectHost": "Vyberte hostitele", - "noHostSelected": "Nebyl vybrán žádný hostitel", - "move": "Pohyb", - "copy": "Kopie", - "upload": "Nahrát", - "itemsSelected": "Vybrána položka {{count}}", - "itemsSelected_other": "Vybráno {{count}} položek", - "ready": "Připraveno", - "loadingHosts": "Načítání hostitelů...", - "name": "Jméno", - "modified": "Upraveno", - "size": "Velikost", - "noFiles": "Žádné soubory", - "open": "OTEVŘENO", - "up": "Nahoru", - "refresh": "Obnovit", - "copyToTarget": "Kopírovat do cílového adresáře", - "rename": "Přejmenovat", - "delete": "Vymazat", - "editPermissions": "Upravit oprávnění", - "createNewFolder": "Vytvořit novou složku", - "deleteSelectedItems": "Smazat vybrané položky", - "deleteConfirm": "Smazat {{count}} vybranou položku?", - "deleteConfirm_other": "Smazat {{count}} vybraných položek?", - "cancel": "Zrušit", - "save": "Uložit", - "folderCreated": "Složka vytvořena", - "itemRenamed": "Položka přejmenována", - "actionFailed": "Akce se nezdařila", - "itemsDeleted": "{{count}} položka smazána", - "itemsDeleted_other": "{{count}} položek smazáno", - "deleteFailed": "Smazání se nezdařilo", - "permissionsUpdated": "Oprávnění aktualizována", - "noRemoteSessionConnected": "Není připojena žádná vzdálená relace", - "failedToListFiles": "Seznam souborů se nepodařilo zobrazit", - "failedToRefresh": "Obnovení se nezdařilo", - "destinationInsideSource": "Cíl nemůže být uvnitř vybrané zdrojové cesty.", - "sameHostNoOp": "Zdroj a cíl jsou stejná složka." - }, "tunnels": { "noSshTunnels": "Žádné SSH tunely", "createFirstTunnelMessage": "Zatím jste nevytvořili žádné SSH tunely. Chcete-li začít, nakonfigurujte tunelová připojení ve Správci hostitele.", diff --git a/src/ui/locales/translated/da_DK.json b/src/ui/locales/translated/da_DK.json index 530ca29da..5dc961ac4 100644 --- a/src/ui/locales/translated/da_DK.json +++ b/src/ui/locales/translated/da_DK.json @@ -715,7 +715,6 @@ "hostManager": "Værtsadministrator", "credentials": "Akkreditiver", "portForwarding": "Videresendelse", - "sftp": "SFTP", "connections": "Forbindelser", "serial": "Seriel", "keepSideMenuOpen": "Hold menuen åben", @@ -767,14 +766,12 @@ "tabRdp": "Landdistriktsudviklingsplan", "tabVnc": "VNC", "tabTunnels": "Tunneler", - "tabFiles": "Filer", "tabStats": "Værtsmålinger", "tabTelnet": "Telnet", "tabSharing": "Deling", "tabAuthentication": "Godkendelse", "terminal": "Terminal", "tunnel": "Tunnel", - "fileManager": "Filhåndtering", "serverStats": "Værtsmålinger", "status": { "available": "Tilgængelig", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} sprunget over (ingen IP fundet)", "proxmoxStatsUrlCopied": "Proxmox Stats URL kopieret til udklipsholder", "copyProxmoxStatsUrlAction": "Kopiér Proxmox Stats URL", - "enableFileManagerMonitor": "Aktivér Filhåndtering", - "enableFileManagerMonitorDesc": "Gennemse og administrer filer på denne vært via SFTP", - "scpLegacyLabel": "SCP Legacy-tilstand", - "scpLegacyDesc": "Brug ældre filoverførsel til servere, der ikke understøtter SFTP-undersystemet (f.eks. integrerede eller minimale SSH-servere).", - "defaultPathLabel": "Standardsti", - "fileManagerPathHint": "Den mappe, der skal åbnes, når filhåndteringen starter for denne vært.", "statusChecksLabel": "Statuskontroller", "enableStatusChecks": "Aktivér statuskontroller", "enableStatusChecksDesc": "Ping denne vært regelmæssigt for at bekræfte tilgængelighed", @@ -1278,7 +1269,6 @@ "deletedHost": "Slettet {{name}}", "copiedToClipboard": "Kopieret til udklipsholderen", "terminalUrlCopied": "Terminal-URL kopieret", - "fileManagerUrlCopied": "URL'en til filhåndtering er kopieret", "tunnelUrlCopied": "Tunnel-URL kopieret", "dockerUrlCopied": "Docker-URL kopieret", "hostMetricsUrlCopied": "URL til værtsmålinger kopieret", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopiér adresse", "copyLink": "Kopiér link", "copyTerminalUrlAction": "Kopiér terminal-URL", - "copyFileManagerUrlAction": "Kopiér URL til filhåndtering", "copyTunnelUrlAction": "Kopiér tunnel-URL", "copyDockerUrlAction": "Kopiér Docker-URL", "copyHostMetricsUrlAction": "Kopiér URL for værtsmålinger", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Fjern fra Split" } }, - "fileManager": { - "noHostSelected": "Ingen vært valgt", - "initializingEditor": "Initialiserer editor...", - "file": "Fil", - "folder": "Folder", - "uploadFile": "Upload fil", - "downloadFile": "Download", - "extractArchive": "Uddrag af arkivet", - "extractingArchive": "Udtrækker {{name}}...", - "archiveExtractedSuccessfully": "{{name}} blev udpakket", - "extractFailed": "Udpakning mislykkedes", - "compressFile": "Komprimer fil", - "compressFiles": "Komprimer filer", - "compressFilesDesc": "Komprimer {{count}} elementer til et arkiv", - "archiveName": "Arkivnavn", - "enterArchiveName": "Indtast arkivnavn...", - "compressionFormat": "Kompressionsformat", - "selectedFiles": "Valgte filer", - "andMoreFiles": "og {{count}} mere...", - "compress": "Sammenpresse", - "compressingFiles": "Komprimerer {{count}} elementer til {{name}}...", - "filesCompressedSuccessfully": "{{name}} er oprettet", - "compressFailed": "Komprimering mislykkedes", - "edit": "Redigere", - "preview": "Forhåndsvisning", - "previous": "Tidligere", - "next": "Næste", - "pageXOfY": "Side {{current}} af {{total}}", - "zoomOut": "Zoom ud", - "zoomIn": "Zoom ind", - "newFile": "Ny fil", - "newFolder": "Ny mappe", - "rename": "Omdøb", - "uploading": "Uploader...", - "uploadingFile": "Uploader {{name}}...", - "fileName": "Filnavn", - "folderName": "Mappenavn", - "fileUploadedSuccessfully": "Filen \"{{name}}\" er blevet uploadet", - "failedToUploadFile": "Kunne ikke uploade filen", - "uploadingFolderFiles": "Uploader {{count}} fil(er)...", - "uploadedFolderFiles": "Uploadede {{count}} fil(er)", - "uploadedFolderPartial": "Upload af {{uploaded}} fil(er), {{failed}} mislykkedes", - "fileDownloadedSuccessfully": "Filen \"{{name}}\" er blevet downloadet", - "failedToDownloadFile": "Kunne ikke downloade filen", - "fileCreatedSuccessfully": "Filen \"{{name}}\" er blevet oprettet", - "folderCreatedSuccessfully": "Mappen \"{{name}}\" er blevet oprettet", - "failedToCreateItem": "Kunne ikke oprette elementet", - "operationFailed": "{{operation}} handling mislykkedes for {{name}}: {{error}}", - "failedToResolveSymlink": "Kunne ikke løse symbolsk link", - "itemsDeletedSuccessfully": "{{count}} elementer er blevet slettet", - "failedToDeleteItems": "Kunne ikke slette elementer", - "sudoPasswordRequired": "Administratoradgangskode påkrævet", - "enterSudoPassword": "Indtast sudo-adgangskoden for at fortsætte denne handling", - "sudoPassword": "Sudo-adgangskode", - "sudoOperationFailed": "Sudo-handlingen mislykkedes", - "sudoAuthFailed": "Sudo-godkendelse mislykkedes", - "dragFilesToUpload": "Slip filer her for at uploade", - "dropToUploadHere": "Slip for at uploade her", - "dropToDownloadHere": "Slip for at downloade her", - "localFiles": "Lokal", - "showLocalFiles": "Vis lokale filer (delt visning)", - "hideLocalFiles": "Skjul lokale filer", - "localHome": "Hjemmemappe", - "localPathPlaceholder": "Lokal mappesti", - "localShowHidden": "Vis skjulte filer", - "localHideHidden": "Skjul skjulte filer", - "localRevealInFileManager": "Vis i systemfilhåndtering", - "localCannotOpenFolder": "Kan ikke åbne mappen", - "localItemCount": "{{count}} elementer", - "localSelectedCount": "{{count}} valgt", - "back": "Tilbage", - "forward": "Forward", - "up": "Op", - "kind": "Slags", - "localUploadingProgress": "Uploader {{current}} af {{total}}", - "localDownloadingProgress": "Downloader {{current}} af {{total}}", - "localTransferInFlight": "{{count}} på én gang", - "localPreparingDownload": "Forbereder download...", - "localTransferCancelled": "Overførsel annulleret", - "localNothingToTransfer": "Intet at overføre", - "localUploadComplete": "Uploadede {{count}} fil(er)", - "localUploadFailed": "Upload mislykkedes", - "localUploadPartial": "Upload af {{done}} fil(er), {{failed}} mislykkedes", - "localDownloadComplete": "Downloadede {{count}} fil(er)", - "localDownloadFailed": "Download mislykkedes", - "localDownloadPartial": "Download af {{done}} fil(er), {{failed}} mislykkedes", - "localReplaceExistingPrompt": "{{count}} elementer findes allerede i denne mappe. Skal de erstattes?", - "localReplace": "Erstatte", - "localSkipExisting": "Springe", - "localSkippedExisting": "Springede {{count}} eksisterende element(er) over", - "localUnsafeNamesSkipped": "Springede {{count}} elementer over, hvis navne ikke kan bruges på denne computer", - "localOpen": "Åben", - "localOpenFolder": "Åbn mappe", - "localUploadToRemote": "Upload til server", - "localUploadToRemoteMany": "Upload {{count}} elementer til serveren", - "localMoveToTrash": "Flyt til papirkurven", - "localMoveToTrashMany": "Flyt {{count}} elementer til papirkurven", - "localTrashConfirmSingle": "Flytte \"{{name}}\" til papirkurven?", - "localTrashConfirmFolder": "Flytte mappen \"{{name}}\" og alt dens indhold til papirkurven?", - "localTrashConfirmMany": "Flytte {{count}} elementer til papirkurven?", - "localTrashed": "Flyttede {{count}} element(er) til papirkurven", - "localTrashFailed": "Kunne ikke flytte til papirkurven", - "localRenameFailed": "Omdøbning mislykkedes", - "localCreateFailed": "Kunne ikke oprette elementet", - "goToParentFolder": "Gå til overordnet mappe", - "resizeSidebar": "Træk for at ændre størrelsen på sidebjælken (dobbeltklik for at nulstille)", - "resizeLocalPane": "Træk for at ændre størrelsen på den lokale rude (dobbeltklik for at nulstille)", - "columns": "Kolonner", - "columnsHint": "Højreklik for at vælge kolonner", - "emptyFolder": "Denne mappe er tom", - "searchFiles": "Søg i filer...", - "upload": "Upload", - "selectHostToStart": "Vælg en vært for at starte filhåndtering", - "sshRequiredForFileManager": "Filhåndtering kræver SSH. Denne vært har ikke SSH aktiveret.", - "failedToConnect": "Kunne ikke oprette forbindelse til SSH", - "failedToLoadDirectory": "Kunne ikke indlæse mappen", - "noSSHConnection": "Ingen SSH-forbindelse tilgængelig", - "copy": "Kopi", - "cut": "Skære", - "paste": "Indsæt", - "copyPath": "Kopiér sti", - "copyPaths": "Kopier stier", - "delete": "Slet", - "properties": "Ejendomme", - "refresh": "Opfriske", - "downloadFiles": "Download {{count}} filer til browseren", - "copyFiles": "Kopiér {{count}} elementer", - "cutFiles": "Klip {{count}} elementer", - "deleteFiles": "Slet {{count}} elementer", - "filesCopiedToClipboard": "{{count}} elementer kopieret til udklipsholderen", - "filesCutToClipboard": "{{count}} elementer klippet til udklipsholder", - "pathCopiedToClipboard": "Sti kopieret til udklipsholder", - "pathsCopiedToClipboard": "{{count}} stier kopieret til udklipsholder", - "failedToCopyPath": "Kunne ikke kopiere stien til udklipsholderen", - "copyFolderLink": "Kopiér link til mappe", - "copyCurrentFolderLink": "Kopiér link til aktuel mappe", - "folderLinkCopied": "Mappelink kopieret til udklipsholder", - "failedToCopyFolderLink": "Kunne ikke kopiere mappelinket", - "movedItems": "Flyttede {{count}} elementer", - "failedToDeleteItem": "Kunne ikke slette elementet", - "itemRenamedSuccessfully": "{{type}} er omdøbt", - "failedToRenameItem": "Kunne ikke omdøbe elementet", - "download": "Download", - "openExternalEditor": "Åbn eksternt", - "chooseExternalEditor": "Vælg ekstern editor", - "externalEditorSelected": "Ekstern editor valgt", - "externalEditorDesktopOnly": "Ekstern editor er kun tilgængelig i desktop-appen", - "externalEditorOpened": "Åbnet i ekstern editor. Hvis du gemmer der, uploades ændringerne tilbage til serveren.", - "failedToOpenExternalEditor": "Kunne ikke åbne den eksterne editor", - "failedToSelectExternalEditor": "Kunne ikke vælge ekstern editor", - "permissions": "Tilladelser", - "size": "Størrelse", - "modified": "Ændret", - "path": "Sti", - "confirmDelete": "Er du sikker på, at du vil slette {{name}}?", - "permissionDenied": "Tilladelse nægtet", - "serverError": "Serverfejl", - "fileSavedSuccessfully": "Filen er gemt", - "failedToSaveFile": "Kunne ikke gemme filen", - "confirmDeleteSingleItem": "Er du sikker på, at du vil slette \"{{name}} \" permanent?", - "confirmDeleteMultipleItems": "Er du sikker på, at du vil slette {{count}} elementer permanent?", - "confirmDeleteMultipleItemsWithFolders": "Er du sikker på, at du vil slette {{count}} elementer permanent? Dette inkluderer mapper og deres indhold.", - "confirmDeleteFolder": "Er du sikker på, at du vil slette mappen \"{{name}}\" og alt dens indhold permanent?", - "permanentDeleteWarning": "Denne handling kan ikke fortrydes. Elementet/elementerne vil blive permanent slettet fra serveren.", - "moveToTrashWarning": "Elementet/elementerne vil blive flyttet til Termix-papirkurven og kan gendannes, inden opbevaringsperioden udløber.", - "itemsMovedToTrash": "{{count}} element(er) flyttet til papirkurven", - "trash": "Skrald", - "trashDescription": "Slettede elementer gemmes i {{days}} dag(e) på denne vært.", - "trashLoadFailed": "Kunne ikke indlæse papirkurven", - "trashEmpty": "Papirkurven er tom", - "trashRestore": "Gendan", - "trashRestored": "Gendannet {{name}}", - "trashRestoreFailed": "Elementet kunne ikke gendannes. Den oprindelige sti findes muligvis allerede.", - "trashDeletePermanently": "Slet permanent", - "trashDeleteConfirm": "Vil du slette {{name}}permanent? Dette kan ikke fortrydes.", - "trashPermanentlyDeleted": "Permanent slettet {{name}}", - "trashDeleteFailed": "Kunne ikke slette elementet permanent", - "trashEmptyAction": "Tøm papirkurven", - "trashEmptyConfirm": "Vil du slette alle elementer i papirkurven permanent? Dette kan ikke fortrydes.", - "trashEmptied": "Skraldespanden tømt", - "trashRetention": "Automatisk oprydning efter (dage)", - "trashRetentionSaved": "Papirkurvsopbevaring opdateret", - "trashRetentionFailed": "Kunne ikke opdatere opbevaring af papirkurven", - "trashUnavailableConfirm": "Dette element kan ikke flyttes til papirkurven, normalt fordi det er på et andet filsystem eller kræver forhøjede tilladelser. Slet det permanent i stedet?", - "recent": "Nylig", - "pinned": "Fastgjort", - "folderShortcuts": "Mappegenveje", - "failedToReconnectSSH": "Kunne ikke genoprette forbindelsen til SSH-sessionen", - "openTerminalHere": "Åbn terminalen her", - "run": "Løbe", - "openTerminalInFolder": "Åbn Terminal i denne mappe", - "openTerminalInFileLocation": "Åbn terminalen på filplaceringen", - "runningFile": "Løb - {{file}}", - "onlyRunExecutableFiles": "Kan kun køre eksekverbare filer", - "directories": "Kataloger", - "removedFromRecentFiles": "Fjernede \"{{name}}\" fra seneste filer", - "removeFailed": "Fjernelse mislykkedes", - "unpinnedSuccessfully": "\"{{name}}\" blev frigjort", - "unpinFailed": "Frigørelse mislykkedes", - "removedShortcut": "Fjernede genvejen \"{{name}}\"", - "removeShortcutFailed": "Fjernelse af genvej mislykkedes", - "clearedAllRecentFiles": "Rydde alle seneste filer", - "clearFailed": "Rydning mislykkedes", - "removeFromRecentFiles": "Fjern fra seneste filer", - "clearAllRecentFiles": "Ryd alle seneste filer", - "unpinFile": "Frigør fil", - "removeShortcut": "Fjern genvej", - "pinFile": "Fastgør fil", - "addToShortcuts": "Føj til genveje", - "pasteFailed": "Indsætning mislykkedes", - "noUndoableActions": "Ingen handlinger, der kan fortrydes", - "undoCopySuccess": "Fortrydt kopiering: Slettede {{count}} kopierede filer", - "undoCopyFailedDelete": "Fortryd mislykkedes: Kunne ikke slette nogen kopierede filer", - "undoCopyFailedNoInfo": "Fortryd mislykkedes: Kunne ikke finde kopierede filoplysninger", - "undoMoveSuccess": "Fortrydt flyttehandling: Flyttede {{count}} filer tilbage til den oprindelige placering", - "undoMoveFailedMove": "Fortryd mislykkedes: Kunne ikke flytte nogen filer tilbage", - "undoMoveFailedNoInfo": "Fortryd mislykkedes: Kunne ikke finde oplysninger om den flyttede fil", - "undoDeleteNotSupported": "Sletningen kan ikke fortrydes: Filerne er blevet permanent slettet fra serveren", - "undoTypeNotSupported": "Ikke-understøttet fortrydelseshandlingstype", - "undoOperationFailed": "Fortrydelseshandling mislykkedes", - "unknownError": "Ukendt fejl", - "confirm": "Bekræfte", - "find": "Finde...", - "replace": "Erstatte", - "downloadInstead": "Download i stedet", - "keyboardShortcuts": "Tastaturgenveje", - "searchAndReplace": "Søg og erstat", - "editing": "Redigering", - "search": "Søge", - "findNext": "Find næste", - "findPrevious": "Find forrige", - "save": "Spare", - "selectAll": "Vælg alle", - "undo": "Fortryd", - "redo": "Gentag", - "moveLineUp": "Flyt linje op", - "moveLineDown": "Flyt linje ned", - "toggleComment": "Slå kommentar til/fra", - "autoComplete": "Autofuldførelse", - "imageLoadError": "Billedet kunne ikke indlæses", - "startTyping": "Begynd at skrive...", - "unknownSize": "Ukendt størrelse", - "fileIsEmpty": "Filen er tom", - "largeFileWarning": "Advarsel om stor fil", - "largeFileWarningDesc": "Denne fil er {{size}} stor, hvilket kan forårsage problemer med ydeevnen, når den åbnes som tekst.", - "fileNotFoundAndRemoved": "Filen \"{{name}}\" blev ikke fundet og er blevet fjernet fra seneste/fastgjorte filer", - "failedToLoadFile": "Kunne ikke indlæse filen: {{error}}", - "serverErrorOccurred": "Der opstod en serverfejl. Prøv igen senere.", - "autoSaveFailed": "Automatisk lagring mislykkedes", - "fileAutoSaved": "Fil gemt automatisk", - "moveFileFailed": "Kunne ikke flytte {{name}}", - "moveOperationFailed": "Flytteoperationen mislykkedes", - "canOnlyCompareFiles": "Kan kun sammenligne to filer", - "comparingFiles": "Sammenligning af filer: {{file1}} og {{file2}}", - "dragFailed": "Trækhandlingen mislykkedes", - "filePinnedSuccessfully": "Filen \"{{name}}\" er blevet fastgjort", - "pinFileFailed": "Kunne ikke fastlåse filen", - "fileUnpinnedSuccessfully": "Filen \"{{name}}\" blev frigjort", - "unpinFileFailed": "Kunne ikke frigøre filen", - "shortcutAddedSuccessfully": "Mappens genvej \"{{name}}\" blev tilføjet", - "addShortcutFailed": "Kunne ikke tilføje genvej", - "operationCompletedSuccessfully": "{{operation}} {{count}} elementer er blevet genanvendt", - "operationCompleted": "{{operation}} {{count}} elementer", - "downloadFileSuccess": "Fil {{name}} er blevet downloadet", - "downloadFileFailed": "Download mislykkedes", - "downloadingFile": "Downloader {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Flyt til {{name}}", - "diffCompareWith": "Differens sammenligning med {{name}}", - "dragOutsideToDownload": "Træk uden for vinduet for at downloade ({{count}} filer)", - "newFolderDefault": "NyMappe", - "newFileDefault": "Nyfil.txt", - "successfullyMovedItems": "{{count}} elementer er flyttet til {{target}}", - "move": "Flytte", - "searchInFile": "Søg i filen (Ctrl+F)", - "showKeyboardShortcuts": "Vis tastaturgenveje", - "decreaseFontSize": "Formindsk skriftstørrelsen", - "increaseFontSize": "Øg skriftstørrelsen", - "startWritingMarkdown": "Begynd at skrive dit indhold til nedskrivning...", - "loadingFileComparison": "Indlæser filsammenligning...", - "reload": "Genindlæs", - "compare": "Sammenligne", - "sideBySide": "Side om side", - "inline": "Indlejret", - "fileComparison": "Filsammenligning: {{file1}} vs. {{file2}}", - "fileTooLarge": "Filen er for stor: {{error}}", - "sshConnectionFailed": "SSH-forbindelse mislykkedes. Kontroller venligst din forbindelse til {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Kunne ikke indlæse filen: {{error}}", - "connecting": "Forbinder...", - "connectedSuccessfully": "Forbindelsen er oprettet", - "totpVerificationFailed": "TOTP-bekræftelse mislykkedes", - "warpgateVerificationFailed": "Warpgate-godkendelse mislykkedes", - "authenticationFailed": "Godkendelse mislykkedes", - "incorrectPassphrase": "Forkert adgangskode. Prøv igen.", - "verificationCodePrompt": "Bekræftelseskode:", - "changePermissions": "Ændre tilladelser", - "currentPermissions": "Nuværende tilladelser", - "owner": "Ejer", - "group": "Gruppe", - "others": "Andre", - "read": "Læse", - "write": "Skrive", - "execute": "Udfør", - "permissionsChangedSuccessfully": "Tilladelserne er ændret", - "failedToChangePermissions": "Kunne ikke ændre tilladelser", - "name": "Navn", - "sortByName": "Navn", - "sortByDate": "Dato ændret", - "sortBySize": "Størrelse", - "ascending": "Stigende", - "descending": "Faldende", - "root": "Rod", - "new": "Ny", - "sortBy": "Sortér efter", - "items": "Varer", - "selected": "Valgt", - "editor": "Redaktør", - "octal": "Oktal", - "storage": "Opbevaring", - "disk": "Disk", - "used": "Brugt", - "of": "af", - "toggleSidebar": "Slå sidebjælken til/fra", - "compactLayout": "Brug kompakt layout", - "comfortableLayout": "Brug et komfortabelt layout", - "cannotLoadPdf": "Kan ikke indlæse PDF'en", - "pdfLoadError": "Der opstod en fejl under indlæsning af denne PDF-fil.", - "loadingPdf": "Indlæser PDF...", - "loadingPage": "Indlæser side..." - }, "transfer": { "copyToHost": "Kopiér til vært…", "moveToHost": "Flyt til vært…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Genforsøg mislykkedes", "transferFailedRetryHint": "Delvise data blev gemt på destinationen. Forsøget genoptages, når forbindelsen er tilbage." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Overfør filer mellem to SSH-værter.", - "sourceServer": "Kildeserver", - "destinationServer": "Destinationsserver", - "selectHost": "Vælg vært", - "noHostSelected": "Ingen vært valgt", - "move": "Flytte", - "copy": "Kopi", - "upload": "Upload", - "itemsSelected": "{{count}} element valgt", - "itemsSelected_other": "{{count}} elementer valgt", - "ready": "Parat", - "loadingHosts": "Indlæser værter...", - "name": "Navn", - "modified": "Ændret", - "size": "Størrelse", - "noFiles": "Ingen filer", - "open": "Åben", - "up": "Op", - "refresh": "Opfriske", - "copyToTarget": "Kopiér til målmappe", - "rename": "Omdøb", - "delete": "Slet", - "editPermissions": "Rediger tilladelser", - "createNewFolder": "Opret ny mappe", - "deleteSelectedItems": "Slet valgte elementer", - "deleteConfirm": "Slet {{count}} det valgte element?", - "deleteConfirm_other": "Slet {{count}} valgte elementer?", - "cancel": "Ophæve", - "save": "Spare", - "folderCreated": "Mappe oprettet", - "itemRenamed": "Varen er omdøbt", - "actionFailed": "Handlingen mislykkedes", - "itemsDeleted": "{{count}} element slettet", - "itemsDeleted_other": "{{count}} elementer slettet", - "deleteFailed": "Sletning mislykkedes", - "permissionsUpdated": "Tilladelser opdateret", - "noRemoteSessionConnected": "Ingen fjernsession tilsluttet", - "failedToListFiles": "Kunne ikke liste filer", - "failedToRefresh": "Kunne ikke opdatere", - "destinationInsideSource": "Destinationen må ikke være inden for den valgte kildesti.", - "sameHostNoOp": "Kilde og destination er den samme mappe." - }, "tunnels": { "noSshTunnels": "Ingen SSH-tunneler", "createFirstTunnelMessage": "Du har endnu ikke oprettet nogen SSH-tunneler. Konfigurer tunnelforbindelser i Host Manager for at komme i gang.", diff --git a/src/ui/locales/translated/de_DE.json b/src/ui/locales/translated/de_DE.json index 118e61334..0772d93fe 100644 --- a/src/ui/locales/translated/de_DE.json +++ b/src/ui/locales/translated/de_DE.json @@ -715,7 +715,6 @@ "hostManager": "Host-Manager", "credentials": "Zugangsdaten", "portForwarding": "Weiterleitung", - "sftp": "SFTP", "connections": "Verbindungen", "serial": "Seriell", "keepSideMenuOpen": "Menü geöffnet lassen", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tunnel", - "tabFiles": "Dateien", "tabStats": "Host-Metriken", "tabTelnet": "Telnet", "tabSharing": "Freigabe", "tabAuthentication": "Authentifizierung", "terminal": "Terminal", "tunnel": "Tunnel", - "fileManager": "Dateimanager", "serverStats": "Host-Metriken", "status": { "available": "Verfügbar", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} übersprungen (keine IP-Adresse gefunden)", "proxmoxStatsUrlCopied": "Proxmox-Statistik-URL in die Zwischenablage kopiert", "copyProxmoxStatsUrlAction": "Proxmox-Statistik-URL kopieren", - "enableFileManagerMonitor": "Dateimanager aktivieren", - "enableFileManagerMonitorDesc": "Dateien auf diesem Host über SFTP durchsuchen und verwalten", - "scpLegacyLabel": "SCP-Legacy-Modus", - "scpLegacyDesc": "Verwenden Sie die Legacy-Dateiübertragung für Server, die das SFTP-Subsystem nicht unterstützen (z. B. eingebettete oder minimale SSH-Server).", - "defaultPathLabel": "Standardpfad", - "fileManagerPathHint": "Das Verzeichnis, das geöffnet wird, wenn der Dateimanager für diesen Host gestartet wird.", "statusChecksLabel": "Statusprüfungen", "enableStatusChecks": "Statusprüfungen aktivieren", "enableStatusChecksDesc": "Diesen Host regelmäßig anpingen, um die Verfügbarkeit zu überprüfen", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} gelöscht", "copiedToClipboard": "In die Zwischenablage kopiert", "terminalUrlCopied": "Terminal-URL kopiert", - "fileManagerUrlCopied": "Dateimanager-URL kopiert", "tunnelUrlCopied": "Tunnel-URL kopiert", "dockerUrlCopied": "Docker-URL kopiert", "hostMetricsUrlCopied": "Host-Metriken-URL kopiert", @@ -1296,7 +1286,6 @@ "copyAddress": "Adresse kopieren", "copyLink": "Link kopieren", "copyTerminalUrlAction": "Terminal-URL kopieren", - "copyFileManagerUrlAction": "Dateimanager-URL kopieren", "copyTunnelUrlAction": "Tunnel-URL kopieren", "copyDockerUrlAction": "Docker-URL kopieren", "copyHostMetricsUrlAction": "Host-Metriken-URL kopieren", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Aus Teilung entfernen" } }, - "fileManager": { - "noHostSelected": "Kein Host ausgewählt", - "initializingEditor": "Editor wird initialisiert...", - "file": "Datei", - "folder": "Ordner", - "uploadFile": "Datei hochladen", - "downloadFile": "Herunterladen", - "extractArchive": "Archiv entpacken", - "extractingArchive": "Extrahiere {{name}}...", - "archiveExtractedSuccessfully": "{{name}} erfolgreich extrahiert", - "extractFailed": "Extrahieren fehlgeschlagen", - "compressFile": "Datei komprimieren", - "compressFiles": "Dateien komprimieren", - "compressFilesDesc": "{{count}} Elemente in ein Archiv komprimieren", - "archiveName": "Archivname", - "enterArchiveName": "Archivname eingeben...", - "compressionFormat": "Komprimierungsformat", - "selectedFiles": "Ausgewählte Dateien", - "andMoreFiles": "und {{count}} weitere...", - "compress": "Komprimieren", - "compressingFiles": "Komprimiere {{count}} Elemente in {{name}}...", - "filesCompressedSuccessfully": "{{name}} erfolgreich erstellt", - "compressFailed": "Komprimierung fehlgeschlagen", - "edit": "Bearbeiten", - "preview": "Vorschau", - "previous": "Zurück", - "next": "Weiter", - "pageXOfY": "Seite {{current}} von {{total}}", - "zoomOut": "Verkleinern", - "zoomIn": "Vergrößern", - "newFile": "Neue Datei", - "newFolder": "Neuer Ordner", - "rename": "Umbenennen", - "uploading": "Lade hoch...", - "uploadingFile": "Lade {{name}} hoch...", - "fileName": "Dateiname", - "folderName": "Ordnername", - "fileUploadedSuccessfully": "Datei \"{{name}}\" erfolgreich hochgeladen", - "failedToUploadFile": "Hochladen der Datei fehlgeschlagen", - "uploadingFolderFiles": "Uploading {{count}} file(s)...", - "uploadedFolderFiles": "{{count}} Datei(en) erfolgreich hochgeladen", - "uploadedFolderPartial": "Hochladen von {{uploaded}} Datei(en), {{failed}} fehlgeschlagen", - "fileDownloadedSuccessfully": "Datei \"{{name}}\" erfolgreich heruntergeladen", - "failedToDownloadFile": "Herunterladen der Datei fehlgeschlagen", - "fileCreatedSuccessfully": "Datei \"{{name}}\" erfolgreich erstellt", - "folderCreatedSuccessfully": "Ordner \"{{name}}\" erfolgreich erstellt", - "failedToCreateItem": "Fehler beim Erstellen des Elements", - "operationFailed": "{{operation}}-Operation für {{name}} fehlgeschlagen: {{error}}", - "failedToResolveSymlink": "Fehler beim Auflösen des Symlinks", - "itemsDeletedSuccessfully": "{{count}} Elemente erfolgreich gelöscht", - "failedToDeleteItems": "Fehler beim Löschen der Elemente", - "sudoPasswordRequired": "Sudo-Passwort erforderlich", - "enterSudoPassword": "Sudo-Passwort eingeben, um diesen Vorgang fortzusetzen", - "sudoPassword": "Sudo-Passwort", - "sudoOperationFailed": "Sudo-Vorgang fehlgeschlagen", - "sudoAuthFailed": "Sudo-Authentifizierung fehlgeschlagen", - "dragFilesToUpload": "Dateien hier ablegen, um sie hochzuladen", - "dropToUploadHere": "Hier zum Hochladen ablegen", - "dropToDownloadHere": "Hier klicken zum Herunterladen", - "localFiles": "Lokal", - "showLocalFiles": "Lokale Dateien anzeigen (geteilte Ansicht)", - "hideLocalFiles": "Lokale Dateien ausblenden", - "localHome": "Startseitenordner", - "localPathPlaceholder": "Lokaler Ordnerpfad", - "localShowHidden": "Versteckte Dateien anzeigen", - "localHideHidden": "Versteckte Dateien ausblenden", - "localRevealInFileManager": "Im Systemdateimanager anzeigen", - "localCannotOpenFolder": "Ordner kann nicht geöffnet werden", - "localItemCount": "{{count}} Artikel", - "localSelectedCount": "{{count}} ausgewählt", - "back": "Zurück", - "forward": "Nach vorne", - "up": "Hoch", - "kind": "Art", - "localUploadingProgress": "Hochladen von {{current}} von {{total}}", - "localDownloadingProgress": "Herunterladen von {{current}} von {{total}}", - "localTransferInFlight": "{{count}} gleichzeitig", - "localPreparingDownload": "Download wird vorbereitet...", - "localTransferCancelled": "Transfer storniert", - "localNothingToTransfer": "Nichts zu übertragen", - "localUploadComplete": "Es wurden {{count}} Dateien hochgeladen", - "localUploadFailed": "Hochladen fehlgeschlagen", - "localUploadPartial": "Hochladen von {{done}} Datei(en), {{failed}} fehlgeschlagen", - "localDownloadComplete": "Heruntergeladene Datei(en) {{count}}", - "localDownloadFailed": "Download fehlgeschlagen", - "localDownloadPartial": "Download der Datei(en) {{done}} , {{failed}} fehlgeschlagen", - "localReplaceExistingPrompt": "{{count}} In diesem Ordner sind bereits Elemente vorhanden. Sollen sie ersetzt werden?", - "localReplace": "Ersetzen", - "localSkipExisting": "Überspringen", - "localSkippedExisting": "Übersprungene {{count}} vorhandene Elemente", - "localUnsafeNamesSkipped": "{{count}} Elemente übersprungen, deren Namen auf diesem Computer nicht verwendet werden können", - "localOpen": "Offen", - "localOpenFolder": "Ordner öffnen", - "localUploadToRemote": "Auf Server hochladen", - "localUploadToRemoteMany": "{{count}} Elemente auf den Server hochladen", - "localMoveToTrash": "In den Papierkorb verschieben", - "localMoveToTrashMany": "Verschiebe {{count}} Objekte in den Papierkorb.", - "localTrashConfirmSingle": "\"{{name}}\" in den Papierkorb verschieben?", - "localTrashConfirmFolder": "Den Ordner \"{{name}}\" und seinen gesamten Inhalt in den Papierkorb verschieben?", - "localTrashConfirmMany": "{{count}} Elemente in den Papierkorb verschieben?", - "localTrashed": "{{count}} Elemente wurden in den Papierkorb verschoben.", - "localTrashFailed": "Konnte nicht in den Mülleimer bewegt werden.", - "localRenameFailed": "Umbenennen fehlgeschlagen", - "localCreateFailed": "Element konnte nicht erstellt werden", - "goToParentFolder": "Gehe zum übergeordneten Ordner", - "resizeSidebar": "Ziehen Sie, um die Seitenleiste in der Größe anzupassen (Doppelklick zum Zurücksetzen).", - "resizeLocalPane": "Ziehen Sie, um die Größe des lokalen Fensters zu ändern (Doppelklick zum Zurücksetzen).", - "columns": "Spalten", - "columnsHint": "Klicken Sie mit der rechten Maustaste, um Spalten auszuwählen.", - "emptyFolder": "Dieser Ordner ist leer", - "searchFiles": "Dateien durchsuchen...", - "upload": "Hochladen", - "selectHostToStart": "Wählen Sie einen Host aus, um die Dateiverwaltung zu starten", - "sshRequiredForFileManager": "Dateimanager erfordert SSH. SSH ist auf diesem Host nicht aktiviert.", - "failedToConnect": "Verbindung zu SSH fehlgeschlagen", - "failedToLoadDirectory": "Fehler beim Laden des Verzeichnisses", - "noSSHConnection": "Keine SSH-Verbindung verfügbar", - "copy": "Kopieren", - "cut": "Ausschneiden", - "paste": "Einfügen", - "copyPath": "Pfad kopieren", - "copyPaths": "Pfade kopieren", - "delete": "Löschen", - "properties": "Eigenschaften", - "refresh": "Aktualisieren", - "downloadFiles": "{{count}} Dateien über den Browser herunterladen", - "copyFiles": "{{count}} Elemente kopieren", - "cutFiles": "{{count}} Elemente ausschneiden", - "deleteFiles": "{{count}} Elemente löschen", - "filesCopiedToClipboard": "{{count}} Elemente in die Zwischenablage kopiert", - "filesCutToClipboard": "{{count}} Elemente in die Zwischenablage ausgeschnitten", - "pathCopiedToClipboard": "Pfad in die Zwischenablage kopiert", - "pathsCopiedToClipboard": "{{count}} Pfade in die Zwischenablage kopiert", - "failedToCopyPath": "Fehler beim Kopieren des Pfads in die Zwischenablage", - "copyFolderLink": "Link zum Ordner kopieren", - "copyCurrentFolderLink": "Link zum aktuellen Ordner kopieren", - "folderLinkCopied": "Ordner-Link in die Zwischenablage kopiert", - "failedToCopyFolderLink": "Fehler beim Kopieren des Ordner-Links", - "movedItems": "{{count}} Elemente verschoben", - "failedToDeleteItem": "Fehler beim Löschen des Elements", - "itemRenamedSuccessfully": "{{type}} erfolgreich umbenannt", - "failedToRenameItem": "Fehler beim Umbenennen des Elements", - "download": "Herunterladen", - "openExternalEditor": "Extern öffnen", - "chooseExternalEditor": "Externen Editor auswählen", - "externalEditorSelected": "Externer Editor ausgewählt", - "externalEditorDesktopOnly": "Externer Editor ist nur in der Desktop-App verfügbar", - "externalEditorOpened": "Im externen Editor geöffnet. Beim Speichern werden die Änderungen auf den Server hochgeladen.", - "failedToOpenExternalEditor": "Fehler beim Öffnen des externen Editors", - "failedToSelectExternalEditor": "Fehler bei der Auswahl des externen Editors", - "permissions": "Berechtigungen", - "size": "Größe", - "modified": "Geändert", - "path": "Pfad", - "confirmDelete": "Sind Sie sicher, dass Sie {{name}} löschen möchten?", - "permissionDenied": "Zugriff verweigert", - "serverError": "Serverfehler", - "fileSavedSuccessfully": "Datei erfolgreich gespeichert", - "failedToSaveFile": "Fehler beim Speichern der Datei", - "confirmDeleteSingleItem": "Sind Sie sicher, dass Sie \"{{name}}\" endgültig löschen möchten?", - "confirmDeleteMultipleItems": "Sind Sie sicher, dass Sie {{count}} Elemente endgültig löschen möchten?", - "confirmDeleteMultipleItemsWithFolders": "Sind Sie sicher, dass Sie {{count}} Elemente endgültig löschen möchten? Dies beinhaltet auch Ordner und deren Inhalte.", - "confirmDeleteFolder": "Sind Sie sicher, dass Sie den Ordner \"{{name}}\" und seinen gesamten Inhalt endgültig löschen möchten?", - "permanentDeleteWarning": "Diese Aktion kann nicht rückgängig gemacht werden. Die Elemente werden endgültig vom Server gelöscht.", - "moveToTrashWarning": "Die Elemente werden in den Termix-Papierkorb verschoben und können vor Ablauf der Aufbewahrungsfrist wiederhergestellt werden.", - "itemsMovedToTrash": "{{count}} Elemente in den Papierkorb verschoben", - "trash": "Müll", - "trashDescription": "Gelöschte Elemente werden auf diesem Host für {{days}} Tage aufbewahrt.", - "trashLoadFailed": "Der Papierkorb konnte nicht geladen werden.", - "trashEmpty": "Der Mülleimer ist leer", - "trashRestore": "Wiederherstellen", - "trashRestored": "Wiederhergestellt {{name}}", - "trashRestoreFailed": "Das Element konnte nicht wiederhergestellt werden. Der ursprüngliche Pfad existiert möglicherweise bereits.", - "trashDeletePermanently": "Endgültig löschen", - "trashDeleteConfirm": "{{name}}endgültig löschen? Dies kann nicht rückgängig gemacht werden.", - "trashPermanentlyDeleted": "Endgültig gelöscht {{name}}", - "trashDeleteFailed": "Fehler beim endgültigen Löschen des Eintrags", - "trashEmptyAction": "Mülleimer leeren", - "trashEmptyConfirm": "Alle Elemente im Papierkorb endgültig löschen? Dies kann nicht rückgängig gemacht werden.", - "trashEmptied": "Müll geleert", - "trashRetention": "Automatische Bereinigung nach (Tagen)", - "trashRetentionSaved": "Müllspeicherung aktualisiert", - "trashRetentionFailed": "Aktualisierung der Papierkorb-Aufbewahrung fehlgeschlagen", - "trashUnavailableConfirm": "Dieses Element kann nicht in den Papierkorb verschoben werden, üblicherweise weil es sich auf einem anderen Dateisystem befindet oder erhöhte Berechtigungen erfordert. Soll es stattdessen endgültig gelöscht werden?", - "recent": "Zuletzt verwendet", - "pinned": "Angepinnt", - "folderShortcuts": "Ordner-Verknüpfungen", - "failedToReconnectSSH": "Fehler beim Wiederherstellen der SSH-Sitzung", - "openTerminalHere": "Terminal hier öffnen", - "run": "Ausführen", - "openTerminalInFolder": "Terminal in diesem Ordner öffnen", - "openTerminalInFileLocation": "Terminal am Dateispeicherort öffnen", - "runningFile": "Läuft - {{file}}", - "onlyRunExecutableFiles": "Nur ausführbare Dateien können ausgeführt werden", - "directories": "Verzeichnisse", - "removedFromRecentFiles": "\"{{name}}\" aus den zuletzt verwendeten Dateien entfernt", - "removeFailed": "Entfernen fehlgeschlagen", - "unpinnedSuccessfully": "\"{{name}}\" erfolgreich gelöst", - "unpinFailed": "Lösen fehlgeschlagen", - "removedShortcut": "Verknüpfung \"{{name}}\" entfernt", - "removeShortcutFailed": "Entfernen der Verknüpfung fehlgeschlagen", - "clearedAllRecentFiles": "Alle zuletzt verwendeten Dateien gelöscht", - "clearFailed": "Löschen fehlgeschlagen", - "removeFromRecentFiles": "Aus zuletzt verwendeten Dateien entfernen", - "clearAllRecentFiles": "Alle zuletzt verwendeten Dateien löschen", - "unpinFile": "Datei lösen", - "removeShortcut": "Verknüpfung entfernen", - "pinFile": "Datei anheften", - "addToShortcuts": "Zu Verknüpfungen hinzufügen", - "pasteFailed": "Einfügen fehlgeschlagen", - "noUndoableActions": "Keine rückgängig zu machenden Aktionen", - "undoCopySuccess": "Kopiervorgang rückgängig gemacht: {{count}} kopierte Dateien gelöscht", - "undoCopyFailedDelete": "Rückgängig fehlgeschlagen: Konnte keine kopierten Dateien löschen", - "undoCopyFailedNoInfo": "Rückgängig fehlgeschlagen: Informationen zu kopierten Dateien nicht gefunden", - "undoMoveSuccess": "Verschiebevorgang rückgängig gemacht: {{count}} Dateien an den ursprünglichen Ort zurückverschoben", - "undoMoveFailedMove": "Rückgängig fehlgeschlagen: Konnte keine Dateien zurückverschieben", - "undoMoveFailedNoInfo": "Rückgängig fehlgeschlagen: Informationen zu verschobenen Dateien nicht gefunden", - "undoDeleteNotSupported": "Löschvorgang kann nicht rückgängig gemacht werden: Dateien wurden dauerhaft vom Server gelöscht", - "undoTypeNotSupported": "Nicht unterstützter Typ für Rückgängig-Vorgang", - "undoOperationFailed": "Rückgängig-Vorgang fehlgeschlagen", - "unknownError": "Unbekannter Fehler", - "confirm": "Bestätigen", - "find": "Suchen...", - "replace": "Ersetzen", - "downloadInstead": "Stattdessen herunterladen", - "keyboardShortcuts": "Tastaturkürzel", - "searchAndReplace": "Suchen & Ersetzen", - "editing": "Bearbeiten", - "search": "Suchen", - "findNext": "Weitersuchen", - "findPrevious": "Vorheriges suchen", - "save": "Speichern", - "selectAll": "Alles auswählen", - "undo": "Rückgängig", - "redo": "Wiederholen", - "moveLineUp": "Zeile nach oben verschieben", - "moveLineDown": "Zeile nach unten verschieben", - "toggleComment": "Kommentar umschalten", - "autoComplete": "Autovervollständigung", - "imageLoadError": "Bild konnte nicht geladen werden", - "startTyping": "Mit der Eingabe beginnen...", - "unknownSize": "Unbekannte Größe", - "fileIsEmpty": "Datei ist leer", - "largeFileWarning": "Warnung bei großen Dateien", - "largeFileWarningDesc": "Diese Datei ist {{size}} groß, was beim Öffnen als Text zu Leistungsproblemen führen kann.", - "fileNotFoundAndRemoved": "Datei \"{{name}}\" nicht gefunden und wurde aus den zuletzt verwendeten/angehefteten Dateien entfernt.", - "failedToLoadFile": "Datei konnte nicht geladen werden: {{error}}", - "serverErrorOccurred": "Ein Serverfehler ist aufgetreten. Bitte versuchen Sie es später erneut.", - "autoSaveFailed": "Automatisches Speichern fehlgeschlagen", - "fileAutoSaved": "Datei automatisch gespeichert", - "moveFileFailed": "Verschieben von {{name}} fehlgeschlagen", - "moveOperationFailed": "Verschiebevorgang fehlgeschlagen", - "canOnlyCompareFiles": "Es können nur zwei Dateien verglichen werden", - "comparingFiles": "Vergleiche Dateien: {{file1}} und {{file2}}", - "dragFailed": "Ziehvorgang fehlgeschlagen", - "filePinnedSuccessfully": "Datei \"{{name}}\" wurde erfolgreich angeheftet", - "pinFileFailed": "Anheften der Datei fehlgeschlagen", - "fileUnpinnedSuccessfully": "Datei \"{{name}}\" wurde erfolgreich abgeheftet", - "unpinFileFailed": "Loslösen der Datei fehlgeschlagen", - "shortcutAddedSuccessfully": "Ordnerverknüpfung \"{{name}}\" erfolgreich hinzugefügt", - "addShortcutFailed": "Hinzufügen der Verknüpfung fehlgeschlagen", - "operationCompletedSuccessfully": "{{operation}} {{count}} Elemente erfolgreich abgeschlossen", - "operationCompleted": "{{operation}} {{count}} Elemente", - "downloadFileSuccess": "Datei {{name}} erfolgreich heruntergeladen", - "downloadFileFailed": "Download fehlgeschlagen", - "downloadingFile": "Herunterladen {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Verschieben nach {{name}}", - "diffCompareWith": "Diff-Vergleich mit {{name}}", - "dragOutsideToDownload": "Zum Herunterladen aus dem Fenster ziehen ({{count}} Dateien)", - "newFolderDefault": "Neuer Ordner", - "newFileDefault": "Neue Datei.txt", - "successfullyMovedItems": "{{count}} Elemente erfolgreich nach {{target}} verschoben", - "move": "Verschieben", - "searchInFile": "In Datei suchen (Strg+F)", - "showKeyboardShortcuts": "Tastaturkürzel anzeigen", - "decreaseFontSize": "Schriftgröße verkleinern", - "increaseFontSize": "Schriftgröße vergrößern", - "startWritingMarkdown": "Beginnen Sie mit der Eingabe Ihres Markdown-Inhalts...", - "loadingFileComparison": "Dateivergleich wird geladen...", - "reload": "Neu laden", - "compare": "Vergleichen", - "sideBySide": "Nebeneinander", - "inline": "Inline", - "fileComparison": "Dateivergleich: {{file1}} vs. {{file2}}", - "fileTooLarge": "Datei zu groß: {{error}}", - "sshConnectionFailed": "SSH-Verbindung fehlgeschlagen. Bitte überprüfen Sie Ihre Verbindung zu {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Fehler beim Laden der Datei: {{error}}", - "connecting": "Verbindung wird hergestellt...", - "connectedSuccessfully": "Erfolgreich verbunden", - "totpVerificationFailed": "TOTP-Überprüfung fehlgeschlagen", - "warpgateVerificationFailed": "Warpgate-Authentifizierung fehlgeschlagen", - "authenticationFailed": "Authentifizierung fehlgeschlagen", - "incorrectPassphrase": "Falsche Passphrase. Bitte versuchen Sie es erneut.", - "verificationCodePrompt": "Verifizierungscode:", - "changePermissions": "Berechtigungen ändern", - "currentPermissions": "Aktuelle Berechtigungen", - "owner": "Besitzer", - "group": "Gruppe", - "others": "Andere", - "read": "Lesen", - "write": "Schreiben", - "execute": "Ausführen", - "permissionsChangedSuccessfully": "Berechtigungen erfolgreich geändert", - "failedToChangePermissions": "Berechtigungen konnten nicht geändert werden", - "name": "Name", - "sortByName": "Name", - "sortByDate": "Änderungsdatum", - "sortBySize": "Größe", - "ascending": "Aufsteigend", - "descending": "Absteigend", - "root": "Stammverzeichnis", - "new": "Neu", - "sortBy": "Sortieren nach", - "items": "Elemente", - "selected": "Ausgewählt", - "editor": "Editor", - "octal": "Oktal", - "storage": "Speicher", - "disk": "Datenträger", - "used": "Belegt", - "of": "von", - "toggleSidebar": "Seitenleiste umschalten", - "compactLayout": "Kompaktes Layout verwenden", - "comfortableLayout": "Nutzen Sie ein komfortables Layout", - "cannotLoadPdf": "PDF kann nicht geladen werden", - "pdfLoadError": "Beim Laden dieser PDF-Datei ist ein Fehler aufgetreten.", - "loadingPdf": "PDF wird geladen...", - "loadingPage": "Seite wird geladen..." - }, "transfer": { "copyToHost": "Auf Host kopieren…", "moveToHost": "Auf Host verschieben…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Wiederholung fehlgeschlagen.", "transferFailedRetryHint": "Unvollständige Daten wurden am Ziel beibehalten. Die Wiederholung wird fortgesetzt, sobald die Verbindung wiederhergestellt ist." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Dateien zwischen zwei SSH-Hosts übertragen.", - "sourceServer": "Quellserver", - "destinationServer": "Zielserver", - "selectHost": "Host auswählen", - "noHostSelected": "Kein Host ausgewählt", - "move": "Bewegen", - "copy": "Kopie", - "upload": "Hochladen", - "itemsSelected": "{{count}} Element ausgewählt", - "itemsSelected_other": "{{count}} ausgewählte Elemente", - "ready": "Bereit", - "loadingHosts": "Hosts werden geladen...", - "name": "Name", - "modified": "Modifiziert", - "size": "Größe", - "noFiles": "Keine Dateien", - "open": "Offen", - "up": "Hoch", - "refresh": "Aktualisieren", - "copyToTarget": "In Zielverzeichnis kopieren", - "rename": "Umbenennen", - "delete": "Löschen", - "editPermissions": "Berechtigungen bearbeiten", - "createNewFolder": "Neuen Ordner erstellen", - "deleteSelectedItems": "Ausgewählte Elemente löschen", - "deleteConfirm": "Delete {{count}} selected item?", - "deleteConfirm_other": "{{count}} ausgewählte Elemente löschen?", - "cancel": "Stornieren", - "save": "Speichern", - "folderCreated": "Ordner erstellt", - "itemRenamed": "Artikel umbenannt", - "actionFailed": "Aktion fehlgeschlagen", - "itemsDeleted": "{{count}} Element gelöscht", - "itemsDeleted_other": "{{count}} gelöschte Elemente", - "deleteFailed": "Löschen fehlgeschlagen", - "permissionsUpdated": "Berechtigungen aktualisiert", - "noRemoteSessionConnected": "Keine Remote-Sitzung verbunden", - "failedToListFiles": "Fehler beim Auflisten der Dateien", - "failedToRefresh": "Aktualisierung fehlgeschlagen", - "destinationInsideSource": "Das Ziel darf nicht innerhalb des ausgewählten Quellpfads liegen.", - "sameHostNoOp": "Quell- und Zielordner sind derselbe Ordner." - }, "tunnels": { "noSshTunnels": "Keine SSH-Tunnel", "createFirstTunnelMessage": "Sie haben noch keine SSH-Tunnel erstellt. Konfigurieren Sie Tunnelverbindungen im Host-Manager, um zu beginnen.", diff --git a/src/ui/locales/translated/el_GR.json b/src/ui/locales/translated/el_GR.json index 0d0c7a076..df2985f8f 100644 --- a/src/ui/locales/translated/el_GR.json +++ b/src/ui/locales/translated/el_GR.json @@ -715,7 +715,6 @@ "hostManager": "Διευθυντής Υποδοχής", "credentials": "Διαπιστευτήρια", "portForwarding": "Προώθηση", - "sftp": "SFTP", "connections": "Συνδέσεις", "serial": "Σειράς", "keepSideMenuOpen": "Διατήρηση μενού ανοιχτού", @@ -767,14 +766,12 @@ "tabRdp": "ΠΑΑ", "tabVnc": "VNC", "tabTunnels": "Σήραγγες", - "tabFiles": "Αρχεία", "tabStats": "Μετρήσεις κεντρικού υπολογιστή", "tabTelnet": "Τέλνετ", "tabSharing": "Μοιρασιά", "tabAuthentication": "Πιστοποίηση", "terminal": "Τερματικό", "tunnel": "Σήραγγα", - "fileManager": "Διαχειριστής αρχείων", "serverStats": "Μετρήσεις κεντρικού υπολογιστή", "status": { "available": "Διαθέσιμος", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} παραλείφθηκε (δεν βρέθηκε IP)", "proxmoxStatsUrlCopied": "Η διεύθυνση URL των στατιστικών του Proxmox αντιγράφηκε στο πρόχειρο", "copyProxmoxStatsUrlAction": "Αντιγραφή URL Στατιστικών Proxmox", - "enableFileManagerMonitor": "Ενεργοποίηση Διαχείρισης Αρχείων", - "enableFileManagerMonitorDesc": "Περιηγηθείτε και διαχειριστείτε αρχεία σε αυτόν τον κεντρικό υπολογιστή μέσω SFTP", - "scpLegacyLabel": "Λειτουργία παλαιού τύπου SCP", - "scpLegacyDesc": "Χρησιμοποιήστε μεταφορά αρχείων παλαιού τύπου για διακομιστές που δεν υποστηρίζουν το υποσύστημα SFTP (π.χ. ενσωματωμένοι ή ελάχιστοι διακομιστές SSH).", - "defaultPathLabel": "Προεπιλεγμένη διαδρομή", - "fileManagerPathHint": "Ο κατάλογος που θα ανοίξει κατά την εκκίνηση του διαχειριστή αρχείων για αυτόν τον κεντρικό υπολογιστή.", "statusChecksLabel": "Έλεγχοι κατάστασης", "enableStatusChecks": "Ενεργοποίηση ελέγχων κατάστασης", "enableStatusChecksDesc": "Περιοδική ping σε αυτόν τον κεντρικό υπολογιστή για επαλήθευση διαθεσιμότητας", @@ -1278,7 +1269,6 @@ "deletedHost": "Διαγράφηκε {{name}}", "copiedToClipboard": "Αντιγράφηκε στο πρόχειρο", "terminalUrlCopied": "Η διεύθυνση URL του τερματικού αντιγράφηκε", - "fileManagerUrlCopied": "Η διεύθυνση URL του διαχειριστή αρχείων αντιγράφηκε", "tunnelUrlCopied": "Η διεύθυνση URL της σήραγγας αντιγράφηκε", "dockerUrlCopied": "Η διεύθυνση URL του Docker αντιγράφηκε", "hostMetricsUrlCopied": "Η διεύθυνση URL μετρήσεων κεντρικού υπολογιστή αντιγράφηκε", @@ -1296,7 +1286,6 @@ "copyAddress": "Αντιγραφή διεύθυνσης", "copyLink": "Αντιγραφή συνδέσμου", "copyTerminalUrlAction": "Αντιγραφή URL τερματικού", - "copyFileManagerUrlAction": "Αντιγραφή URL Διαχείρισης Αρχείων", "copyTunnelUrlAction": "Αντιγραφή URL σήραγγας", "copyDockerUrlAction": "Αντιγραφή διεύθυνσης URL του Docker", "copyHostMetricsUrlAction": "Αντιγραφή URL μετρήσεων κεντρικού υπολογιστή", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Αφαίρεση από το Σπλιτ" } }, - "fileManager": { - "noHostSelected": "Δεν έχει επιλεγεί κεντρικός υπολογιστής", - "initializingEditor": "Αρχικοποίηση προγράμματος επεξεργασίας...", - "file": "Αρχείο", - "folder": "Ντοσιέ", - "uploadFile": "Μεταφόρτωση αρχείου", - "downloadFile": "Λήψη", - "extractArchive": "Εξαγωγή αρχείου", - "extractingArchive": "Εξαγωγή {{name}}...", - "archiveExtractedSuccessfully": "{{name}} εξήχθη με επιτυχία", - "extractFailed": "Η εξαγωγή απέτυχε.", - "compressFile": "Συμπίεση αρχείου", - "compressFiles": "Συμπίεση αρχείων", - "compressFilesDesc": "Συμπίεση στοιχείων {{count}} σε ένα αρχείο", - "archiveName": "Όνομα αρχείου", - "enterArchiveName": "Εισαγάγετε όνομα αρχείου...", - "compressionFormat": "Μορφή συμπίεσης", - "selectedFiles": "Επιλεγμένα αρχεία", - "andMoreFiles": "και {{count}} περισσότερα...", - "compress": "Κομπρέσα", - "compressingFiles": "Συμπίεση στοιχείων {{count}} σε {{name}}...", - "filesCompressedSuccessfully": "{{name}} δημιουργήθηκε με επιτυχία", - "compressFailed": "Η συμπίεση απέτυχε", - "edit": "Εκδίδω", - "preview": "Πρεμιέρα", - "previous": "Προηγούμενος", - "next": "Επόμενος", - "pageXOfY": "Σελίδα {{current}} από {{total}}", - "zoomOut": "Σμίκρυνση", - "zoomIn": "Μεγέθυνση", - "newFile": "Νέο αρχείο", - "newFolder": "Νέος φάκελος", - "rename": "Μετονομάζω", - "uploading": "Μεταφόρτωση...", - "uploadingFile": "Μεταφόρτωση {{name}}...", - "fileName": "Όνομα αρχείου", - "folderName": "Όνομα φακέλου", - "fileUploadedSuccessfully": "Το αρχείο \"{{name}}\" μεταφορτώθηκε με επιτυχία", - "failedToUploadFile": "Η μεταφόρτωση του αρχείου απέτυχε", - "uploadingFolderFiles": "Μεταφόρτωση {{count}} αρχείου(ων)...", - "uploadedFolderFiles": "Μεταφορτώθηκαν με επιτυχία τα {{count}} αρχεία", - "uploadedFolderPartial": "Η μεταφόρτωση {{uploaded}} αρχείου(ων), {{failed}} απέτυχε", - "fileDownloadedSuccessfully": "Το αρχείο \"{{name}}\" λήφθηκε με επιτυχία.", - "failedToDownloadFile": "Αποτυχία λήψης αρχείου", - "fileCreatedSuccessfully": "Το αρχείο \"{{name}}\" δημιουργήθηκε με επιτυχία", - "folderCreatedSuccessfully": "Ο φάκελος \"{{name}}\" δημιουργήθηκε με επιτυχία", - "failedToCreateItem": "Η δημιουργία στοιχείου απέτυχε", - "operationFailed": "Η λειτουργία {{operation}} απέτυχε για {{name}}: {{error}}", - "failedToResolveSymlink": "Αποτυχία επίλυσης συμβολικού συνδέσμου", - "itemsDeletedSuccessfully": "{{count}} στοιχεία διαγράφηκαν με επιτυχία", - "failedToDeleteItems": "Η διαγραφή στοιχείων απέτυχε", - "sudoPasswordRequired": "Απαιτείται κωδικός πρόσβασης διαχειριστή", - "enterSudoPassword": "Εισαγάγετε τον κωδικό πρόσβασης sudo για να συνεχίσετε αυτήν τη λειτουργία", - "sudoPassword": "Κωδικός πρόσβασης Sudo", - "sudoOperationFailed": "Η λειτουργία Sudo απέτυχε", - "sudoAuthFailed": "Ο έλεγχος ταυτότητας Sudo απέτυχε.", - "dragFilesToUpload": "Αποθέστε αρχεία εδώ για μεταφόρτωση", - "dropToUploadHere": "Αποθέστε εδώ για μεταφόρτωση", - "dropToDownloadHere": "Αποθέστε για λήψη εδώ", - "localFiles": "Τοπικός", - "showLocalFiles": "Εμφάνιση τοπικών αρχείων (διαιρεμένη προβολή)", - "hideLocalFiles": "Απόκρυψη τοπικών αρχείων", - "localHome": "Αρχική σελίδα", - "localPathPlaceholder": "Διαδρομή τοπικού φακέλου", - "localShowHidden": "Εμφάνιση κρυφών αρχείων", - "localHideHidden": "Απόκρυψη κρυφών αρχείων", - "localRevealInFileManager": "Εμφάνιση στον διαχειριστή αρχείων συστήματος", - "localCannotOpenFolder": "Δεν είναι δυνατό το άνοιγμα του φακέλου", - "localItemCount": "{{count}} στοιχεία", - "localSelectedCount": "επιλεγμένο {{count}}", - "back": "Πίσω", - "forward": "Προς τα εμπρός", - "up": "Επάνω", - "kind": "Είδος", - "localUploadingProgress": "Μεταφόρτωση {{current}} από {{total}}", - "localDownloadingProgress": "Λήψη {{current}} από {{total}}", - "localTransferInFlight": "{{count}} ταυτόχρονα", - "localPreparingDownload": "Προετοιμασία λήψης...", - "localTransferCancelled": "Η μεταφορά ακυρώθηκε.", - "localNothingToTransfer": "Δεν υπάρχει τίποτα προς μεταφορά", - "localUploadComplete": "Μεταφορτώθηκε {{count}} αρχείο(α)", - "localUploadFailed": "Η μεταφόρτωση απέτυχε.", - "localUploadPartial": "Η μεταφόρτωση {{done}} αρχείου(ων), {{failed}} απέτυχε", - "localDownloadComplete": "Λήψη {{count}} αρχείου(ων)", - "localDownloadFailed": "Η λήψη απέτυχε.", - "localDownloadPartial": "Η λήψη του/των {{done}} αρχείου/αρχείων απέτυχε, το {{failed}}", - "localReplaceExistingPrompt": "{{count}} στοιχεία υπάρχουν ήδη σε αυτόν τον φάκελο. Να αντικατασταθούν;", - "localReplace": "Αντικαθιστώ", - "localSkipExisting": "Παραλείπω", - "localSkippedExisting": "Παραλείφθηκαν {{count}} υπάρχοντα στοιχεία", - "localUnsafeNamesSkipped": "Παραλείφθηκαν {{count}} στοιχεία των οποίων τα ονόματα δεν μπορούν να χρησιμοποιηθούν σε αυτόν τον υπολογιστή", - "localOpen": "Ανοιχτό", - "localOpenFolder": "Άνοιγμα φακέλου", - "localUploadToRemote": "Μεταφόρτωση στον διακομιστή", - "localUploadToRemoteMany": "Μεταφόρτωση {{count}} στοιχείων στον διακομιστή", - "localMoveToTrash": "Μετακίνηση στον Κάδο", - "localMoveToTrashMany": "Μετακίνηση {{count}} στοιχείων στον Κάδο Απορριμμάτων", - "localTrashConfirmSingle": "Μετακίνηση του \"{{name}}\" στον Κάδο;", - "localTrashConfirmFolder": "Μετακίνηση του φακέλου \"{{name}}\" και όλου του περιεχομένου του στον Κάδο Απορριμμάτων;", - "localTrashConfirmMany": "Μετακίνηση {{count}} στοιχείων στον Κάδο;", - "localTrashed": "Μετακινήθηκαν {{count}} στοιχεία στον Κάδο Απορριμμάτων", - "localTrashFailed": "Δεν ήταν δυνατή η μετακίνηση στον Κάδο", - "localRenameFailed": "Η μετονομασία απέτυχε.", - "localCreateFailed": "Δεν ήταν δυνατή η δημιουργία στοιχείου", - "goToParentFolder": "Μετάβαση στον γονικό φάκελο", - "resizeSidebar": "Σύρετε για να αλλάξετε το μέγεθος της πλαϊνής γραμμής (κάντε διπλό κλικ για επαναφορά)", - "resizeLocalPane": "Σύρετε για να αλλάξετε το μέγεθος του τοπικού παραθύρου (κάντε διπλό κλικ για επαναφορά)", - "columns": "Στήλες", - "columnsHint": "Κάντε δεξί κλικ για να επιλέξετε στήλες", - "emptyFolder": "Αυτός ο φάκελος είναι κενός", - "searchFiles": "Αναζήτηση αρχείων...", - "upload": "Μεταφόρτωση", - "selectHostToStart": "Επιλέξτε έναν κεντρικό υπολογιστή για να ξεκινήσετε τη διαχείριση αρχείων", - "sshRequiredForFileManager": "Ο διαχειριστής αρχείων απαιτεί SSH. Αυτός ο κεντρικός υπολογιστής δεν έχει ενεργοποιημένο SSH.", - "failedToConnect": "Αποτυχία σύνδεσης με SSH", - "failedToLoadDirectory": "Αποτυχία φόρτωσης καταλόγου", - "noSSHConnection": "Δεν υπάρχει διαθέσιμη σύνδεση SSH", - "copy": "Αντίγραφο", - "cut": "Τομή", - "paste": "Πάστα", - "copyPath": "Αντιγραφή διαδρομής", - "copyPaths": "Διαδρομές αντιγραφής", - "delete": "Διαγράφω", - "properties": "Σκηνικά θέατρου", - "refresh": "Φρεσκάρω", - "downloadFiles": "Λήψη αρχείων {{count}} στο πρόγραμμα περιήγησης", - "copyFiles": "Αντιγραφή {{count}} στοιχείων", - "cutFiles": "Αποκοπή {{count}} στοιχείων", - "deleteFiles": "Διαγραφή {{count}} στοιχείων", - "filesCopiedToClipboard": "{{count}} στοιχεία αντιγράφηκαν στο πρόχειρο", - "filesCutToClipboard": "{{count}} στοιχεία αποκόπηκαν στο πρόχειρο", - "pathCopiedToClipboard": "Η διαδρομή αντιγράφηκε στο πρόχειρο", - "pathsCopiedToClipboard": "{{count}} διαδρομές αντιγράφηκαν στο πρόχειρο", - "failedToCopyPath": "Αποτυχία αντιγραφής διαδρομής στο πρόχειρο", - "copyFolderLink": "Αντιγραφή συνδέσμου σε φάκελο", - "copyCurrentFolderLink": "Αντιγραφή συνδέσμου στον τρέχοντα φάκελο", - "folderLinkCopied": "Ο σύνδεσμος φακέλου αντιγράφηκε στο πρόχειρο", - "failedToCopyFolderLink": "Αποτυχία αντιγραφής συνδέσμου φακέλου", - "movedItems": "Μετακινήθηκαν {{count}} στοιχεία", - "failedToDeleteItem": "Η διαγραφή του στοιχείου απέτυχε", - "itemRenamedSuccessfully": "Το {{type}} μετονομάστηκε με επιτυχία", - "failedToRenameItem": "Η μετονομασία του στοιχείου απέτυχε", - "download": "Λήψη", - "openExternalEditor": "Άνοιγμα εξωτερικά", - "chooseExternalEditor": "Επιλογή εξωτερικού επεξεργαστή", - "externalEditorSelected": "Επιλέχθηκε εξωτερικός επεξεργαστής", - "externalEditorDesktopOnly": "Ο εξωτερικός επεξεργαστής είναι διαθέσιμος μόνο στην εφαρμογή για υπολογιστές", - "externalEditorOpened": "Άνοιξε σε εξωτερικό πρόγραμμα επεξεργασίας. Η αποθήκευση εκεί θα μεταφορτώσει τις αλλαγές πίσω στον διακομιστή.", - "failedToOpenExternalEditor": "Αποτυχία ανοίγματος εξωτερικού προγράμματος επεξεργασίας", - "failedToSelectExternalEditor": "Αποτυχία επιλογής εξωτερικού προγράμματος επεξεργασίας", - "permissions": "Δικαιώματα", - "size": "Μέγεθος", - "modified": "Τροποποιημένο", - "path": "Μονοπάτι", - "confirmDelete": "Είστε σίγουροι ότι θέλετε να διαγράψετε το {{name}};", - "permissionDenied": "Η άδεια απορρίφθηκε", - "serverError": "Σφάλμα διακομιστή", - "fileSavedSuccessfully": "Το αρχείο αποθηκεύτηκε με επιτυχία", - "failedToSaveFile": "Η αποθήκευση του αρχείου απέτυχε", - "confirmDeleteSingleItem": "Είστε σίγουροι ότι θέλετε να διαγράψετε οριστικά το \"{{name}}\";", - "confirmDeleteMultipleItems": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά τα στοιχεία {{count}};", - "confirmDeleteMultipleItemsWithFolders": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά τα στοιχεία {{count}} ; Αυτό περιλαμβάνει τους φακέλους και το περιεχόμενό τους.", - "confirmDeleteFolder": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά τον φάκελο \"{{name}}\" και όλα τα περιεχόμενά του;", - "permanentDeleteWarning": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Τα στοιχεία θα διαγραφούν οριστικά από τον διακομιστή.", - "moveToTrashWarning": "Τα στοιχεία θα μετακινηθούν στον Κάδο Απορριμμάτων του Termix και μπορούν να αποκατασταθούν πριν λήξει η περίοδος διατήρησης.", - "itemsMovedToTrash": "{{count}} στοιχείο(α) μετακινήθηκε(αν) στον Κάδο", - "trash": "Σκουπίδια", - "trashDescription": "Τα διαγραμμένα στοιχεία διατηρούνται για {{days}} ημέρα(ες) σε αυτόν τον κεντρικό υπολογιστή.", - "trashLoadFailed": "Αποτυχία φόρτωσης κάδου απορριμμάτων", - "trashEmpty": "Ο κάδος απορριμμάτων είναι άδειος", - "trashRestore": "Επαναφέρω", - "trashRestored": "Αποκαταστάθηκε {{name}}", - "trashRestoreFailed": "Αποτυχία επαναφοράς στοιχείου. Η αρχική διαδρομή ενδέχεται να υπάρχει ήδη.", - "trashDeletePermanently": "Οριστική διαγραφή", - "trashDeleteConfirm": "Να διαγραφεί οριστικά το {{name}}; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", - "trashPermanentlyDeleted": "Διαγράφηκε οριστικά {{name}}", - "trashDeleteFailed": "Η οριστική διαγραφή του στοιχείου απέτυχε", - "trashEmptyAction": "Άδειασμα Κάδου", - "trashEmptyConfirm": "Να διαγραφούν οριστικά όλα τα στοιχεία στον Κάδο; Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας.", - "trashEmptied": "Ο κάδος αδειάστηκε", - "trashRetention": "Αυτόματος καθαρισμός μετά από (ημέρες)", - "trashRetentionSaved": "Η διατήρηση απορριμμάτων ενημερώθηκε", - "trashRetentionFailed": "Αποτυχία ενημέρωσης διατήρησης κάδου απορριμμάτων", - "trashUnavailableConfirm": "Αυτό το στοιχείο δεν μπορεί να μετακινηθεί στον Κάδο, συνήθως επειδή βρίσκεται σε άλλο σύστημα αρχείων ή απαιτεί αυξημένα δικαιώματα. Να διαγραφεί οριστικά;", - "recent": "Πρόσφατος", - "pinned": "Καρφιτσωμένο", - "folderShortcuts": "Συντομεύσεις φακέλων", - "failedToReconnectSSH": "Αποτυχία επανασύνδεσης συνεδρίας SSH", - "openTerminalHere": "Άνοιγμα τερματικού εδώ", - "run": "Τρέξιμο", - "openTerminalInFolder": "Άνοιγμα τερματικού σε αυτόν τον φάκελο", - "openTerminalInFileLocation": "Άνοιγμα τερματικού στη θέση αρχείου", - "runningFile": "Τρέξιμο - {{file}}", - "onlyRunExecutableFiles": "Μπορεί να εκτελέσει μόνο εκτελέσιμα αρχεία", - "directories": "Κατάλογοι", - "removedFromRecentFiles": "Αφαιρέθηκε το \"{{name}}\" από πρόσφατα αρχεία", - "removeFailed": "Η κατάργηση απέτυχε.", - "unpinnedSuccessfully": "Το \"{{name}}\" ξεκαρφιτσώθηκε με επιτυχία", - "unpinFailed": "Το ξεκαρφίτσωμα απέτυχε.", - "removedShortcut": "Αφαιρέθηκε η συντόμευση \"{{name}}\"", - "removeShortcutFailed": "Η κατάργηση της συντόμευσης απέτυχε.", - "clearedAllRecentFiles": "Διαγράφηκαν όλα τα πρόσφατα αρχεία", - "clearFailed": "Η διαγραφή απέτυχε.", - "removeFromRecentFiles": "Κατάργηση από πρόσφατα αρχεία", - "clearAllRecentFiles": "Διαγραφή όλων των πρόσφατων αρχείων", - "unpinFile": "Ξεκαρφίτσωμα αρχείου", - "removeShortcut": "Κατάργηση συντόμευσης", - "pinFile": "Καρφίτσωμα αρχείου", - "addToShortcuts": "Προσθήκη στις συντομεύσεις", - "pasteFailed": "Η επικόλληση απέτυχε.", - "noUndoableActions": "Δεν υπάρχουν ενέργειες που μπορούν να αναιρεθούν", - "undoCopySuccess": "Αναίρεση λειτουργίας αντιγραφής: Διαγράφηκαν {{count}} αντιγραμμένα αρχεία", - "undoCopyFailedDelete": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η διαγραφή των αντιγραμμένων αρχείων", - "undoCopyFailedNoInfo": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η εύρεση των πληροφοριών του αντιγραμμένου αρχείου", - "undoMoveSuccess": "Αναίρεση λειτουργίας μετακίνησης: Μετακινήθηκαν τα αρχεία {{count}} πίσω στην αρχική τους θέση", - "undoMoveFailedMove": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η επαναφορά κανενός αρχείου", - "undoMoveFailedNoInfo": "Η αναίρεση απέτυχε: Δεν ήταν δυνατή η εύρεση των πληροφοριών του μετακινημένου αρχείου", - "undoDeleteNotSupported": "Η λειτουργία διαγραφής δεν μπορεί να αναιρεθεί: Τα αρχεία έχουν διαγραφεί οριστικά από τον διακομιστή", - "undoTypeNotSupported": "Μη υποστηριζόμενος τύπος λειτουργίας αναίρεσης", - "undoOperationFailed": "Η λειτουργία αναίρεσης απέτυχε", - "unknownError": "Άγνωστο σφάλμα", - "confirm": "Επιβεβαιώνω", - "find": "Εύρημα...", - "replace": "Αντικαθιστώ", - "downloadInstead": "Λήψη εναλλακτικά", - "keyboardShortcuts": "Συντομεύσεις πληκτρολογίου", - "searchAndReplace": "Αναζήτηση & Αντικατάσταση", - "editing": "Επεξεργασία", - "search": "Ερευνα", - "findNext": "Εύρεση επόμενου", - "findPrevious": "Εύρεση προηγούμενου", - "save": "Εκτός", - "selectAll": "Επιλογή όλων", - "undo": "Ξεκάνω", - "redo": "Ξανακάνω", - "moveLineUp": "Μετακίνηση γραμμής προς τα πάνω", - "moveLineDown": "Μετακίνηση γραμμής προς τα κάτω", - "toggleComment": "Εναλλαγή σχολίου", - "autoComplete": "Αυτόματη συμπλήρωση", - "imageLoadError": "Αποτυχία φόρτωσης εικόνας", - "startTyping": "Ξεκινήστε να πληκτρολογείτε...", - "unknownSize": "Άγνωστο μέγεθος", - "fileIsEmpty": "Το αρχείο είναι κενό", - "largeFileWarning": "Προειδοποίηση για μεγάλο αρχείο", - "largeFileWarningDesc": "Αυτό το αρχείο έχει μέγεθος {{size}} , το οποίο ενδέχεται να προκαλέσει προβλήματα απόδοσης όταν ανοιχτεί ως κείμενο.", - "fileNotFoundAndRemoved": "Το αρχείο \"{{name}}\" δεν βρέθηκε και έχει αφαιρεθεί από τα πρόσφατα/καρφιτσωμένα αρχεία", - "failedToLoadFile": "Αποτυχία φόρτωσης αρχείου: {{error}}", - "serverErrorOccurred": "Παρουσιάστηκε σφάλμα διακομιστή. Δοκιμάστε ξανά αργότερα.", - "autoSaveFailed": "Η αυτόματη αποθήκευση απέτυχε", - "fileAutoSaved": "Το αρχείο αποθηκεύτηκε αυτόματα", - "moveFileFailed": "Αποτυχία μετακίνησης {{name}}", - "moveOperationFailed": "Η λειτουργία μετακίνησης απέτυχε", - "canOnlyCompareFiles": "Μπορεί να συγκρίνει μόνο δύο αρχεία", - "comparingFiles": "Σύγκριση αρχείων: {{file1}} και {{file2}}", - "dragFailed": "Η λειτουργία μεταφοράς απέτυχε", - "filePinnedSuccessfully": "Το αρχείο \"{{name}}\" καρφιτσώθηκε με επιτυχία", - "pinFileFailed": "Αποτυχία καρφιτσώματος αρχείου", - "fileUnpinnedSuccessfully": "Το αρχείο \"{{name}}\" ξεκαρφιτσώθηκε με επιτυχία", - "unpinFileFailed": "Αποτυχία ξεκαρφιτσώματος αρχείου", - "shortcutAddedSuccessfully": "Η συντόμευση φακέλου \"{{name}}\" προστέθηκε με επιτυχία", - "addShortcutFailed": "Η προσθήκη συντόμευσης απέτυχε", - "operationCompletedSuccessfully": "{{operation}} {{count}} στοιχεία με επιτυχία", - "operationCompleted": "{{operation}} {{count}} στοιχεία", - "downloadFileSuccess": "Το αρχείο {{name}} λήφθηκε με επιτυχία.", - "downloadFileFailed": "Η λήψη απέτυχε.", - "downloadingFile": "Λήψη {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Μετακίνηση σε {{name}}", - "diffCompareWith": "Διαφορά σε σύγκριση με {{name}}", - "dragOutsideToDownload": "Σύρετε έξω από το παράθυρο για λήψη ({{count}} αρχείων)", - "newFolderDefault": "ΝέοςΦάκελος", - "newFileDefault": "ΝέοΑρχείο.txt", - "successfullyMovedItems": "Επιτυχής μετακίνηση {{count}} στοιχείων στο {{target}}", - "move": "Κίνηση", - "searchInFile": "Αναζήτηση σε αρχείο (Ctrl+F)", - "showKeyboardShortcuts": "Εμφάνιση συντομεύσεων πληκτρολογίου", - "decreaseFontSize": "Μείωση μεγέθους γραμματοσειράς", - "increaseFontSize": "Αύξηση μεγέθους γραμματοσειράς", - "startWritingMarkdown": "Ξεκινήστε να γράφετε το περιεχόμενό σας με έκπτωση...", - "loadingFileComparison": "Φόρτωση σύγκρισης αρχείων...", - "reload": "Γεμίζω πάλι", - "compare": "Συγκρίνω", - "sideBySide": "Πλάι πλάι", - "inline": "Ενσωματωμένο", - "fileComparison": "Σύγκριση αρχείων: {{file1}} έναντι {{file2}}", - "fileTooLarge": "Το αρχείο είναι πολύ μεγάλο: {{error}}", - "sshConnectionFailed": "Η σύνδεση SSH απέτυχε. Ελέγξτε τη σύνδεσή σας με {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Αποτυχία φόρτωσης αρχείου: {{error}}", - "connecting": "Συνδετικός...", - "connectedSuccessfully": "Η σύνδεση ολοκληρώθηκε με επιτυχία", - "totpVerificationFailed": "Η επαλήθευση TOTP απέτυχε.", - "warpgateVerificationFailed": "Η πιστοποίηση Warpgate απέτυχε.", - "authenticationFailed": "Η επαλήθευση ταυτότητας απέτυχε", - "incorrectPassphrase": "Λανθασμένη φράση πρόσβασης. Δοκιμάστε ξανά.", - "verificationCodePrompt": "Κωδικός επαλήθευσης:", - "changePermissions": "Αλλαγή Δικαιωμάτων", - "currentPermissions": "Τρέχοντα Δικαιώματα", - "owner": "Ιδιοκτήτης", - "group": "Ομάδα", - "others": "Άλλοι", - "read": "Ανάγνωση", - "write": "Γράφω", - "execute": "Εκτελώ", - "permissionsChangedSuccessfully": "Τα δικαιώματα άλλαξαν με επιτυχία", - "failedToChangePermissions": "Αποτυχία αλλαγής δικαιωμάτων", - "name": "Ονομα", - "sortByName": "Ονομα", - "sortByDate": "Ημερομηνία τροποποίησης", - "sortBySize": "Μέγεθος", - "ascending": "Αύξουσα", - "descending": "Φθίνων", - "root": "Ρίζα", - "new": "Νέος", - "sortBy": "Ταξινόμηση κατά", - "items": "Αντικείμενα", - "selected": "Επιλεγμένο", - "editor": "Συντάκτης", - "octal": "Οκτάεδρος", - "storage": "Αποθήκευση", - "disk": "Δίσκος", - "used": "Μεταχειρισμένος", - "of": "του", - "toggleSidebar": "Εναλλαγή πλευρικής γραμμής", - "compactLayout": "Χρήση συμπαγούς διάταξης", - "comfortableLayout": "Χρησιμοποιήστε άνετη διάταξη", - "cannotLoadPdf": "Δεν είναι δυνατή η φόρτωση του PDF", - "pdfLoadError": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση αυτού του αρχείου PDF.", - "loadingPdf": "Φόρτωση PDF...", - "loadingPage": "Φόρτωση σελίδας..." - }, "transfer": { "copyToHost": "Αντιγραφή στον κεντρικό υπολογιστή…", "moveToHost": "Μετακίνηση στον κεντρικό υπολογιστή…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Η επανάληψη απέτυχε", "transferFailedRetryHint": "Μερικά δεδομένα διατηρήθηκαν στον προορισμό. Η επανάληψη θα συνεχιστεί όταν αποκατασταθεί η σύνδεση." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Μεταφορά αρχείων μεταξύ δύο κεντρικών υπολογιστών SSH.", - "sourceServer": "Διακομιστής πηγής", - "destinationServer": "Διακομιστής προορισμού", - "selectHost": "Επιλογή κεντρικού υπολογιστή", - "noHostSelected": "Δεν έχει επιλεγεί κεντρικός υπολογιστής", - "move": "Κίνηση", - "copy": "Αντίγραφο", - "upload": "Μεταφόρτωση", - "itemsSelected": "Επιλέχθηκε {{count}} στοιχείο", - "itemsSelected_other": "Επιλέχθηκαν {{count}} στοιχεία", - "ready": "Ετοιμος", - "loadingHosts": "Φόρτωση κεντρικών υπολογιστών...", - "name": "Ονομα", - "modified": "Τροποποιημένο", - "size": "Μέγεθος", - "noFiles": "Δεν υπάρχουν αρχεία", - "open": "Ανοιχτό", - "up": "Επάνω", - "refresh": "Φρεσκάρω", - "copyToTarget": "Αντιγραφή στον κατάλογο προορισμού", - "rename": "Μετονομάζω", - "delete": "Διαγράφω", - "editPermissions": "Επεξεργασία δικαιωμάτων", - "createNewFolder": "Δημιουργία νέου φακέλου", - "deleteSelectedItems": "Διαγραφή επιλεγμένων στοιχείων", - "deleteConfirm": "Διαγραφή του επιλεγμένου {{count}} στοιχείου;", - "deleteConfirm_other": "Διαγραφή {{count}} επιλεγμένων στοιχείων;", - "cancel": "Ματαίωση", - "save": "Εκτός", - "folderCreated": "Ο φάκελος δημιουργήθηκε", - "itemRenamed": "Το στοιχείο μετονομάστηκε", - "actionFailed": "Η ενέργεια απέτυχε", - "itemsDeleted": "{{count}} στοιχείο διαγράφηκε", - "itemsDeleted_other": "{{count}} στοιχεία διαγράφηκαν", - "deleteFailed": "Η διαγραφή απέτυχε.", - "permissionsUpdated": "Τα δικαιώματα ενημερώθηκαν", - "noRemoteSessionConnected": "Δεν έχει συνδεθεί απομακρυσμένη συνεδρία", - "failedToListFiles": "Αποτυχία καταχώρισης αρχείων", - "failedToRefresh": "Η ανανέωση απέτυχε", - "destinationInsideSource": "Ο προορισμός δεν μπορεί να βρίσκεται εντός της επιλεγμένης διαδρομής προέλευσης.", - "sameHostNoOp": "Η προέλευση και ο προορισμός είναι ο ίδιος φάκελος." - }, "tunnels": { "noSshTunnels": "Δεν υπάρχουν σήραγγες SSH", "createFirstTunnelMessage": "Δεν έχετε δημιουργήσει ακόμη σήραγγες SSH. Διαμορφώστε τις συνδέσεις σήραγγας στο Host Manager για να ξεκινήσετε.", diff --git a/src/ui/locales/translated/es_ES.json b/src/ui/locales/translated/es_ES.json index 4d92f6e10..5b26eec86 100644 --- a/src/ui/locales/translated/es_ES.json +++ b/src/ui/locales/translated/es_ES.json @@ -715,7 +715,6 @@ "hostManager": "Gestor de hosts", "credentials": "Credenciales", "portForwarding": "Reenvío", - "sftp": "SFTP", "connections": "Conexiones", "serial": "Serial", "keepSideMenuOpen": "Mantener el menú abierto", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Túneles", - "tabFiles": "Archivos", "tabStats": "Métricas del host", "tabTelnet": "Telnet", "tabSharing": "Compartir", "tabAuthentication": "Autenticación", "terminal": "Terminal", "tunnel": "Túnel", - "fileManager": "Gestor de archivos", "serverStats": "Métricas del host", "status": { "available": "Disponible", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} omitido (no se encontró ninguna IP)", "proxmoxStatsUrlCopied": "Se copió al portapapeles la URL de estadísticas de Proxmox.", "copyProxmoxStatsUrlAction": "Copiar la URL de estadísticas de Proxmox", - "enableFileManagerMonitor": "Habilitar Gestor de archivos", - "enableFileManagerMonitorDesc": "Explorar y gestionar archivos en este host mediante SFTP", - "scpLegacyLabel": "Modo SCP heredado", - "scpLegacyDesc": "Usar transferencia de archivos heredada para servidores que no admiten el subsistema SFTP (p. ej., servidores SSH integrados o mínimos).", - "defaultPathLabel": "Ruta predeterminada", - "fileManagerPathHint": "Directorio que se abre al iniciar el gestor de archivos para este host.", "statusChecksLabel": "Comprobaciones de estado", "enableStatusChecks": "Habilitar comprobaciones de estado", "enableStatusChecksDesc": "Hacer ping periódicamente a este host para verificar su disponibilidad.", @@ -1278,7 +1269,6 @@ "deletedHost": "Eliminado {{name}}", "copiedToClipboard": "Copiado al portapapeles", "terminalUrlCopied": "URL de terminal copiada", - "fileManagerUrlCopied": "URL del gestor de archivos copiada", "tunnelUrlCopied": "URL del túnel copiada", "dockerUrlCopied": "URL de Docker copiada", "hostMetricsUrlCopied": "URL de métricas del host copiada", @@ -1296,7 +1286,6 @@ "copyAddress": "Copiar dirección", "copyLink": "Copiar enlace", "copyTerminalUrlAction": "Copiar URL del terminal", - "copyFileManagerUrlAction": "Copiar URL del gestor de archivos", "copyTunnelUrlAction": "Copiar URL del túnel", "copyDockerUrlAction": "Copiar URL de Docker", "copyHostMetricsUrlAction": "Copiar URL de métricas del host", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Quitar de la división" } }, - "fileManager": { - "noHostSelected": "Ningún host seleccionado", - "initializingEditor": "Inicializando editor...", - "file": "Archivo", - "folder": "Carpeta", - "uploadFile": "Subir archivo", - "downloadFile": "Descargar", - "extractArchive": "Extraer archivo", - "extractingArchive": "Extrayendo {{name}}...", - "archiveExtractedSuccessfully": "{{name}} extraído correctamente", - "extractFailed": "Extracción fallida", - "compressFile": "Comprimir archivo", - "compressFiles": "Comprimir archivos", - "compressFilesDesc": "Comprimir {{count}} elementos en un archivo", - "archiveName": "Nombre del archivo", - "enterArchiveName": "Introduzca el nombre del archivo...", - "compressionFormat": "Formato de compresión", - "selectedFiles": "Archivos seleccionados", - "andMoreFiles": "y {{count}} más...", - "compress": "Comprimir", - "compressingFiles": "Comprimiendo {{count}} elementos en {{name}}...", - "filesCompressedSuccessfully": "{{name}} creado correctamente", - "compressFailed": "La compresión falló", - "edit": "Editar", - "preview": "Vista previa", - "previous": "Anterior", - "next": "Siguiente", - "pageXOfY": "Página {{current}} de {{total}}", - "zoomOut": "Alejar", - "zoomIn": "Acercar", - "newFile": "Nuevo archivo", - "newFolder": "Nueva carpeta", - "rename": "Renombrar", - "uploading": "Subiendo...", - "uploadingFile": "Subiendo {{name}}...", - "fileName": "Nombre del archivo", - "folderName": "Nombre de la carpeta", - "fileUploadedSuccessfully": "Archivo \"{{name}}\" subido correctamente", - "failedToUploadFile": "Error al subir el archivo", - "uploadingFolderFiles": "Subiendo {{count}} archivo(s)...", - "uploadedFolderFiles": "Se subieron correctamente los archivos {{count}}", - "uploadedFolderPartial": "Se subieron {{uploaded}} archivos, {{failed}} falló.", - "fileDownloadedSuccessfully": "Archivo \"{{name}}\" descargado correctamente", - "failedToDownloadFile": "Error al descargar el archivo", - "fileCreatedSuccessfully": "Archivo \"{{name}}\" creado correctamente", - "folderCreatedSuccessfully": "Carpeta \"{{name}}\" creada correctamente", - "failedToCreateItem": "No se pudo crear el elemento", - "operationFailed": "La operación {{operation}} falló para {{name}}: {{error}}", - "failedToResolveSymlink": "No se pudo resolver el enlace simbólico", - "itemsDeletedSuccessfully": "{{count}} elementos eliminados correctamente", - "failedToDeleteItems": "No se pudieron eliminar los elementos", - "sudoPasswordRequired": "Se requiere contraseña de administrador", - "enterSudoPassword": "Introduzca la contraseña sudo para continuar esta operación", - "sudoPassword": "Contraseña sudo", - "sudoOperationFailed": "La operación sudo falló", - "sudoAuthFailed": "La autenticación sudo falló", - "dragFilesToUpload": "Suelte los archivos aquí para subirlos", - "dropToUploadHere": "Arrastra para subir aquí", - "dropToDownloadHere": "Haz clic aquí para descargar", - "localFiles": "Local", - "showLocalFiles": "Mostrar archivos locales (vista dividida)", - "hideLocalFiles": "Ocultar archivos locales", - "localHome": "Carpeta de inicio", - "localPathPlaceholder": "Ruta de la carpeta local", - "localShowHidden": "Mostrar archivos ocultos", - "localHideHidden": "Ocultar archivos ocultos", - "localRevealInFileManager": "Mostrar en el administrador de archivos del sistema", - "localCannotOpenFolder": "No se puede abrir la carpeta", - "localItemCount": "{{count}} elementos", - "localSelectedCount": "{{count}} seleccionado", - "back": "Atrás", - "forward": "Adelante", - "up": "Arriba", - "kind": "Amable", - "localUploadingProgress": "Subiendo {{current}} de {{total}}", - "localDownloadingProgress": "Descargando {{current}} de {{total}}", - "localTransferInFlight": "{{count}} al instante", - "localPreparingDownload": "Preparando la descarga...", - "localTransferCancelled": "Transferencia cancelada", - "localNothingToTransfer": "Nada que transferir", - "localUploadComplete": "Archivo(s) subido(s) {{count}}", - "localUploadFailed": "Error al cargar la página", - "localUploadPartial": "Se subieron {{done}} archivos, {{failed}} falló.", - "localDownloadComplete": "Archivo(s) descargado(s) {{count}}", - "localDownloadFailed": "Falló la descarga.", - "localDownloadPartial": "Se descargaron {{done}} archivos, {{failed}} falló.", - "localReplaceExistingPrompt": "Ya existen {{count}} elementos en esta carpeta. ¿Desea reemplazarlos?", - "localReplace": "Reemplazar", - "localSkipExisting": "Saltar", - "localSkippedExisting": "Se omitieron {{count}} elementos existentes", - "localUnsafeNamesSkipped": "Se omitieron {{count}} elementos cuyos nombres no se pueden usar en este ordenador.", - "localOpen": "Abierto", - "localOpenFolder": "Abrir carpeta", - "localUploadToRemote": "Subir al servidor", - "localUploadToRemoteMany": "Sube {{count}} elementos al servidor", - "localMoveToTrash": "Mover a la basura", - "localMoveToTrashMany": "Mover {{count}} elementos a la Papelera", - "localTrashConfirmSingle": "¿Mover \"{{name}}\" a la papelera?", - "localTrashConfirmFolder": "¿Mover la carpeta \"{{name}}\" y todo su contenido a la Papelera?", - "localTrashConfirmMany": "¿Mover {{count}} elementos a la Papelera?", - "localTrashed": "Se movieron {{count}} elementos a la Papelera", - "localTrashFailed": "No se pudo mover a la papelera", - "localRenameFailed": "Error al cambiar el nombre", - "localCreateFailed": "No se pudo crear el artículo.", - "goToParentFolder": "Ir a la carpeta principal", - "resizeSidebar": "Arrastra para cambiar el tamaño de la barra lateral (haz doble clic para restablecer).", - "resizeLocalPane": "Arrastra para cambiar el tamaño del panel local (haz doble clic para restablecer).", - "columns": "Columnas", - "columnsHint": "Haz clic con el botón derecho para elegir las columnas.", - "emptyFolder": "Esta carpeta está vacía", - "searchFiles": "Buscar archivos...", - "upload": "Subir", - "selectHostToStart": "Seleccione un host para iniciar la gestión de archivos", - "sshRequiredForFileManager": "El gestor de archivos requiere SSH. Este host no tiene SSH habilitado.", - "failedToConnect": "No se pudo conectar a SSH", - "failedToLoadDirectory": "No se pudo cargar el directorio", - "noSSHConnection": "No hay conexión SSH disponible", - "copy": "Copiar", - "cut": "Cortar", - "paste": "Pegar", - "copyPath": "Copiar ruta", - "copyPaths": "Copiar rutas", - "delete": "Eliminar", - "properties": "Propiedades", - "refresh": "Actualizar", - "downloadFiles": "Descargar {{count}} archivos al navegador", - "copyFiles": "Copiar {{count}} elementos", - "cutFiles": "Cortar {{count}} elementos", - "deleteFiles": "Eliminar {{count}} elementos", - "filesCopiedToClipboard": "{{count}} elementos copiados al portapapeles", - "filesCutToClipboard": "{{count}} elementos cortados al portapapeles", - "pathCopiedToClipboard": "Ruta copiada al portapapeles", - "pathsCopiedToClipboard": "{{count}} rutas copiadas al portapapeles", - "failedToCopyPath": "Error al copiar la ruta al portapapeles", - "copyFolderLink": "Copiar enlace a la carpeta", - "copyCurrentFolderLink": "Copiar enlace a la carpeta actual", - "folderLinkCopied": "Enlace a la carpeta copiado al portapapeles", - "failedToCopyFolderLink": "Error al copiar el enlace a la carpeta", - "movedItems": "{{count}} elementos movidos", - "failedToDeleteItem": "Error al eliminar el elemento", - "itemRenamedSuccessfully": "{{type}} renombrado con éxito", - "failedToRenameItem": "Error al renombrar el elemento", - "download": "Descargar", - "openExternalEditor": "Abrir externamente", - "chooseExternalEditor": "Elegir editor externo", - "externalEditorSelected": "Editor externo seleccionado", - "externalEditorDesktopOnly": "El editor externo solo está disponible en la aplicación de escritorio", - "externalEditorOpened": "Abierto en el editor externo. Al guardar allí se subirán los cambios al servidor.", - "failedToOpenExternalEditor": "Error al abrir el editor externo", - "failedToSelectExternalEditor": "Error al seleccionar el editor externo", - "permissions": "Permisos", - "size": "Tamaño", - "modified": "Modificado", - "path": "Ruta", - "confirmDelete": "¿Está seguro de que desea eliminar {{name}}?", - "permissionDenied": "Permiso denegado", - "serverError": "Error del servidor", - "fileSavedSuccessfully": "Archivo guardado con éxito", - "failedToSaveFile": "Error al guardar el archivo", - "confirmDeleteSingleItem": "¿Está seguro de que desea eliminar permanentemente \"{{name}}\"?", - "confirmDeleteMultipleItems": "¿Está seguro de que desea eliminar permanentemente {{count}} elementos?", - "confirmDeleteMultipleItemsWithFolders": "¿Está seguro de que desea eliminar permanentemente {{count}} elementos? Esto incluye carpetas y su contenido.", - "confirmDeleteFolder": "¿Está seguro de que desea eliminar permanentemente la carpeta \"{{name}}\" y todo su contenido?", - "permanentDeleteWarning": "Esta acción no se puede deshacer. Los elementos se eliminarán permanentemente del servidor.", - "moveToTrashWarning": "El/los elemento(s) se moverán a la Papelera de Termix y se podrán restaurar antes de que expire el período de retención.", - "itemsMovedToTrash": "{{count}} elemento(s) movido(s) a la Papelera", - "trash": "Basura", - "trashDescription": "Los elementos eliminados se conservan durante {{days}} día(s) en este host.", - "trashLoadFailed": "No se pudo cargar la papelera.", - "trashEmpty": "La papelera está vacía", - "trashRestore": "Restaurar", - "trashRestored": "Restaurado {{name}}", - "trashRestoreFailed": "No se pudo restaurar el elemento. Es posible que la ruta original ya exista.", - "trashDeletePermanently": "Eliminar permanentemente", - "trashDeleteConfirm": "¿Eliminar permanentemente {{name}}? Esto no se puede deshacer.", - "trashPermanentlyDeleted": "Eliminado permanentemente {{name}}", - "trashDeleteFailed": "No se pudo eliminar el elemento de forma permanente.", - "trashEmptyAction": "Vaciar la basura", - "trashEmptyConfirm": "¿Eliminar permanentemente todos los elementos de la Papelera? Esta acción es irreversible.", - "trashEmptied": "Basura vaciada", - "trashRetention": "Limpieza automática después de (días)", - "trashRetentionSaved": "Se ha actualizado la retención de basura.", - "trashRetentionFailed": "Error al actualizar la retención de la papelera", - "trashUnavailableConfirm": "Este elemento no se puede mover a la Papelera, normalmente porque se encuentra en otro sistema de archivos o requiere permisos de administrador. ¿Desea eliminarlo permanentemente?", - "recent": "Recientes", - "pinned": "Fijados", - "folderShortcuts": "Accesos directos a carpetas", - "failedToReconnectSSH": "Error al reconectar la sesión SSH", - "openTerminalHere": "Abrir terminal aquí", - "run": "Ejecutar", - "openTerminalInFolder": "Abrir terminal en esta carpeta", - "openTerminalInFileLocation": "Abrir terminal en la ubicación del archivo", - "runningFile": "Ejecutando - {{file}}", - "onlyRunExecutableFiles": "Solo se pueden ejecutar archivos ejecutables", - "directories": "Directorios", - "removedFromRecentFiles": "Se eliminó \"{{name}}\" de los archivos recientes", - "removeFailed": "Error al eliminar", - "unpinnedSuccessfully": "Se desancló \"{{name}}\" correctamente", - "unpinFailed": "Error al desanclar", - "removedShortcut": "Se eliminó el acceso directo \"{{name}}\"", - "removeShortcutFailed": "Error al eliminar el acceso directo", - "clearedAllRecentFiles": "Se borraron todos los archivos recientes", - "clearFailed": "Error al borrar", - "removeFromRecentFiles": "Eliminar de archivos recientes", - "clearAllRecentFiles": "Borrar todos los archivos recientes", - "unpinFile": "Desanclar archivo", - "removeShortcut": "Eliminar acceso directo", - "pinFile": "Anclar archivo", - "addToShortcuts": "Añadir a accesos directos", - "pasteFailed": "Error al pegar", - "noUndoableActions": "No hay acciones para deshacer", - "undoCopySuccess": "Se deshizo la copia: se eliminaron {{count}} archivos copiados", - "undoCopyFailedDelete": "Error al deshacer: no se pudo eliminar ningún archivo copiado", - "undoCopyFailedNoInfo": "Error al deshacer: no se encontró información de los archivos copiados", - "undoMoveSuccess": "Se deshizo el movimiento: se movieron {{count}} archivos a su ubicación original", - "undoMoveFailedMove": "Error al deshacer: no se pudo mover ningún archivo de vuelta", - "undoMoveFailedNoInfo": "Error al deshacer: no se encontró información de los archivos movidos", - "undoDeleteNotSupported": "La operación de eliminación no se puede deshacer: los archivos se han eliminado permanentemente del servidor", - "undoTypeNotSupported": "Tipo de operación de deshacer no compatible", - "undoOperationFailed": "Error en la operación de deshacer", - "unknownError": "Error desconocido", - "confirm": "Confirmar", - "find": "Buscar...", - "replace": "Reemplazar", - "downloadInstead": "Descargar en su lugar", - "keyboardShortcuts": "Atajos de teclado", - "searchAndReplace": "Buscar y reemplazar", - "editing": "Edición", - "search": "Buscar", - "findNext": "Buscar siguiente", - "findPrevious": "Buscar anterior", - "save": "Guardar", - "selectAll": "Seleccionar todo", - "undo": "Deshacer", - "redo": "Rehacer", - "moveLineUp": "Mover línea hacia arriba", - "moveLineDown": "Mover línea hacia abajo", - "toggleComment": "Alternar comentario", - "autoComplete": "Autocompletar", - "imageLoadError": "Error al cargar la imagen", - "startTyping": "Empiece a escribir...", - "unknownSize": "Tamaño desconocido", - "fileIsEmpty": "El archivo está vacío", - "largeFileWarning": "Advertencia de archivo grande", - "largeFileWarningDesc": "Este archivo tiene un tamaño de {{size}}, lo que puede causar problemas de rendimiento al abrirlo como texto.", - "fileNotFoundAndRemoved": "El archivo \"{{name}}\" no se encontró y se ha eliminado de los archivos recientes/fijados.", - "failedToLoadFile": "Error al cargar el archivo: {{error}}", - "serverErrorOccurred": "Se ha producido un error en el servidor. Vuelva a intentarlo más tarde.", - "autoSaveFailed": "Error en el autoguardado", - "fileAutoSaved": "Archivo autoguardado", - "moveFileFailed": "Error al mover {{name}}", - "moveOperationFailed": "Error en la operación de mover", - "canOnlyCompareFiles": "Solo se pueden comparar dos archivos", - "comparingFiles": "Comparando archivos: {{file1}} y {{file2}}", - "dragFailed": "Error en la operación de arrastre", - "filePinnedSuccessfully": "Archivo \"{{name}}\" fijado correctamente", - "pinFileFailed": "Error al fijar el archivo", - "fileUnpinnedSuccessfully": "Archivo \"{{name}}\" desfijado correctamente", - "unpinFileFailed": "No se pudo desanclar el archivo", - "shortcutAddedSuccessfully": "Acceso directo a la carpeta \"{{name}}\" añadido correctamente", - "addShortcutFailed": "No se pudo añadir el acceso directo", - "operationCompletedSuccessfully": "{{operation}} {{count}} elementos correctamente", - "operationCompleted": "{{operation}} {{count}} elementos", - "downloadFileSuccess": "Archivo {{name}} descargado correctamente", - "downloadFileFailed": "Error en la descarga", - "downloadingFile": "Descargando {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Mover a {{name}}", - "diffCompareWith": "Comparar diferencias con {{name}}", - "dragOutsideToDownload": "Arrastrar fuera de la ventana para descargar ({{count}} archivos)", - "newFolderDefault": "NuevaCarpeta", - "newFileDefault": "NuevoArchivo.txt", - "successfullyMovedItems": "{{count}} elementos movidos correctamente a {{target}}", - "move": "Mover", - "searchInFile": "Buscar en archivo (Ctrl+F)", - "showKeyboardShortcuts": "Mostrar atajos de teclado", - "decreaseFontSize": "Reducir tamaño de fuente", - "increaseFontSize": "Aumentar tamaño de fuente", - "startWritingMarkdown": "Empiece a escribir su contenido Markdown...", - "loadingFileComparison": "Cargando comparación de archivos...", - "reload": "Recargar", - "compare": "Comparar", - "sideBySide": "Lado a lado", - "inline": "En línea", - "fileComparison": "Comparación de archivos: {{file1}} vs {{file2}}", - "fileTooLarge": "Archivo demasiado grande: {{error}}", - "sshConnectionFailed": "Error de conexión SSH. Verifique su conexión a {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Error al cargar el archivo: {{error}}", - "connecting": "Conectando...", - "connectedSuccessfully": "Conectado correctamente", - "totpVerificationFailed": "Error en la verificación TOTP", - "warpgateVerificationFailed": "Error de autenticación de Warpgate", - "authenticationFailed": "Error de autenticación", - "incorrectPassphrase": "Frase de contraseña incorrecta. Inténtelo de nuevo.", - "verificationCodePrompt": "Código de verificación:", - "changePermissions": "Cambiar permisos", - "currentPermissions": "Permisos actuales", - "owner": "Propietario", - "group": "Grupo", - "others": "Otros", - "read": "Lectura", - "write": "Escritura", - "execute": "Ejecución", - "permissionsChangedSuccessfully": "Permisos cambiados correctamente", - "failedToChangePermissions": "Error al cambiar los permisos", - "name": "Nombre", - "sortByName": "Nombre", - "sortByDate": "Fecha de modificación", - "sortBySize": "Tamaño", - "ascending": "Ascendente", - "descending": "Descendente", - "root": "Raíz", - "new": "Nuevo", - "sortBy": "Ordenar por", - "items": "Elementos", - "selected": "Seleccionados", - "editor": "Editor", - "octal": "Octal", - "storage": "Almacenamiento", - "disk": "Disco", - "used": "Usado", - "of": "de", - "toggleSidebar": "Alternar barra lateral", - "compactLayout": "Utilice un diseño compacto.", - "comfortableLayout": "Utilice una disposición cómoda", - "cannotLoadPdf": "No se puede cargar el PDF", - "pdfLoadError": "Se produjo un error al cargar este archivo PDF.", - "loadingPdf": "Cargando PDF...", - "loadingPage": "Cargando página..." - }, "transfer": { "copyToHost": "Copiar al host…", "moveToHost": "Mover al host…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Error al reintentar", "transferFailedRetryHint": "Se conservaron datos parciales en el destino. El reintento se reanudará cuando se restablezca la conexión." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Transferir archivos entre dos hosts SSH.", - "sourceServer": "Servidor de origen", - "destinationServer": "Servidor de destino", - "selectHost": "Seleccione el host", - "noHostSelected": "No se ha seleccionado ningún host.", - "move": "Mover", - "copy": "Copiar", - "upload": "Subir", - "itemsSelected": "{{count}} elemento seleccionado", - "itemsSelected_other": "{{count}} elementos seleccionados", - "ready": "Listo", - "loadingHosts": "Cargando hosts...", - "name": "Nombre", - "modified": "Modificado", - "size": "Tamaño", - "noFiles": "No hay archivos", - "open": "Abierto", - "up": "Arriba", - "refresh": "Refrescar", - "copyToTarget": "Copiar al directorio de destino", - "rename": "Rebautizar", - "delete": "Borrar", - "editPermissions": "Editar permisos", - "createNewFolder": "Crear nueva carpeta", - "deleteSelectedItems": "Eliminar elementos seleccionados", - "deleteConfirm": "¿Eliminar {{count}} elemento seleccionado?", - "deleteConfirm_other": "¿Eliminar {{count}} elementos seleccionados?", - "cancel": "Cancelar", - "save": "Ahorrar", - "folderCreated": "Carpeta creada", - "itemRenamed": "Artículo renombrado", - "actionFailed": "La acción fracasó", - "itemsDeleted": "{{count}} elemento eliminado", - "itemsDeleted_other": "{{count}} elementos eliminados", - "deleteFailed": "Error al eliminar", - "permissionsUpdated": "Permisos actualizados", - "noRemoteSessionConnected": "No hay ninguna sesión remota conectada.", - "failedToListFiles": "No se pudieron listar los archivos.", - "failedToRefresh": "No se pudo actualizar", - "destinationInsideSource": "El destino no puede estar dentro de la ruta de origen seleccionada.", - "sameHostNoOp": "La carpeta de origen y la de destino son la misma." - }, "tunnels": { "noSshTunnels": "Sin túneles SSH", "createFirstTunnelMessage": "Aún no has creado ningún túnel SSH. Configura las conexiones de túnel en el Administrador de hosts para empezar.", diff --git a/src/ui/locales/translated/fi_FI.json b/src/ui/locales/translated/fi_FI.json index 0ae40938d..8639887c6 100644 --- a/src/ui/locales/translated/fi_FI.json +++ b/src/ui/locales/translated/fi_FI.json @@ -715,7 +715,6 @@ "hostManager": "Isäntäpäällikkö", "credentials": "Valtakirjat", "portForwarding": "Edelleenlähetys", - "sftp": "SFTP", "connections": "Yhteydet", "serial": "Sarjanumero", "keepSideMenuOpen": "Pidä valikko auki", @@ -767,14 +766,12 @@ "tabRdp": "Maaseudun kehittämisohjelma", "tabVnc": "VNC", "tabTunnels": "Tunnelit", - "tabFiles": "Tiedostot", "tabStats": "Isännän mittarit", "tabTelnet": "Telnet", "tabSharing": "Jakaminen", "tabAuthentication": "Todennus", "terminal": "Terminaali", "tunnel": "Tunneli", - "fileManager": "Tiedostonhallinta", "serverStats": "Isännän mittarit", "status": { "available": "Saatavilla", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} ohitettu (IP-osoitetta ei löytynyt)", "proxmoxStatsUrlCopied": "Proxmox-tilastojen URL-osoite kopioitu leikepöydälle", "copyProxmoxStatsUrlAction": "Kopioi Proxmox-tilastojen URL-osoite", - "enableFileManagerMonitor": "Ota tiedostonhallinta käyttöön", - "enableFileManagerMonitorDesc": "Selaa ja hallinnoi tiedostoja tällä isännöinnillä SFTP:n kautta", - "scpLegacyLabel": "SCP:n vanha tila", - "scpLegacyDesc": "Käytä vanhaa tiedostonsiirtoa palvelimilla, jotka eivät tue SFTP-alijärjestelmää (esim. sulautetut tai minimaaliset SSH-palvelimet).", - "defaultPathLabel": "Oletuspolku", - "fileManagerPathHint": "Hakemisto, joka avataan, kun tiedostonhallinta käynnistyy tälle isännälle.", "statusChecksLabel": "Tilatarkastukset", "enableStatusChecks": "Ota käyttöön tilan tarkistukset", "enableStatusChecksDesc": "Lähetä tälle isännälle säännöllisesti ping-viesti saatavuuden varmistamiseksi.", @@ -1278,7 +1269,6 @@ "deletedHost": "Poistettu {{name}}", "copiedToClipboard": "Kopioitu leikepöydälle", "terminalUrlCopied": "Päätelaitteen URL-osoite kopioitu", - "fileManagerUrlCopied": "Tiedostonhallinnan URL-osoite kopioitu", "tunnelUrlCopied": "Tunnelin URL-osoite kopioitu", "dockerUrlCopied": "Dockerin URL-osoite kopioitu", "hostMetricsUrlCopied": "Isäntämittarien URL-osoite kopioitu", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopioi osoite", "copyLink": "Kopioi linkki", "copyTerminalUrlAction": "Kopioi terminaalin URL-osoite", - "copyFileManagerUrlAction": "Kopioi tiedostonhallinnan URL-osoite", "copyTunnelUrlAction": "Kopioi tunnelin URL-osoite", "copyDockerUrlAction": "Kopioi Dockerin URL-osoite", "copyHostMetricsUrlAction": "Kopioi isäntätietojen URL-osoite", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Poista jaosta" } }, - "fileManager": { - "noHostSelected": "Ei valittua isäntää", - "initializingEditor": "Editorin alustaminen...", - "file": "Tiedosto", - "folder": "Kansio", - "uploadFile": "Lähetä tiedosto", - "downloadFile": "Lataa", - "extractArchive": "Pura arkisto", - "extractingArchive": "Puretaan {{name}}...", - "archiveExtractedSuccessfully": "{{name}} uutettu onnistuneesti", - "extractFailed": "Poiminta epäonnistui", - "compressFile": "Pakkaa tiedosto", - "compressFiles": "Pakkaa tiedostot", - "compressFilesDesc": "Pakkaa {{count}} kohdetta arkistoon", - "archiveName": "Arkiston nimi", - "enterArchiveName": "Anna arkiston nimi...", - "compressionFormat": "Pakkausmuoto", - "selectedFiles": "Valitut tiedostot", - "andMoreFiles": "ja {{count}} lisää...", - "compress": "Pakkaa", - "compressingFiles": "Pakkaamalla {{count}} alkiota {{name}}...", - "filesCompressedSuccessfully": "{{name}} luotu onnistuneesti", - "compressFailed": "Pakkaus epäonnistui", - "edit": "Muokata", - "preview": "Esikatselu", - "previous": "Edellinen", - "next": "Seuraava", - "pageXOfY": "Sivu {{current}} / {{total}}", - "zoomOut": "Loitonna", - "zoomIn": "Lähennä", - "newFile": "Uusi tiedosto", - "newFolder": "Uusi kansio", - "rename": "Nimeä uudelleen", - "uploading": "Ladataan...", - "uploadingFile": "Ladataan {{name}}...", - "fileName": "Tiedoston nimi", - "folderName": "Kansion nimi", - "fileUploadedSuccessfully": "Tiedosto \"{{name}}\" ladattu onnistuneesti", - "failedToUploadFile": "Tiedoston lataaminen epäonnistui", - "uploadingFolderFiles": "Ladataan {{count}} tiedostoa/tiedostoja...", - "uploadedFolderFiles": "{{count}} tiedostoa ladattu onnistuneesti", - "uploadedFolderPartial": "{{uploaded}} tiedoston/tiedostojen {{failed}} lataaminen epäonnistui", - "fileDownloadedSuccessfully": "Tiedosto \"{{name}}\" ladattu onnistuneesti", - "failedToDownloadFile": "Tiedoston lataaminen epäonnistui", - "fileCreatedSuccessfully": "Tiedosto \"{{name}}\" luotu onnistuneesti", - "folderCreatedSuccessfully": "Kansio \"{{name}}\" luotu onnistuneesti", - "failedToCreateItem": "Kohteen luominen epäonnistui", - "operationFailed": "{{operation}} toiminto epäonnistui kohteelle {{name}}: {{error}}", - "failedToResolveSymlink": "Symlinkin ratkaiseminen epäonnistui", - "itemsDeletedSuccessfully": "{{count}} kohdetta poistettu onnistuneesti", - "failedToDeleteItems": "Kohteiden poistaminen epäonnistui", - "sudoPasswordRequired": "Järjestelmänvalvojan salasana vaaditaan", - "enterSudoPassword": "Anna sudo-salasana jatkaaksesi tätä toimintoa", - "sudoPassword": "Sudo-salasana", - "sudoOperationFailed": "Sudo-toiminto epäonnistui", - "sudoAuthFailed": "Sudo-todennus epäonnistui", - "dragFilesToUpload": "Pudota tiedostot tähän ladataksesi", - "dropToUploadHere": "Pudota ladataksesi tähän", - "dropToDownloadHere": "Pudota ladataksesi tästä", - "localFiles": "Paikallinen", - "showLocalFiles": "Näytä paikalliset tiedostot (jaettu näkymä)", - "hideLocalFiles": "Piilota paikalliset tiedostot", - "localHome": "Kotikansio", - "localPathPlaceholder": "Paikallisen kansion polku", - "localShowHidden": "Näytä piilotetut tiedostot", - "localHideHidden": "Piilota piilotetut tiedostot", - "localRevealInFileManager": "Näytä järjestelmän tiedostonhallinnassa", - "localCannotOpenFolder": "Kansiota ei voi avata", - "localItemCount": "{{count}} kohdetta", - "localSelectedCount": "{{count}} valittu", - "back": "Takaisin", - "forward": "Eteenpäin", - "up": "Ylös", - "kind": "Ystävällinen", - "localUploadingProgress": "Ladataan {{current}} / {{total}}", - "localDownloadingProgress": "Ladataan {{current}} / {{total}}", - "localTransferInFlight": "{{count}} kerralla", - "localPreparingDownload": "Valmistellaan latausta...", - "localTransferCancelled": "Siirto peruttu", - "localNothingToTransfer": "Ei mitään siirrettävää", - "localUploadComplete": "Ladatut {{count}} tiedostot", - "localUploadFailed": "Lataus epäonnistui", - "localUploadPartial": "{{done}} tiedoston/tiedostojen {{failed}} lataaminen epäonnistui", - "localDownloadComplete": "Ladatut {{count}} tiedostot", - "localDownloadFailed": "Lataus epäonnistui", - "localDownloadPartial": "{{done}} tiedoston/tiedostojen {{failed}} lataus epäonnistui", - "localReplaceExistingPrompt": "{{count}} kohdetta on jo tässä kansiossa. Korvataanko ne?", - "localReplace": "Korvata", - "localSkipExisting": "Ohita", - "localSkippedExisting": "Ohitettu {{count}} olemassa olevaa kohdetta/kohteita", - "localUnsafeNamesSkipped": "Ohitettiin {{count}} kohdetta, joiden nimiä ei voi käyttää tässä tietokoneessa", - "localOpen": "Avata", - "localOpenFolder": "Avaa kansio", - "localUploadToRemote": "Lähetä palvelimelle", - "localUploadToRemoteMany": "Lataa {{count}} kohdetta palvelimelle", - "localMoveToTrash": "Siirrä roskakoriin", - "localMoveToTrashMany": "Siirrä {{count}} kohdetta roskakoriin", - "localTrashConfirmSingle": "Siirretäänkö \"{{name}}\" roskakoriin?", - "localTrashConfirmFolder": "Siirretäänkö kansio \"{{name}}\" ja kaikki sen sisältö roskakoriin?", - "localTrashConfirmMany": "Siirretäänkö {{count}} kohdetta roskakoriin?", - "localTrashed": "Siirretty {{count}} kohdetta roskakoriin", - "localTrashFailed": "Roskakoriin siirtäminen epäonnistui", - "localRenameFailed": "Uudelleennimeäminen epäonnistui", - "localCreateFailed": "Kohteen luominen epäonnistui", - "goToParentFolder": "Siirry pääkansioon", - "resizeSidebar": "Muuta sivupalkin kokoa vetämällä (nollaa kaksoisnapsauttamalla)", - "resizeLocalPane": "Vedä muuttaaksesi paikallisen ruudun kokoa (kaksoisnapsauta palauttaaksesi alkuperäisen)", - "columns": "Sarakkeet", - "columnsHint": "Valitse sarakkeet napsauttamalla hiiren kakkospainikkeella", - "emptyFolder": "Tämä kansio on tyhjä", - "searchFiles": "Hae tiedostoja...", - "upload": "Lataa", - "selectHostToStart": "Valitse isäntä tiedostonhallinnan aloittamiseksi", - "sshRequiredForFileManager": "Tiedostonhallinta vaatii SSH:n. Tässä isännässä SSH ei ole käytössä.", - "failedToConnect": "SSH-yhteyden muodostaminen epäonnistui", - "failedToLoadDirectory": "Hakemiston lataaminen epäonnistui", - "noSSHConnection": "SSH-yhteyttä ei ole saatavilla", - "copy": "Kopioida", - "cut": "Leikata", - "paste": "Liitä", - "copyPath": "Kopioi polku", - "copyPaths": "Kopioi polut", - "delete": "Poistaa", - "properties": "Ominaisuudet", - "refresh": "Päivitä", - "downloadFiles": "Lataa {{count}} tiedostoa selaimeen", - "copyFiles": "Kopioi {{count}} kohdetta", - "cutFiles": "Leikkaa {{count}} kohdetta", - "deleteFiles": "Poista {{count}} kohdetta", - "filesCopiedToClipboard": "{{count}} kohdetta kopioitiin leikepöydälle", - "filesCutToClipboard": "{{count}} kohdetta leikataan leikepöydälle", - "pathCopiedToClipboard": "Polku kopioitu leikepöydälle", - "pathsCopiedToClipboard": "{{count}} polut kopioitu leikepöydälle", - "failedToCopyPath": "Polun kopioiminen leikepöydälle epäonnistui", - "copyFolderLink": "Kopioi linkki kansioon", - "copyCurrentFolderLink": "Kopioi linkki nykyiseen kansioon", - "folderLinkCopied": "Kansiolinkki kopioitu leikepöydälle", - "failedToCopyFolderLink": "Kansiolinkin kopiointi epäonnistui", - "movedItems": "Siirretty {{count}} kohdetta", - "failedToDeleteItem": "Kohteen poistaminen epäonnistui", - "itemRenamedSuccessfully": "{{type}} nimettiin uudelleen onnistuneesti", - "failedToRenameItem": "Kohteen uudelleennimeäminen epäonnistui", - "download": "Lataa", - "openExternalEditor": "Avaa ulkopuolelta", - "chooseExternalEditor": "Valitse ulkoinen editori", - "externalEditorSelected": "Ulkoinen editori valittu", - "externalEditorDesktopOnly": "Ulkoinen editori on käytettävissä vain työpöytäsovelluksessa", - "externalEditorOpened": "Avattu ulkoisessa editorissa. Tallentamalla sinne muutokset latautuvat takaisin palvelimelle.", - "failedToOpenExternalEditor": "Ulkoisen editorin avaaminen epäonnistui", - "failedToSelectExternalEditor": "Ulkoisen editorin valinta epäonnistui", - "permissions": "Käyttöoikeudet", - "size": "Koko", - "modified": "Muokattu", - "path": "Polku", - "confirmDelete": "Oletko varma, että haluat poistaa {{name}}?", - "permissionDenied": "Lupa evätty", - "serverError": "Palvelinvirhe", - "fileSavedSuccessfully": "Tiedosto tallennettu onnistuneesti", - "failedToSaveFile": "Tiedoston tallennus epäonnistui", - "confirmDeleteSingleItem": "Oletko varma, että haluat poistaa pysyvästi \"{{name}}\"?", - "confirmDeleteMultipleItems": "Oletko varma, että haluat poistaa pysyvästi {{count}} kohdetta?", - "confirmDeleteMultipleItemsWithFolders": "Haluatko varmasti poistaa pysyvästi {{count}} kohdetta? Tämä koskee myös kansioita ja niiden sisältöä.", - "confirmDeleteFolder": "Oletko varma, että haluat poistaa pysyvästi kansion \"{{name}}\" ja kaiken sen sisällön?", - "permanentDeleteWarning": "Tätä toimintoa ei voi perua. Kohde/kohteet poistetaan pysyvästi palvelimelta.", - "moveToTrashWarning": "Kohde(et) siirretään Termixin roskakoriin ja ne voidaan palauttaa ennen säilytysajan päättymistä.", - "itemsMovedToTrash": "{{count}} kohdetta siirretty roskakoriin", - "trash": "Roska", - "trashDescription": "Poistetut kohteet säilytetään tällä isännöinnillä {{days}} päivää.", - "trashLoadFailed": "Roskakorin lataaminen epäonnistui", - "trashEmpty": "Roskakori on tyhjä", - "trashRestore": "Palauttaa", - "trashRestored": "Palautettu {{name}}", - "trashRestoreFailed": "Kohteen palauttaminen epäonnistui. Alkuperäinen polku saattaa jo olla olemassa.", - "trashDeletePermanently": "Poista pysyvästi", - "trashDeleteConfirm": "Poistetaanko {{name}}pysyvästi? Tätä ei voi perua.", - "trashPermanentlyDeleted": "Poistettu pysyvästi {{name}}", - "trashDeleteFailed": "Kohteen pysyvä poistaminen epäonnistui", - "trashEmptyAction": "Tyhjennä roskakori", - "trashEmptyConfirm": "Poistetaanko kaikki roskakorissa olevat kohteet pysyvästi? Tätä ei voi perua.", - "trashEmptied": "Roskakori tyhjennetty", - "trashRetention": "Automaattinen puhdistus (päivän) kuluttua", - "trashRetentionSaved": "Roskakorin säilytys päivitetty", - "trashRetentionFailed": "Roskakorin säilytystietojen päivittäminen epäonnistui.", - "trashUnavailableConfirm": "Tätä kohdetta ei voida siirtää roskakoriin, yleensä siksi, että se on toisessa tiedostojärjestelmässä tai vaatii laajennetut käyttöoikeudet. Poistetaanko se pysyvästi?", - "recent": "Viimeaikaiset", - "pinned": "Kiinnitetty", - "folderShortcuts": "Kansion pikavalinnat", - "failedToReconnectSSH": "SSH-istunnon uudelleenyhdistäminen epäonnistui", - "openTerminalHere": "Avaa terminaali tästä", - "run": "Juokse", - "openTerminalInFolder": "Avaa pääte tässä kansiossa", - "openTerminalInFileLocation": "Avaa pääte tiedoston sijainnissa", - "runningFile": "Juokseminen - {{file}}", - "onlyRunExecutableFiles": "Voi suorittaa vain suoritettavia tiedostoja", - "directories": "Hakemistot", - "removedFromRecentFiles": "Poistettu \"{{name}}\" viimeisimmistä tiedostoista", - "removeFailed": "Poisto epäonnistui", - "unpinnedSuccessfully": "Pinnauksen \"{{name}}\" irrotettu onnistuneesti", - "unpinFailed": "Irrottaminen epäonnistui", - "removedShortcut": "Pikakuvake \"{{name}} \" poistettu", - "removeShortcutFailed": "Pikakuvakkeen poistaminen epäonnistui", - "clearedAllRecentFiles": "Tyhjensi kaikki viimeisimmät tiedostot", - "clearFailed": "Tyhjennys epäonnistui", - "removeFromRecentFiles": "Poista viimeisimmistä tiedostoista", - "clearAllRecentFiles": "Tyhjennä kaikki viimeisimmät tiedostot", - "unpinFile": "Irrota tiedosto", - "removeShortcut": "Poista pikakuvake", - "pinFile": "Kiinnitä tiedosto", - "addToShortcuts": "Lisää pikakuvakkeisiin", - "pasteFailed": "Liittäminen epäonnistui", - "noUndoableActions": "Ei peruuttamattomia toimintoja", - "undoCopySuccess": "Kopioinnin peruutus: Poistettu {{count}} kopioitua tiedostoa", - "undoCopyFailedDelete": "Kumoaminen epäonnistui: Kopioituja tiedostoja ei voitu poistaa", - "undoCopyFailedNoInfo": "Kumoaminen epäonnistui: Kopioituja tiedostotietoja ei löytynyt", - "undoMoveSuccess": "Peruttu siirto: Siirsi {{count}} tiedostoa takaisin alkuperäiseen sijaintiinsa", - "undoMoveFailedMove": "Kumoaminen epäonnistui: Tiedostoja ei voitu siirtää takaisin", - "undoMoveFailedNoInfo": "Kumoaminen epäonnistui: Siirretyn tiedoston tietoja ei löytynyt", - "undoDeleteNotSupported": "Poistoa ei voi perua: Tiedostot on poistettu pysyvästi palvelimelta", - "undoTypeNotSupported": "Ei-tuettu kumoamistoimintotyyppi", - "undoOperationFailed": "Kumoamistoiminto epäonnistui", - "unknownError": "Tuntematon virhe", - "confirm": "Vahvistaa", - "find": "Löytää...", - "replace": "Korvata", - "downloadInstead": "Lataa sen sijaan", - "keyboardShortcuts": "Pikanäppäimet", - "searchAndReplace": "Etsi ja korvaa", - "editing": "Muokkaus", - "search": "Haku", - "findNext": "Etsi seuraava", - "findPrevious": "Etsi edellinen", - "save": "Tallentaa", - "selectAll": "Valitse kaikki", - "undo": "Kumoa", - "redo": "Tee uudelleen", - "moveLineUp": "Siirrä riviä ylös", - "moveLineDown": "Siirrä rivi alas", - "toggleComment": "Näytä kommentti", - "autoComplete": "Automaattinen täydennys", - "imageLoadError": "Kuvan lataaminen epäonnistui", - "startTyping": "Aloita kirjoittaminen...", - "unknownSize": "Tuntematon koko", - "fileIsEmpty": "Tiedosto on tyhjä", - "largeFileWarning": "Varoitus suuresta tiedostosta", - "largeFileWarningDesc": "Tiedoston koko on {{size}} , mikä voi aiheuttaa suorituskykyongelmia avattaessa sitä tekstinä.", - "fileNotFoundAndRemoved": "Tiedostoa \"{{name}}\" ei löytynyt ja se on poistettu viimeksi käytettyjen/kiinnitettyjen tiedostojen joukosta.", - "failedToLoadFile": "Tiedoston lataus epäonnistui: {{error}}", - "serverErrorOccurred": "Palvelinvirhe. Yritä myöhemmin uudelleen.", - "autoSaveFailed": "Automaattinen tallennus epäonnistui", - "fileAutoSaved": "Tiedosto tallennettu automaattisesti", - "moveFileFailed": "Siirto epäonnistui {{name}}", - "moveOperationFailed": "Siirto epäonnistui", - "canOnlyCompareFiles": "Voi verrata vain kahta tiedostoa", - "comparingFiles": "Tiedostojen vertailu: {{file1}} ja {{file2}}", - "dragFailed": "Vetotoiminto epäonnistui", - "filePinnedSuccessfully": "Tiedosto \"{{name}}\" kiinnitetty onnistuneesti", - "pinFileFailed": "Tiedoston kiinnittäminen epäonnistui", - "fileUnpinnedSuccessfully": "Tiedosto \"{{name}}\" irrotettu onnistuneesti", - "unpinFileFailed": "Tiedoston irrottaminen epäonnistui", - "shortcutAddedSuccessfully": "Kansion pikakuvake \"{{name}}\" lisätty onnistuneesti", - "addShortcutFailed": "Pikakuvakkeen lisääminen epäonnistui", - "operationCompletedSuccessfully": "{{operation}} {{count}} kohteita onnistuneesti", - "operationCompleted": "{{operation}} {{count}} kohdetta", - "downloadFileSuccess": "Tiedosto {{name}} ladattu onnistuneesti", - "downloadFileFailed": "Lataus epäonnistui", - "downloadingFile": "Ladataan {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Siirrä kohtaan {{name}}", - "diffCompareWith": "Vertaa eroa kohtaan {{name}}", - "dragOutsideToDownload": "Vedä ikkunan ulkopuolelle ladataksesi ({{count}} tiedostoa)", - "newFolderDefault": "Uusi kansio", - "newFileDefault": "UusiTiedosto.txt", - "successfullyMovedItems": "{{count}} kohdetta siirretty onnistuneesti kohteeseen {{target}}", - "move": "Liikkua", - "searchInFile": "Hae tiedostosta (Ctrl+F)", - "showKeyboardShortcuts": "Näytä pikanäppäimet", - "decreaseFontSize": "Pienennä fonttikokoa", - "increaseFontSize": "Suurenna fonttikokoa", - "startWritingMarkdown": "Aloita Markdown-sisältösi kirjoittaminen...", - "loadingFileComparison": "Tiedostojen vertailua ladataan...", - "reload": "Lataa uudelleen", - "compare": "Vertaa", - "sideBySide": "Rinnakkain", - "inline": "Rivissä", - "fileComparison": "Tiedostovertailu: {{file1}} vs. {{file2}}", - "fileTooLarge": "Tiedosto liian suuri: {{error}}", - "sshConnectionFailed": "SSH-yhteys epäonnistui. Tarkista yhteytesi kohteeseen {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Tiedoston lataus epäonnistui: {{error}}", - "connecting": "Yhdistetään...", - "connectedSuccessfully": "Yhteys onnistui", - "totpVerificationFailed": "TOTP-vahvistus epäonnistui", - "warpgateVerificationFailed": "Warpgate-todennus epäonnistui", - "authenticationFailed": "Todennus epäonnistui", - "incorrectPassphrase": "Virheellinen salasana. Yritä uudelleen.", - "verificationCodePrompt": "Vahvistuskoodi:", - "changePermissions": "Muuta käyttöoikeuksia", - "currentPermissions": "Nykyiset käyttöoikeudet", - "owner": "Omistaja", - "group": "Ryhmä", - "others": "Muut", - "read": "Lukea", - "write": "Kirjoittaa", - "execute": "Suorittaa", - "permissionsChangedSuccessfully": "Käyttöoikeudet muutettu onnistuneesti", - "failedToChangePermissions": "Käyttöoikeuksien muuttaminen epäonnistui", - "name": "Nimi", - "sortByName": "Nimi", - "sortByDate": "Muokkauspäivämäärä", - "sortBySize": "Koko", - "ascending": "Nouseva", - "descending": "Laskeva", - "root": "Juuri", - "new": "Uusi", - "sortBy": "Lajittele", - "items": "Kohteet", - "selected": "Valittu", - "editor": "Toimittaja", - "octal": "Oktaali", - "storage": "Säilytys", - "disk": "Levy", - "used": "Käytetty", - "of": "jostakin", - "toggleSidebar": "Näytä/piilota sivupalkki", - "compactLayout": "Käytä kompaktia asettelua", - "comfortableLayout": "Käytä mukavaa asettelua", - "cannotLoadPdf": "PDF-tiedoston lataaminen ei onnistu", - "pdfLoadError": "PDF-tiedoston lataamisessa tapahtui virhe.", - "loadingPdf": "Ladataan PDF-tiedostoa...", - "loadingPage": "Sivua ladataan..." - }, "transfer": { "copyToHost": "Kopioi isäntäkoneeseen…", "moveToHost": "Siirrä isäntäkoneeseen…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Uudelleenyritys epäonnistui", "transferFailedRetryHint": "Kohteessa säilytettiin vain osa tiedoista. Uudelleenyritys jatkuu, kun yhteys on palannut." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Siirrä tiedostoja kahden SSH-isännän välillä.", - "sourceServer": "Lähdepalvelin", - "destinationServer": "Kohdepalvelin", - "selectHost": "Valitse isäntä", - "noHostSelected": "Ei valittua isäntää", - "move": "Liikkua", - "copy": "Kopioida", - "upload": "Lataa", - "itemsSelected": "{{count}} kohde valittu", - "itemsSelected_other": "{{count}} kohdetta valittu", - "ready": "Valmis", - "loadingHosts": "Ladataan isäntiä...", - "name": "Nimi", - "modified": "Muokattu", - "size": "Koko", - "noFiles": "Ei tiedostoja", - "open": "Avata", - "up": "Ylös", - "refresh": "Päivitä", - "copyToTarget": "Kopioi kohdehakemistoon", - "rename": "Nimeä uudelleen", - "delete": "Poistaa", - "editPermissions": "Muokkausoikeudet", - "createNewFolder": "Luo uusi kansio", - "deleteSelectedItems": "Poista valitut kohteet", - "deleteConfirm": "Poistetaanko {{count}} valittu kohde?", - "deleteConfirm_other": "Poistetaanko {{count}} valitut kohteet?", - "cancel": "Peruuttaa", - "save": "Tallentaa", - "folderCreated": "Kansio luotu", - "itemRenamed": "Kohteen nimi on muutettu", - "actionFailed": "Toiminto epäonnistui", - "itemsDeleted": "{{count}} kohde poistettu", - "itemsDeleted_other": "{{count}} kohdetta poistettu", - "deleteFailed": "Poisto epäonnistui", - "permissionsUpdated": "Käyttöoikeudet päivitetty", - "noRemoteSessionConnected": "Ei etäistuntoa yhdistettynä", - "failedToListFiles": "Tiedostojen listaaminen epäonnistui", - "failedToRefresh": "Päivitys epäonnistui", - "destinationInsideSource": "Kohde ei voi olla valitun lähdepolun sisällä.", - "sameHostNoOp": "Lähde- ja kohdekansiot ovat samassa kansiossa." - }, "tunnels": { "noSshTunnels": "Ei SSH-tunneleita", "createFirstTunnelMessage": "Et ole vielä luonut SSH-tunneleita. Aloita määrittämällä tunneliyhteydet Host Managerissa.", diff --git a/src/ui/locales/translated/fr_FR.json b/src/ui/locales/translated/fr_FR.json index e9e8d5361..1ecda9538 100644 --- a/src/ui/locales/translated/fr_FR.json +++ b/src/ui/locales/translated/fr_FR.json @@ -715,7 +715,6 @@ "hostManager": "Gestionnaire d'hôtes", "credentials": "Identifiants", "portForwarding": "Expéditeur", - "sftp": "SFTP", "connections": "Connexions", "serial": "Série", "keepSideMenuOpen": "Garder le menu ouvert", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tunnels", - "tabFiles": "Fichiers", "tabStats": "Métriques de l'hôte", "tabTelnet": "Telnet", "tabSharing": "Partage", "tabAuthentication": "Authentification", "terminal": "Terminal", "tunnel": "Tunnel", - "fileManager": "Gestionnaire de fichiers", "serverStats": "Métriques de l'hôte", "status": { "available": "Disponible", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} ignoré (aucune adresse IP trouvée)", "proxmoxStatsUrlCopied": "URL des statistiques Proxmox copiée dans le presse-papiers", "copyProxmoxStatsUrlAction": "Copier l'URL des statistiques Proxmox", - "enableFileManagerMonitor": "Activer le gestionnaire de fichiers", - "enableFileManagerMonitorDesc": "Parcourir et gérer les fichiers de cet hôte via SFTP", - "scpLegacyLabel": "Mode SCP hérité", - "scpLegacyDesc": "Utiliser le transfert de fichiers hérité pour les serveurs qui ne prennent pas en charge le sous-système SFTP (par exemple, serveurs SSH embarqués ou minimalistes).", - "defaultPathLabel": "Chemin par défaut", - "fileManagerPathHint": "Le répertoire à ouvrir lors du lancement du gestionnaire de fichiers pour cet hôte.", "statusChecksLabel": "Vérifications d'état", "enableStatusChecks": "Activer les vérifications d'état", "enableStatusChecksDesc": "Envoyer périodiquement un ping à cet hôte pour vérifier sa disponibilité", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} supprimé", "copiedToClipboard": "Copié dans le presse-papiers", "terminalUrlCopied": "URL du terminal copiée", - "fileManagerUrlCopied": "URL du gestionnaire de fichiers copiée", "tunnelUrlCopied": "URL du tunnel copiée", "dockerUrlCopied": "URL Docker copiée", "hostMetricsUrlCopied": "URL des métriques de l'hôte copiée", @@ -1296,7 +1286,6 @@ "copyAddress": "Copier l'adresse", "copyLink": "Copier le lien", "copyTerminalUrlAction": "Copier l'URL du terminal", - "copyFileManagerUrlAction": "Copier l'URL du gestionnaire de fichiers", "copyTunnelUrlAction": "Copier l'URL du tunnel", "copyDockerUrlAction": "Copier l'URL Docker", "copyHostMetricsUrlAction": "Copier l'URL des métriques de l'hôte", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Retirer de la division" } }, - "fileManager": { - "noHostSelected": "Aucun hôte sélectionné", - "initializingEditor": "Initialisation de l'éditeur...", - "file": "Fichier", - "folder": "Dossier", - "uploadFile": "Téléverser un fichier", - "downloadFile": "Télécharger", - "extractArchive": "Extraire l'archive", - "extractingArchive": "Extraction de {{name}}...", - "archiveExtractedSuccessfully": "{{name}} extrait avec succès", - "extractFailed": "Échec de l'extraction", - "compressFile": "Compresser le fichier", - "compressFiles": "Compresser les fichiers", - "compressFilesDesc": "Compresser {{count}} éléments en une archive", - "archiveName": "Nom de l'archive", - "enterArchiveName": "Entrez le nom de l'archive...", - "compressionFormat": "Format de compression", - "selectedFiles": "Fichiers sélectionnés", - "andMoreFiles": "et {{count}} de plus...", - "compress": "Compresser", - "compressingFiles": "Compression de {{count}} éléments en {{name}}...", - "filesCompressedSuccessfully": "{{name}} créé avec succès", - "compressFailed": "Échec de la compression", - "edit": "Modifier", - "preview": "Aperçu", - "previous": "Précédent", - "next": "Suivant", - "pageXOfY": "Page {{current}} sur {{total}}", - "zoomOut": "Zoom arrière", - "zoomIn": "Zoom avant", - "newFile": "Nouveau fichier", - "newFolder": "Nouveau dossier", - "rename": "Renommer", - "uploading": "Téléversement...", - "uploadingFile": "Téléversement de {{name}}...", - "fileName": "Nom du fichier", - "folderName": "Nom du dossier", - "fileUploadedSuccessfully": "Fichier \"{{name}}\" téléversé avec succès", - "failedToUploadFile": "Échec du téléversement du fichier", - "uploadingFolderFiles": "Téléchargement du ou des fichiers {{count}}...", - "uploadedFolderFiles": "Fichier(s) {{count}} téléchargé(s) avec succès", - "uploadedFolderPartial": "Le(s) fichier(s) {{uploaded}} téléchargé(s), {{failed}} a échoué", - "fileDownloadedSuccessfully": "Fichier \"{{name}}\" téléchargé avec succès", - "failedToDownloadFile": "Échec du téléchargement du fichier", - "fileCreatedSuccessfully": "Fichier \"{{name}}\" créé avec succès", - "folderCreatedSuccessfully": "Dossier \"{{name}}\" créé avec succès", - "failedToCreateItem": "Échec de la création de l'élément", - "operationFailed": "L'opération {{operation}} a échoué pour {{name}} : {{error}}", - "failedToResolveSymlink": "Échec de la résolution du lien symbolique", - "itemsDeletedSuccessfully": "{{count}} éléments supprimés avec succès", - "failedToDeleteItems": "Échec de la suppression des éléments", - "sudoPasswordRequired": "Mot de passe administrateur requis", - "enterSudoPassword": "Entrez le mot de passe sudo pour continuer cette opération", - "sudoPassword": "Mot de passe sudo", - "sudoOperationFailed": "L'opération sudo a échoué", - "sudoAuthFailed": "L'authentification sudo a échoué", - "dragFilesToUpload": "Déposez les fichiers ici pour les téléverser", - "dropToUploadHere": "Déposez votre fichier ici.", - "dropToDownloadHere": "Cliquez ici pour télécharger", - "localFiles": "Locale", - "showLocalFiles": "Afficher les fichiers locaux (vue fractionnée)", - "hideLocalFiles": "Masquer les fichiers locaux", - "localHome": "Dossier personnel", - "localPathPlaceholder": "chemin du dossier local", - "localShowHidden": "Afficher les fichiers cachés", - "localHideHidden": "Masquer les fichiers cachés", - "localRevealInFileManager": "Afficher dans le gestionnaire de fichiers système", - "localCannotOpenFolder": "Impossible d'ouvrir le dossier", - "localItemCount": "{{count}} articles", - "localSelectedCount": "{{count}} sélectionné", - "back": "Dos", - "forward": "Avant", - "up": "En haut", - "kind": "Gentil", - "localUploadingProgress": "Téléchargement de {{current}} de {{total}}", - "localDownloadingProgress": "Téléchargement de {{current}} de {{total}}", - "localTransferInFlight": "{{count}} à la fois", - "localPreparingDownload": "Préparation du téléchargement...", - "localTransferCancelled": "Transfert annulé", - "localNothingToTransfer": "Rien à transférer", - "localUploadComplete": "Fichier(s) téléchargé(s) {{count}}", - "localUploadFailed": "Échec du chargement", - "localUploadPartial": "Le(s) fichier(s) {{done}} téléchargé(s), {{failed}} a échoué", - "localDownloadComplete": "Fichier(s) téléchargé(s) {{count}}", - "localDownloadFailed": "Échec du téléchargement", - "localDownloadPartial": "Téléchargement du(des) fichier(s) {{done}} , {{failed}} échoué", - "localReplaceExistingPrompt": "{{count}} éléments existent déjà dans ce dossier. Les remplacer ?", - "localReplace": "Remplacer", - "localSkipExisting": "Sauter", - "localSkippedExisting": "Élément(s) existant(s) ignoré(s) {{count}}", - "localUnsafeNamesSkipped": "Élément(s) ignoré(s) {{count}} dont le nom ne peut pas être utilisé sur cet ordinateur", - "localOpen": "Ouvrir", - "localOpenFolder": "Ouvrir le dossier", - "localUploadToRemote": "Téléverser sur le serveur", - "localUploadToRemoteMany": "Téléverser {{count}} éléments sur le serveur", - "localMoveToTrash": "Déplacer vers la corbeille", - "localMoveToTrashMany": "Déplacer les éléments {{count}} vers la Corbeille", - "localTrashConfirmSingle": "Déplacer «{{name}}» vers la Corbeille ?", - "localTrashConfirmFolder": "Déplacer le dossier «{{name}}» et tout son contenu vers la Corbeille ?", - "localTrashConfirmMany": "Déplacer {{count}} éléments vers la Corbeille ?", - "localTrashed": "Élément(s) déplacé(s) {{count}} vers la Corbeille", - "localTrashFailed": "Impossible de déplacer vers la poubelle", - "localRenameFailed": "Échec du renommage", - "localCreateFailed": "Impossible de créer l'élément", - "goToParentFolder": "Aller au dossier parent", - "resizeSidebar": "Faites glisser pour redimensionner la barre latérale (double-cliquez pour réinitialiser)", - "resizeLocalPane": "Faites glisser pour redimensionner le volet local (double-cliquez pour réinitialiser)", - "columns": "Colonnes", - "columnsHint": "Cliquez avec le bouton droit pour choisir les colonnes", - "emptyFolder": "Ce dossier est vide", - "searchFiles": "Rechercher des fichiers...", - "upload": "Téléverser", - "selectHostToStart": "Sélectionnez un hôte pour démarrer la gestion des fichiers", - "sshRequiredForFileManager": "Le gestionnaire de fichiers nécessite SSH. Cet hôte n'a pas SSH activé.", - "failedToConnect": "Échec de la connexion SSH", - "failedToLoadDirectory": "Échec du chargement du répertoire", - "noSSHConnection": "Aucune connexion SSH disponible", - "copy": "Copier", - "cut": "Couper", - "paste": "Coller", - "copyPath": "Copier le chemin", - "copyPaths": "Copier les chemins", - "delete": "Supprimer", - "properties": "Propriétés", - "refresh": "Actualiser", - "downloadFiles": "Télécharger {{count}} fichiers dans le navigateur", - "copyFiles": "Copier {{count}} éléments", - "cutFiles": "Couper {{count}} éléments", - "deleteFiles": "Supprimer {{count}} éléments", - "filesCopiedToClipboard": "{{count}} éléments copiés dans le presse-papiers", - "filesCutToClipboard": "{{count}} éléments coupés dans le presse-papiers", - "pathCopiedToClipboard": "Chemin copié dans le presse-papiers", - "pathsCopiedToClipboard": "{{count}} chemins copiés dans le presse-papiers", - "failedToCopyPath": "Échec de la copie du chemin dans le presse-papiers", - "copyFolderLink": "Copier le lien vers le dossier", - "copyCurrentFolderLink": "Copier le lien vers le dossier actuel", - "folderLinkCopied": "Lien du dossier copié dans le presse-papiers", - "failedToCopyFolderLink": "Échec de la copie du lien du dossier", - "movedItems": "{{count}} élément(s) déplacé(s)", - "failedToDeleteItem": "Échec de la suppression de l’élément", - "itemRenamedSuccessfully": "{{type}} renommé avec succès", - "failedToRenameItem": "Échec du renommage de l’élément", - "download": "Télécharger", - "openExternalEditor": "Ouvrir avec l’éditeur externe", - "chooseExternalEditor": "Choisir l’éditeur externe", - "externalEditorSelected": "Éditeur externe sélectionné", - "externalEditorDesktopOnly": "L’éditeur externe est uniquement disponible dans l’application de bureau", - "externalEditorOpened": "Ouvert dans l’éditeur externe. Les modifications enregistrées seront renvoyées vers le serveur.", - "failedToOpenExternalEditor": "Échec de l’ouverture de l’éditeur externe", - "failedToSelectExternalEditor": "Échec de la sélection de l’éditeur externe", - "permissions": "Permissions", - "size": "Taille", - "modified": "Modifié", - "path": "Chemin", - "confirmDelete": "Êtes-vous sûr de vouloir supprimer {{name}} ?", - "permissionDenied": "Permission refusée", - "serverError": "Erreur serveur", - "fileSavedSuccessfully": "Fichier enregistré avec succès", - "failedToSaveFile": "Échec de l’enregistrement du fichier", - "confirmDeleteSingleItem": "Êtes-vous sûr de vouloir supprimer définitivement « {{name}} » ?", - "confirmDeleteMultipleItems": "Êtes-vous sûr de vouloir supprimer définitivement {{count}} éléments ?", - "confirmDeleteMultipleItemsWithFolders": "Êtes-vous sûr de vouloir supprimer définitivement {{count}} éléments ? Cela inclut les dossiers et leur contenu.", - "confirmDeleteFolder": "Êtes-vous sûr de vouloir supprimer définitivement le dossier « {{name}} » et tout son contenu ?", - "permanentDeleteWarning": "Cette action est irréversible. Le ou les éléments seront définitivement supprimés du serveur.", - "moveToTrashWarning": "Les éléments seront déplacés vers la corbeille Termix et pourront être restaurés avant l'expiration de la période de conservation.", - "itemsMovedToTrash": "{{count}} élément(s) déplacé(s) vers la Corbeille", - "trash": "Poubelle", - "trashDescription": "Les éléments supprimés sont conservés pendant {{days}} jour(s) sur cet hôte.", - "trashLoadFailed": "Échec du chargement de la corbeille", - "trashEmpty": "La poubelle est vide.", - "trashRestore": "Restaurer", - "trashRestored": "Restauré {{name}}", - "trashRestoreFailed": "Impossible de restaurer l'élément. Le chemin d'accès d'origine existe peut-être déjà.", - "trashDeletePermanently": "Supprimer définitivement", - "trashDeleteConfirm": "Supprimer définitivement {{name}}? Cette action est irréversible.", - "trashPermanentlyDeleted": "Supprimé définitivement {{name}}", - "trashDeleteFailed": "Échec de la suppression définitive de l'élément", - "trashEmptyAction": "Vider la poubelle", - "trashEmptyConfirm": "Supprimer définitivement tous les éléments de la Corbeille ? Cette action est irréversible.", - "trashEmptied": "Poubelle vidée", - "trashRetention": "Nettoyage automatique après (jours)", - "trashRetentionSaved": "Mise à jour concernant la conservation des déchets", - "trashRetentionFailed": "Échec de la mise à jour de la conservation de la corbeille", - "trashUnavailableConfirm": "Cet élément ne peut pas être déplacé vers la Corbeille, généralement parce qu'il se trouve sur un autre système de fichiers ou qu'il nécessite des autorisations élevées. Voulez-vous plutôt le supprimer définitivement ?", - "recent": "Récents", - "pinned": "Épinglés", - "folderShortcuts": "Raccourcis de dossiers", - "failedToReconnectSSH": "Échec de la reconnexion de la session SSH", - "openTerminalHere": "Ouvrir le terminal ici", - "run": "Exécuter", - "openTerminalInFolder": "Ouvrir le terminal dans ce dossier", - "openTerminalInFileLocation": "Ouvrir le terminal à l'emplacement du fichier", - "runningFile": "En cours d'exécution - {{file}}", - "onlyRunExecutableFiles": "Seuls les fichiers exécutables peuvent être exécutés", - "directories": "Dossiers", - "removedFromRecentFiles": "« {{name}} » retiré des fichiers récents", - "removeFailed": "Échec de la suppression", - "unpinnedSuccessfully": "« {{name}} » désépinglé avec succès", - "unpinFailed": "Échec du désépinglage", - "removedShortcut": "Raccourci « {{name}} » supprimé", - "removeShortcutFailed": "Échec de la suppression du raccourci", - "clearedAllRecentFiles": "Tous les fichiers récents ont été effacés", - "clearFailed": "Échec de l'effacement", - "removeFromRecentFiles": "Retirer des fichiers récents", - "clearAllRecentFiles": "Effacer tous les fichiers récents", - "unpinFile": "Désépingler le fichier", - "removeShortcut": "Supprimer le raccourci", - "pinFile": "Épingler le fichier", - "addToShortcuts": "Ajouter aux raccourcis", - "pasteFailed": "Échec du collage", - "noUndoableActions": "Aucune action annulable", - "undoCopySuccess": "Annulation de la copie : {{count}} fichier(s) copié(s) supprimé(s)", - "undoCopyFailedDelete": "Échec de l'annulation : impossible de supprimer les fichiers copiés", - "undoCopyFailedNoInfo": "Échec de l'annulation : informations sur les fichiers copiés introuvables", - "undoMoveSuccess": "Annulation du déplacement : {{count}} fichier(s) remis à leur emplacement d’origine", - "undoMoveFailedMove": "Échec de l'annulation : impossible de remettre les fichiers en place", - "undoMoveFailedNoInfo": "Échec de l'annulation : informations sur les fichiers déplacés introuvables", - "undoDeleteNotSupported": "La suppression ne peut pas être annulée : les fichiers ont été définitivement supprimés du serveur", - "undoTypeNotSupported": "Type d’annulation non pris en charge", - "undoOperationFailed": "Échec de l’annulation", - "unknownError": "Erreur inconnue", - "confirm": "Confirmer", - "find": "Rechercher...", - "replace": "Remplacer", - "downloadInstead": "Télécharger à la place", - "keyboardShortcuts": "Raccourcis clavier", - "searchAndReplace": "Rechercher et remplacer", - "editing": "Édition", - "search": "Rechercher", - "findNext": "Rechercher le suivant", - "findPrevious": "Rechercher le précédent", - "save": "Enregistrer", - "selectAll": "Tout sélectionner", - "undo": "Annuler", - "redo": "Rétablir", - "moveLineUp": "Déplacer la ligne vers le haut", - "moveLineDown": "Déplacer la ligne vers le bas", - "toggleComment": "Commenter/Décommenter", - "autoComplete": "Complétion automatique", - "imageLoadError": "Échec du chargement de l’image", - "startTyping": "Commencez à taper…", - "unknownSize": "Taille inconnue", - "fileIsEmpty": "Le fichier est vide", - "largeFileWarning": "Avertissement – Fichier volumineux", - "largeFileWarningDesc": "Ce fichier a une taille de {{size}}, ce qui peut entraîner des problèmes de performances lorsqu’il est ouvert en tant que texte.", - "fileNotFoundAndRemoved": "Le fichier « {{name}} » est introuvable et a été retiré des fichiers récents/épinglés.", - "failedToLoadFile": "Échec du chargement du fichier : {{error}}", - "serverErrorOccurred": "Une erreur serveur s’est produite. Veuillez réessayer plus tard.", - "autoSaveFailed": "Échec de l’enregistrement automatique", - "fileAutoSaved": "Fichier enregistré automatiquement", - "moveFileFailed": "Échec du déplacement de {{name}}", - "moveOperationFailed": "L’opération de déplacement a échoué", - "canOnlyCompareFiles": "Il est uniquement possible de comparer deux fichiers", - "comparingFiles": "Comparaison des fichiers : {{file1}} et {{file2}}", - "dragFailed": "L’opération de glisser-déposer a échoué", - "filePinnedSuccessfully": "Le fichier « {{name}} » a été épinglé avec succès", - "pinFileFailed": "Échec de l’épinglage du fichier", - "fileUnpinnedSuccessfully": "Le fichier « {{name}} » a été désépinglé avec succès", - "unpinFileFailed": "Impossible de désépingler le fichier", - "shortcutAddedSuccessfully": "Raccourci de dossier « {{name}} » ajouté avec succès", - "addShortcutFailed": "Impossible d'ajouter le raccourci", - "operationCompletedSuccessfully": "{{operation}} {{count}} éléments avec succès", - "operationCompleted": "{{operation}} {{count}} éléments", - "downloadFileSuccess": "Fichier {{name}} téléchargé avec succès", - "downloadFileFailed": "Échec du téléchargement", - "downloadingFile": "Téléchargement de {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Déplacer vers {{name}}", - "diffCompareWith": "Diff avec {{name}}", - "dragOutsideToDownload": "Faites glisser hors de la fenêtre pour télécharger ({{count}} fichiers)", - "newFolderDefault": "Nouveau dossier", - "newFileDefault": "Nouveau fichier.txt", - "successfullyMovedItems": "{{count}} éléments déplacés avec succès vers {{target}}", - "move": "Déplacer", - "searchInFile": "Rechercher dans le fichier (Ctrl+F)", - "showKeyboardShortcuts": "Afficher les raccourcis clavier", - "decreaseFontSize": "Diminuer la taille de police", - "increaseFontSize": "Augmenter la taille de police", - "startWritingMarkdown": "Commencez à rédiger votre contenu markdown...", - "loadingFileComparison": "Chargement de la comparaison de fichiers...", - "reload": "Recharger", - "compare": "Comparer", - "sideBySide": "Côte à côte", - "inline": "En ligne", - "fileComparison": "Comparaison de fichiers : {{file1}} vs {{file2}}", - "fileTooLarge": "Fichier trop volumineux : {{error}}", - "sshConnectionFailed": "La connexion SSH a échoué. Veuillez vérifier votre connexion à {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Impossible de charger le fichier : {{error}}", - "connecting": "Connexion en cours...", - "connectedSuccessfully": "Connecté avec succès", - "totpVerificationFailed": "Échec de la vérification TOTP", - "warpgateVerificationFailed": "Échec de l'authentification Warpgate", - "authenticationFailed": "Échec de l'authentification", - "incorrectPassphrase": "Phrase secrète incorrecte. Veuillez réessayer.", - "verificationCodePrompt": "Code de vérification :", - "changePermissions": "Modifier les permissions", - "currentPermissions": "Permissions actuelles", - "owner": "Propriétaire", - "group": "Groupe", - "others": "Autres", - "read": "Lecture", - "write": "Écriture", - "execute": "Exécution", - "permissionsChangedSuccessfully": "Permissions modifiées avec succès", - "failedToChangePermissions": "Échec de la modification des permissions", - "name": "Nom", - "sortByName": "Nom", - "sortByDate": "Date de modification", - "sortBySize": "Taille", - "ascending": "Croissant", - "descending": "Décroissant", - "root": "Racine", - "new": "Nouveau", - "sortBy": "Trier par", - "items": "Éléments", - "selected": "Sélectionné", - "editor": "Éditeur", - "octal": "Octal", - "storage": "Stockage", - "disk": "Disque", - "used": "Utilisé", - "of": "sur", - "toggleSidebar": "Afficher/Masquer la barre latérale", - "compactLayout": "Utilisez une mise en page compacte", - "comfortableLayout": "Utilisez une disposition confortable", - "cannotLoadPdf": "Impossible de charger le PDF", - "pdfLoadError": "Une erreur s'est produite lors du chargement de ce fichier PDF.", - "loadingPdf": "Chargement du PDF...", - "loadingPage": "Chargement de la page..." - }, "transfer": { "copyToHost": "Copier vers l’hôte…", "moveToHost": "Déplacer vers l’hôte…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Échec de la nouvelle tentative", "transferFailedRetryHint": "Des données partielles ont été conservées sur la destination. La nouvelle tentative reprendra lorsque la connexion sera rétablie." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Transférer des fichiers entre deux hôtes SSH.", - "sourceServer": "Serveur source", - "destinationServer": "Serveur de destination", - "selectHost": "Sélectionner l'hôte", - "noHostSelected": "Aucun hôte sélectionné", - "move": "Se déplacer", - "copy": "Copie", - "upload": "Télécharger", - "itemsSelected": "{{count}} élément sélectionné", - "itemsSelected_other": "{{count}} éléments sélectionnés", - "ready": "Prêt", - "loadingHosts": "Chargement des hôtes...", - "name": "Nom", - "modified": "Modifié", - "size": "Taille", - "noFiles": "Aucun fichier", - "open": "Ouvrir", - "up": "En haut", - "refresh": "Rafraîchir", - "copyToTarget": "Copier dans le répertoire cible", - "rename": "Rebaptiser", - "delete": "Supprimer", - "editPermissions": "Autorisations de modification", - "createNewFolder": "Créer un nouveau dossier", - "deleteSelectedItems": "Supprimer les éléments sélectionnés", - "deleteConfirm": "Supprimer l'élément sélectionné {{count}} ?", - "deleteConfirm_other": "Supprimer les éléments sélectionnés {{count}} ?", - "cancel": "Annuler", - "save": "Sauvegarder", - "folderCreated": "Dossier créé", - "itemRenamed": "Article renommé", - "actionFailed": "L'action a échoué", - "itemsDeleted": "{{count}} élément supprimé", - "itemsDeleted_other": "{{count}} éléments supprimés", - "deleteFailed": "Échec de la suppression", - "permissionsUpdated": "Autorisations mises à jour", - "noRemoteSessionConnected": "Aucune session distante connectée", - "failedToListFiles": "Impossible de lister les fichiers", - "failedToRefresh": "Échec de l'actualisation", - "destinationInsideSource": "La destination ne peut pas se trouver à l'intérieur du chemin source sélectionné.", - "sameHostNoOp": "Le dossier source et le dossier de destination sont identiques." - }, "tunnels": { "noSshTunnels": "Aucun tunnel SSH", "createFirstTunnelMessage": "Vous n'avez encore créé aucun tunnel SSH. Configurez des connexions de tunnel dans le Gestionnaire d'hôtes pour commencer.", diff --git a/src/ui/locales/translated/he_IL.json b/src/ui/locales/translated/he_IL.json index 59eec733a..4c45385ee 100644 --- a/src/ui/locales/translated/he_IL.json +++ b/src/ui/locales/translated/he_IL.json @@ -715,7 +715,6 @@ "hostManager": "מנהל מארח", "credentials": "אישורים", "portForwarding": "העברה", - "sftp": "SFTP", "connections": "חיבורים", "serial": "סִדרָתִי", "keepSideMenuOpen": "שמור את התפריט פתוח", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "מנהרות", - "tabFiles": "קבצים", "tabStats": "מדדי מארח", "tabTelnet": "טלנט", "tabSharing": "שיתוף", "tabAuthentication": "אימות", "terminal": "מָסוֹף", "tunnel": "מִנהָרָה", - "fileManager": "מנהל הקבצים", "serverStats": "מדדי מארח", "status": { "available": "זָמִין", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} דילג (לא נמצאה כתובת IP)", "proxmoxStatsUrlCopied": "כתובת האתר של סטטיסטיקות Proxmox הועתקה ללוח", "copyProxmoxStatsUrlAction": "העתק כתובת URL של סטטיסטיקות Proxmox", - "enableFileManagerMonitor": "הפעל את מנהל הקבצים", - "enableFileManagerMonitorDesc": "עיון וניהול קבצים במארח זה דרך SFTP", - "scpLegacyLabel": "מצב SCP Legacy", - "scpLegacyDesc": "השתמש בהעברת קבצים מדור קודם עבור שרתים שאינם תומכים בתת-מערכת SFTP (למשל שרתי SSH מוטמעים או מינימליים).", - "defaultPathLabel": "נתיב ברירת מחדל", - "fileManagerPathHint": "הספרייה שתיפתח כאשר מנהל הקבצים יופעל עבור מארח זה.", "statusChecksLabel": "בדיקות סטטוס", "enableStatusChecks": "הפעל בדיקות סטטוס", "enableStatusChecksDesc": "שלח פינג למארח הזה מעת לעת כדי לוודא זמינות", @@ -1278,7 +1269,6 @@ "deletedHost": "נמחק {{name}}", "copiedToClipboard": "הועתק ללוח", "terminalUrlCopied": "כתובת ה-URL של הטרמינל הועתקה", - "fileManagerUrlCopied": "כתובת ה-URL של מנהל הקבצים הועתקה", "tunnelUrlCopied": "כתובת ה-URL של המנהרה הועתקה", "dockerUrlCopied": "כתובת ה-URL של Docker הועתקה", "hostMetricsUrlCopied": "כתובת האתר של מדדי המארח הועתקה", @@ -1296,7 +1286,6 @@ "copyAddress": "העתקת כתובת", "copyLink": "העתק קישור", "copyTerminalUrlAction": "העתקת כתובת URL של הטרמינל", - "copyFileManagerUrlAction": "העתקת כתובת URL של מנהל הקבצים", "copyTunnelUrlAction": "העתקת כתובת URL של המנהרה", "copyDockerUrlAction": "העתקת כתובת URL של Docker", "copyHostMetricsUrlAction": "העתקת כתובת URL של מדדי מארח", @@ -1857,336 +1846,6 @@ "removeFromSplit": "הסר מפיצול" } }, - "fileManager": { - "noHostSelected": "לא נבחר מארח", - "initializingEditor": "מאתחל עורך...", - "file": "קוֹבֶץ", - "folder": "תיקייה", - "uploadFile": "העלאת קובץ", - "downloadFile": "הורדה", - "extractArchive": "ארכיון חילוץ", - "extractingArchive": "מחלץ {{name}}...", - "archiveExtractedSuccessfully": "{{name}} חולץ בהצלחה", - "extractFailed": "החילוץ נכשל", - "compressFile": "דחיסת קובץ", - "compressFiles": "דחיסת קבצים", - "compressFilesDesc": "דחיסת {{count}} פריטים לתוך ארכיון", - "archiveName": "שם הארכיון", - "enterArchiveName": "הזן שם ארכיון...", - "compressionFormat": "פורמט דחיסה", - "selectedFiles": "קבצים נבחרים", - "andMoreFiles": "ועוד {{count}}...", - "compress": "לִדחוֹס", - "compressingFiles": "דחיסת {{count}} פריטים לתוך {{name}}...", - "filesCompressedSuccessfully": "{{name}} נוצר בהצלחה", - "compressFailed": "הדחיסה נכשלה", - "edit": "לַעֲרוֹך", - "preview": "תצוגה מקדימה", - "previous": "קוֹדֵם", - "next": "הַבָּא", - "pageXOfY": "עמוד {{current}} מתוך {{total}}", - "zoomOut": "התקרבות", - "zoomIn": "לְהִתְמַקֵד", - "newFile": "קובץ חדש", - "newFolder": "תיקייה חדשה", - "rename": "שינוי שם", - "uploading": "מעלה...", - "uploadingFile": "מעלה {{name}}...", - "fileName": "שם הקובץ", - "folderName": "שם התיקייה", - "fileUploadedSuccessfully": "הקובץ \"{{name}}\" הועלה בהצלחה", - "failedToUploadFile": "העלאת הקובץ נכשלה", - "uploadingFolderFiles": "מעלה קובץ/ים {{count}}...", - "uploadedFolderFiles": "הקבצים {{count}} הועלו בהצלחה", - "uploadedFolderPartial": "העלאת קובץ/קבצים {{uploaded}} , {{failed}} נכשלה", - "fileDownloadedSuccessfully": "הקובץ \"{{name}}\" הורד בהצלחה", - "failedToDownloadFile": "הורדת הקובץ נכשלה", - "fileCreatedSuccessfully": "הקובץ \"{{name}}\" נוצר בהצלחה", - "folderCreatedSuccessfully": "תיקיית \"{{name}}\" נוצרה בהצלחה", - "failedToCreateItem": "יצירת הפריט נכשלה", - "operationFailed": "הפעולה {{operation}} נכשלה עבור {{name}}: {{error}}", - "failedToResolveSymlink": "נכשל בפענוח הסימבילינק", - "itemsDeletedSuccessfully": "{{count}} פריטים נמחקו בהצלחה", - "failedToDeleteItems": "מחיקת הפריטים נכשלה", - "sudoPasswordRequired": "נדרשת סיסמת מנהל", - "enterSudoPassword": "הזן את סיסמת הסודו כדי להמשיך בפעולה זו", - "sudoPassword": "סיסמת סודו", - "sudoOperationFailed": "פעולת הסודו נכשלה", - "sudoAuthFailed": "אימות סודו נכשל", - "dragFilesToUpload": "שחררו קבצים כאן כדי להעלות", - "dropToUploadHere": "שחרר כדי להעלות כאן", - "dropToDownloadHere": "שחררו להורדה כאן", - "localFiles": "מְקוֹמִי", - "showLocalFiles": "הצגת קבצים מקומיים (תצוגה מפוצלת)", - "hideLocalFiles": "הסתר קבצים מקומיים", - "localHome": "תיקיית הבית", - "localPathPlaceholder": "נתיב התיקייה המקומית", - "localShowHidden": "הצג קבצים מוסתרים", - "localHideHidden": "הסתר קבצים נסתרים", - "localRevealInFileManager": "חשיפה במנהל הקבצים של המערכת", - "localCannotOpenFolder": "לא ניתן לפתוח תיקייה", - "localItemCount": "פריטים {{count}}", - "localSelectedCount": "{{count}} נבחר", - "back": "בְּחֲזָרָה", - "forward": "קָדִימָה", - "up": "לְמַעלָה", - "kind": "סוּג", - "localUploadingProgress": "מעלה {{current}} מתוך {{total}}", - "localDownloadingProgress": "מוריד {{current}} מתוך {{total}}", - "localTransferInFlight": "{{count}} בבת אחת", - "localPreparingDownload": "מכין הורדה...", - "localTransferCancelled": "ההעברה בוטלה", - "localNothingToTransfer": "אין מה להעביר", - "localUploadComplete": "הועלו {{count}} קבצים", - "localUploadFailed": "ההעלאה נכשלה", - "localUploadPartial": "העלאת קובץ/קבצים {{done}} , {{failed}} נכשלה", - "localDownloadComplete": "הורד/י קובץ/ים {{count}}", - "localDownloadFailed": "ההורדה נכשלה", - "localDownloadPartial": "הורדת הקובץ/קבצים {{done}} , {{failed}} נכשלה", - "localReplaceExistingPrompt": "פריט/ים {{count}} כבר קיים/ים בתיקייה זו. האם להחליף אותם?", - "localReplace": "לְהַחלִיף", - "localSkipExisting": "לְדַלֵג", - "localSkippedExisting": "דילג על {{count}} פריטים קיימים", - "localUnsafeNamesSkipped": "דילגתי על {{count}} פריטים ששמם אינו ניתן לשימוש במחשב זה", - "localOpen": "לִפְתוֹחַ", - "localOpenFolder": "פתיחת תיקייה", - "localUploadToRemote": "העלאה לשרת", - "localUploadToRemoteMany": "העלה {{count}} פריטים לשרת", - "localMoveToTrash": "העברה לאשפה", - "localMoveToTrashMany": "העבר {{count}} פריטים לאשפה", - "localTrashConfirmSingle": "להעביר את \"{{name}}\" לאשפה?", - "localTrashConfirmFolder": "להעביר את התיקייה \"{{name}}\" ואת כל תוכנה לפח האשפה?", - "localTrashConfirmMany": "להעביר {{count}} פריטים לאשפה?", - "localTrashed": "הועבר/י {{count}} פריט/ים לאשפה", - "localTrashFailed": "לא ניתן היה להעביר לאשפה", - "localRenameFailed": "שינוי השם נכשל", - "localCreateFailed": "לא ניתן היה ליצור פריט", - "goToParentFolder": "עבור לתיקיית האב", - "resizeSidebar": "גרור כדי לשנות את גודל סרגל הצד (לחץ פעמיים כדי לאפס)", - "resizeLocalPane": "גרור כדי לשנות את גודל החלונית המקומית (לחץ פעמיים כדי לאפס)", - "columns": "עמודות", - "columnsHint": "לחיצה ימנית כדי לבחור עמודות", - "emptyFolder": "תיקייה זו ריקה", - "searchFiles": "חיפוש קבצים...", - "upload": "העלאה", - "selectHostToStart": "בחר מארח כדי להתחיל בניהול קבצים", - "sshRequiredForFileManager": "מנהל הקבצים דורש SSH. שרת זה אינו מופעל באמצעות SSH.", - "failedToConnect": "נכשלה ההתחברות ל-SSH", - "failedToLoadDirectory": "טעינת הספרייה נכשלה", - "noSSHConnection": "אין חיבור SSH זמין", - "copy": "לְהַעְתִיק", - "cut": "גְזִירָה", - "paste": "לְהַדבִּיק", - "copyPath": "העתק נתיב", - "copyPaths": "העתקת נתיבים", - "delete": "לִמְחוֹק", - "properties": "נכסים", - "refresh": "לְרַעֲנֵן", - "downloadFiles": "הורד {{count}} קבצים לדפדפן", - "copyFiles": "העתקת {{count}} פריטים", - "cutFiles": "גזור {{count}} פריטים", - "deleteFiles": "מחיקת {{count}} פריטים", - "filesCopiedToClipboard": "{{count}} פריטים הועתקו ללוח", - "filesCutToClipboard": "{{count}} פריטים נחתכו ללוח", - "pathCopiedToClipboard": "הנתיב הועתק ללוח", - "pathsCopiedToClipboard": "נתיבים {{count}} הועתקו ללוח", - "failedToCopyPath": "נכשלה העתקת הנתיב ללוח", - "copyFolderLink": "העתקת קישור לתיקייה", - "copyCurrentFolderLink": "העתקת קישור לתיקייה הנוכחית", - "folderLinkCopied": "קישור לתיקייה הועתק ללוח", - "failedToCopyFolderLink": "נכשל בהעתקת קישור לתיקייה", - "movedItems": "הועבר {{count}} פריטים", - "failedToDeleteItem": "מחיקת הפריט נכשלה", - "itemRenamedSuccessfully": "שם השינוי של {{type}} הצליח", - "failedToRenameItem": "שינוי שם הפריט נכשל", - "download": "הורדה", - "openExternalEditor": "פתוח חיצונית", - "chooseExternalEditor": "בחירת עורך חיצוני", - "externalEditorSelected": "עורך חיצוני נבחר", - "externalEditorDesktopOnly": "עורך חיצוני זמין רק באפליקציית שולחן העבודה", - "externalEditorOpened": "נפתח בעורך חיצוני. שמירה שם תעלה את השינויים בחזרה לשרת.", - "failedToOpenExternalEditor": "נכשלה פתיחת העורך החיצוני", - "failedToSelectExternalEditor": "נכשלה בחירת עורך חיצוני", - "permissions": "הרשאות", - "size": "גוֹדֶל", - "modified": "שונה", - "path": "נָתִיב", - "confirmDelete": "האם אתה בטוח שאתה רוצה למחוק {{name}}?", - "permissionDenied": "ההרשאה נדחתה", - "serverError": "שגיאת שרת", - "fileSavedSuccessfully": "הקובץ נשמר בהצלחה", - "failedToSaveFile": "שמירת הקובץ נכשלה", - "confirmDeleteSingleItem": "האם אתה בטוח שברצונך למחוק לצמיתות את \"{{name}}\"?", - "confirmDeleteMultipleItems": "האם אתה בטוח שברצונך למחוק לצמיתות {{count}} פריטים?", - "confirmDeleteMultipleItemsWithFolders": "האם אתה בטוח שברצונך למחוק לצמיתות את הפריטים {{count}} ? זה כולל תיקיות ותוכן שלהן.", - "confirmDeleteFolder": "האם אתה בטוח שברצונך למחוק לצמיתות את התיקייה \"{{name}}\" ואת כל תוכנה?", - "permanentDeleteWarning": "לא ניתן לבטל פעולה זו. הפריט/ים יימחקו לצמיתות מהשרת.", - "moveToTrashWarning": "הפריט/ים יועברו לפח האשפה של Termix וניתן יהיה לשחזר אותם לפני שתקופת השמירה תפוג.", - "itemsMovedToTrash": "פריט/ים {{count}} הועברו לאשפה", - "trash": "אַשׁפָּה", - "trashDescription": "פריטים שנמחקו נשמרים למשך {{days}} יום/ימים במארח זה.", - "trashLoadFailed": "טעינת האשפה נכשלה", - "trashEmpty": "האשפה ריקה", - "trashRestore": "לְשַׁחְזֵר", - "trashRestored": "שוחזר {{name}}", - "trashRestoreFailed": "שחזור הפריט נכשל. ייתכן שהנתיב המקורי כבר קיים.", - "trashDeletePermanently": "מחיקה לצמיתות", - "trashDeleteConfirm": "למחוק לצמיתות את {{name}}? לא ניתן לבטל פעולה זו.", - "trashPermanentlyDeleted": "נמחק לצמיתות {{name}}", - "trashDeleteFailed": "נכשלה המחיקה לצמיתות של הפריט", - "trashEmptyAction": "רוקנו את האשפה", - "trashEmptyConfirm": "למחוק לצמיתות כל פריט באשפה? לא ניתן לבטל פעולה זו.", - "trashEmptied": "ריקון האשפה", - "trashRetention": "ניקוי אוטומטי לאחר (ימים)", - "trashRetentionSaved": "שמירת האשפה עודכנה", - "trashRetentionFailed": "נכשל עדכון שמירת האשפה", - "trashUnavailableConfirm": "לא ניתן להעביר פריט זה לאשפה, בדרך כלל מכיוון שהוא נמצא במערכת קבצים אחרת או דורש הרשאות מוגברות. האם למחוק אותו לצמיתות במקום זאת?", - "recent": "אחרונים", - "pinned": "מוצמד", - "folderShortcuts": "קיצורי דרך לתיקיות", - "failedToReconnectSSH": "נכשל בחיבור מחדש של סשן SSH", - "openTerminalHere": "פתח את הטרמינל כאן", - "run": "לָרוּץ", - "openTerminalInFolder": "פתח את הטרמינל בתיקייה זו", - "openTerminalInFileLocation": "פתיחת הטרמינל במיקום הקובץ", - "runningFile": "ריצה - {{file}}", - "onlyRunExecutableFiles": "יכול להריץ רק קבצי הפעלה", - "directories": "מדריכים", - "removedFromRecentFiles": "הוסר \"{{name}}\" מהקבצים האחרונים", - "removeFailed": "ההסרה נכשלה", - "unpinnedSuccessfully": "\"{{name}}\" בוטל בהצלחה", - "unpinFailed": "ביטול ההצמדה נכשל", - "removedShortcut": "קיצור הדרך \"{{name}}\" הוסר", - "removeShortcutFailed": "הסרת קיצור הדרך נכשלה", - "clearedAllRecentFiles": "ניקה את כל הקבצים האחרונים", - "clearFailed": "ניקוי נכשל", - "removeFromRecentFiles": "הסר מהקבצים האחרונים", - "clearAllRecentFiles": "נקה את כל הקבצים האחרונים", - "unpinFile": "ביטול הצמדת קובץ", - "removeShortcut": "הסר קיצור דרך", - "pinFile": "קובץ הצמדה", - "addToShortcuts": "הוסף לקיצורי דרך", - "pasteFailed": "ההדבקה נכשלה", - "noUndoableActions": "אין פעולות שניתן לבטל", - "undoCopySuccess": "פעולת העתקה שבוטלה: מחיקת {{count}} קבצים שהועתקו", - "undoCopyFailedDelete": "ביטול נכשל: לא ניתן היה למחוק קבצים שהועתקו", - "undoCopyFailedNoInfo": "ביטול נכשל: לא ניתן היה למצוא את פרטי הקובץ שהועתק", - "undoMoveSuccess": "פעולת העברה שבוטלה: העבירו {{count}} קבצים חזרה למיקום המקורי", - "undoMoveFailedMove": "ביטול נכשל: לא ניתן היה להעביר קבצים בחזרה", - "undoMoveFailedNoInfo": "ביטול נכשל: לא ניתן היה למצוא מידע על הקובץ שהועבר", - "undoDeleteNotSupported": "לא ניתן לבטל את פעולת המחיקה: הקבצים נמחקו לצמיתות מהשרת", - "undoTypeNotSupported": "סוג פעולת ביטול לא נתמך", - "undoOperationFailed": "פעולת הביטול נכשלה", - "unknownError": "שגיאה לא ידועה", - "confirm": "לְאַשֵׁר", - "find": "לִמצוֹא...", - "replace": "לְהַחלִיף", - "downloadInstead": "הורד במקום זאת", - "keyboardShortcuts": "קיצורי מקלדת", - "searchAndReplace": "חיפוש והחלפה", - "editing": "עֲרִיכָה", - "search": "לְחַפֵּשׂ", - "findNext": "מצא את הבא", - "findPrevious": "מצא את הקודם", - "save": "לְהַצִיל", - "selectAll": "בחר הכל", - "undo": "לְבַטֵל", - "redo": "לַעֲשׂוֹת שׁוּב", - "moveLineUp": "הזזת שורה למעלה", - "moveLineDown": "הזזת שורה למטה", - "toggleComment": "החלף/הפעל תגובה", - "autoComplete": "השלמה אוטומטית", - "imageLoadError": "טעינת התמונה נכשלה", - "startTyping": "התחל להקליד...", - "unknownSize": "גודל לא ידוע", - "fileIsEmpty": "הקובץ ריק", - "largeFileWarning": "אזהרת קובץ גדול", - "largeFileWarningDesc": "קובץ זה הוא בגודל {{size}} , דבר שעלול לגרום לבעיות ביצועים בעת פתיחה כטקסט.", - "fileNotFoundAndRemoved": "הקובץ \"{{name}}\" לא נמצא והוסר מהקבצים האחרונים/המוצמדים", - "failedToLoadFile": "טעינת הקובץ נכשלה: {{error}}", - "serverErrorOccurred": "אירעה שגיאת שרת. אנא נסה שוב מאוחר יותר.", - "autoSaveFailed": "השמירה האוטומטית נכשלה", - "fileAutoSaved": "קובץ נשמר אוטומטית", - "moveFileFailed": "נכשל בהזזת {{name}}", - "moveOperationFailed": "פעולת ההעברה נכשלה", - "canOnlyCompareFiles": "ניתן להשוות רק שני קבצים", - "comparingFiles": "השוואת קבצים: {{file1}} ו- {{file2}}", - "dragFailed": "פעולת הגרירה נכשלה", - "filePinnedSuccessfully": "הקובץ \"{{name}}\" הוצמד בהצלחה", - "pinFileFailed": "נכשל בהצמדת הקובץ", - "fileUnpinnedSuccessfully": "הקובץ \"{{name}}\" נותק בהצלחה", - "unpinFileFailed": "נכשל ניתוק הקובץ", - "shortcutAddedSuccessfully": "קיצור הדרך לתיקייה \"{{name}}\" נוסף בהצלחה", - "addShortcutFailed": "הוספת קיצור דרך נכשלה", - "operationCompletedSuccessfully": "{{operation}} {{count}} פריטים הצליחו", - "operationCompleted": "{{operation}} {{count}} פריטים", - "downloadFileSuccess": "הקובץ {{name}} הורד בהצלחה", - "downloadFileFailed": "ההורדה נכשלה", - "downloadingFile": "מוריד {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "מעבר אל {{name}}", - "diffCompareWith": "השוואת הבדלים עם {{name}}", - "dragOutsideToDownload": "גרור את הקבצים אל מחוץ לחלון כדי להוריד אותם ({{count}})", - "newFolderDefault": "תיקייה חדשה", - "newFileDefault": "קובץ חדש.txt", - "successfullyMovedItems": "הועבר בהצלחה {{count}} פריטים אל {{target}}", - "move": "מַהֲלָך", - "searchInFile": "חיפוש בקובץ (Ctrl+F)", - "showKeyboardShortcuts": "הצג קיצורי מקלדת", - "decreaseFontSize": "הקטנת גודל הגופן", - "increaseFontSize": "הגדלת גודל הגופן", - "startWritingMarkdown": "התחל לכתוב את תוכן ההנחה שלך...", - "loadingFileComparison": "טוען השוואת קבצים...", - "reload": "לִטעוֹן מִחָדָשׁ", - "compare": "לְהַשְׁווֹת", - "sideBySide": "זֶה בְּצַד זֶה", - "inline": "מוטבע", - "fileComparison": "השוואת קבצים: {{file1}} לעומת {{file2}}", - "fileTooLarge": "קובץ גדול מדי: {{error}}", - "sshConnectionFailed": "חיבור SSH נכשל. אנא בדוק את החיבור שלך אל {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "טעינת הקובץ נכשלה: {{error}}", - "connecting": "מְקַשֵׁר...", - "connectedSuccessfully": "התחבר בהצלחה", - "totpVerificationFailed": "אימות TOTP נכשל", - "warpgateVerificationFailed": "אימות Warpgate נכשל", - "authenticationFailed": "האימות נכשל", - "incorrectPassphrase": "סיסמה שגויה. אנא נסה שוב.", - "verificationCodePrompt": "קוד אימות:", - "changePermissions": "שינוי הרשאות", - "currentPermissions": "הרשאות נוכחיות", - "owner": "בַּעַל", - "group": "קְבוּצָה", - "others": "אחרים", - "read": "לִקְרוֹא", - "write": "לִכתוֹב", - "execute": "לְבַצֵעַ", - "permissionsChangedSuccessfully": "ההרשאות שונו בהצלחה", - "failedToChangePermissions": "שינוי ההרשאות נכשל", - "name": "שֵׁם", - "sortByName": "שֵׁם", - "sortByDate": "תאריך שינוי", - "sortBySize": "גוֹדֶל", - "ascending": "עוֹלֶה", - "descending": "יורד", - "root": "שׁוֹרֶשׁ", - "new": "חָדָשׁ", - "sortBy": "מיין לפי", - "items": "פריטים", - "selected": "נִבחָר", - "editor": "עוֹרֵך", - "octal": "אוקטלי", - "storage": "אִחסוּן", - "disk": "דִיסק", - "used": "מְשׁוּמָשׁ", - "of": "שֶׁל", - "toggleSidebar": "הפעלה/כיבוי סרגל צד", - "compactLayout": "השתמש בפריסה קומפקטית", - "comfortableLayout": "השתמש בפריסה נוחה", - "cannotLoadPdf": "לא ניתן לטעון את ה-PDF", - "pdfLoadError": "אירעה שגיאה בטעינת קובץ PDF זה.", - "loadingPdf": "טוען PDF...", - "loadingPage": "טוען דף..." - }, "transfer": { "copyToHost": "העתק למארח…", "moveToHost": "מעבר למארח…", @@ -2292,50 +1951,6 @@ "retryTransferError": "ניסיון חוזר נכשל", "transferFailedRetryHint": "נתונים חלקיים נשמרו ביעד. ניסיון חוזר יחודש כאשר החיבור יחזור." }, - "sftpTransfer": { - "title": "SFTP", - "description": "העברת קבצים בין שני מארחי SSH.", - "sourceServer": "שרת המקור", - "destinationServer": "שרת יעד", - "selectHost": "בחר מארח", - "noHostSelected": "לא נבחר מארח", - "move": "מַהֲלָך", - "copy": "לְהַעְתִיק", - "upload": "העלאה", - "itemsSelected": "פריט {{count}} נבחר", - "itemsSelected_other": "{{count}} פריטים נבחרו", - "ready": "מוּכָן", - "loadingHosts": "טוען מארחים...", - "name": "שֵׁם", - "modified": "שונה", - "size": "גוֹדֶל", - "noFiles": "אין קבצים", - "open": "לִפְתוֹחַ", - "up": "לְמַעלָה", - "refresh": "לְרַעֲנֵן", - "copyToTarget": "העתק לספריית היעד", - "rename": "שינוי שם", - "delete": "לִמְחוֹק", - "editPermissions": "עריכת הרשאות", - "createNewFolder": "צור תיקייה חדשה", - "deleteSelectedItems": "מחק פריטים שנבחרו", - "deleteConfirm": "למחוק את הפריט {{count}} שנבחר?", - "deleteConfirm_other": "למחוק את {{count}} הפריטים שנבחרו?", - "cancel": "לְבַטֵל", - "save": "לְהַצִיל", - "folderCreated": "תיקייה נוצרה", - "itemRenamed": "שם הפריט", - "actionFailed": "הפעולה נכשלה", - "itemsDeleted": "פריט {{count}} נמחק", - "itemsDeleted_other": "{{count}} פריטים נמחקו", - "deleteFailed": "המחיקה נכשלה", - "permissionsUpdated": "ההרשאות עודכנו", - "noRemoteSessionConnected": "אין חיבור סשן מרחוק", - "failedToListFiles": "רשימת הקבצים נכשלה", - "failedToRefresh": "רענון נכשל", - "destinationInsideSource": "היעד לא יכול להיות בתוך נתיב המקור שנבחר.", - "sameHostNoOp": "המקור והיעד הם אותה תיקייה." - }, "tunnels": { "noSshTunnels": "אין מנהרות SSH", "createFirstTunnelMessage": "עדיין לא יצרת מנהרות SSH. הגדר חיבורי מנהרות במנהל המארח כדי להתחיל.", diff --git a/src/ui/locales/translated/hi_IN.json b/src/ui/locales/translated/hi_IN.json index cb9eb7fb7..6143b1a22 100644 --- a/src/ui/locales/translated/hi_IN.json +++ b/src/ui/locales/translated/hi_IN.json @@ -715,7 +715,6 @@ "hostManager": "होस्ट प्रबंधक", "credentials": "क्रेडेंशियल्स", "portForwarding": "अग्रेषित करना", - "sftp": "एसएफटीपी", "connections": "कनेक्शन्स", "serial": "सीरियल", "keepSideMenuOpen": "मेनू खुला रखें", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "टनलें", - "tabFiles": "फ़ाइलें", "tabStats": "होस्ट मेट्रिक्स", "tabTelnet": "Telnet", "tabSharing": "साझाकरण", "tabAuthentication": "प्रमाणीकरण", "terminal": "टर्मिनल", "tunnel": "टनल", - "fileManager": "फ़ाइल प्रबंधक", "serverStats": "होस्ट मेट्रिक्स", "status": { "available": "उपलब्ध", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} को छोड़ दिया गया (कोई आईपी नहीं मिला)", "proxmoxStatsUrlCopied": "Proxmox Stats URL क्लिपबोर्ड पर कॉपी हो गया है", "copyProxmoxStatsUrlAction": "Proxmox Stats URL कॉपी करें", - "enableFileManagerMonitor": "फ़ाइल प्रबंधक सक्षम करें", - "enableFileManagerMonitorDesc": "इस होस्ट पर SFTP के ज़रिए फ़ाइलें ब्राउज़ और प्रबंधित करें", - "scpLegacyLabel": "SCP लीगेसी मोड", - "scpLegacyDesc": "उन सर्वरों के लिए लीगेसी फ़ाइल स्थानांतरण का उपयोग करें जो SFTP सबसिस्टम का समर्थन नहीं करते (जैसे एम्बेडेड या न्यूनतम SSH सर्वर)।", - "defaultPathLabel": "डिफ़ॉल्ट पथ", - "fileManagerPathHint": "जब इस होस्ट के लिए फ़ाइल प्रबंधक लॉन्च होता है तो खोलने के लिए निर्देशिका।", "statusChecksLabel": "स्थिति जाँच", "enableStatusChecks": "स्थिति जाँच सक्षम करें", "enableStatusChecksDesc": "उपलब्धता सत्यापित करने के लिए समय-समय पर इस होस्ट को पिंग करें", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} हटा दिया गया", "copiedToClipboard": "क्लिपबोर्ड पर कॉपी किया गया", "terminalUrlCopied": "टर्मिनल URL कॉपी किया गया", - "fileManagerUrlCopied": "फ़ाइल प्रबंधक URL कॉपी किया गया", "tunnelUrlCopied": "टनल URL कॉपी किया गया", "dockerUrlCopied": "Docker URL कॉपी किया गया", "hostMetricsUrlCopied": "होस्ट मेट्रिक्स URL कॉपी किया गया", @@ -1296,7 +1286,6 @@ "copyAddress": "पता कॉपी करें", "copyLink": "लिंक कॉपी करें", "copyTerminalUrlAction": "टर्मिनल URL कॉपी करें", - "copyFileManagerUrlAction": "फ़ाइल प्रबंधक URL कॉपी करें", "copyTunnelUrlAction": "टनल URL कॉपी करें", "copyDockerUrlAction": "Docker URL कॉपी करें", "copyHostMetricsUrlAction": "होस्ट मेट्रिक्स URL कॉपी करें", @@ -1857,336 +1846,6 @@ "removeFromSplit": "विभाजन से हटाएँ" } }, - "fileManager": { - "noHostSelected": "कोई होस्ट चयनित नहीं", - "initializingEditor": "संपादक आरंभ हो रहा है...", - "file": "फ़ाइल", - "folder": "फ़ोल्डर", - "uploadFile": "फ़ाइल अपलोड करें", - "downloadFile": "डाउनलोड करें", - "extractArchive": "संग्रह निकालें", - "extractingArchive": "{{name}} निकाला जा रहा है...", - "archiveExtractedSuccessfully": "{{name}} सफलतापूर्वक निकाला गया", - "extractFailed": "निकालने में विफल", - "compressFile": "फ़ाइल संपीड़ित करें", - "compressFiles": "फ़ाइलें संपीड़ित करें", - "compressFilesDesc": "{{count}} आइटम को एक संग्रह में संपीड़ित करें", - "archiveName": "संग्रह का नाम", - "enterArchiveName": "संग्रह का नाम दर्ज करें...", - "compressionFormat": "संपीड़न प्रारूप", - "selectedFiles": "चयनित फ़ाइलें", - "andMoreFiles": "और {{count}}...", - "compress": "संपीड़ित करें", - "compressingFiles": "{{count}} आइटम को {{name}} में संपीड़ित किया जा रहा है...", - "filesCompressedSuccessfully": "{{name}} सफलतापूर्वक बनाया गया", - "compressFailed": "संपीड़न विफल", - "edit": "संपादित करें", - "preview": "पूर्वावलोकन", - "previous": "पिछला", - "next": "अगला", - "pageXOfY": "पृष्ठ {{current}} / {{total}}", - "zoomOut": "ज़ूम आउट", - "zoomIn": "ज़ूम इन", - "newFile": "नई फ़ाइल", - "newFolder": "नया फ़ोल्डर", - "rename": "नाम बदलें", - "uploading": "अपलोड हो रहा है...", - "uploadingFile": "{{name}} अपलोड हो रहा है...", - "fileName": "फ़ाइल का नाम", - "folderName": "फ़ोल्डर का नाम", - "fileUploadedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक अपलोड हो गई", - "failedToUploadFile": "फ़ाइल अपलोड करने में विफल", - "uploadingFolderFiles": "{{count}} फ़ाइलें अपलोड की जा रही हैं...", - "uploadedFolderFiles": "{{count}} फ़ाइलें सफलतापूर्वक अपलोड हो गईं", - "uploadedFolderPartial": "{{uploaded}} फ़ाइलें अपलोड की गईं, {{failed}} विफल रहीं", - "fileDownloadedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक डाउनलोड हो गई", - "failedToDownloadFile": "फ़ाइल डाउनलोड करने में विफल", - "fileCreatedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक बनाई गई", - "folderCreatedSuccessfully": "फ़ोल्डर \"{{name}}\" सफलतापूर्वक बनाया गया", - "failedToCreateItem": "आइटम बनाने में विफल", - "operationFailed": "{{name}} के लिए {{operation}} कार्रवाई विफल: {{error}}", - "failedToResolveSymlink": "symlink को हल करने में विफल", - "itemsDeletedSuccessfully": "{{count}} आइटम सफलतापूर्वक हटाए गए", - "failedToDeleteItems": "आइटम हटाने में विफल", - "sudoPasswordRequired": "प्रशासक पासवर्ड आवश्यक है", - "enterSudoPassword": "इस कार्रवाई को जारी रखने के लिए sudo पासवर्ड दर्ज करें", - "sudoPassword": "sudo पासवर्ड", - "sudoOperationFailed": "sudo संचालन विफल", - "sudoAuthFailed": "sudo प्रमाणीकरण विफल", - "dragFilesToUpload": "अपलोड करने के लिए फ़ाइलें यहाँ छोड़ें", - "dropToUploadHere": "अपलोड करने के लिए यहां ड्रॉप करें", - "dropToDownloadHere": "डाउनलोड करने के लिए यहां क्लिक करें", - "localFiles": "स्थानीय", - "showLocalFiles": "स्थानीय फ़ाइलें दिखाएँ (स्प्लिट व्यू)", - "hideLocalFiles": "स्थानीय फ़ाइलों को छुपाएँ", - "localHome": "होम फ़ोल्डर", - "localPathPlaceholder": "स्थानीय फ़ोल्डर पथ", - "localShowHidden": "छिपी हुई फ़ाइलें दिखाएँ", - "localHideHidden": "छिपी हुई फ़ाइलों को छुपाएँ", - "localRevealInFileManager": "सिस्टम फ़ाइल प्रबंधक में दिखाएँ", - "localCannotOpenFolder": "फ़ोल्डर नहीं खोला जा सकता", - "localItemCount": "{{count}} आइटम", - "localSelectedCount": "{{count}} चयनित", - "back": "पीछे", - "forward": "आगे", - "up": "ऊपर", - "kind": "दयालु", - "localUploadingProgress": "{{total}} में से {{current}} अपलोड किया जा रहा है", - "localDownloadingProgress": "{{total}} में से {{current}} डाउनलोड हो रहा है", - "localTransferInFlight": "{{count}} एक साथ", - "localPreparingDownload": "डाउनलोड की तैयारी हो रही है...", - "localTransferCancelled": "स्थानांतरण रद्द", - "localNothingToTransfer": "स्थानांतरित करने के लिए कुछ भी नहीं है", - "localUploadComplete": "{{count}} फाइल अपलोड की गई", - "localUploadFailed": "अपलोड विफल", - "localUploadPartial": "{{done}} फ़ाइलें अपलोड की गईं, {{failed}} विफल रहीं", - "localDownloadComplete": "डाउनलोड की गई {{count}} फ़ाइलें", - "localDownloadFailed": "डाउनलोड विफल", - "localDownloadPartial": "{{done}} फ़ाइलें डाउनलोड की गईं, {{failed}} डाउनलोड विफल रहा।", - "localReplaceExistingPrompt": "इस फ़ोल्डर में पहले से ही {{count}} आइटम मौजूद हैं। क्या आप उन्हें बदलना चाहेंगे?", - "localReplace": "प्रतिस्थापित करें", - "localSkipExisting": "छोडना", - "localSkippedExisting": "मौजूदा आइटमों में से {{count}} को छोड़ दिया गया", - "localUnsafeNamesSkipped": "इस कंप्यूटर पर जिन आइटमों के नाम इस्तेमाल नहीं किए जा सकते, उन्हें छोड़ दिया गया है {{count}}", - "localOpen": "खुला", - "localOpenFolder": "फोल्डर खोलें", - "localUploadToRemote": "सर्वर पर अपलोड करें", - "localUploadToRemoteMany": "सर्वर पर {{count}} आइटम अपलोड करें", - "localMoveToTrash": "ट्रैश में ले जाएं", - "localMoveToTrashMany": "{{count}} आइटम ट्रैश में ले जाएं", - "localTrashConfirmSingle": "क्या आप \"{{name}}\" को ट्रैश में ले जाना चाहते हैं?", - "localTrashConfirmFolder": "क्या आप फोल्डर \"{{name}}\" और उसकी सभी सामग्री को ट्रैश में ले जाना चाहते हैं?", - "localTrashConfirmMany": "क्या {{count}} आइटम ट्रैश में ले जाएं?", - "localTrashed": "{{count}} आइटम ट्रैश में भेज दिए गए हैं", - "localTrashFailed": "ट्रैश में नहीं ले जाया जा सका", - "localRenameFailed": "नाम बदलने में विफल रहा", - "localCreateFailed": "आइटम नहीं बनाया जा सका", - "goToParentFolder": "मूल फ़ोल्डर पर जाएँ", - "resizeSidebar": "साइडबार का आकार बदलने के लिए उसे खींचें (रीसेट करने के लिए उस पर डबल-क्लिक करें)", - "resizeLocalPane": "स्थानीय फलक का आकार बदलने के लिए उसे खींचें (आकार बदलने के लिए उस पर दो बार क्लिक करें)", - "columns": "कॉलम", - "columnsHint": "कॉलम चुनने के लिए राइट-क्लिक करें", - "emptyFolder": "यह फ़ोल्डर खाली है", - "searchFiles": "फ़ाइलें खोजें...", - "upload": "अपलोड करें", - "selectHostToStart": "फ़ाइल प्रबंधन शुरू करने के लिए एक होस्ट चुनें", - "sshRequiredForFileManager": "फ़ाइल प्रबंधक को SSH की आवश्यकता है। इस होस्ट में SSH सक्षम नहीं है।", - "failedToConnect": "SSH से कनेक्ट करने में विफल", - "failedToLoadDirectory": "निर्देशिका लोड करने में विफल", - "noSSHConnection": "कोई SSH कनेक्शन उपलब्ध नहीं है", - "copy": "कॉपी", - "cut": "काटें", - "paste": "चिपकाएँ", - "copyPath": "पथ कॉपी करें", - "copyPaths": "पथ कॉपी करें", - "delete": "हटाएँ", - "properties": "गुण", - "refresh": "ताज़ा करें", - "downloadFiles": "{{count}} फ़ाइलें ब्राउज़र पर डाउनलोड करें", - "copyFiles": "{{count}} आइटम कॉपी करें", - "cutFiles": "{{count}} आइटम काटें", - "deleteFiles": "{{count}} आइटम हटाएँ", - "filesCopiedToClipboard": "{{count}} आइटम क्लिपबोर्ड पर कॉपी किए गए", - "filesCutToClipboard": "{{count}} आइटम क्लिपबोर्ड पर काटे गए", - "pathCopiedToClipboard": "पथ क्लिपबोर्ड पर कॉपी किया गया", - "pathsCopiedToClipboard": "{{count}} पथ क्लिपबोर्ड पर कॉपी किए गए", - "failedToCopyPath": "क्लिपबोर्ड पर पथ कॉपी करने में विफल", - "copyFolderLink": "फ़ोल्डर का लिंक कॉपी करें", - "copyCurrentFolderLink": "वर्तमान फ़ोल्डर का लिंक कॉपी करें", - "folderLinkCopied": "फ़ोल्डर लिंक क्लिपबोर्ड पर कॉपी किया गया", - "failedToCopyFolderLink": "फ़ोल्डर लिंक कॉपी करने में विफल", - "movedItems": "{{count}} आइटम स्थानांतरित किए गए", - "failedToDeleteItem": "आइटम हटाने में विफल", - "itemRenamedSuccessfully": "{{type}} का नाम सफलतापूर्वक बदल दिया गया", - "failedToRenameItem": "आइटम का नाम बदलने में विफल", - "download": "डाउनलोड", - "openExternalEditor": "बाह्य संपादक खोलें", - "chooseExternalEditor": "बाह्य संपादक चुनें", - "externalEditorSelected": "बाह्य संपादक चयनित", - "externalEditorDesktopOnly": "बाह्य संपादक केवल डेस्कटॉप ऐप में उपलब्ध है", - "externalEditorOpened": "बाह्य संपादक में खोला गया। वहाँ सहेजने पर परिवर्तन सर्वर पर वापस अपलोड हो जाएँगे।", - "failedToOpenExternalEditor": "बाह्य संपादक खोलने में विफल", - "failedToSelectExternalEditor": "बाह्य संपादक चुनने में विफल", - "permissions": "अनुमतियाँ", - "size": "आकार", - "modified": "संशोधित", - "path": "पथ", - "confirmDelete": "क्या आप वाकई {{name}} हटाना चाहते हैं?", - "permissionDenied": "अनुमति अस्वीकृत", - "serverError": "सर्वर त्रुटि", - "fileSavedSuccessfully": "फ़ाइल सफलतापूर्वक सहेजी गई", - "failedToSaveFile": "फ़ाइल सहेजने में विफल", - "confirmDeleteSingleItem": "क्या आप वाकई \"{{name}}\" को स्थायी रूप से हटाना चाहते हैं?", - "confirmDeleteMultipleItems": "क्या आप वाकई {{count}} आइटम स्थायी रूप से हटाना चाहते हैं?", - "confirmDeleteMultipleItemsWithFolders": "क्या आप वाकई {{count}} आइटम स्थायी रूप से हटाना चाहते हैं? इसमें फ़ोल्डर और उनकी सामग्री शामिल है।", - "confirmDeleteFolder": "क्या आप वाकई फ़ोल्डर \"{{name}}\" और उसकी सभी सामग्री को स्थायी रूप से हटाना चाहते हैं?", - "permanentDeleteWarning": "यह क्रिया पूर्ववत नहीं की जा सकती। आइटम सर्वर से स्थायी रूप से हटा दिए जाएँगे।", - "moveToTrashWarning": "आइटम को टर्मिक्स ट्रैश में भेज दिया जाएगा और प्रतिधारण अवधि समाप्त होने से पहले इसे पुनर्स्थापित किया जा सकता है।", - "itemsMovedToTrash": "{{count}} आइटम ट्रैश में चले गए", - "trash": "कचरा", - "trashDescription": "हटाए गए आइटम इस होस्ट पर {{days}} दिनों तक रखे जाते हैं।", - "trashLoadFailed": "ट्रैश लोड करने में विफल", - "trashEmpty": "कचरा पात्र खाली है", - "trashRestore": "पुनर्स्थापित करना", - "trashRestored": "पुनर्स्थापित {{name}}", - "trashRestoreFailed": "आइटम को पुनर्स्थापित करने में विफल। मूल पथ पहले से मौजूद हो सकता है।", - "trashDeletePermanently": "स्थायी रूप से हटाएं", - "trashDeleteConfirm": "{{name}}को स्थायी रूप से हटा दें? इसे पूर्ववत नहीं किया जा सकता।", - "trashPermanentlyDeleted": "स्थायी रूप से हटा दिया गया {{name}}", - "trashDeleteFailed": "आइटम को स्थायी रूप से हटाने में विफल रहा", - "trashEmptyAction": "कचरा खाली करें", - "trashEmptyConfirm": "ट्रैश में मौजूद सभी आइटम को स्थायी रूप से हटाना चाहते हैं? इसे वापस नहीं लिया जा सकता।", - "trashEmptied": "कचरा खाली कर दिया गया", - "trashRetention": "(दिनों) के बाद स्वचालित सफाई", - "trashRetentionSaved": "कचरा संग्रहण प्रणाली को अद्यतन किया गया", - "trashRetentionFailed": "ट्रैश रिटेंशन को अपडेट करने में विफलता", - "trashUnavailableConfirm": "इस आइटम को ट्रैश में नहीं ले जाया जा सकता, आमतौर पर इसलिए क्योंकि यह किसी अन्य फ़ाइल सिस्टम पर है या इसके लिए उच्च अनुमतियों की आवश्यकता होती है। क्या इसे स्थायी रूप से हटा देना बेहतर होगा?", - "recent": "हाल ही", - "pinned": "पिन किए गए", - "folderShortcuts": "फ़ोल्डर शॉर्टकट", - "failedToReconnectSSH": "SSH सत्र पुनः कनेक्ट करने में विफल", - "openTerminalHere": "यहाँ टर्मिनल खोलें", - "run": "चलाएँ", - "openTerminalInFolder": "इस फ़ोल्डर में टर्मिनल खोलें", - "openTerminalInFileLocation": "फ़ाइल स्थान पर टर्मिनल खोलें", - "runningFile": "चल रहा है - {{file}}", - "onlyRunExecutableFiles": "केवल एक्सीक्यूटेबल फ़ाइलें ही चलाई जा सकती हैं", - "directories": "निर्देशिकाएँ", - "removedFromRecentFiles": "हाल की फ़ाइलों से \"{{name}}\" हटा दिया गया", - "removeFailed": "हटाना विफल", - "unpinnedSuccessfully": "\"{{name}}\" सफलतापूर्वक अनपिन किया गया", - "unpinFailed": "अनपिन विफल", - "removedShortcut": "शॉर्टकट \"{{name}}\" हटा दिया गया", - "removeShortcutFailed": "शॉर्टकट हटाना विफल", - "clearedAllRecentFiles": "सभी हाल की फ़ाइलें साफ़ कर दी गईं", - "clearFailed": "साफ़ करना विफल", - "removeFromRecentFiles": "हाल की फ़ाइलों से हटाएँ", - "clearAllRecentFiles": "सभी हाल की फ़ाइलें साफ़ करें", - "unpinFile": "फ़ाइल अनपिन करें", - "removeShortcut": "शॉर्टकट हटाएँ", - "pinFile": "फ़ाइल पिन करें", - "addToShortcuts": "शॉर्टकट में जोड़ें", - "pasteFailed": "चिपकाना विफल", - "noUndoableActions": "कोई पूर्ववत करने योग्य कार्रवाई नहीं", - "undoCopySuccess": "कॉपी कार्रवाई पूर्ववत की गई: {{count}} कॉपी की गई फ़ाइलें हटा दी गईं", - "undoCopyFailedDelete": "पूर्ववत विफल: कोई भी कॉपी की गई फ़ाइल नहीं हटा सका", - "undoCopyFailedNoInfo": "पूर्ववत विफल: कॉपी की गई फ़ाइल की जानकारी नहीं मिली", - "undoMoveSuccess": "स्थानांतरण कार्रवाई पूर्ववत की गई: {{count}} फ़ाइलें वापस मूल स्थान पर ले जाई गईं", - "undoMoveFailedMove": "पूर्ववत विफल: कोई भी फ़ाइल वापस नहीं ले जा सका", - "undoMoveFailedNoInfo": "पूर्ववत विफल: स्थानांतरित फ़ाइल की जानकारी नहीं मिली", - "undoDeleteNotSupported": "हटाने की कार्रवाई पूर्ववत नहीं की जा सकती: फ़ाइलें सर्वर से स्थायी रूप से हटा दी गई हैं", - "undoTypeNotSupported": "असमर्थित पूर्ववत कार्रवाई प्रकार", - "undoOperationFailed": "पूर्ववत कार्रवाई विफल", - "unknownError": "अज्ञात त्रुटि", - "confirm": "पुष्टि करें", - "find": "खोजें...", - "replace": "बदलें", - "downloadInstead": "इसके बजाय डाउनलोड करें", - "keyboardShortcuts": "कीबोर्ड शॉर्टकट्स", - "searchAndReplace": "खोजें और बदलें", - "editing": "संपादन", - "search": "खोजें", - "findNext": "अगला खोजें", - "findPrevious": "पिछला खोजें", - "save": "सहेजें", - "selectAll": "सभी चुनें", - "undo": "पूर्ववत करें", - "redo": "फिर से करें", - "moveLineUp": "पंक्ति ऊपर ले जाएँ", - "moveLineDown": "पंक्ति नीचे ले जाएँ", - "toggleComment": "टिप्पणी टॉगल करें", - "autoComplete": "स्वतः पूर्ण करें", - "imageLoadError": "छवि लोड करने में विफल", - "startTyping": "टाइप करना शुरू करें...", - "unknownSize": "अज्ञात आकार", - "fileIsEmpty": "फ़ाइल खाली है", - "largeFileWarning": "बड़ी फ़ाइल की चेतावनी", - "largeFileWarningDesc": "यह फ़ाइल {{size}} आकार की है, जिसे टेक्स्ट के रूप में खोलने पर प्रदर्शन समस्याएँ हो सकती हैं।", - "fileNotFoundAndRemoved": "फ़ाइल \"{{name}}\" नहीं मिली और हाल की/पिन की गई फ़ाइलों से हटा दी गई है", - "failedToLoadFile": "फ़ाइल लोड करने में विफल: {{error}}", - "serverErrorOccurred": "सर्वर त्रुटि हुई। कृपया बाद में पुनः प्रयास करें।", - "autoSaveFailed": "स्वतः सहेजना विफल", - "fileAutoSaved": "फ़ाइल स्वतः सहेजी गई", - "moveFileFailed": "{{name}} को स्थानांतरित करने में विफल", - "moveOperationFailed": "स्थानांतरण क्रिया विफल", - "canOnlyCompareFiles": "केवल दो फ़ाइलों की तुलना की जा सकती है", - "comparingFiles": "फ़ाइलों की तुलना: {{file1}} और {{file2}}", - "dragFailed": "खींचने की क्रिया विफल", - "filePinnedSuccessfully": "फ़ाइल \"{{name}}\" सफलतापूर्वक पिन की गई", - "pinFileFailed": "फ़ाइल पिन करने में विफल", - "fileUnpinnedSuccessfully": "फ़ाइल \"{{name}}\" अनपिन की गई सफलतापूर्वक", - "unpinFileFailed": "फ़ाइल अनपिन करने में विफल", - "shortcutAddedSuccessfully": "फ़ोल्डर शॉर्टकट \"{{name}}\" सफलतापूर्वक जोड़ा गया", - "addShortcutFailed": "शॉर्टकट जोड़ने में विफल", - "operationCompletedSuccessfully": "{{operation}} {{count}} आइटम सफलतापूर्वक", - "operationCompleted": "{{operation}} {{count}} आइटम", - "downloadFileSuccess": "फ़ाइल {{name}} सफलतापूर्वक डाउनलोड हुई", - "downloadFileFailed": "डाउनलोड विफल", - "downloadingFile": "डाउनलोड हो रहा है {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "{{name}} में ले जाएँ", - "diffCompareWith": "{{name}} के साथ डिफ तुलना करें", - "dragOutsideToDownload": "डाउनलोड करने के लिए विंडो से बाहर खींचें ({{count}} फ़ाइलें)", - "newFolderDefault": "नया फ़ोल्डर", - "newFileDefault": "नई फ़ाइल.txt", - "successfullyMovedItems": "{{count}} आइटम सफलतापूर्वक {{target}} में ले जाए गए", - "move": "स्थानांतरित करें", - "searchInFile": "फ़ाइल में खोजें (Ctrl+F)", - "showKeyboardShortcuts": "कीबोर्ड शॉर्टकट दिखाएँ", - "decreaseFontSize": "फ़ॉन्ट आकार घटाएँ", - "increaseFontSize": "फ़ॉन्ट आकार बढ़ाएँ", - "startWritingMarkdown": "अपनी Markdown सामग्री लिखना शुरू करें...", - "loadingFileComparison": "फ़ाइल तुलना लोड हो रही है...", - "reload": "पुनः लोड करें", - "compare": "तुलना करें", - "sideBySide": "साथ-साथ", - "inline": "इनलाइन", - "fileComparison": "फ़ाइल तुलना: {{file1}} बनाम {{file2}}", - "fileTooLarge": "फ़ाइल बहुत बड़ी है: {{error}}", - "sshConnectionFailed": "SSH कनेक्शन विफल। कृपया {{name}} ({{ip}}:{{port}}) से अपना कनेक्शन जाँचें।", - "loadFileFailed": "फ़ाइल लोड करने में विफल: {{error}}", - "connecting": "कनेक्ट हो रहा है...", - "connectedSuccessfully": "सफलतापूर्वक कनेक्ट हुआ", - "totpVerificationFailed": "TOTP सत्यापन विफल", - "warpgateVerificationFailed": "Warpgate सत्यापन विफल", - "authenticationFailed": "प्रमाणीकरण विफल", - "incorrectPassphrase": "गलत पासफ़्रेज़। कृपया पुनः प्रयास करें।", - "verificationCodePrompt": "सत्यापन कोड:", - "changePermissions": "अनुमतियाँ बदलें", - "currentPermissions": "वर्तमान अनुमतियाँ", - "owner": "स्वामी", - "group": "समूह", - "others": "अन्य", - "read": "पठन", - "write": "लेखन", - "execute": "निष्पादन", - "permissionsChangedSuccessfully": "अनुमतियाँ सफलतापूर्वक बदल दी गईं", - "failedToChangePermissions": "अनुमतियाँ बदलने में विफल", - "name": "नाम", - "sortByName": "नाम", - "sortByDate": "संशोधन तिथि", - "sortBySize": "आकार", - "ascending": "आरोही", - "descending": "अवरोही", - "root": "रूट", - "new": "नया", - "sortBy": "क्रमबद्ध करें", - "items": "आइटम", - "selected": "चयनित", - "editor": "संपादक", - "octal": "ऑक्टल", - "storage": "भंडारण", - "disk": "डिस्क", - "used": "प्रयुक्त", - "of": "में से", - "toggleSidebar": "साइडबार टॉगल करें", - "compactLayout": "कॉम्पैक्ट लेआउट का उपयोग करें", - "comfortableLayout": "सुविधाजनक लेआउट का उपयोग करें", - "cannotLoadPdf": "PDF लोड नहीं कर सकता", - "pdfLoadError": "इस PDF फ़ाइल को लोड करने में त्रुटि हुई।", - "loadingPdf": "PDF लोड हो रहा है...", - "loadingPage": "पृष्ठ लोड हो रहा है..." - }, "transfer": { "copyToHost": "होस्ट पर कॉपी करें…", "moveToHost": "होस्ट पर ले जाएँ…", @@ -2292,50 +1951,6 @@ "retryTransferError": "पुनः प्रयास विफल रहा।", "transferFailedRetryHint": "आंशिक डेटा गंतव्य पर रखा गया था। कनेक्शन वापस आने पर पुनः प्रयास फिर से शुरू होगा।" }, - "sftpTransfer": { - "title": "एसएफटीपी", - "description": "दो SSH होस्ट के बीच फ़ाइलें स्थानांतरित करें।", - "sourceServer": "स्रोत सर्वर", - "destinationServer": "गंतव्य सर्वर", - "selectHost": "होस्ट का चयन करें", - "noHostSelected": "कोई होस्ट चयनित नहीं है", - "move": "कदम", - "copy": "प्रतिलिपि", - "upload": "अपलोड करें", - "itemsSelected": "{{count}} आइटम चयनित", - "itemsSelected_other": "{{count}} आइटम चयनित", - "ready": "तैयार", - "loadingHosts": "होस्ट लोड हो रहे हैं...", - "name": "नाम", - "modified": "संशोधित", - "size": "आकार", - "noFiles": "कोई फ़ाइल नहीं", - "open": "खुला", - "up": "ऊपर", - "refresh": "ताज़ा करना", - "copyToTarget": "लक्ष्य निर्देशिका में प्रतिलिपि करें", - "rename": "नाम बदलें", - "delete": "मिटाना", - "editPermissions": "संपादन अनुमतियाँ", - "createNewFolder": "नया फ़ोल्डर बनाएं", - "deleteSelectedItems": "चयनित आइटम हटाएं", - "deleteConfirm": "चयनित आइटम हटाएं {{count}}?", - "deleteConfirm_other": "चयनित आइटम हटाएं {{count}}?", - "cancel": "रद्द करना", - "save": "बचाना", - "folderCreated": "फ़ोल्डर बनाया गया", - "itemRenamed": "आइटम का नाम बदल दिया गया", - "actionFailed": "क्रिया: विफल रही है", - "itemsDeleted": "{{count}} आइटम हटा दिया गया", - "itemsDeleted_other": "{{count}} आइटम हटा दिए गए", - "deleteFailed": "डिलीट विफल", - "permissionsUpdated": "अनुमतियाँ अपडेट की गईं", - "noRemoteSessionConnected": "कोई रिमोट सेशन कनेक्ट नहीं हुआ", - "failedToListFiles": "फ़ाइलों को सूचीबद्ध करने में विफल", - "failedToRefresh": "रीफ़्रेश करने में विफल", - "destinationInsideSource": "गंतव्य चयनित स्रोत पथ के अंदर नहीं हो सकता।", - "sameHostNoOp": "स्रोत और गंतव्य एक ही फ़ोल्डर हैं।" - }, "tunnels": { "noSshTunnels": "कोई SSH टनल नहीं", "createFirstTunnelMessage": "आपने अभी तक कोई SSH टनल नहीं बनाई है। शुरू करने के लिए होस्ट प्रबंधक में टनल कनेक्शन कॉन्फ़िगर करें।", diff --git a/src/ui/locales/translated/hu_HU.json b/src/ui/locales/translated/hu_HU.json index 55c173103..d1657ab8e 100644 --- a/src/ui/locales/translated/hu_HU.json +++ b/src/ui/locales/translated/hu_HU.json @@ -715,7 +715,6 @@ "hostManager": "Házigazda-kezelő", "credentials": "Hitelesítő adatok", "portForwarding": "Szállítmányozás", - "sftp": "SFTP", "connections": "Kapcsolatok", "serial": "Sorozatszám", "keepSideMenuOpen": "Menü nyitva tartása", @@ -767,14 +766,12 @@ "tabRdp": "Vidékfejlesztési Program", "tabVnc": "VNC", "tabTunnels": "Alagutak", - "tabFiles": "Fájlok", "tabStats": "Gazdagép-metrikák", "tabTelnet": "Telnet", "tabSharing": "Megosztás", "tabAuthentication": "Hitelesítés", "terminal": "Terminál", "tunnel": "Alagút", - "fileManager": "Fájlkezelő", "serverStats": "Gazdagép-metrikák", "status": { "available": "Elérhető", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} kihagyva (nem található IP cím)", "proxmoxStatsUrlCopied": "Proxmox Statisztikai URL a vágólapra másolva", "copyProxmoxStatsUrlAction": "Proxmox Statisztika URL másolása", - "enableFileManagerMonitor": "Fájlkezelő engedélyezése", - "enableFileManagerMonitorDesc": "Fájlok böngészése és kezelése ezen a gazdagépen SFTP-n keresztül", - "scpLegacyLabel": "SCP örökölt mód", - "scpLegacyDesc": "Használjon hagyományos fájlátvitelt olyan szerverekhez, amelyek nem támogatják az SFTP alrendszert (pl. beágyazott vagy minimális SSH szerverek).", - "defaultPathLabel": "Alapértelmezett elérési út", - "fileManagerPathHint": "A könyvtár, amely megnyílik, amikor a fájlkezelő elindul ehhez a gazdagéphez.", "statusChecksLabel": "Állapotellenőrzések", "enableStatusChecks": "Állapotellenőrzések engedélyezése", "enableStatusChecksDesc": "Rendszeresen pingelje ezt a gazdagépet az elérhetőség ellenőrzéséhez", @@ -1278,7 +1269,6 @@ "deletedHost": "Törölt {{name}}", "copiedToClipboard": "Vágólapra másolva", "terminalUrlCopied": "Terminál URL-címe másolva", - "fileManagerUrlCopied": "Fájlkezelő URL-címe másolva", "tunnelUrlCopied": "Alagút URL-címe másolva", "dockerUrlCopied": "Docker URL másolva", "hostMetricsUrlCopied": "Gazdagép-metrikák URL-címe kimásolva", @@ -1296,7 +1286,6 @@ "copyAddress": "Cím másolása", "copyLink": "Link másolása", "copyTerminalUrlAction": "Terminál URL másolása", - "copyFileManagerUrlAction": "Fájlkezelő URL-címének másolása", "copyTunnelUrlAction": "Alagút URL-címének másolása", "copyDockerUrlAction": "Docker URL másolása", "copyHostMetricsUrlAction": "Gazdagép-metrikák URL-címének másolása", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Eltávolítás a felosztásból" } }, - "fileManager": { - "noHostSelected": "Nincs kiválasztva gazdagép", - "initializingEditor": "Szerkesztő inicializálása...", - "file": "Fájl", - "folder": "Mappa", - "uploadFile": "Fájl feltöltése", - "downloadFile": "Letöltés", - "extractArchive": "Archívum kibontása", - "extractingArchive": "{{name}} kinyerése ...", - "archiveExtractedSuccessfully": "{{name}} sikeresen kibontva", - "extractFailed": "Kibontás sikertelen", - "compressFile": "Fájl tömörítése", - "compressFiles": "Fájlok tömörítése", - "compressFilesDesc": "Tömörítsen {{count}} elemet archívumba", - "archiveName": "Archívum neve", - "enterArchiveName": "Adja meg az archívum nevét...", - "compressionFormat": "Tömörítési formátum", - "selectedFiles": "Kiválasztott fájlok", - "andMoreFiles": "és {{count}} több...", - "compress": "Borogatás", - "compressingFiles": "{{count}} elem tömörítése {{name}}...", - "filesCompressedSuccessfully": "{{name}} sikeresen létrehozva", - "compressFailed": "Sikertelen tömörítés", - "edit": "Szerkesztés", - "preview": "Előnézet", - "previous": "Előző", - "next": "Következő", - "pageXOfY": "{{total}} oldalból a {{current}} oldal", - "zoomOut": "Kicsinyítés", - "zoomIn": "Nagyítás", - "newFile": "Új fájl", - "newFolder": "Új mappa", - "rename": "Átnevezés", - "uploading": "Feltöltés...", - "uploadingFile": "Feltöltés {{name}}...", - "fileName": "Fájlnév", - "folderName": "Mappa neve", - "fileUploadedSuccessfully": "A(z) \"{{name}}\" fájl feltöltése sikeresen megtörtént.", - "failedToUploadFile": "Nem sikerült feltölteni a fájlt", - "uploadingFolderFiles": "{{count}} fájl(ok) feltöltése...", - "uploadedFolderFiles": "{{count}} fájl(ok) feltöltve sikeresen", - "uploadedFolderPartial": "{{uploaded}} fájl(ok) feltöltése, {{failed}} sikertelen", - "fileDownloadedSuccessfully": "A(z) \"{{name}}\" fájl sikeresen letöltve", - "failedToDownloadFile": "Nem sikerült letölteni a fájlt", - "fileCreatedSuccessfully": "A(z) \"{{name}}\" fájl sikeresen létrehozva", - "folderCreatedSuccessfully": "A(z) \"{{name}}\" mappa sikeresen létrehozva", - "failedToCreateItem": "Nem sikerült létrehozni az elemet", - "operationFailed": "{{operation}} művelet sikertelen a következőhöz: {{name}}: {{error}}", - "failedToResolveSymlink": "Nem sikerült feloldani a szimbolikus linket", - "itemsDeletedSuccessfully": "{{count}} elem sikeresen törölve", - "failedToDeleteItems": "Nem sikerült törölni az elemeket", - "sudoPasswordRequired": "Rendszergazdai jelszó szükséges", - "enterSudoPassword": "Írja be a sudo jelszót a művelet folytatásához", - "sudoPassword": "Sudo jelszó", - "sudoOperationFailed": "Sudo művelet sikertelen", - "sudoAuthFailed": "Sudo hitelesítés sikertelen", - "dragFilesToUpload": "Húzd ide a fájlokat a feltöltéshez", - "dropToUploadHere": "Húzd ide a feltöltéshez", - "dropToDownloadHere": "Húzd ide a letöltéshez", - "localFiles": "Helyi", - "showLocalFiles": "Helyi fájlok megjelenítése (osztott nézet)", - "hideLocalFiles": "Helyi fájlok elrejtése", - "localHome": "Saját mappa", - "localPathPlaceholder": "Helyi mappa elérési útja", - "localShowHidden": "Rejtett fájlok megjelenítése", - "localHideHidden": "Rejtett fájlok elrejtése", - "localRevealInFileManager": "Megjelenítés a rendszerfájl-kezelőben", - "localCannotOpenFolder": "Nem lehet megnyitni a mappát", - "localItemCount": "{{count}} elem", - "localSelectedCount": "{{count}} kiválasztva", - "back": "Vissza", - "forward": "Előre", - "up": "Fel", - "kind": "Fajta", - "localUploadingProgress": "{{current}} / {{total}} feltöltése", - "localDownloadingProgress": "{{current}} letöltése a következőből: {{total}}", - "localTransferInFlight": "{{count}} egyszerre", - "localPreparingDownload": "Letöltés előkészítése...", - "localTransferCancelled": "Átutalás törölve", - "localNothingToTransfer": "Nincs mit átutalni", - "localUploadComplete": "Feltöltött {{count}} fájl(ok)", - "localUploadFailed": "Feltöltés sikertelen", - "localUploadPartial": "{{done}} fájl(ok) feltöltése, {{failed}} sikertelen", - "localDownloadComplete": "Letöltött {{count}} fájl(ok)", - "localDownloadFailed": "Letöltés sikertelen", - "localDownloadPartial": "{{done}} fájl(ok) letöltése, {{failed}} sikertelen", - "localReplaceExistingPrompt": "{{count}} elem már létezik ebben a mappában. Cserélje ki őket?", - "localReplace": "Csere", - "localSkipExisting": "Kihagyás", - "localSkippedExisting": "Kihagyott {{count}} meglévő elem(ek)", - "localUnsafeNamesSkipped": "Kihagyott {{count}} elem(ek), amelyek neve nem használható ezen a számítógépen", - "localOpen": "Nyitott", - "localOpenFolder": "Mappa megnyitása", - "localUploadToRemote": "Feltöltés a szerverre", - "localUploadToRemoteMany": "Tölts fel {{count}} elemet a szerverre", - "localMoveToTrash": "Áthelyezés a kukába", - "localMoveToTrashMany": "{{count}} elem áthelyezése a Kukába", - "localTrashConfirmSingle": "Áthelyeznéd a(z) \"{{name}}\"-t a Kukába?", - "localTrashConfirmFolder": "Áthelyezi a(z) \"{{name}}\" mappát és annak teljes tartalmát a Kukába?", - "localTrashConfirmMany": "Áthelyezi a {{count}} elemet a Kukába?", - "localTrashed": "{{count}} elem áthelyezve a Kukába", - "localTrashFailed": "Nem sikerült a Kukába helyezni", - "localRenameFailed": "Átnevezés sikertelen", - "localCreateFailed": "Nem sikerült létrehozni az elemet", - "goToParentFolder": "Ugrás a szülőmappára", - "resizeSidebar": "Húzással átméretezheted az oldalsávot (dupla kattintással visszaállíthatod)", - "resizeLocalPane": "Húzással átméretezheti a helyi panelt (dupla kattintással visszaállíthatja)", - "columns": "Oszlopok", - "columnsHint": "Kattintson jobb gombbal az oszlopok kiválasztásához", - "emptyFolder": "Ez a mappa üres", - "searchFiles": "Fájlok keresése...", - "upload": "Feltöltés", - "selectHostToStart": "Válasszon ki egy gazdagépet a fájlkezelés megkezdéséhez", - "sshRequiredForFileManager": "A fájlkezelő SSH-t igényel. Ezen a gazdagépen nincs engedélyezve az SSH.", - "failedToConnect": "Nem sikerült csatlakozni az SSH-hoz", - "failedToLoadDirectory": "Nem sikerült betölteni a könyvtárat", - "noSSHConnection": "Nincs elérhető SSH-kapcsolat", - "copy": "Másolat", - "cut": "Vágott", - "paste": "Paszta", - "copyPath": "Útvonal másolása", - "copyPaths": "Útvonalak másolása", - "delete": "Töröl", - "properties": "Tulajdonságok", - "refresh": "Frissítés", - "downloadFiles": "{{count}} fájlok letöltése böngészőbe", - "copyFiles": "{{count}} elem másolása", - "cutFiles": "{{count}} elem kivágása", - "deleteFiles": "{{count}} elem törlése", - "filesCopiedToClipboard": "{{count}} elem másolva a vágólapra", - "filesCutToClipboard": "{{count}} elem a vágólapra vágva", - "pathCopiedToClipboard": "Útvonal a vágólapra másolva", - "pathsCopiedToClipboard": "{{count}} elérési utak másolva a vágólapra", - "failedToCopyPath": "Nem sikerült a vágólapra másolni az elérési utat", - "copyFolderLink": "Link másolása mappába", - "copyCurrentFolderLink": "Link másolása az aktuális mappába", - "folderLinkCopied": "Mappa linkje a vágólapra másolva", - "failedToCopyFolderLink": "Nem sikerült a mappa linkjét másolni", - "movedItems": "Áthelyezett {{count}} elem", - "failedToDeleteItem": "Nem sikerült törölni az elemet", - "itemRenamedSuccessfully": "{{type}} átnevezése sikeresen megtörtént", - "failedToRenameItem": "Nem sikerült átnevezni az elemet", - "download": "Letöltés", - "openExternalEditor": "Külsőleg nyitott", - "chooseExternalEditor": "Válasszon külső szerkesztőt", - "externalEditorSelected": "Külső szerkesztő kiválasztva", - "externalEditorDesktopOnly": "A külső szerkesztő csak az asztali alkalmazásban érhető el.", - "externalEditorOpened": "Külső szerkesztőben megnyitva. A mentés oda feltölti a módosításokat a szerverre.", - "failedToOpenExternalEditor": "Nem sikerült megnyitni a külső szerkesztőt", - "failedToSelectExternalEditor": "Nem sikerült kiválasztani a külső szerkesztőt", - "permissions": "Engedélyek", - "size": "Méret", - "modified": "Módosított", - "path": "Útvonal", - "confirmDelete": "Biztosan törölni szeretnéd ezt: {{name}}?", - "permissionDenied": "Engedély megtagadva", - "serverError": "Szerverhiba", - "fileSavedSuccessfully": "Fájl mentése sikeresen megtörtént", - "failedToSaveFile": "Nem sikerült menteni a fájlt", - "confirmDeleteSingleItem": "Biztosan véglegesen törölni szeretnéd a(z) \"{{name}} \" fájlt?", - "confirmDeleteMultipleItems": "Biztosan véglegesen törölni szeretnéd a(z) {{count}} elemet?", - "confirmDeleteMultipleItemsWithFolders": "Biztosan véglegesen törölni szeretnéd a(z) {{count}} elemet? Ez a mappákat és azok tartalmát is tartalmazza.", - "confirmDeleteFolder": "Biztosan véglegesen törölni szeretné a(z) \"{{name}}\" mappát és annak teljes tartalmát?", - "permanentDeleteWarning": "Ez a művelet nem vonható vissza. Az elem(ek) véglegesen törlődnek a szerverről.", - "moveToTrashWarning": "Az elem(ek) a Termix Kukába kerülnek, és a megőrzési időszak lejárta előtt visszaállíthatók.", - "itemsMovedToTrash": "{{count}} elem áthelyezve a Kukába", - "trash": "Szemét", - "trashDescription": "A törölt elemek {{days}} napig maradnak meg ezen a gépen.", - "trashLoadFailed": "Nem sikerült betölteni a Kuka mappát", - "trashEmpty": "A kuka üres", - "trashRestore": "Visszaállítás", - "trashRestored": "Visszaállított {{name}}", - "trashRestoreFailed": "Nem sikerült visszaállítani az elemet. Az eredeti elérési út már létezhet.", - "trashDeletePermanently": "Végleges törlés", - "trashDeleteConfirm": "Véglegesen törölni szeretnéd a(z) {{name}}elemet? Ez a művelet nem vonható vissza.", - "trashPermanentlyDeleted": "Véglegesen törölve {{name}}", - "trashDeleteFailed": "Nem sikerült véglegesen törölni az elemet", - "trashEmptyAction": "Ürítse ki a szemetet", - "trashEmptyConfirm": "Véglegesen törli az összes elemet a Kukából? Ez a művelet nem vonható vissza.", - "trashEmptied": "Szemét kiürítve", - "trashRetention": "Automatikus tisztítás (nap) után", - "trashRetentionSaved": "A kuka megőrzése frissítve", - "trashRetentionFailed": "Nem sikerült frissíteni a Kuka megőrzési idejét", - "trashUnavailableConfirm": "Ez az elem nem helyezhető a Kukába, általában azért, mert egy másik fájlrendszeren van, vagy emelt szintű jogosultságokat igényel. Inkább véglegesen törli?", - "recent": "Legutóbbi", - "pinned": "Rögzítve", - "folderShortcuts": "Mappa parancsikonok", - "failedToReconnectSSH": "Nem sikerült újracsatlakozni az SSH-munkamenethez", - "openTerminalHere": "Nyissa meg a terminált itt", - "run": "Fut", - "openTerminalInFolder": "Nyissa meg a terminált ebben a mappában", - "openTerminalInFileLocation": "Nyissa meg a terminált a fájl helyén", - "runningFile": "Futás - {{file}}", - "onlyRunExecutableFiles": "Csak futtatható fájlokat futtathat", - "directories": "Könyvtárak", - "removedFromRecentFiles": "Eltávolította a(z) „{{name}}” elemet a legutóbbi fájlok közül", - "removeFailed": "Eltávolítás sikertelen", - "unpinnedSuccessfully": "A(z) \"{{name}}\" rögzítésének feloldása sikeresen megtörtént.", - "unpinFailed": "Feloldás sikertelen", - "removedShortcut": "Eltávolította a(z) \"{{name}} \" parancsikont", - "removeShortcutFailed": "A parancsikon eltávolítása sikertelen", - "clearedAllRecentFiles": "Az összes legutóbbi fájl törölve", - "clearFailed": "Törlés sikertelen", - "removeFromRecentFiles": "Eltávolítás a legutóbbi fájlok közül", - "clearAllRecentFiles": "Az összes legutóbbi fájl törlése", - "unpinFile": "Fájl rögzítésének feloldása", - "removeShortcut": "Parancsikon eltávolítása", - "pinFile": "Fájl rögzítése", - "addToShortcuts": "Hozzáadás a parancsikonokhoz", - "pasteFailed": "Beillesztés sikertelen", - "noUndoableActions": "Nincsenek visszavonható műveletek", - "undoCopySuccess": "Visszavont másolási művelet: Törölt {{count}} másolt fájlok", - "undoCopyFailedDelete": "Visszavonás sikertelen: Nem sikerült törölni a másolt fájlokat", - "undoCopyFailedNoInfo": "Visszavonás sikertelen: Nem találhatók másolt fájlinformációk", - "undoMoveSuccess": "Visszavont áthelyezési művelet: {{count}} fájl visszahelyezése az eredeti helyére", - "undoMoveFailedMove": "Visszavonás sikertelen: Nem sikerült fájlokat visszahelyezni", - "undoMoveFailedNoInfo": "Visszavonás sikertelen: Nem találhatók áthelyezett fájlinformációk", - "undoDeleteNotSupported": "A törlési művelet nem vonható vissza: A fájlok véglegesen törölve lettek a szerverről.", - "undoTypeNotSupported": "Nem támogatott visszavonási művelettípus", - "undoOperationFailed": "A visszavonási művelet sikertelen", - "unknownError": "Ismeretlen hiba", - "confirm": "Megerősítés", - "find": "Lelet...", - "replace": "Csere", - "downloadInstead": "Letöltés helyett", - "keyboardShortcuts": "Billentyűparancsok", - "searchAndReplace": "Keresés és csere", - "editing": "Szerkesztés", - "search": "Keresés", - "findNext": "Következő keresése", - "findPrevious": "Előző keresése", - "save": "Megtakarítás", - "selectAll": "Összes kijelölése", - "undo": "Visszavonás", - "redo": "Újra", - "moveLineUp": "Sor felfelé mozgatása", - "moveLineDown": "Sor mozgatása lejjebb", - "toggleComment": "Hozzászólás be-/kikapcsolása", - "autoComplete": "Automatikus kiegészítés", - "imageLoadError": "Nem sikerült betölteni a képet", - "startTyping": "Kezdj el gépelni...", - "unknownSize": "Ismeretlen méret", - "fileIsEmpty": "A fájl üres", - "largeFileWarning": "Nagy fájlra vonatkozó figyelmeztetés", - "largeFileWarningDesc": "A fájl mérete {{size}} , ami teljesítményproblémákat okozhat szövegként megnyitva.", - "fileNotFoundAndRemoved": "A(z) „{{name}}” fájl nem található, és eltávolításra került a legutóbbi/rögzített fájlok közül.", - "failedToLoadFile": "Nem sikerült betölteni a fájlt: {{error}}", - "serverErrorOccurred": "Szerverhiba történt. Kérjük, próbálja meg később.", - "autoSaveFailed": "Automatikus mentés sikertelen", - "fileAutoSaved": "Fájl automatikusan mentve", - "moveFileFailed": "Nem sikerült áthelyezni {{name}}", - "moveOperationFailed": "Áthelyezési művelet sikertelen", - "canOnlyCompareFiles": "Csak két fájlt lehet összehasonlítani", - "comparingFiles": "Fájlok összehasonlítása: {{file1}} és {{file2}}", - "dragFailed": "Húzási művelet sikertelen", - "filePinnedSuccessfully": "A(z) „{{name}}” fájl rögzítése sikeresen megtörtént.", - "pinFileFailed": "Nem sikerült rögzíteni a fájlt", - "fileUnpinnedSuccessfully": "A(z) „{{name}}” fájl rögzítése sikeresen feloldva", - "unpinFileFailed": "Nem sikerült feloldani a fájl rögzítését", - "shortcutAddedSuccessfully": "A(z) „{{name}}” mappa parancsikonja sikeresen hozzáadva", - "addShortcutFailed": "Nem sikerült hozzáadni a parancsikont", - "operationCompletedSuccessfully": "{{operation}} {{count}} elem sikeresen", - "operationCompleted": "{{operation}} {{count}} elem", - "downloadFileSuccess": "A {{name}} fájl sikeresen letöltve", - "downloadFileFailed": "Letöltés sikertelen", - "downloadingFile": "{{name}}… letöltése", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Áthelyezés ide: {{name}}", - "diffCompareWith": "Különbség összehasonlítása {{name}}-val", - "dragOutsideToDownload": "Húzd az ablakon kívülre a letöltéshez ({{count}} fájl)", - "newFolderDefault": "Új mappa", - "newFileDefault": "ÚjFájl.txt", - "successfullyMovedItems": "{{count}} elem sikeresen áthelyezve ide: {{target}}", - "move": "Mozog", - "searchInFile": "Keresés a fájlban (Ctrl+F)", - "showKeyboardShortcuts": "Billentyűparancsok megjelenítése", - "decreaseFontSize": "Betűméret csökkentése", - "increaseFontSize": "Betűméret növelése", - "startWritingMarkdown": "Kezdj el írni a Markdown tartalmaidat...", - "loadingFileComparison": "Fájl-összehasonlítás betöltése...", - "reload": "Újratöltés", - "compare": "Összehasonlítás", - "sideBySide": "Egymás mellett", - "inline": "Beágyazott", - "fileComparison": "Fájl-összehasonlítás: {{file1}} vs {{file2}}", - "fileTooLarge": "A fájl túl nagy: {{error}}", - "sshConnectionFailed": "SSH kapcsolat sikertelen. Kérjük, ellenőrizze a kapcsolatot a következővel: {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Nem sikerült betölteni a fájlt: {{error}}", - "connecting": "Kapcsolódás...", - "connectedSuccessfully": "Sikeres csatlakozás", - "totpVerificationFailed": "TOTP-ellenőrzés sikertelen", - "warpgateVerificationFailed": "Warpgate hitelesítés sikertelen", - "authenticationFailed": "Hitelesítés sikertelen", - "incorrectPassphrase": "Hibás jelszó. Kérjük, próbálja újra.", - "verificationCodePrompt": "Ellenőrző kód:", - "changePermissions": "Engedélyek módosítása", - "currentPermissions": "Jelenlegi engedélyek", - "owner": "Tulajdonos", - "group": "Csoport", - "others": "Mások", - "read": "Olvas", - "write": "Írás", - "execute": "Végrehajtás", - "permissionsChangedSuccessfully": "Engedélyek sikeresen módosítva", - "failedToChangePermissions": "Nem sikerült módosítani az engedélyeket", - "name": "Név", - "sortByName": "Név", - "sortByDate": "Módosítás dátuma", - "sortBySize": "Méret", - "ascending": "Növekvő", - "descending": "Csökkenő", - "root": "Gyökér", - "new": "Új", - "sortBy": "Rendezés alapja", - "items": "Tételek", - "selected": "Kiválasztott", - "editor": "Szerkesztő", - "octal": "Oktális", - "storage": "Tárolás", - "disk": "Korong", - "used": "Használt", - "of": "a", - "toggleSidebar": "Oldalsáv be- és kikapcsolása", - "compactLayout": "Kompakt elrendezés használata", - "comfortableLayout": "Használjon kényelmes elrendezést", - "cannotLoadPdf": "Nem tudom betölteni a PDF-et", - "pdfLoadError": "Hiba történt a PDF fájl betöltésekor.", - "loadingPdf": "PDF betöltése...", - "loadingPage": "Oldal betöltése..." - }, "transfer": { "copyToHost": "Másolás a… gazdagépre", "moveToHost": "Áthelyezés a… gazdagépre", @@ -2292,50 +1951,6 @@ "retryTransferError": "Újrapróbálkozás sikertelen", "transferFailedRetryHint": "Részleges adatok maradtak a célállomáson. Az újrapróbálkozás a kapcsolat helyreállásakor folytatódik." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Fájlok átvitele két SSH hoszt között.", - "sourceServer": "Forráskiszolgáló", - "destinationServer": "Célkiszolgáló", - "selectHost": "Válasszon gazdagépet", - "noHostSelected": "Nincs kiválasztva gazdagép", - "move": "Mozog", - "copy": "Másolat", - "upload": "Feltöltés", - "itemsSelected": "{{count}} elem kiválasztva", - "itemsSelected_other": "{{count}} elem kijelölve", - "ready": "Kész", - "loadingHosts": "Gazdagépek betöltése...", - "name": "Név", - "modified": "Módosított", - "size": "Méret", - "noFiles": "Nincsenek fájlok", - "open": "Nyitott", - "up": "Fel", - "refresh": "Frissítés", - "copyToTarget": "Másolás a célkönyvtárba", - "rename": "Átnevezés", - "delete": "Töröl", - "editPermissions": "Szerkesztési engedélyek", - "createNewFolder": "Új mappa létrehozása", - "deleteSelectedItems": "Kijelölt elemek törlése", - "deleteConfirm": "Törli a {{count}} kijelölt elemet?", - "deleteConfirm_other": "Törli a {{count}} kijelölt elemeket?", - "cancel": "Mégsem", - "save": "Megtakarítás", - "folderCreated": "Mappa létrehozva", - "itemRenamed": "Elem átnevezve", - "actionFailed": "Sikertelen művelet", - "itemsDeleted": "{{count}} elem törölve", - "itemsDeleted_other": "{{count}} elem törölve", - "deleteFailed": "Törlés sikertelen", - "permissionsUpdated": "Engedélyek frissítve", - "noRemoteSessionConnected": "Nincs távoli munkamenet csatlakoztatva", - "failedToListFiles": "Nem sikerült listázni a fájlokat", - "failedToRefresh": "Nem sikerült frissíteni", - "destinationInsideSource": "A cél nem lehet a kiválasztott forrásútvonalon belül.", - "sameHostNoOp": "A forrás- és célmappa ugyanaz." - }, "tunnels": { "noSshTunnels": "Nincsenek SSH alagutak", "createFirstTunnelMessage": "Még nem hozott létre SSH alagutakat. A kezdéshez konfigurálja az alagútkapcsolatokat a Host Managerben.", diff --git a/src/ui/locales/translated/id_ID.json b/src/ui/locales/translated/id_ID.json index 3d25d0475..6ebd0bddd 100644 --- a/src/ui/locales/translated/id_ID.json +++ b/src/ui/locales/translated/id_ID.json @@ -715,7 +715,6 @@ "hostManager": "Manajer Host", "credentials": "Kredensial", "portForwarding": "Meneruskan", - "sftp": "SFTP", "connections": "Koneksi", "serial": "Serial", "keepSideMenuOpen": "Biarkan Menu Tetap Terbuka", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Terowongan", - "tabFiles": "Berkas", "tabStats": "Metrik Host", "tabTelnet": "Telnet", "tabSharing": "Membagikan", "tabAuthentication": "Autentikasi", "terminal": "Terminal", "tunnel": "Terowongan", - "fileManager": "Pengelola File", "serverStats": "Metrik Host", "status": { "available": "Tersedia", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} dilewati (tidak ditemukan IP)", "proxmoxStatsUrlCopied": "URL Statistik Proxmox disalin ke papan klip", "copyProxmoxStatsUrlAction": "Salin URL Statistik Proxmox", - "enableFileManagerMonitor": "Aktifkan Pengelola File", - "enableFileManagerMonitorDesc": "Jelajahi dan kelola file di host ini melalui SFTP.", - "scpLegacyLabel": "Mode Warisan SCP", - "scpLegacyDesc": "Gunakan transfer file lama untuk server yang tidak mendukung subsistem SFTP (misalnya server SSH terintegrasi atau minimal).", - "defaultPathLabel": "Jalur Default", - "fileManagerPathHint": "Direktori yang akan dibuka saat pengelola file diluncurkan untuk host ini.", "statusChecksLabel": "Pemeriksaan Status", "enableStatusChecks": "Aktifkan Pemeriksaan Status", "enableStatusChecksDesc": "Lakukan ping secara berkala ke host ini untuk memverifikasi ketersediaan.", @@ -1278,7 +1269,6 @@ "deletedHost": "Dihapus {{name}}", "copiedToClipboard": "Disalin ke papan klip", "terminalUrlCopied": "URL terminal disalin", - "fileManagerUrlCopied": "URL Pengelola File disalin", "tunnelUrlCopied": "URL terowongan disalin", "dockerUrlCopied": "URL Docker disalin", "hostMetricsUrlCopied": "URL Metrik Host disalin", @@ -1296,7 +1286,6 @@ "copyAddress": "Salin Alamat", "copyLink": "Salin Tautan", "copyTerminalUrlAction": "Salin URL Terminal", - "copyFileManagerUrlAction": "Salin URL Pengelola File", "copyTunnelUrlAction": "Salin URL Terowongan", "copyDockerUrlAction": "Salin URL Docker", "copyHostMetricsUrlAction": "Salin URL Metrik Host", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Hapus dari Split" } }, - "fileManager": { - "noHostSelected": "Tidak ada pembawa acara yang dipilih.", - "initializingEditor": "Menginisialisasi editor...", - "file": "Mengajukan", - "folder": "Map", - "uploadFile": "Unggah File", - "downloadFile": "Unduh", - "extractArchive": "Ekstrak Arsip", - "extractingArchive": "Mengekstrak {{name}}...", - "archiveExtractedSuccessfully": "{{name}} berhasil diekstrak", - "extractFailed": "Ekstraksi gagal", - "compressFile": "Kompres File", - "compressFiles": "Kompres File", - "compressFilesDesc": "Kompres {{count}} item ke dalam arsip", - "archiveName": "Nama Arsip", - "enterArchiveName": "Masukkan nama arsip...", - "compressionFormat": "Format Kompresi", - "selectedFiles": "File terpilih", - "andMoreFiles": "dan {{count}} lainnya...", - "compress": "Kompres", - "compressingFiles": "Mengompres {{count}} item menjadi {{name}}...", - "filesCompressedSuccessfully": "{{name}} berhasil dibuat", - "compressFailed": "Kompresi gagal", - "edit": "Edit", - "preview": "Pratinjau", - "previous": "Sebelumnya", - "next": "Berikutnya", - "pageXOfY": "Halaman {{current}} dari {{total}}", - "zoomOut": "Perkecil tampilan", - "zoomIn": "Perbesar", - "newFile": "Berkas Baru", - "newFolder": "Folder Baru", - "rename": "Ganti nama", - "uploading": "Sedang mengunggah...", - "uploadingFile": "Mengunggah {{name}}...", - "fileName": "Nama File", - "folderName": "Nama Folder", - "fileUploadedSuccessfully": "Berkas \"{{name}}\" berhasil diunggah", - "failedToUploadFile": "Gagal mengunggah file", - "uploadingFolderFiles": "Mengunggah {{count}} berkas...", - "uploadedFolderFiles": "Berhasil mengunggah {{count}} berkas", - "uploadedFolderPartial": "Pengunggahan {{uploaded}} berkas, {{failed}} gagal", - "fileDownloadedSuccessfully": "File \"{{name}}\" berhasil diunduh", - "failedToDownloadFile": "Gagal mengunduh file", - "fileCreatedSuccessfully": "File \"{{name}}\" berhasil dibuat", - "folderCreatedSuccessfully": "Folder \"{{name}}\" berhasil dibuat", - "failedToCreateItem": "Gagal membuat item", - "operationFailed": "Operasi {{operation}} gagal untuk {{name}}: {{error}}", - "failedToResolveSymlink": "Gagal menyelesaikan symlink", - "itemsDeletedSuccessfully": "{{count}} item berhasil dihapus", - "failedToDeleteItems": "Gagal menghapus item", - "sudoPasswordRequired": "Kata Sandi Administrator Diperlukan", - "enterSudoPassword": "Masukkan kata sandi sudo untuk melanjutkan operasi ini.", - "sudoPassword": "Kata sandi Sudo", - "sudoOperationFailed": "Operasi Sudo gagal", - "sudoAuthFailed": "Autentikasi Sudo gagal", - "dragFilesToUpload": "Seret file ke sini untuk mengunggah", - "dropToUploadHere": "Unggah di sini", - "dropToDownloadHere": "Klik di sini untuk mengunduh.", - "localFiles": "Lokal", - "showLocalFiles": "Tampilkan file lokal (tampilan terpisah)", - "hideLocalFiles": "Sembunyikan file lokal", - "localHome": "Folder beranda", - "localPathPlaceholder": "Jalur folder lokal", - "localShowHidden": "Tampilkan file tersembunyi", - "localHideHidden": "Sembunyikan file tersembunyi", - "localRevealInFileManager": "Ungkapkan di pengelola file sistem", - "localCannotOpenFolder": "Tidak dapat membuka folder", - "localItemCount": "{{count}} item", - "localSelectedCount": "{{count}} terpilih", - "back": "Kembali", - "forward": "Maju", - "up": "Ke atas", - "kind": "Baik", - "localUploadingProgress": "Mengunggah {{current}} dari {{total}}", - "localDownloadingProgress": "Mengunduh {{current}} dari {{total}}", - "localTransferInFlight": "{{count}} sekaligus", - "localPreparingDownload": "Mempersiapkan pengunduhan...", - "localTransferCancelled": "Transfer dibatalkan", - "localNothingToTransfer": "Tidak ada yang perlu ditransfer", - "localUploadComplete": "Berkas {{count}} telah diunggah", - "localUploadFailed": "Unggahan gagal", - "localUploadPartial": "Pengunggahan {{done}} berkas, {{failed}} gagal", - "localDownloadComplete": "Berkas {{count}} telah diunduh", - "localDownloadFailed": "Pengunduhan gagal", - "localDownloadPartial": "Pengunduhan {{done}} berkas, {{failed}} gagal", - "localReplaceExistingPrompt": "{{count}} item sudah ada di folder ini. Ganti item tersebut?", - "localReplace": "Mengganti", - "localSkipExisting": "Melewati", - "localSkippedExisting": "Melewati {{count}} item yang sudah ada", - "localUnsafeNamesSkipped": "Melewati {{count}} item karena namanya tidak dapat digunakan di komputer ini", - "localOpen": "Membuka", - "localOpenFolder": "Buka folder", - "localUploadToRemote": "Unggah ke server", - "localUploadToRemoteMany": "Unggah {{count}} item ke server", - "localMoveToTrash": "Pindahkan ke Tempat Sampah", - "localMoveToTrashMany": "Pindahkan {{count}} item ke Sampah", - "localTrashConfirmSingle": "Pindahkan \"{{name}}\" ke Sampah?", - "localTrashConfirmFolder": "Pindahkan folder \"{{name}}\" beserta seluruh isinya ke Sampah?", - "localTrashConfirmMany": "Pindahkan {{count}} item ke Sampah?", - "localTrashed": "Memindahkan {{count}} item ke Sampah", - "localTrashFailed": "Tidak bisa dipindahkan ke tempat sampah", - "localRenameFailed": "Penggantian nama gagal", - "localCreateFailed": "Tidak dapat membuat item", - "goToParentFolder": "Buka folder induk", - "resizeSidebar": "Seret untuk mengubah ukuran bilah sisi (klik dua kali untuk mengatur ulang)", - "resizeLocalPane": "Seret untuk mengubah ukuran panel lokal (klik dua kali untuk mengatur ulang)", - "columns": "Kolom", - "columnsHint": "Klik kanan untuk memilih kolom", - "emptyFolder": "Folder ini kosong", - "searchFiles": "Cari file...", - "upload": "Mengunggah", - "selectHostToStart": "Pilih host untuk memulai manajemen file.", - "sshRequiredForFileManager": "Pengelola file memerlukan SSH. Host ini tidak mengaktifkan SSH.", - "failedToConnect": "Gagal terhubung ke SSH", - "failedToLoadDirectory": "Gagal memuat direktori", - "noSSHConnection": "Tidak ada koneksi SSH yang tersedia.", - "copy": "Menyalin", - "cut": "Memotong", - "paste": "Pasta", - "copyPath": "Salin Jalur", - "copyPaths": "Salin Jalur", - "delete": "Menghapus", - "properties": "Properti", - "refresh": "Menyegarkan", - "downloadFiles": "Unduh {{count}} file ke Browser", - "copyFiles": "Salin {{count}} item", - "cutFiles": "Potong {{count}} item", - "deleteFiles": "Hapus {{count}} item", - "filesCopiedToClipboard": "{{count}} item disalin ke papan klip", - "filesCutToClipboard": "{{count}} item dipotong ke papan klip", - "pathCopiedToClipboard": "Jalur disalin ke papan klip", - "pathsCopiedToClipboard": "{{count}} jalur disalin ke papan klip", - "failedToCopyPath": "Gagal menyalin jalur ke papan klip.", - "copyFolderLink": "Salin Tautan ke Folder", - "copyCurrentFolderLink": "Salin Tautan ke Folder Saat Ini", - "folderLinkCopied": "Tautan folder disalin ke papan klip", - "failedToCopyFolderLink": "Gagal menyalin tautan folder", - "movedItems": "Memindahkan {{count}} item", - "failedToDeleteItem": "Gagal menghapus item", - "itemRenamedSuccessfully": "{{type}} berhasil diganti namanya", - "failedToRenameItem": "Gagal mengganti nama item", - "download": "Unduh", - "openExternalEditor": "Buka secara eksternal", - "chooseExternalEditor": "Pilih editor eksternal", - "externalEditorSelected": "Editor eksternal terpilih", - "externalEditorDesktopOnly": "Editor eksternal hanya tersedia di aplikasi desktop.", - "externalEditorOpened": "Dibuka di editor eksternal. Menyimpan di sana akan mengunggah perubahan kembali ke server.", - "failedToOpenExternalEditor": "Gagal membuka editor eksternal", - "failedToSelectExternalEditor": "Gagal memilih editor eksternal", - "permissions": "Izin", - "size": "Ukuran", - "modified": "Dimodifikasi", - "path": "Jalur", - "confirmDelete": "Apakah Anda yakin ingin menghapus {{name}}?", - "permissionDenied": "Izin ditolak", - "serverError": "Kesalahan Server", - "fileSavedSuccessfully": "Berkas berhasil disimpan.", - "failedToSaveFile": "Gagal menyimpan file", - "confirmDeleteSingleItem": "Apakah Anda yakin ingin menghapus \"{{name}} \" secara permanen?", - "confirmDeleteMultipleItems": "Apakah Anda yakin ingin menghapus {{count}} item secara permanen?", - "confirmDeleteMultipleItemsWithFolders": "Apakah Anda yakin ingin menghapus {{count}} item secara permanen? Ini termasuk folder dan isinya.", - "confirmDeleteFolder": "Apakah Anda yakin ingin menghapus folder \"{{name}}\" beserta seluruh isinya secara permanen?", - "permanentDeleteWarning": "Tindakan ini tidak dapat dibatalkan. Item tersebut akan dihapus secara permanen dari server.", - "moveToTrashWarning": "Item tersebut akan dipindahkan ke Tempat Sampah Termix dan dapat dipulihkan sebelum masa penyimpanan berakhir.", - "itemsMovedToTrash": "{{count}} item dipindahkan ke Sampah", - "trash": "Sampah", - "trashDescription": "Item yang dihapus akan tetap tersimpan selama {{days}} hari di host ini.", - "trashLoadFailed": "Sampah gagal dimuat", - "trashEmpty": "Tempat sampah sudah kosong", - "trashRestore": "Memulihkan", - "trashRestored": "Dipulihkan {{name}}", - "trashRestoreFailed": "Gagal memulihkan item. Jalur asli mungkin sudah ada.", - "trashDeletePermanently": "Hapus secara permanen", - "trashDeleteConfirm": "Hapus secara permanen {{name}}? Tindakan ini tidak dapat dibatalkan.", - "trashPermanentlyDeleted": "Dihapus secara permanen {{name}}", - "trashDeleteFailed": "Gagal menghapus item secara permanen.", - "trashEmptyAction": "Kosongkan Tempat Sampah", - "trashEmptyConfirm": "Menghapus semua item di Sampah secara permanen? Tindakan ini tidak dapat dibatalkan.", - "trashEmptied": "Sampah dikosongkan", - "trashRetention": "Pembersihan otomatis setelah (hari)", - "trashRetentionSaved": "Pembaruan tentang retensi sampah", - "trashRetentionFailed": "Gagal memperbarui pengaturan penyimpanan sampah.", - "trashUnavailableConfirm": "Item ini tidak dapat dipindahkan ke Sampah, biasanya karena berada di sistem file lain atau memerlukan hak akses administrator. Hapus saja secara permanen?", - "recent": "Terkini", - "pinned": "Disematkan", - "folderShortcuts": "Pintasan Folder", - "failedToReconnectSSH": "Gagal menyambungkan kembali sesi SSH", - "openTerminalHere": "Buka Terminal di Sini", - "run": "Berlari", - "openTerminalInFolder": "Buka Terminal di Folder Ini", - "openTerminalInFileLocation": "Buka Terminal di Lokasi File", - "runningFile": "Berlari - {{file}}", - "onlyRunExecutableFiles": "Hanya dapat menjalankan file yang dapat dieksekusi.", - "directories": "Direktori", - "removedFromRecentFiles": "Menghapus \"{{name}}\" dari file terbaru", - "removeFailed": "Hapus yang gagal", - "unpinnedSuccessfully": "Pin \"{{name}}\" berhasil dilepas", - "unpinFailed": "Gagal membuka pin", - "removedShortcut": "Pintasan \"{{name}} \" telah dihapus", - "removeShortcutFailed": "Penghapusan pintasan gagal.", - "clearedAllRecentFiles": "Semua file terbaru telah dihapus.", - "clearFailed": "Hapus gagal", - "removeFromRecentFiles": "Hapus dari berkas terbaru", - "clearAllRecentFiles": "Hapus semua file terbaru", - "unpinFile": "Lepaskan pin file", - "removeShortcut": "Hapus pintasan", - "pinFile": "Pin file", - "addToShortcuts": "Tambahkan ke pintasan", - "pasteFailed": "Pasta gagal", - "noUndoableActions": "Tidak ada tindakan yang tidak dapat dibatalkan.", - "undoCopySuccess": "Batalkan operasi penyalinan: Menghapus {{count}} file yang disalin", - "undoCopyFailedDelete": "Pembatalan gagal: Tidak dapat menghapus file yang disalin.", - "undoCopyFailedNoInfo": "Pembatalan gagal: Informasi file yang disalin tidak ditemukan.", - "undoMoveSuccess": "Operasi pemindahan dibatalkan: {{count}} berkas dipindahkan kembali ke lokasi semula", - "undoMoveFailedMove": "Pembatalan gagal: Tidak dapat memindahkan file apa pun kembali.", - "undoMoveFailedNoInfo": "Pembatalan gagal: Informasi file yang dipindahkan tidak ditemukan.", - "undoDeleteNotSupported": "Operasi penghapusan tidak dapat dibatalkan: File telah dihapus secara permanen dari server.", - "undoTypeNotSupported": "Jenis operasi undo yang tidak didukung", - "undoOperationFailed": "Operasi pembatalan gagal", - "unknownError": "Kesalahan tidak dikenal", - "confirm": "Mengonfirmasi", - "find": "Menemukan...", - "replace": "Mengganti", - "downloadInstead": "Unduh sebagai gantinya", - "keyboardShortcuts": "Pintasan Keyboard", - "searchAndReplace": "Cari & Ganti", - "editing": "Pengeditan", - "search": "Mencari", - "findNext": "Temukan Selanjutnya", - "findPrevious": "Temukan Sebelumnya", - "save": "Menyimpan", - "selectAll": "Pilih Semua", - "undo": "Membuka", - "redo": "Mengulangi", - "moveLineUp": "Susunan Langkah", - "moveLineDown": "Geser Garis ke Bawah", - "toggleComment": "Alihkan Komentar", - "autoComplete": "Pelengkapan Otomatis", - "imageLoadError": "Gambar gagal dimuat", - "startTyping": "Mulai mengetik...", - "unknownSize": "Ukuran tidak diketahui", - "fileIsEmpty": "Berkas kosong", - "largeFileWarning": "Peringatan Ukuran File Besar", - "largeFileWarningDesc": "File ini berukuran {{size}} , yang dapat menyebabkan masalah kinerja saat dibuka sebagai teks.", - "fileNotFoundAndRemoved": "Berkas \"{{name}}\" tidak ditemukan dan telah dihapus dari berkas terbaru/yang disematkan", - "failedToLoadFile": "Gagal memuat file: {{error}}", - "serverErrorOccurred": "Terjadi kesalahan server. Silakan coba lagi nanti.", - "autoSaveFailed": "Penyimpanan otomatis gagal", - "fileAutoSaved": "Berkas tersimpan otomatis", - "moveFileFailed": "Gagal memindahkan {{name}}", - "moveOperationFailed": "Operasi pemindahan gagal", - "canOnlyCompareFiles": "Hanya dapat membandingkan dua file.", - "comparingFiles": "Membandingkan berkas: {{file1}} dan {{file2}}", - "dragFailed": "Operasi seret gagal", - "filePinnedSuccessfully": "Berkas \"{{name}}\" berhasil disematkan", - "pinFileFailed": "Gagal menyematkan file", - "fileUnpinnedSuccessfully": "Berkas \"{{name}}\" berhasil dilepas dari pin", - "unpinFileFailed": "Gagal melepaskan pin file", - "shortcutAddedSuccessfully": "Pintasan folder \"{{name}}\" berhasil ditambahkan", - "addShortcutFailed": "Gagal menambahkan pintasan", - "operationCompletedSuccessfully": "{{operation}} {{count}} item berhasil", - "operationCompleted": "{{operation}} {{count}} item", - "downloadFileSuccess": "File {{name}} berhasil diunduh", - "downloadFileFailed": "Pengunduhan gagal", - "downloadingFile": "Mengunduh {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Pindah ke {{name}}", - "diffCompareWith": "Bandingkan perbedaan dengan {{name}}", - "dragOutsideToDownload": "Seret ke luar jendela untuk mengunduh ({{count}} file)", - "newFolderDefault": "Folder Baru", - "newFileDefault": "File Baru.txt", - "successfullyMovedItems": "Berhasil memindahkan {{count}} item ke {{target}}", - "move": "Bergerak", - "searchInFile": "Cari di dalam file (Ctrl+F)", - "showKeyboardShortcuts": "Tampilkan pintasan keyboard", - "decreaseFontSize": "Kurangi ukuran font", - "increaseFontSize": "Perbesar ukuran font", - "startWritingMarkdown": "Mulailah menulis konten markdown Anda...", - "loadingFileComparison": "Sedang memuat perbandingan file...", - "reload": "Muat ulang", - "compare": "Membandingkan", - "sideBySide": "Berdampingan", - "inline": "Sejajar", - "fileComparison": "Perbandingan File: {{file1}} vs {{file2}}", - "fileTooLarge": "Ukuran file terlalu besar: {{error}}", - "sshConnectionFailed": "Koneksi SSH gagal. Harap periksa koneksi Anda ke {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Gagal memuat file: {{error}}", - "connecting": "Menghubungkan...", - "connectedSuccessfully": "Terhubung berhasil", - "totpVerificationFailed": "Verifikasi TOTP gagal", - "warpgateVerificationFailed": "Autentikasi Warpgate gagal", - "authenticationFailed": "Autentikasi gagal", - "incorrectPassphrase": "Kata sandi salah. Silakan coba lagi.", - "verificationCodePrompt": "Kode verifikasi:", - "changePermissions": "Ubah Izin", - "currentPermissions": "Izin Saat Ini", - "owner": "Pemilik", - "group": "Kelompok", - "others": "Yang lain", - "read": "Membaca", - "write": "Menulis", - "execute": "Menjalankan", - "permissionsChangedSuccessfully": "Izin berhasil diubah.", - "failedToChangePermissions": "Gagal mengubah izin.", - "name": "Nama", - "sortByName": "Nama", - "sortByDate": "Tanggal Diubah", - "sortBySize": "Ukuran", - "ascending": "Naik", - "descending": "Menurun", - "root": "Akar", - "new": "Baru", - "sortBy": "Urutkan Berdasarkan", - "items": "Barang-barang", - "selected": "Terpilih", - "editor": "Editor", - "octal": "Oktal", - "storage": "Penyimpanan", - "disk": "Disk", - "used": "Digunakan", - "of": "dari", - "toggleSidebar": "Alihkan Sidebar", - "compactLayout": "Gunakan tata letak yang ringkas.", - "comfortableLayout": "Gunakan tata letak yang nyaman.", - "cannotLoadPdf": "Tidak dapat memuat PDF", - "pdfLoadError": "Terjadi kesalahan saat memuat file PDF ini.", - "loadingPdf": "Sedang memuat PDF...", - "loadingPage": "Sedang memuat halaman..." - }, "transfer": { "copyToHost": "Salin ke host…", "moveToHost": "Pindah ke host…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Percobaan ulang gagal.", "transferFailedRetryHint": "Sebagian data tersimpan di tujuan. Upaya pengulangan akan dilanjutkan saat koneksi kembali pulih." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Mentransfer file antara dua host SSH.", - "sourceServer": "Server Sumber", - "destinationServer": "Server Tujuan", - "selectHost": "Pilih tuan rumah", - "noHostSelected": "Tidak ada pembawa acara yang dipilih.", - "move": "Bergerak", - "copy": "Menyalin", - "upload": "Mengunggah", - "itemsSelected": "{{count}} item dipilih", - "itemsSelected_other": "{{count}} item terpilih", - "ready": "Siap", - "loadingHosts": "Memuat host...", - "name": "Nama", - "modified": "Dimodifikasi", - "size": "Ukuran", - "noFiles": "Tidak ada berkas", - "open": "Membuka", - "up": "Ke atas", - "refresh": "Menyegarkan", - "copyToTarget": "Salin ke Direktori Target", - "rename": "Ganti nama", - "delete": "Menghapus", - "editPermissions": "Edit Izin", - "createNewFolder": "Buat Folder Baru", - "deleteSelectedItems": "Hapus Item yang Dipilih", - "deleteConfirm": "Hapus {{count}} item yang dipilih?", - "deleteConfirm_other": "Hapus {{count}} item yang dipilih?", - "cancel": "Membatalkan", - "save": "Menyimpan", - "folderCreated": "Folder telah dibuat.", - "itemRenamed": "Item diganti namanya", - "actionFailed": "Tindakan gagal", - "itemsDeleted": "{{count}} item dihapus", - "itemsDeleted_other": "{{count}} item dihapus", - "deleteFailed": "Penghapusan gagal", - "permissionsUpdated": "Izin telah diperbarui.", - "noRemoteSessionConnected": "Tidak ada sesi jarak jauh yang terhubung.", - "failedToListFiles": "Gagal menampilkan daftar file.", - "failedToRefresh": "Gagal memperbarui", - "destinationInsideSource": "Tujuan tidak boleh berada di dalam jalur sumber yang dipilih.", - "sameHostNoOp": "Sumber dan tujuan berada di folder yang sama." - }, "tunnels": { "noSshTunnels": "Tidak ada terowongan SSH", "createFirstTunnelMessage": "Anda belum membuat terowongan SSH apa pun. Konfigurasikan koneksi terowongan di Host Manager untuk memulai.", diff --git a/src/ui/locales/translated/it_IT.json b/src/ui/locales/translated/it_IT.json index fd0208d8d..1d64b4a5d 100644 --- a/src/ui/locales/translated/it_IT.json +++ b/src/ui/locales/translated/it_IT.json @@ -715,7 +715,6 @@ "hostManager": "Gestione host", "credentials": "Credenziali", "portForwarding": "Inoltro", - "sftp": "SFTP", "connections": "Connessioni", "serial": "Seriale", "keepSideMenuOpen": "Mantieni il menu aperto", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tunnel", - "tabFiles": "File", "tabStats": "Metriche host", "tabTelnet": "Telnet", "tabSharing": "Condivisione", "tabAuthentication": "Autenticazione", "terminal": "Terminale", "tunnel": "Tunnel", - "fileManager": "File Manager", "serverStats": "Metriche host", "status": { "available": "Disponibile", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} saltato (nessun IP trovato)", "proxmoxStatsUrlCopied": "URL delle statistiche di Proxmox copiato negli appunti", "copyProxmoxStatsUrlAction": "Copia l'URL delle statistiche di Proxmox", - "enableFileManagerMonitor": "Abilita File Manager", - "enableFileManagerMonitorDesc": "Sfoglia e gestisci i file su questo host tramite SFTP", - "scpLegacyLabel": "Modalità SCP legacy", - "scpLegacyDesc": "Usa il trasferimento file legacy per server che non supportano il sottosistema SFTP (ad es. server SSH embedded o minimali).", - "defaultPathLabel": "Percorso predefinito", - "fileManagerPathHint": "La directory da aprire all'avvio del file manager per questo host.", "statusChecksLabel": "Controlli di stato", "enableStatusChecks": "Abilita controlli di stato", "enableStatusChecksDesc": "Esegui periodicamente il ping di questo host per verificarne la disponibilità", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} eliminato", "copiedToClipboard": "Copiato negli appunti", "terminalUrlCopied": "URL terminale copiato", - "fileManagerUrlCopied": "URL file manager copiato", "tunnelUrlCopied": "URL tunnel copiato", "dockerUrlCopied": "URL Docker copiato", "hostMetricsUrlCopied": "URL metriche host copiato", @@ -1296,7 +1286,6 @@ "copyAddress": "Copia indirizzo", "copyLink": "Copia link", "copyTerminalUrlAction": "Copia URL Terminal", - "copyFileManagerUrlAction": "Copia URL File Manager", "copyTunnelUrlAction": "Copia URL Tunnel", "copyDockerUrlAction": "Copia URL Docker", "copyHostMetricsUrlAction": "Copia URL metriche host", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Rimuovi dalla divisione" } }, - "fileManager": { - "noHostSelected": "Nessun host selezionato", - "initializingEditor": "Inizializzazione editor...", - "file": "File", - "folder": "Cartella", - "uploadFile": "Carica file", - "downloadFile": "Scarica", - "extractArchive": "Estrai archivio", - "extractingArchive": "Estrazione di {{name}}...", - "archiveExtractedSuccessfully": "{{name}} estratto con successo", - "extractFailed": "Estrazione non riuscita", - "compressFile": "Comprimi file", - "compressFiles": "Comprimi file", - "compressFilesDesc": "Comprimi {{count}} elementi in un archivio", - "archiveName": "Nome archivio", - "enterArchiveName": "Inserisci nome archivio...", - "compressionFormat": "Formato compressione", - "selectedFiles": "File selezionati", - "andMoreFiles": "e altri {{count}}...", - "compress": "Comprimi", - "compressingFiles": "Comprimendo {{count}} elementi in {{name}}...", - "filesCompressedSuccessfully": "{{name}} creato con successo", - "compressFailed": "Compressione non riuscita", - "edit": "Modifica", - "preview": "Anteprima", - "previous": "Precedente", - "next": "Successivo", - "pageXOfY": "Pagina {{current}} di {{total}}", - "zoomOut": "Riduci zoom", - "zoomIn": "Aumenta zoom", - "newFile": "Nuovo file", - "newFolder": "Nuova cartella", - "rename": "Rinomina", - "uploading": "Caricamento in corso...", - "uploadingFile": "Caricamento di {{name}}...", - "fileName": "Nome file", - "folderName": "Nome cartella", - "fileUploadedSuccessfully": "File \"{{name}}\" caricato con successo", - "failedToUploadFile": "Caricamento file non riuscito", - "uploadingFolderFiles": "Caricamento del/dei file {{count}} in corso...", - "uploadedFolderFiles": "File caricati con successo {{count}}", - "uploadedFolderPartial": "Caricamento del/i file {{uploaded}} non riuscito/i {{failed}}", - "fileDownloadedSuccessfully": "File \"{{name}}\" scaricato con successo", - "failedToDownloadFile": "Scaricamento file non riuscito", - "fileCreatedSuccessfully": "File \"{{name}}\" creato con successo", - "folderCreatedSuccessfully": "Cartella \"{{name}}\" creata con successo", - "failedToCreateItem": "Creazione elemento non riuscita", - "operationFailed": "Operazione {{operation}} non riuscita per {{name}}: {{error}}", - "failedToResolveSymlink": "Risoluzione del collegamento simbolico non riuscita", - "itemsDeletedSuccessfully": "{{count}} elementi eliminati con successo", - "failedToDeleteItems": "Eliminazione elementi non riuscita", - "sudoPasswordRequired": "Password amministratore richiesta", - "enterSudoPassword": "Inserisci la password sudo per continuare l'operazione", - "sudoPassword": "Password sudo", - "sudoOperationFailed": "Operazione sudo non riuscita", - "sudoAuthFailed": "Autenticazione sudo non riuscita", - "dragFilesToUpload": "Trascina qui i file per caricarli", - "dropToUploadHere": "Trascina qui per caricare", - "dropToDownloadHere": "Scarica qui", - "localFiles": "Locale", - "showLocalFiles": "Mostra i file locali (vista divisa)", - "hideLocalFiles": "Nascondi i file locali", - "localHome": "Cartella Home", - "localPathPlaceholder": "Percorso della cartella locale", - "localShowHidden": "Mostra i file nascosti", - "localHideHidden": "Nascondi i file nascosti", - "localRevealInFileManager": "Mostra nel gestore file di sistema", - "localCannotOpenFolder": "Impossibile aprire la cartella", - "localItemCount": "{{count}} elementi", - "localSelectedCount": "{{count}} selezionato", - "back": "Indietro", - "forward": "Inoltrare", - "up": "Su", - "kind": "Tipo", - "localUploadingProgress": "Caricamento di {{current}} di {{total}}", - "localDownloadingProgress": "Download di {{current}} di {{total}}", - "localTransferInFlight": "{{count}} subito", - "localPreparingDownload": "Download in corso...", - "localTransferCancelled": "Trasferimento annullato", - "localNothingToTransfer": "Nulla da trasferire", - "localUploadComplete": "Caricato/i file {{count}}", - "localUploadFailed": "Caricamento non riuscito", - "localUploadPartial": "Caricamento del/i file {{done}} non riuscito/i {{failed}}", - "localDownloadComplete": "Scaricato il/i file {{count}}", - "localDownloadFailed": "Download non riuscito", - "localDownloadPartial": "Scaricamento del/dei file {{done}} , {{failed}} non riuscito", - "localReplaceExistingPrompt": "{{count}} L'elemento/gli elementi esistono già in questa cartella. Sostituirli?", - "localReplace": "Sostituire", - "localSkipExisting": "Saltare", - "localSkippedExisting": "Saltato {{count}} elemento/i esistente/i", - "localUnsafeNamesSkipped": "Saltati {{count}} elementi i cui nomi non possono essere utilizzati su questo computer", - "localOpen": "Aprire", - "localOpenFolder": "Apri la cartella", - "localUploadToRemote": "Carica sul server", - "localUploadToRemoteMany": "Carica {{count}} elementi sul server", - "localMoveToTrash": "Sposta nel Cestino", - "localMoveToTrashMany": "Sposta {{count}} elementi nel Cestino", - "localTrashConfirmSingle": "Spostare \"{{name}}\" nel Cestino?", - "localTrashConfirmFolder": "Spostare la cartella \"{{name}}\" e tutto il suo contenuto nel Cestino?", - "localTrashConfirmMany": "Spostare {{count}} elementi nel Cestino?", - "localTrashed": "Spostati {{count}} elementi nel Cestino", - "localTrashFailed": "Impossibile spostare nel Cestino", - "localRenameFailed": "Rinominare non riuscito", - "localCreateFailed": "Impossibile creare l'elemento", - "goToParentFolder": "Vai alla cartella principale", - "resizeSidebar": "Trascina per ridimensionare la barra laterale (doppio clic per ripristinare le dimensioni originali)", - "resizeLocalPane": "Trascina per ridimensionare il riquadro locale (doppio clic per ripristinare le dimensioni originali)", - "columns": "Colonne", - "columnsHint": "Fare clic con il pulsante destro del mouse per scegliere le colonne", - "emptyFolder": "Questa cartella è vuota", - "searchFiles": "Cerca file...", - "upload": "Carica", - "selectHostToStart": "Seleziona un host per avviare la gestione file", - "sshRequiredForFileManager": "Il gestore file richiede SSH. Questo host non ha SSH abilitato.", - "failedToConnect": "Connessione a SSH non riuscita", - "failedToLoadDirectory": "Caricamento directory non riuscito", - "noSSHConnection": "Nessuna connessione SSH disponibile", - "copy": "Copia", - "cut": "Taglia", - "paste": "Incolla", - "copyPath": "Copia percorso", - "copyPaths": "Copia percorsi", - "delete": "Elimina", - "properties": "Proprietà", - "refresh": "Aggiorna", - "downloadFiles": "Scarica {{count}} file nel browser", - "copyFiles": "Copia {{count}} elementi", - "cutFiles": "Taglia {{count}} elementi", - "deleteFiles": "Elimina {{count}} elementi", - "filesCopiedToClipboard": "{{count}} elementi copiati negli appunti", - "filesCutToClipboard": "{{count}} elementi tagliati negli appunti", - "pathCopiedToClipboard": "Percorso copiato negli appunti", - "pathsCopiedToClipboard": "{{count}} percorsi copiati negli appunti", - "failedToCopyPath": "Impossibile copiare il percorso negli appunti", - "copyFolderLink": "Copia collegamento alla cartella", - "copyCurrentFolderLink": "Copia collegamento alla cartella corrente", - "folderLinkCopied": "Collegamento alla cartella copiato negli appunti", - "failedToCopyFolderLink": "Impossibile copiare il collegamento alla cartella", - "movedItems": "Spostati {{count}} elementi", - "failedToDeleteItem": "Impossibile eliminare l'elemento", - "itemRenamedSuccessfully": "{{type}} rinominato con successo", - "failedToRenameItem": "Impossibile rinominare l'elemento", - "download": "Scarica", - "openExternalEditor": "Apri esternamente", - "chooseExternalEditor": "Scegli editor esterno", - "externalEditorSelected": "Editor esterno selezionato", - "externalEditorDesktopOnly": "L'editor esterno è disponibile solo nell'app desktop", - "externalEditorOpened": "Aperto nell'editor esterno. Salvando lì, le modifiche verranno caricate nuovamente sul server.", - "failedToOpenExternalEditor": "Impossibile aprire l'editor esterno", - "failedToSelectExternalEditor": "Impossibile selezionare l'editor esterno", - "permissions": "Permessi", - "size": "Dimensione", - "modified": "Modificato", - "path": "Percorso", - "confirmDelete": "Sei sicuro di voler eliminare {{name}}?", - "permissionDenied": "Permesso negato", - "serverError": "Errore del server", - "fileSavedSuccessfully": "File salvato con successo", - "failedToSaveFile": "Impossibile salvare il file", - "confirmDeleteSingleItem": "Sei sicuro di voler eliminare definitivamente \"{{name}}\"?", - "confirmDeleteMultipleItems": "Sei sicuro di voler eliminare definitivamente {{count}} elementi?", - "confirmDeleteMultipleItemsWithFolders": "Sei sicuro di voler eliminare definitivamente {{count}} elementi? Sono incluse cartelle e il loro contenuto.", - "confirmDeleteFolder": "Sei sicuro di voler eliminare definitivamente la cartella \"{{name}}\" e tutto il suo contenuto?", - "permanentDeleteWarning": "Questa azione non può essere annullata. Gli elementi verranno eliminati definitivamente dal server.", - "moveToTrashWarning": "L'articolo o gli articoli verranno spostati nel Cestino Termix e potranno essere ripristinati prima della scadenza del periodo di conservazione.", - "itemsMovedToTrash": "{{count}} elemento/i spostato/i nel Cestino", - "trash": "Spazzatura", - "trashDescription": "Gli elementi eliminati vengono conservati per {{days}} giorni su questo host.", - "trashLoadFailed": "Impossibile caricare il Cestino", - "trashEmpty": "Il cestino è vuoto", - "trashRestore": "Ripristinare", - "trashRestored": "Ripristinato {{name}}", - "trashRestoreFailed": "Impossibile ripristinare l'elemento. Il percorso originale potrebbe già esistere.", - "trashDeletePermanently": "Eliminare definitivamente", - "trashDeleteConfirm": "Eliminare definitivamente {{name}}? Questa operazione non può essere annullata.", - "trashPermanentlyDeleted": "Eliminato definitivamente {{name}}", - "trashDeleteFailed": "Impossibile eliminare definitivamente l'elemento", - "trashEmptyAction": "Svuotare il cestino", - "trashEmptyConfirm": "Eliminare definitivamente tutti gli elementi nel Cestino? Questa operazione non può essere annullata.", - "trashEmptied": "Cestino svuotato", - "trashRetention": "Pulizia automatica dopo (giorni)", - "trashRetentionSaved": "Gestione dei rifiuti aggiornata", - "trashRetentionFailed": "Impossibile aggiornare la conservazione dei rifiuti", - "trashUnavailableConfirm": "Questo elemento non può essere spostato nel Cestino, solitamente perché si trova su un altro file system o richiede privilegi elevati. Vuoi eliminarlo definitivamente?", - "recent": "Recenti", - "pinned": "Fissati", - "folderShortcuts": "Collegamenti alle cartelle", - "failedToReconnectSSH": "Impossibile riconnettersi alla sessione SSH", - "openTerminalHere": "Apri terminale qui", - "run": "Esegui", - "openTerminalInFolder": "Apri terminale in questa cartella", - "openTerminalInFileLocation": "Apri terminale nella posizione del file", - "runningFile": "In esecuzione - {{file}}", - "onlyRunExecutableFiles": "È possibile eseguire solo file eseguibili", - "directories": "Directory", - "removedFromRecentFiles": "Rimosso \"{{name}}\" dai file recenti", - "removeFailed": "Rimozione non riuscita", - "unpinnedSuccessfully": "Rimosso \"{{name}}\" dai preferiti con successo", - "unpinFailed": "Rimozione dai preferiti non riuscita", - "removedShortcut": "Rimosso collegamento \"{{name}}\"", - "removeShortcutFailed": "Rimozione collegamento non riuscita", - "clearedAllRecentFiles": "Cancellati tutti i file recenti", - "clearFailed": "Cancellazione non riuscita", - "removeFromRecentFiles": "Rimuovi dai file recenti", - "clearAllRecentFiles": "Cancella tutti i file recenti", - "unpinFile": "Rimuovi dai preferiti", - "removeShortcut": "Rimuovi collegamento", - "pinFile": "Aggiungi ai preferiti", - "addToShortcuts": "Aggiungi ai collegamenti", - "pasteFailed": "Incolla non riuscito", - "noUndoableActions": "Nessuna azione annullabile", - "undoCopySuccess": "Operazione di copia annullata: eliminati {{count}} file copiati", - "undoCopyFailedDelete": "Annullamento non riuscito: impossibile eliminare i file copiati", - "undoCopyFailedNoInfo": "Annullamento non riuscito: impossibile trovare informazioni sui file copiati", - "undoMoveSuccess": "Operazione di spostamento annullata: riportati {{count}} file nella posizione originale", - "undoMoveFailedMove": "Annullamento non riuscito: impossibile riportare i file indietro", - "undoMoveFailedNoInfo": "Annullamento non riuscito: impossibile trovare informazioni sui file spostati", - "undoDeleteNotSupported": "L'operazione di eliminazione non può essere annullata: i file sono stati eliminati permanentemente dal server", - "undoTypeNotSupported": "Tipo di operazione di annullamento non supportato", - "undoOperationFailed": "Operazione di annullamento non riuscita", - "unknownError": "Errore sconosciuto", - "confirm": "Conferma", - "find": "Trova...", - "replace": "Sostituisci", - "downloadInstead": "Scarica invece", - "keyboardShortcuts": "Scorciatoie da tastiera", - "searchAndReplace": "Cerca e sostituisci", - "editing": "Modifica", - "search": "Cerca", - "findNext": "Trova successivo", - "findPrevious": "Trova precedente", - "save": "Salva", - "selectAll": "Seleziona tutto", - "undo": "Annulla", - "redo": "Ripristina", - "moveLineUp": "Sposta riga su", - "moveLineDown": "Sposta riga giù", - "toggleComment": "Commenta/Decommenta", - "autoComplete": "Completamento automatico", - "imageLoadError": "Caricamento immagine non riuscito", - "startTyping": "Inizia a digitare...", - "unknownSize": "Dimensione sconosciuta", - "fileIsEmpty": "Il file è vuoto", - "largeFileWarning": "Avviso file di grandi dimensioni", - "largeFileWarningDesc": "Questo file ha una dimensione di {{size}}, che potrebbe causare problemi di prestazioni se aperto come testo.", - "fileNotFoundAndRemoved": "File \"{{name}}\" non trovato ed è stato rimosso dai file recenti/fissati", - "failedToLoadFile": "Caricamento del file non riuscito: {{error}}", - "serverErrorOccurred": "Si è verificato un errore del server. Riprova più tardi.", - "autoSaveFailed": "Salvataggio automatico non riuscito", - "fileAutoSaved": "File salvato automaticamente", - "moveFileFailed": "Spostamento di {{name}} non riuscito", - "moveOperationFailed": "Operazione di spostamento non riuscita", - "canOnlyCompareFiles": "È possibile confrontare solo due file", - "comparingFiles": "Confronto dei file: {{file1}} e {{file2}}", - "dragFailed": "Operazione di trascinamento non riuscita", - "filePinnedSuccessfully": "File \"{{name}}\" fissato con successo", - "pinFileFailed": "Fissaggio del file non riuscito", - "fileUnpinnedSuccessfully": "File \"{{name}}\" rimosso dai fissati con successo", - "unpinFileFailed": "Impossibile rimuovere il file fissato", - "shortcutAddedSuccessfully": "Collegamento alla cartella \"{{name}}\" aggiunto con successo", - "addShortcutFailed": "Impossibile aggiungere il collegamento", - "operationCompletedSuccessfully": "{{operation}} {{count}} elementi con successo", - "operationCompleted": "{{operation}} {{count}} elementi", - "downloadFileSuccess": "File {{name}} scaricato con successo", - "downloadFileFailed": "Download non riuscito", - "downloadingFile": "Download in corso {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Sposta in {{name}}", - "diffCompareWith": "Diff con {{name}}", - "dragOutsideToDownload": "Trascina fuori dalla finestra per scaricare ({{count}} file)", - "newFolderDefault": "NuovaCartella", - "newFileDefault": "NuovoFile.txt", - "successfullyMovedItems": "{{count}} elementi spostati con successo in {{target}}", - "move": "Sposta", - "searchInFile": "Cerca nel file (Ctrl+F)", - "showKeyboardShortcuts": "Mostra scorciatoie da tastiera", - "decreaseFontSize": "Riduci dimensione carattere", - "increaseFontSize": "Aumenta dimensione carattere", - "startWritingMarkdown": "Inizia a scrivere il tuo contenuto markdown...", - "loadingFileComparison": "Caricamento confronto file in corso...", - "reload": "Ricarica", - "compare": "Confronta", - "sideBySide": "Fianco a fianco", - "inline": "In linea", - "fileComparison": "Confronto file: {{file1}} vs {{file2}}", - "fileTooLarge": "File troppo grande: {{error}}", - "sshConnectionFailed": "Connessione SSH non riuscita. Controlla la connessione a {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Caricamento del file non riuscito: {{error}}", - "connecting": "Connessione in corso...", - "connectedSuccessfully": "Connesso con successo", - "totpVerificationFailed": "Verifica TOTP non riuscita", - "warpgateVerificationFailed": "Autenticazione Warpgate non riuscita", - "authenticationFailed": "Autenticazione non riuscita", - "incorrectPassphrase": "Frase di accesso non corretta. Riprova.", - "verificationCodePrompt": "Codice di verifica:", - "changePermissions": "Modifica permessi", - "currentPermissions": "Permessi attuali", - "owner": "Proprietario", - "group": "Gruppo", - "others": "Altri", - "read": "Lettura", - "write": "Scrittura", - "execute": "Esecuzione", - "permissionsChangedSuccessfully": "Permessi modificati con successo", - "failedToChangePermissions": "Modifica dei permessi non riuscita", - "name": "Nome", - "sortByName": "Nome", - "sortByDate": "Data di modifica", - "sortBySize": "Dimensione", - "ascending": "Crescente", - "descending": "Decrescente", - "root": "Radice", - "new": "Nuovo", - "sortBy": "Ordina per", - "items": "Elementi", - "selected": "Selezionati", - "editor": "Editor", - "octal": "Ottale", - "storage": "Storage", - "disk": "Disco", - "used": "Usato", - "of": "di", - "toggleSidebar": "Mostra/Nascondi barra laterale", - "compactLayout": "Utilizzare un layout compatto", - "comfortableLayout": "Utilizzare una disposizione comoda", - "cannotLoadPdf": "Impossibile caricare il PDF", - "pdfLoadError": "Si è verificato un errore durante il caricamento del file PDF.", - "loadingPdf": "Caricamento PDF in corso...", - "loadingPage": "Caricamento pagina in corso..." - }, "transfer": { "copyToHost": "Copia su host…", "moveToHost": "Sposta su host…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Riprova fallita", "transferFailedRetryHint": "I dati parziali sono stati conservati sulla destinazione. Il nuovo tentativo riprenderà quando la connessione sarà ripristinata." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Trasferire file tra due host SSH.", - "sourceServer": "Server sorgente", - "destinationServer": "Server di destinazione", - "selectHost": "Seleziona l'host", - "noHostSelected": "Nessun host selezionato", - "move": "Mossa", - "copy": "Copia", - "upload": "Caricamento", - "itemsSelected": "{{count}} elemento selezionato", - "itemsSelected_other": "{{count}} elementi selezionati", - "ready": "Pronto", - "loadingHosts": "Caricamento host in corso...", - "name": "Nome", - "modified": "Modificato", - "size": "Misurare", - "noFiles": "Nessun file", - "open": "Aprire", - "up": "Su", - "refresh": "Rinfrescato", - "copyToTarget": "Copia nella directory di destinazione", - "rename": "Rinominare", - "delete": "Eliminare", - "editPermissions": "Modifica autorizzazioni", - "createNewFolder": "Crea una nuova cartella", - "deleteSelectedItems": "Elimina gli elementi selezionati", - "deleteConfirm": "Eliminare l'elemento selezionato {{count}}?", - "deleteConfirm_other": "Eliminare gli elementi selezionati {{count}}?", - "cancel": "Cancellare", - "save": "Salva", - "folderCreated": "Cartella creata", - "itemRenamed": "Elemento rinominato", - "actionFailed": "L'azione è fallita", - "itemsDeleted": "{{count}} elemento eliminato", - "itemsDeleted_other": "{{count}} elementi eliminati", - "deleteFailed": "Eliminazione non riuscita", - "permissionsUpdated": "Autorizzazioni aggiornate", - "noRemoteSessionConnected": "Nessuna sessione remota connessa", - "failedToListFiles": "Impossibile elencare i file", - "failedToRefresh": "Aggiornamento non riuscito", - "destinationInsideSource": "La destinazione non può trovarsi all'interno del percorso di origine selezionato.", - "sameHostNoOp": "La cartella di origine e quella di destinazione sono la stessa." - }, "tunnels": { "noSshTunnels": "Nessun tunnel SSH", "createFirstTunnelMessage": "Non hai ancora creato nessun tunnel SSH. Configura le connessioni tunnel nel Gestore host per iniziare.", diff --git a/src/ui/locales/translated/ja_JP.json b/src/ui/locales/translated/ja_JP.json index 620d44b51..7c8a33702 100644 --- a/src/ui/locales/translated/ja_JP.json +++ b/src/ui/locales/translated/ja_JP.json @@ -715,7 +715,6 @@ "hostManager": "ホストマネージャー", "credentials": "認証情報", "portForwarding": "転送", - "sftp": "SFTP", "connections": "接続", "serial": "シリアル", "keepSideMenuOpen": "メニューを開いたままにする", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "トンネル", - "tabFiles": "ファイル", "tabStats": "ホストメトリクス", "tabTelnet": "Telnet", "tabSharing": "共有", "tabAuthentication": "認証", "terminal": "ターミナル", "tunnel": "トンネル", - "fileManager": "ファイルマネージャー", "serverStats": "ホストメトリクス", "status": { "available": "利用可能", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} スキップしました(IPアドレスが見つかりませんでした)", "proxmoxStatsUrlCopied": "Proxmox Stats URL がクリップボードにコピーされました", "copyProxmoxStatsUrlAction": "Proxmox統計情報のURLをコピーする", - "enableFileManagerMonitor": "ファイルマネージャーを有効にする", - "enableFileManagerMonitorDesc": "このホストのファイルを SFTP で閲覧・管理します", - "scpLegacyLabel": "SCP レガシーモード", - "scpLegacyDesc": "SFTP サブシステムをサポートしないサーバー(組み込みや最小構成の SSH サーバーなど)に対してレガシーファイル転送を使用します", - "defaultPathLabel": "デフォルトパス", - "fileManagerPathHint": "このホストのファイルマネージャー起動時に開くディレクトリ", "statusChecksLabel": "ステータスチェック", "enableStatusChecks": "ステータスチェックを有効にする", "enableStatusChecksDesc": "定期的にこのホストに ping を送信して可用性を確認します", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} を削除しました", "copiedToClipboard": "クリップボードにコピーしました", "terminalUrlCopied": "ターミナルURLをコピーしました", - "fileManagerUrlCopied": "ファイルマネージャーURLをコピーしました", "tunnelUrlCopied": "トンネルURLをコピーしました", "dockerUrlCopied": "Docker URLをコピーしました", "hostMetricsUrlCopied": "ホストメトリクスURLをコピーしました", @@ -1296,7 +1286,6 @@ "copyAddress": "アドレスをコピー", "copyLink": "リンクをコピー", "copyTerminalUrlAction": "ターミナル URL をコピー", - "copyFileManagerUrlAction": "ファイルマネージャー URL をコピー", "copyTunnelUrlAction": "トンネル URL をコピー", "copyDockerUrlAction": "Docker URL をコピー", "copyHostMetricsUrlAction": "ホストメトリクス URL をコピー", @@ -1857,336 +1846,6 @@ "removeFromSplit": "分割から削除" } }, - "fileManager": { - "noHostSelected": "ホストが選択されていません", - "initializingEditor": "エディターを初期化中...", - "file": "ファイル", - "folder": "フォルダー", - "uploadFile": "ファイルをアップロード", - "downloadFile": "ダウンロード", - "extractArchive": "アーカイブを展開", - "extractingArchive": "{{name}} を展開中...", - "archiveExtractedSuccessfully": "{{name}} の展開が完了しました", - "extractFailed": "展開に失敗しました", - "compressFile": "ファイルを圧縮", - "compressFiles": "ファイルを圧縮", - "compressFilesDesc": "{{count}} 個の項目をアーカイブに圧縮", - "archiveName": "アーカイブ名", - "enterArchiveName": "アーカイブ名を入力...", - "compressionFormat": "圧縮形式", - "selectedFiles": "選択したファイル", - "andMoreFiles": "他 {{count}} 件...", - "compress": "圧縮", - "compressingFiles": "{{count}} 個の項目を {{name}} に圧縮中...", - "filesCompressedSuccessfully": "{{name}} が正常に作成されました", - "compressFailed": "圧縮に失敗しました", - "edit": "編集", - "preview": "プレビュー", - "previous": "前へ", - "next": "次へ", - "pageXOfY": "{{current}} / {{total}} ページ", - "zoomOut": "縮小", - "zoomIn": "拡大", - "newFile": "新規ファイル", - "newFolder": "新規フォルダ", - "rename": "名前を変更", - "uploading": "アップロード中...", - "uploadingFile": "{{name}} をアップロード中...", - "fileName": "ファイル名", - "folderName": "フォルダ名", - "fileUploadedSuccessfully": "ファイル \"{{name}}\" のアップロードが完了しました", - "failedToUploadFile": "ファイルのアップロードに失敗しました", - "uploadingFolderFiles": "{{count}} 個のファイルをアップロードしています...", - "uploadedFolderFiles": "{{count}} 個のファイルが正常にアップロードされました", - "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", - "fileDownloadedSuccessfully": "ファイル \"{{name}}\" のダウンロードが完了しました", - "failedToDownloadFile": "ファイルのダウンロードに失敗しました", - "fileCreatedSuccessfully": "ファイル \"{{name}}\" が正常に作成されました", - "folderCreatedSuccessfully": "フォルダ \"{{name}}\" が正常に作成されました", - "failedToCreateItem": "アイテムの作成に失敗しました", - "operationFailed": "{{name}} の {{operation}} 操作が失敗しました: {{error}}", - "failedToResolveSymlink": "シンボリックリンクの解決に失敗しました", - "itemsDeletedSuccessfully": "{{count}} 個のアイテムが正常に削除されました", - "failedToDeleteItems": "アイテムの削除に失敗しました", - "sudoPasswordRequired": "管理者パスワードが必要です", - "enterSudoPassword": "操作を続行するには sudo パスワードを入力してください", - "sudoPassword": "Sudo パスワード", - "sudoOperationFailed": "Sudo 操作に失敗しました", - "sudoAuthFailed": "Sudo 認証に失敗しました", - "dragFilesToUpload": "ここにファイルをドロップしてアップロード", - "dropToUploadHere": "ここにアップロードしてください", - "dropToDownloadHere": "ここからダウンロードしてください", - "localFiles": "地元", - "showLocalFiles": "ローカルファイルを表示する(分割表示)", - "hideLocalFiles": "ローカルファイルを非表示にする", - "localHome": "ホームフォルダ", - "localPathPlaceholder": "ローカルフォルダパス", - "localShowHidden": "隠しファイルを表示する", - "localHideHidden": "隠しファイルを隠す", - "localRevealInFileManager": "システムファイルマネージャで表示", - "localCannotOpenFolder": "フォルダーを開けません", - "localItemCount": "{{count}} アイテム", - "localSelectedCount": "{{count}} 選択済み", - "back": "戻る", - "forward": "フォワード", - "up": "上", - "kind": "親切", - "localUploadingProgress": "{{current}} / {{total}} をアップロード中", - "localDownloadingProgress": "{{current}} をダウンロード中 {{total}}", - "localTransferInFlight": "{{count}} すぐに", - "localPreparingDownload": "ダウンロード準備中...", - "localTransferCancelled": "転送がキャンセルされました", - "localNothingToTransfer": "譲渡するものはありません", - "localUploadComplete": "{{count}} 個のファイルをアップロードしました", - "localUploadFailed": "アップロードに失敗しました", - "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", - "localDownloadComplete": "{{count}} 個のファイルをダウンロードしました", - "localDownloadFailed": "ダウンロードに失敗しました", - "localDownloadPartial": "{{done}} 個のファイルをダウンロードしましたが、 {{failed}} 失敗しました。", - "localReplaceExistingPrompt": "このフォルダには既に{{count}} 個のアイテムが存在します。置き換えますか?", - "localReplace": "交換する", - "localSkipExisting": "スキップ", - "localSkippedExisting": "既存のアイテムをスキップしました {{count}}", - "localUnsafeNamesSkipped": "このコンピューターでは名前を使用できない {{count}} 個のアイテムをスキップしました", - "localOpen": "開ける", - "localOpenFolder": "フォルダーを開く", - "localUploadToRemote": "サーバーにアップロード", - "localUploadToRemoteMany": "{{count}} 個のアイテムをサーバーにアップロードします", - "localMoveToTrash": "ゴミ箱に移動", - "localMoveToTrashMany": "{{count}} 個のアイテムをゴミ箱に移動します", - "localTrashConfirmSingle": "\"{{name}}\" をゴミ箱に移動しますか?", - "localTrashConfirmFolder": "フォルダ「{{name}}」とその中のすべての内容をゴミ箱に移動しますか?", - "localTrashConfirmMany": "{{count}} 個のアイテムをゴミ箱に移動しますか?", - "localTrashed": "{{count}} 個のアイテムをゴミ箱に移動しました", - "localTrashFailed": "ゴミ箱に移動できませんでした", - "localRenameFailed": "名前変更に失敗しました", - "localCreateFailed": "アイテムを作成できませんでした", - "goToParentFolder": "親フォルダへ移動", - "resizeSidebar": "サイドバーのサイズを変更するにはドラッグしてください(ダブルクリックでリセットできます)。", - "resizeLocalPane": "ローカルペインのサイズを変更するにはドラッグしてください(ダブルクリックでリセットできます)。", - "columns": "コラム", - "columnsHint": "右クリックして列を選択", - "emptyFolder": "このフォルダーは空です", - "searchFiles": "ファイルを検索...", - "upload": "アップロード", - "selectHostToStart": "ファイル管理を開始するホストを選択してください", - "sshRequiredForFileManager": "ファイルマネージャーには SSH が必要です。このホストでは SSH が有効になっていません。", - "failedToConnect": "SSH への接続に失敗しました", - "failedToLoadDirectory": "ディレクトリの読み込みに失敗しました", - "noSSHConnection": "利用可能な SSH 接続がありません", - "copy": "コピー", - "cut": "切り取り", - "paste": "貼り付け", - "copyPath": "パスをコピー", - "copyPaths": "パスをコピー", - "delete": "削除", - "properties": "プロパティ", - "refresh": "更新", - "downloadFiles": "{{count}} 個のファイルをブラウザにダウンロード", - "copyFiles": "{{count}} 個のアイテムをコピー", - "cutFiles": "{{count}} 個のアイテムを切り取り", - "deleteFiles": "{{count}} 個のアイテムを削除", - "filesCopiedToClipboard": "{{count}} 個のアイテムがクリップボードにコピーされました", - "filesCutToClipboard": "{{count}} 個のアイテムがクリップボードに切り取られました", - "pathCopiedToClipboard": "パスがクリップボードにコピーされました", - "pathsCopiedToClipboard": "{{count}} 個のパスがクリップボードにコピーされました", - "failedToCopyPath": "パスをクリップボードにコピーできませんでした", - "copyFolderLink": "フォルダへのリンクをコピー", - "copyCurrentFolderLink": "現在のフォルダへのリンクをコピー", - "folderLinkCopied": "フォルダリンクをクリップボードにコピーしました", - "failedToCopyFolderLink": "フォルダリンクをコピーできませんでした", - "movedItems": "{{count}} 個のアイテムを移動しました", - "failedToDeleteItem": "アイテムを削除できませんでした", - "itemRenamedSuccessfully": "{{type}} の名前を変更しました", - "failedToRenameItem": "アイテムの名前を変更できませんでした", - "download": "ダウンロード", - "openExternalEditor": "外部エディタで開く", - "chooseExternalEditor": "外部エディタを選択", - "externalEditorSelected": "外部エディタを選択しました", - "externalEditorDesktopOnly": "外部エディタはデスクトップアプリでのみ利用可能です", - "externalEditorOpened": "外部エディタで開きました。保存すると変更がサーバーにアップロードされます。", - "failedToOpenExternalEditor": "外部エディタを開けませんでした", - "failedToSelectExternalEditor": "外部エディタを選択できませんでした", - "permissions": "アクセス権限", - "size": "サイズ", - "modified": "更新日時", - "path": "パス", - "confirmDelete": "本当に {{name}} を削除しますか?", - "permissionDenied": "アクセスが拒否されました", - "serverError": "サーバーエラー", - "fileSavedSuccessfully": "ファイルを保存しました", - "failedToSaveFile": "ファイルを保存できませんでした", - "confirmDeleteSingleItem": "\"{{name}}\" を完全に削除してもよろしいですか?", - "confirmDeleteMultipleItems": "{{count}} 個のアイテムを完全に削除してもよろしいですか?", - "confirmDeleteMultipleItemsWithFolders": "{{count}} 個のアイテムを完全に削除してもよろしいですか?フォルダとその内容も含まれます。", - "confirmDeleteFolder": "フォルダ \"{{name}}\" とその内容をすべて完全に削除してもよろしいですか?", - "permanentDeleteWarning": "この操作は元に戻せません。アイテムはサーバーから完全に削除されます。", - "moveToTrashWarning": "該当品はTermixのゴミ箱に移動され、保管期間が終了する前に復元することができます。", - "itemsMovedToTrash": "{{count}} 個のアイテムがゴミ箱に移動されました", - "trash": "ごみ", - "trashDescription": "削除されたアイテムは、このホスト上で {{days}} 日間保持されます。", - "trashLoadFailed": "ゴミ箱の読み込みに失敗しました", - "trashEmpty": "ゴミ箱は空です", - "trashRestore": "復元する", - "trashRestored": "復元済み {{name}}", - "trashRestoreFailed": "アイテムの復元に失敗しました。元のパスが既に存在している可能性があります。", - "trashDeletePermanently": "完全に削除する", - "trashDeleteConfirm": "{{name}}を完全に削除しますか?これは元に戻せません。", - "trashPermanentlyDeleted": "完全に削除されました {{name}}", - "trashDeleteFailed": "アイテムの完全削除に失敗しました", - "trashEmptyAction": "空のゴミ箱", - "trashEmptyConfirm": "ゴミ箱内のすべてのアイテムを完全に削除しますか?この操作は元に戻せません。", - "trashEmptied": "ゴミを空にする", - "trashRetention": "(日数)後に自動クリーンアップ", - "trashRetentionSaved": "ゴミ保持に関する最新情報", - "trashRetentionFailed": "ゴミ箱の保持期間の更新に失敗しました", - "trashUnavailableConfirm": "このアイテムはゴミ箱に移動できません。通常、別のファイルシステム上にあるか、管理者権限が必要なためです。代わりに完全に削除しますか?", - "recent": "最近使用した項目", - "pinned": "ピン留め", - "folderShortcuts": "フォルダショートカット", - "failedToReconnectSSH": "SSH セッションに再接続できませんでした", - "openTerminalHere": "ここでターミナルを開く", - "run": "実行", - "openTerminalInFolder": "このフォルダでターミナルを開く", - "openTerminalInFileLocation": "ファイルの場所でターミナルを開く", - "runningFile": "実行中 - {{file}}", - "onlyRunExecutableFiles": "実行可能ファイルのみ実行できます", - "directories": "ディレクトリ", - "removedFromRecentFiles": "最近使用したファイルから \"{{name}}\" を削除しました", - "removeFailed": "削除に失敗しました", - "unpinnedSuccessfully": "\"{{name}}\" のピン留めを解除しました", - "unpinFailed": "ピン留め解除に失敗しました", - "removedShortcut": "ショートカット \"{{name}}\" を削除しました", - "removeShortcutFailed": "ショートカットの削除に失敗しました", - "clearedAllRecentFiles": "最近使用したファイルをすべて消去しました", - "clearFailed": "消去に失敗しました", - "removeFromRecentFiles": "最近使用したファイルから削除", - "clearAllRecentFiles": "最近使用したファイルをすべて消去", - "unpinFile": "ファイルのピン留めを解除", - "removeShortcut": "ショートカットを削除", - "pinFile": "ファイルをピン留め", - "addToShortcuts": "ショートカットに追加", - "pasteFailed": "貼り付けに失敗しました", - "noUndoableActions": "元に戻せる操作はありません", - "undoCopySuccess": "コピー操作を元に戻しました: {{count}} 個のコピーファイルを削除しました", - "undoCopyFailedDelete": "元に戻せませんでした: コピーファイルを削除できませんでした", - "undoCopyFailedNoInfo": "元に戻せませんでした: コピーファイル情報が見つかりませんでした", - "undoMoveSuccess": "移動操作を元に戻しました: {{count}} 個のファイルを元の場所に戻しました", - "undoMoveFailedMove": "元に戻せませんでした: ファイルを元の場所に戻せませんでした", - "undoMoveFailedNoInfo": "元に戻せませんでした: 移動ファイル情報が見つかりませんでした", - "undoDeleteNotSupported": "削除操作は元に戻せません: ファイルはサーバーから完全に削除されました", - "undoTypeNotSupported": "元に戻せない操作タイプです", - "undoOperationFailed": "元に戻す操作に失敗しました", - "unknownError": "不明なエラー", - "confirm": "確認", - "find": "検索...", - "replace": "置換", - "downloadInstead": "代わりにダウンロード", - "keyboardShortcuts": "キーボードショートカット", - "searchAndReplace": "検索と置換", - "editing": "編集", - "search": "検索", - "findNext": "次を検索", - "findPrevious": "前を検索", - "save": "保存", - "selectAll": "すべて選択", - "undo": "元に戻す", - "redo": "やり直し", - "moveLineUp": "行を上に移動", - "moveLineDown": "行を下に移動", - "toggleComment": "コメント行の切り替え", - "autoComplete": "自動補完", - "imageLoadError": "画像の読み込みに失敗しました", - "startTyping": "入力を開始...", - "unknownSize": "サイズ不明", - "fileIsEmpty": "ファイルは空です", - "largeFileWarning": "大きなファイルの警告", - "largeFileWarningDesc": "このファイルのサイズは{{size}}で、テキストとして開くとパフォーマンスの問題が発生する可能性があります。", - "fileNotFoundAndRemoved": "ファイル \"{{name}}\" が見つからないため、最近使ったファイル/ピン留めから削除されました。", - "failedToLoadFile": "ファイルの読み込みに失敗しました: {{error}}", - "serverErrorOccurred": "サーバーエラーが発生しました。後でもう一度お試しください。", - "autoSaveFailed": "自動保存に失敗しました", - "fileAutoSaved": "ファイルを自動保存しました", - "moveFileFailed": "{{name}} の移動に失敗しました", - "moveOperationFailed": "移動操作に失敗しました", - "canOnlyCompareFiles": "2つのファイルのみ比較できます", - "comparingFiles": "ファイルを比較中: {{file1}} と {{file2}}", - "dragFailed": "ドラッグ操作に失敗しました", - "filePinnedSuccessfully": "ファイル \"{{name}}\" をピン留めしました", - "pinFileFailed": "ファイルのピン留めに失敗しました", - "fileUnpinnedSuccessfully": "ファイル \"{{name}}\" のピン留めを解除しました", - "unpinFileFailed": "ファイルのピン留め解除に失敗しました", - "shortcutAddedSuccessfully": "フォルダショートカット \"{{name}}\" を追加しました", - "addShortcutFailed": "ショートカットの追加に失敗しました", - "operationCompletedSuccessfully": "{{count}} アイテムを正常に{{operation}}しました", - "operationCompleted": "{{count}} アイテムを{{operation}}しました", - "downloadFileSuccess": "ファイル {{name}} のダウンロードが完了しました", - "downloadFileFailed": "ダウンロードに失敗しました", - "downloadingFile": "ダウンロード中 {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "{{name}} に移動", - "diffCompareWith": "{{name}} と差分比較", - "dragOutsideToDownload": "ウィンドウ外にドラッグしてダウンロード ({{count}} ファイル)", - "newFolderDefault": "新規フォルダ", - "newFileDefault": "新規ファイル.txt", - "successfullyMovedItems": "{{count}} アイテムを {{target}} に移動しました", - "move": "移動", - "searchInFile": "ファイル内検索 (Ctrl+F)", - "showKeyboardShortcuts": "キーボードショートカットを表示", - "decreaseFontSize": "フォントサイズを縮小", - "increaseFontSize": "フォントサイズを拡大", - "startWritingMarkdown": "マークダウンコンテンツの作成を開始...", - "loadingFileComparison": "ファイル比較を読み込み中...", - "reload": "再読み込み", - "compare": "比較", - "sideBySide": "横並び表示", - "inline": "インライン表示", - "fileComparison": "ファイル比較: {{file1}} 対 {{file2}}", - "fileTooLarge": "ファイルが大きすぎます: {{error}}", - "sshConnectionFailed": "SSH接続に失敗しました。 {{name}} ({{ip}}:{{port}}) への接続を確認してください", - "loadFileFailed": "ファイルの読み込みに失敗しました: {{error}}", - "connecting": "接続中...", - "connectedSuccessfully": "接続しました", - "totpVerificationFailed": "TOTP検証に失敗しました", - "warpgateVerificationFailed": "Warpgate認証に失敗しました", - "authenticationFailed": "認証に失敗しました", - "incorrectPassphrase": "パスフレーズが間違っています。もう一度お試しください。", - "verificationCodePrompt": "認証コード:", - "changePermissions": "パーミッション変更", - "currentPermissions": "現在のパーミッション", - "owner": "所有者", - "group": "グループ", - "others": "その他", - "read": "読み取り", - "write": "書き込み", - "execute": "実行", - "permissionsChangedSuccessfully": "パーミッションが正常に変更されました", - "failedToChangePermissions": "パーミッションの変更に失敗しました", - "name": "名前", - "sortByName": "名前", - "sortByDate": "更新日時", - "sortBySize": "サイズ", - "ascending": "昇順", - "descending": "降順", - "root": "ルート", - "new": "新規", - "sortBy": "並べ替え", - "items": "アイテム", - "selected": "選択中", - "editor": "エディター", - "octal": "8進数", - "storage": "ストレージ", - "disk": "ディスク", - "used": "使用量", - "of": "の", - "toggleSidebar": "サイドバー切り替え", - "compactLayout": "コンパクトなレイアウトを使用する", - "comfortableLayout": "快適なレイアウトを使用する", - "cannotLoadPdf": "PDFを読み込めません", - "pdfLoadError": "このPDFファイルの読み込み中にエラーが発生しました。", - "loadingPdf": "PDFを読み込み中...", - "loadingPage": "ページを読み込み中..." - }, "transfer": { "copyToHost": "ホストにコピー...", "moveToHost": "ホストに移動...", @@ -2292,50 +1951,6 @@ "retryTransferError": "再試行に失敗しました", "transferFailedRetryHint": "部分的なデータが宛先に保持されました。接続が復旧したら再試行が再開されます。" }, - "sftpTransfer": { - "title": "SFTP", - "description": "2つのSSHホスト間でファイルを転送します。", - "sourceServer": "ソースサーバー", - "destinationServer": "宛先サーバー", - "selectHost": "ホストを選択", - "noHostSelected": "ホストが選択されていません", - "move": "動く", - "copy": "コピー", - "upload": "アップロード", - "itemsSelected": "{{count}} アイテムが選択されました", - "itemsSelected_other": "{{count}} 個のアイテムが選択されました", - "ready": "準備ができて", - "loadingHosts": "ホストを読み込んでいます...", - "name": "名前", - "modified": "修正済み", - "size": "サイズ", - "noFiles": "ファイルなし", - "open": "開ける", - "up": "上", - "refresh": "更新", - "copyToTarget": "ターゲットディレクトリにコピー", - "rename": "名前を変更する", - "delete": "消去", - "editPermissions": "権限の編集", - "createNewFolder": "新しいフォルダーを作成する", - "deleteSelectedItems": "選択したアイテムを削除", - "deleteConfirm": "選択した項目 {{count}} を削除しますか?", - "deleteConfirm_other": "選択した項目 {{count}} を削除しますか?", - "cancel": "キャンセル", - "save": "保存", - "folderCreated": "フォルダが作成されました", - "itemRenamed": "アイテム名が変更されました", - "actionFailed": "アクションが失敗しました", - "itemsDeleted": "{{count}} アイテムが削除されました", - "itemsDeleted_other": "{{count}} 個のアイテムが削除されました", - "deleteFailed": "削除に失敗しました", - "permissionsUpdated": "権限が更新されました", - "noRemoteSessionConnected": "リモートセッションが接続されていません", - "failedToListFiles": "ファイルの一覧表示に失敗しました", - "failedToRefresh": "更新に失敗しました", - "destinationInsideSource": "宛先は、選択したソースパス内にあってはなりません。", - "sameHostNoOp": "ソースと宛先は同じフォルダです。" - }, "tunnels": { "noSshTunnels": "SSHトンネルがありません", "createFirstTunnelMessage": "まだSSHトンネルが作成されていません。開始するにはホストマネージャーでトンネル接続を設定してください。", diff --git a/src/ui/locales/translated/ko_KR.json b/src/ui/locales/translated/ko_KR.json index eb5cec8a9..b685f937b 100644 --- a/src/ui/locales/translated/ko_KR.json +++ b/src/ui/locales/translated/ko_KR.json @@ -715,7 +715,6 @@ "hostManager": "호스트 관리자", "credentials": "자격 증명", "portForwarding": "전송", - "sftp": "SFTP", "connections": "연결", "serial": "시리얼", "keepSideMenuOpen": "메뉴를 열어 두세요", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "터널", - "tabFiles": "파일", "tabStats": "호스트 메트릭", "tabTelnet": "텔넷", "tabSharing": "공유", "tabAuthentication": "인증", "terminal": "터미널", "tunnel": "터널", - "fileManager": "파일 관리자", "serverStats": "호스트 메트릭", "status": { "available": "사용 가능", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} 건너뛰었습니다(IP 주소를 찾을 수 없음)", "proxmoxStatsUrlCopied": "Proxmox 통계 URL이 클립보드에 복사되었습니다.", "copyProxmoxStatsUrlAction": "Proxmox 통계 URL 복사", - "enableFileManagerMonitor": "파일 관리자 활성화", - "enableFileManagerMonitorDesc": "SFTP를 통해 이 호스트의 파일 탐색 및 관리", - "scpLegacyLabel": "SCP 레거시 모드", - "scpLegacyDesc": "SFTP 하위 시스템을 지원하지 않는 서버(예: 임베디드 또는 최소 SSH 서버)에 레거시 파일 전송을 사용합니다.", - "defaultPathLabel": "기본 경로", - "fileManagerPathHint": "이 호스트의 파일 관리자 실행 시 열리는 디렉터리입니다.", "statusChecksLabel": "상태 확인", "enableStatusChecks": "상태 확인 활성화", "enableStatusChecksDesc": "주기적으로 이 호스트에 ping을 보내 가용성을 확인합니다.", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} 삭제됨", "copiedToClipboard": "클립보드에 복사됨", "terminalUrlCopied": "터미널 URL 복사됨", - "fileManagerUrlCopied": "파일 관리자 URL 복사됨", "tunnelUrlCopied": "터널 URL 복사됨", "dockerUrlCopied": "Docker URL 복사됨", "hostMetricsUrlCopied": "호스트 메트릭 URL 복사됨", @@ -1296,7 +1286,6 @@ "copyAddress": "주소 복사", "copyLink": "링크 복사", "copyTerminalUrlAction": "터미널 URL 복사", - "copyFileManagerUrlAction": "파일 관리자 URL 복사", "copyTunnelUrlAction": "터널 URL 복사", "copyDockerUrlAction": "Docker URL 복사", "copyHostMetricsUrlAction": "호스트 메트릭 URL 복사", @@ -1857,336 +1846,6 @@ "removeFromSplit": "분할에서 제거" } }, - "fileManager": { - "noHostSelected": "선택된 호스트 없음", - "initializingEditor": "편집기 초기화 중...", - "file": "파일", - "folder": "폴더", - "uploadFile": "파일 업로드", - "downloadFile": "다운로드", - "extractArchive": "아카이브 추출", - "extractingArchive": "{{name}} 압축 해제 중...", - "archiveExtractedSuccessfully": "{{name}} 압축 해제 완료", - "extractFailed": "압축 해제 실패", - "compressFile": "파일 압축", - "compressFiles": "파일 압축", - "compressFilesDesc": "{{count}}개 항목을 아카이브로 압축", - "archiveName": "아카이브 이름", - "enterArchiveName": "아카이브 이름 입력...", - "compressionFormat": "압축 형식", - "selectedFiles": "선택한 파일", - "andMoreFiles": "그리고 {{count}}개 더...", - "compress": "압축", - "compressingFiles": "{{count}}개 항목을 {{name}}(으)로 압축 중...", - "filesCompressedSuccessfully": "{{name}} 생성 완료", - "compressFailed": "압축 실패", - "edit": "편집", - "preview": "미리보기", - "previous": "이전", - "next": "다음", - "pageXOfY": "{{total}}페이지 중 {{current}}페이지", - "zoomOut": "축소", - "zoomIn": "확대", - "newFile": "새 파일", - "newFolder": "새 폴더", - "rename": "이름 변경", - "uploading": "업로드 중...", - "uploadingFile": "{{name}} 업로드 중...", - "fileName": "파일 이름", - "folderName": "폴더 이름", - "fileUploadedSuccessfully": "파일 \"{{name}}\" 업로드 완료", - "failedToUploadFile": "파일 업로드 실패", - "uploadingFolderFiles": "{{count}} 파일을 업로드하는 중...", - "uploadedFolderFiles": "{{count}} 파일을 성공적으로 업로드했습니다.", - "uploadedFolderPartial": "업로드된 파일 {{uploaded}} 개 중 {{failed}} 개에 실패했습니다.", - "fileDownloadedSuccessfully": "파일 \"{{name}}\" 다운로드 완료", - "failedToDownloadFile": "파일 다운로드 실패", - "fileCreatedSuccessfully": "파일 \"{{name}}\" 생성 완료", - "folderCreatedSuccessfully": "폴더 \"{{name}}\" 생성 완료", - "failedToCreateItem": "항목 생성 실패", - "operationFailed": "{{name}}의 {{operation}} 작업 실패: {{error}}", - "failedToResolveSymlink": "심볼릭 링크 해석 실패", - "itemsDeletedSuccessfully": "{{count}}개 항목이 성공적으로 삭제되었습니다", - "failedToDeleteItems": "항목 삭제 실패", - "sudoPasswordRequired": "관리자 비밀번호가 필요합니다", - "enterSudoPassword": "계속하려면 sudo 암호를 입력하세요", - "sudoPassword": "Sudo 암호", - "sudoOperationFailed": "Sudo 작업 실패", - "sudoAuthFailed": "Sudo 인증 실패", - "dragFilesToUpload": "업로드할 파일을 여기에 놓으세요", - "dropToUploadHere": "여기에 드롭하여 업로드하세요", - "dropToDownloadHere": "여기를 클릭하여 다운로드하세요", - "localFiles": "현지의", - "showLocalFiles": "로컬 파일 표시(분할 화면)", - "hideLocalFiles": "로컬 파일 숨기기", - "localHome": "홈 폴더", - "localPathPlaceholder": "로컬 폴더 경로", - "localShowHidden": "숨겨진 파일 표시", - "localHideHidden": "숨겨진 파일 숨기기", - "localRevealInFileManager": "시스템 파일 관리자에서 보기", - "localCannotOpenFolder": "폴더를 열 수 없습니다", - "localItemCount": "{{count}} 항목", - "localSelectedCount": "{{count}} 선택됨", - "back": "뒤쪽에", - "forward": "앞으로", - "up": "위로", - "kind": "친절한", - "localUploadingProgress": "{{current}} 의 {{total}}를 업로드 중입니다.", - "localDownloadingProgress": "{{current}} 의 {{total}} 다운로드 중", - "localTransferInFlight": "{{count}} 즉시", - "localPreparingDownload": "다운로드 준비 중...", - "localTransferCancelled": "이적 취소됨", - "localNothingToTransfer": "이체할 내용이 없습니다", - "localUploadComplete": "{{count}} 개의 파일을 업로드했습니다.", - "localUploadFailed": "업로드 실패", - "localUploadPartial": "업로드된 파일 {{done}} 개, {{failed}} 개에 실패했습니다.", - "localDownloadComplete": "{{count}} 파일을 다운로드했습니다.", - "localDownloadFailed": "다운로드 실패", - "localDownloadPartial": "{{done}} 파일 다운로드 실패, {{failed}}", - "localReplaceExistingPrompt": "이 폴더에 {{count}} 항목이 이미 있습니다. 바꾸시겠습니까?", - "localReplace": "바꾸다", - "localSkipExisting": "건너뛰다", - "localSkippedExisting": "기존 항목 {{count}} 개를 건너뛰었습니다.", - "localUnsafeNamesSkipped": "이 컴퓨터에서 사용할 수 없는 이름을 가진 {{count}} 항목을 건너뛰었습니다.", - "localOpen": "열려 있는", - "localOpenFolder": "폴더 열기", - "localUploadToRemote": "서버에 업로드", - "localUploadToRemoteMany": "{{count}} 아이템을 서버에 업로드하세요", - "localMoveToTrash": "쓰레기통으로 이동", - "localMoveToTrashMany": "{{count}} 항목을 휴지통으로 이동", - "localTrashConfirmSingle": "\"{{name}}\"를 휴지통으로 옮기시겠습니까?", - "localTrashConfirmFolder": "폴더 \"{{name}}\"와 그 안의 모든 내용을 휴지통으로 옮기시겠습니까?", - "localTrashConfirmMany": "{{count}} 항목을 휴지통으로 옮기시겠습니까?", - "localTrashed": "{{count}} 항목을 휴지통으로 이동했습니다.", - "localTrashFailed": "쓰레기통으로 옮길 수 없었습니다", - "localRenameFailed": "이름 변경 실패", - "localCreateFailed": "항목을 생성할 수 없습니다.", - "goToParentFolder": "상위 폴더로 이동", - "resizeSidebar": "드래그하여 사이드바 크기를 조절하세요 (더블 클릭하면 원래 크기로 돌아갑니다).", - "resizeLocalPane": "드래그하여 로컬 창의 크기를 조정하세요(두 번 클릭하면 원래 크기로 돌아갑니다).", - "columns": "컬럼", - "columnsHint": "마우스 오른쪽 버튼을 클릭하여 열을 선택하세요.", - "emptyFolder": "이 폴더는 비어 있습니다", - "searchFiles": "파일 검색...", - "upload": "업로드", - "selectHostToStart": "파일 관리를 시작할 호스트를 선택하세요", - "sshRequiredForFileManager": "파일 관리자에는 SSH가 필요합니다. 이 호스트에는 SSH가 활성화되어 있지 않습니다.", - "failedToConnect": "SSH 연결 실패", - "failedToLoadDirectory": "디렉터리 로드 실패", - "noSSHConnection": "SSH 연결을 사용할 수 없음", - "copy": "복사", - "cut": "잘라내기", - "paste": "붙여넣기", - "copyPath": "경로 복사", - "copyPaths": "경로 복사", - "delete": "삭제", - "properties": "속성", - "refresh": "새로 고침", - "downloadFiles": "브라우저로 {{count}}개 파일 다운로드", - "copyFiles": "{{count}}개 항목 복사", - "cutFiles": "{{count}}개 항목 잘라내기", - "deleteFiles": "{{count}}개 항목 삭제", - "filesCopiedToClipboard": "{{count}}개 항목이 클립보드에 복사되었습니다", - "filesCutToClipboard": "{{count}}개 항목을 클립보드로 잘라냈습니다", - "pathCopiedToClipboard": "경로가 클립보드에 복사되었습니다", - "pathsCopiedToClipboard": "{{count}}개 경로가 클립보드에 복사되었습니다", - "failedToCopyPath": "클립보드에 경로를 복사하지 못했습니다", - "copyFolderLink": "폴더 링크 복사", - "copyCurrentFolderLink": "현재 폴더 링크 복사", - "folderLinkCopied": "폴더 링크가 클립보드에 복사되었습니다", - "failedToCopyFolderLink": "폴더 링크를 복사하지 못했습니다", - "movedItems": "{{count}}개 항목을 이동했습니다", - "failedToDeleteItem": "항목을 삭제하지 못했습니다", - "itemRenamedSuccessfully": "{{type}} 이름이 변경되었습니다", - "failedToRenameItem": "항목 이름을 변경하지 못했습니다", - "download": "다운로드", - "openExternalEditor": "외부 편집기로 열기", - "chooseExternalEditor": "외부 편집기 선택", - "externalEditorSelected": "외부 편집기가 선택되었습니다", - "externalEditorDesktopOnly": "외부 편집기는 데스크톱 앱에서만 사용할 수 있습니다", - "externalEditorOpened": "외부 편집기에서 열렸습니다. 저장하면 변경 사항이 서버에 업로드됩니다.", - "failedToOpenExternalEditor": "외부 편집기를 열지 못했습니다", - "failedToSelectExternalEditor": "외부 편집기를 선택하지 못했습니다", - "permissions": "권한", - "size": "크기", - "modified": "수정됨", - "path": "경로", - "confirmDelete": "{{name}}을(를) 삭제하시겠습니까?", - "permissionDenied": "권한이 거부되었습니다", - "serverError": "서버 오류", - "fileSavedSuccessfully": "파일이 저장되었습니다", - "failedToSaveFile": "파일을 저장하지 못했습니다", - "confirmDeleteSingleItem": "\"{{name}}\"을(를) 영구적으로 삭제하시겠습니까?", - "confirmDeleteMultipleItems": "{{count}}개 항목을 영구적으로 삭제하시겠습니까?", - "confirmDeleteMultipleItemsWithFolders": "{{count}}개 항목을 영구적으로 삭제하시겠습니까? 폴더 및 해당 내용이 포함됩니다.", - "confirmDeleteFolder": "\"{{name}}\" 폴더와 모든 내용을 영구적으로 삭제하시겠습니까?", - "permanentDeleteWarning": "이 작업은 되돌릴 수 없습니다. 항목이 서버에서 영구적으로 삭제됩니다.", - "moveToTrashWarning": "해당 항목은 Termix 휴지통으로 이동되며, 보존 기간이 만료되기 전에 복원할 수 있습니다.", - "itemsMovedToTrash": "{{count}} 항목이 휴지통으로 이동되었습니다.", - "trash": "쓰레기", - "trashDescription": "삭제된 항목은 이 호스트에 {{days}} 일 동안 보관됩니다.", - "trashLoadFailed": "휴지통을 불러오는 데 실패했습니다.", - "trashEmpty": "쓰레기통이 비어있습니다", - "trashRestore": "복원하다", - "trashRestored": "복원됨 {{name}}", - "trashRestoreFailed": "항목 복원에 실패했습니다. 원래 경로가 이미 존재할 수 있습니다.", - "trashDeletePermanently": "영구 삭제", - "trashDeleteConfirm": "{{name}}를 영구적으로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", - "trashPermanentlyDeleted": "영구 삭제됨 {{name}}", - "trashDeleteFailed": "항목을 영구적으로 삭제하는 데 실패했습니다.", - "trashEmptyAction": "쓰레기통을 비우세요", - "trashEmptyConfirm": "휴지통에 있는 모든 항목을 영구적으로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", - "trashEmptied": "쓰레기통이 비워졌습니다", - "trashRetention": "자동 정리 시간 (일)", - "trashRetentionSaved": "휴지통 보존 시간 업데이트됨", - "trashRetentionFailed": "휴지통 보존 기간 업데이트에 실패했습니다.", - "trashUnavailableConfirm": "이 항목은 다른 파일 시스템에 있거나 관리자 권한이 필요하기 때문에 휴지통으로 이동할 수 없습니다. 영구적으로 삭제하시겠습니까?", - "recent": "최근 항목", - "pinned": "고정됨", - "folderShortcuts": "폴더 바로 가기", - "failedToReconnectSSH": "SSH 세션 재연결에 실패했습니다", - "openTerminalHere": "여기서 터미널 열기", - "run": "실행", - "openTerminalInFolder": "이 폴더에서 터미널 열기", - "openTerminalInFileLocation": "파일 위치에서 터미널 열기", - "runningFile": "실행 중 - {{file}}", - "onlyRunExecutableFiles": "실행 파일만 실행할 수 있습니다", - "directories": "디렉터리", - "removedFromRecentFiles": "최근 파일에서 \"{{name}}\" 제거됨", - "removeFailed": "제거 실패", - "unpinnedSuccessfully": "\"{{name}}\" 고정 해제 완료", - "unpinFailed": "고정 해제 실패", - "removedShortcut": "바로 가기 \"{{name}}\" 제거됨", - "removeShortcutFailed": "바로 가기 제거 실패", - "clearedAllRecentFiles": "모든 최근 파일 지움", - "clearFailed": "지우기 실패", - "removeFromRecentFiles": "최근 파일에서 제거", - "clearAllRecentFiles": "모든 최근 파일 지우기", - "unpinFile": "파일 고정 해제", - "removeShortcut": "바로 가기 제거", - "pinFile": "파일 고정", - "addToShortcuts": "바로 가기에 추가", - "pasteFailed": "붙여넣기 실패", - "noUndoableActions": "실행 취소할 동작 없음", - "undoCopySuccess": "복사 작업 실행 취소: 복사된 파일 {{count}}개 삭제됨", - "undoCopyFailedDelete": "실행 취소 실패: 복사된 파일을 삭제할 수 없음", - "undoCopyFailedNoInfo": "실행 취소 실패: 복사된 파일 정보를 찾을 수 없음", - "undoMoveSuccess": "이동 작업 실행 취소: {{count}}개 파일을 원래 위치로 다시 이동했습니다", - "undoMoveFailedMove": "실행 취소 실패: 파일을 되돌릴 수 없음", - "undoMoveFailedNoInfo": "실행 취소 실패: 이동된 파일 정보를 찾을 수 없음", - "undoDeleteNotSupported": "삭제 작업은 실행 취소할 수 없습니다: 서버에서 파일이 영구 삭제됨", - "undoTypeNotSupported": "지원되지 않는 실행 취소 작업 유형", - "undoOperationFailed": "실행 취소 작업 실패", - "unknownError": "알 수 없는 오류", - "confirm": "확인", - "find": "찾기...", - "replace": "바꾸기", - "downloadInstead": "대신 다운로드", - "keyboardShortcuts": "키보드 단축키", - "searchAndReplace": "검색 및 바꾸기", - "editing": "편집", - "search": "검색", - "findNext": "다음 찾기", - "findPrevious": "이전 찾기", - "save": "저장", - "selectAll": "모두 선택", - "undo": "실행 취소", - "redo": "다시 실행", - "moveLineUp": "줄 위로 이동", - "moveLineDown": "줄 아래로 이동", - "toggleComment": "주석 토글", - "autoComplete": "자동 완성", - "imageLoadError": "이미지를 불러오지 못했습니다", - "startTyping": "입력하세요...", - "unknownSize": "알 수 없는 크기", - "fileIsEmpty": "파일이 비어 있습니다", - "largeFileWarning": "대용량 파일 경고", - "largeFileWarningDesc": "이 파일의 크기는 {{size}}이며, 텍스트로 열 경우 성능 문제가 발생할 수 있습니다.", - "fileNotFoundAndRemoved": "\"{{name}}\" 파일을 찾을 수 없어 최근/고정 파일에서 제거되었습니다.", - "failedToLoadFile": "파일을 불러오지 못했습니다: {{error}}", - "serverErrorOccurred": "서버 오류가 발생했습니다. 나중에 다시 시도하세요.", - "autoSaveFailed": "자동 저장 실패", - "fileAutoSaved": "파일 자동 저장됨", - "moveFileFailed": "{{name}} 이동 실패", - "moveOperationFailed": "이동 작업 실패", - "canOnlyCompareFiles": "파일 두 개만 비교할 수 있습니다.", - "comparingFiles": "파일 비교 중: {{file1}} 및 {{file2}}", - "dragFailed": "드래그 작업 실패", - "filePinnedSuccessfully": "\"{{name}}\" 파일이 고정되었습니다.", - "pinFileFailed": "파일 고정 실패", - "fileUnpinnedSuccessfully": "\"{{name}}\" 파일이 고정 해제되었습니다.", - "unpinFileFailed": "파일 고정 해제 실패", - "shortcutAddedSuccessfully": "폴더 바로가기 \"{{name}}\"이 추가되었습니다.", - "addShortcutFailed": "바로가기 추가 실패", - "operationCompletedSuccessfully": "{{count}}개 항목 {{operation}} 성공", - "operationCompleted": "{{count}}개 항목 {{operation}}", - "downloadFileSuccess": "파일 {{name}} 다운로드 성공", - "downloadFileFailed": "다운로드 실패", - "downloadingFile": "{{name}}… 다운로드 중", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "{{name}}로 이동", - "diffCompareWith": "{{name}}와/과 Diff 비교", - "dragOutsideToDownload": "창 밖으로 드래그하여 다운로드 ({{count}}개 파일)", - "newFolderDefault": "새 폴더", - "newFileDefault": "새 파일.txt", - "successfullyMovedItems": "{{count}}개 항목을 {{target}}에 성공적으로 이동했습니다.", - "move": "이동", - "searchInFile": "파일에서 검색 (Ctrl+F)", - "showKeyboardShortcuts": "키보드 단축키 보기", - "decreaseFontSize": "글꼴 크기 줄이기", - "increaseFontSize": "글꼴 크기 늘리기", - "startWritingMarkdown": "마크다운 내용 작성을 시작하세요...", - "loadingFileComparison": "파일 비교 로딩 중...", - "reload": "새로 고침", - "compare": "비교", - "sideBySide": "나란히 보기", - "inline": "인라인", - "fileComparison": "파일 비교: {{file1}} vs {{file2}}", - "fileTooLarge": "파일이 너무 큽니다: {{error}}", - "sshConnectionFailed": "SSH 연결 실패. {{name}}({{ip}}:{{port}})에 대한 연결을 확인하세요.", - "loadFileFailed": "파일 불러오기 실패: {{error}}", - "connecting": "연결 중...", - "connectedSuccessfully": "연결 성공", - "totpVerificationFailed": "TOTP 인증 실패", - "warpgateVerificationFailed": "Warpgate 인증 실패", - "authenticationFailed": "인증 실패", - "incorrectPassphrase": "잘못된 암호입니다. 다시 시도하세요.", - "verificationCodePrompt": "인증 코드:", - "changePermissions": "권한 변경", - "currentPermissions": "현재 권한", - "owner": "소유자", - "group": "그룹", - "others": "기타", - "read": "읽기", - "write": "쓰기", - "execute": "실행", - "permissionsChangedSuccessfully": "권한이 성공적으로 변경되었습니다", - "failedToChangePermissions": "권한 변경에 실패했습니다", - "name": "이름", - "sortByName": "이름", - "sortByDate": "수정일", - "sortBySize": "크기", - "ascending": "오름차순", - "descending": "내림차순", - "root": "루트", - "new": "새로 만들기", - "sortBy": "정렬 기준", - "items": "항목", - "selected": "선택됨", - "editor": "편집기", - "octal": "8진수", - "storage": "저장소", - "disk": "디스크", - "used": "사용", - "of": "중", - "toggleSidebar": "사이드바 표시/숨기기", - "compactLayout": "간결한 레이아웃을 사용하세요", - "comfortableLayout": "편안한 레이아웃을 사용하세요", - "cannotLoadPdf": "PDF를 로드할 수 없습니다", - "pdfLoadError": "이 PDF 파일을 로드하는 중 오류가 발생했습니다.", - "loadingPdf": "PDF 로드 중...", - "loadingPage": "페이지 로드 중..." - }, "transfer": { "copyToHost": "호스트로 복사…", "moveToHost": "호스트로 이동…", @@ -2292,50 +1951,6 @@ "retryTransferError": "재시도 실패", "transferFailedRetryHint": "대상에 부분 데이터가 유지되었습니다. 연결이 복구되면 다시 시도하면 이어서 전송됩니다." }, - "sftpTransfer": { - "title": "SFTP", - "description": "두 SSH 호스트 간에 파일을 전송합니다.", - "sourceServer": "소스 서버", - "destinationServer": "대상 서버", - "selectHost": "호스트를 선택하세요", - "noHostSelected": "호스트가 선택되지 않았습니다.", - "move": "이동하다", - "copy": "복사", - "upload": "업로드", - "itemsSelected": "{{count}} 항목이 선택되었습니다", - "itemsSelected_other": "{{count}} 항목이 선택되었습니다", - "ready": "준비가 된", - "loadingHosts": "호스트를 불러오는 중...", - "name": "이름", - "modified": "수정됨", - "size": "크기", - "noFiles": "파일 없음", - "open": "열려 있는", - "up": "위로", - "refresh": "새로 고치다", - "copyToTarget": "대상 디렉터리로 복사", - "rename": "이름 변경", - "delete": "삭제", - "editPermissions": "편집 권한", - "createNewFolder": "새 폴더 만들기", - "deleteSelectedItems": "선택한 항목 삭제", - "deleteConfirm": "선택한 항목 {{count}} 을 삭제하시겠습니까?", - "deleteConfirm_other": "선택한 항목 {{count}} 을 삭제하시겠습니까?", - "cancel": "취소", - "save": "구하다", - "folderCreated": "폴더가 생성되었습니다", - "itemRenamed": "항목 이름이 변경됨", - "actionFailed": "작업이 실패했습니다", - "itemsDeleted": "{{count}} 항목이 삭제되었습니다", - "itemsDeleted_other": "{{count}} 항목이 삭제되었습니다", - "deleteFailed": "삭제 실패", - "permissionsUpdated": "권한이 업데이트되었습니다", - "noRemoteSessionConnected": "원격 세션이 연결되지 않았습니다.", - "failedToListFiles": "파일 목록을 불러오는 데 실패했습니다.", - "failedToRefresh": "새로 고침에 실패했습니다", - "destinationInsideSource": "대상 경로는 선택한 소스 경로 내에 있을 수 없습니다.", - "sameHostNoOp": "원본 폴더와 대상 폴더가 동일합니다." - }, "tunnels": { "noSshTunnels": "SSH 터널 없음", "createFirstTunnelMessage": "아직 SSH 터널을 생성하지 않았습니다. 시작하려면 호스트 관리자에서 터널 연결을 구성하세요.", diff --git a/src/ui/locales/translated/nl_NL.json b/src/ui/locales/translated/nl_NL.json index 0ceb62851..f0f149183 100644 --- a/src/ui/locales/translated/nl_NL.json +++ b/src/ui/locales/translated/nl_NL.json @@ -715,7 +715,6 @@ "hostManager": "Hostmanager", "credentials": "Referenties", "portForwarding": "Doorsturen", - "sftp": "SFTP", "connections": "Verbindingen", "serial": "Serieel", "keepSideMenuOpen": "Menu open houden", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tunnels", - "tabFiles": "Bestanden", "tabStats": "Hoststatistieken", "tabTelnet": "Telnet", "tabSharing": "Delen", "tabAuthentication": "Authenticatie", "terminal": "Terminal", "tunnel": "Tunnel", - "fileManager": "Bestandsbeheerder", "serverStats": "Hoststatistieken", "status": { "available": "Beschikbaar", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} overgeslagen (geen IP-adres gevonden)", "proxmoxStatsUrlCopied": "De URL van Proxmox Stats is naar het klembord gekopieerd.", "copyProxmoxStatsUrlAction": "Kopieer de URL van Proxmox-statistieken", - "enableFileManagerMonitor": "Bestandsbeheer inschakelen", - "enableFileManagerMonitorDesc": "Blader door en beheer bestanden op deze host via SFTP.", - "scpLegacyLabel": "SCP Legacy-modus", - "scpLegacyDesc": "Gebruik de traditionele bestandsoverdrachtsmethode voor servers die het SFTP-subsysteem niet ondersteunen (bijvoorbeeld ingebouwde of minimale SSH-servers).", - "defaultPathLabel": "Standaardpad", - "fileManagerPathHint": "De map die geopend moet worden wanneer de bestandsbeheerder voor deze host wordt gestart.", "statusChecksLabel": "Statuscontroles", "enableStatusChecks": "Statuscontroles inschakelen", "enableStatusChecksDesc": "Controleer periodiek de beschikbaarheid van deze host.", @@ -1278,7 +1269,6 @@ "deletedHost": "Verwijderd {{name}}", "copiedToClipboard": "Gekopieerd naar het klembord", "terminalUrlCopied": "Terminal-URL gekopieerd", - "fileManagerUrlCopied": "URL van bestandsbeheerder gekopieerd", "tunnelUrlCopied": "Tunnel-URL gekopieerd", "dockerUrlCopied": "Docker-URL gekopieerd", "hostMetricsUrlCopied": "URL van hoststatistieken gekopieerd", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopieer adres", "copyLink": "Link kopiëren", "copyTerminalUrlAction": "Terminal-URL kopiëren", - "copyFileManagerUrlAction": "Kopieer de URL van de bestandsbeheerder", "copyTunnelUrlAction": "Kopieer de tunnel-URL", "copyDockerUrlAction": "Kopieer de Docker-URL", "copyHostMetricsUrlAction": "Kopieer de URL voor hoststatistieken", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Verwijderen uit Split" } }, - "fileManager": { - "noHostSelected": "Geen host geselecteerd", - "initializingEditor": "Editor wordt geïnitialiseerd...", - "file": "Bestand", - "folder": "Map", - "uploadFile": "Bestand uploaden", - "downloadFile": "Download", - "extractArchive": "Archief uitpakken", - "extractingArchive": "Extractie van {{name}}...", - "archiveExtractedSuccessfully": "{{name}} succesvol geëxtraheerd", - "extractFailed": "Extractie mislukt", - "compressFile": "Bestand comprimeren", - "compressFiles": "Bestanden comprimeren", - "compressFilesDesc": "Comprimeer {{count}} items tot een archief", - "archiveName": "Archiefnaam", - "enterArchiveName": "Voer de archiefnaam in...", - "compressionFormat": "Compressieformaat", - "selectedFiles": "Geselecteerde bestanden", - "andMoreFiles": "en {{count}} meer...", - "compress": "Comprimeren", - "compressingFiles": "Het comprimeren van {{count}} items naar {{name}}...", - "filesCompressedSuccessfully": "{{name}} succesvol aangemaakt", - "compressFailed": "Compressie mislukt", - "edit": "Bewerking", - "preview": "Voorbeeld", - "previous": "Vorig", - "next": "Volgende", - "pageXOfY": "Pagina {{current}} van {{total}}", - "zoomOut": "Uitzoomen", - "zoomIn": "Inzoomen", - "newFile": "Nieuw bestand", - "newFolder": "Nieuwe map", - "rename": "Naam wijzigen", - "uploading": "Bezig met uploaden...", - "uploadingFile": "Bezig met uploaden {{name}}...", - "fileName": "Bestandsnaam", - "folderName": "Mapnaam", - "fileUploadedSuccessfully": "Bestand \"{{name}}\" succesvol geüpload", - "failedToUploadFile": "Het uploaden van het bestand is mislukt.", - "uploadingFolderFiles": "Bestand(en) {{count}} uploaden...", - "uploadedFolderFiles": "Bestand(en) {{count}} succesvol geüpload", - "uploadedFolderPartial": "Geüploade {{uploaded}} bestand(en), {{failed}} mislukt", - "fileDownloadedSuccessfully": "Bestand \"{{name}}\" succesvol gedownload", - "failedToDownloadFile": "Het downloaden van het bestand is mislukt.", - "fileCreatedSuccessfully": "Bestand \"{{name}}\" is succesvol aangemaakt.", - "folderCreatedSuccessfully": "Map \"{{name}}\" is succesvol aangemaakt", - "failedToCreateItem": "Het aanmaken van het item is mislukt.", - "operationFailed": "{{operation}} bewerking mislukt voor {{name}}: {{error}}", - "failedToResolveSymlink": "Het is niet gelukt om de symbolische link op te lossen.", - "itemsDeletedSuccessfully": "{{count}} items succesvol verwijderd", - "failedToDeleteItems": "Het verwijderen van items is mislukt.", - "sudoPasswordRequired": "Beheerderswachtwoord vereist", - "enterSudoPassword": "Voer het sudo-wachtwoord in om deze bewerking voort te zetten.", - "sudoPassword": "Sudo-wachtwoord", - "sudoOperationFailed": "De sudo-bewerking is mislukt.", - "sudoAuthFailed": "Sudo-authenticatie mislukt", - "dragFilesToUpload": "Sleep bestanden hierheen om ze te uploaden", - "dropToUploadHere": "Sleep hierheen om te uploaden", - "dropToDownloadHere": "Sleep hierheen om te downloaden", - "localFiles": "Lokaal", - "showLocalFiles": "Lokale bestanden weergeven (gesplitste weergave)", - "hideLocalFiles": "Lokale bestanden verbergen", - "localHome": "Startpagina-map", - "localPathPlaceholder": "Lokaal mappad", - "localShowHidden": "Verborgen bestanden weergeven", - "localHideHidden": "Verborgen bestanden verbergen", - "localRevealInFileManager": "Weergeven in de systeembestandsbeheerder", - "localCannotOpenFolder": "Kan de map niet openen", - "localItemCount": "{{count}} items", - "localSelectedCount": "{{count}} geselecteerd", - "back": "Rug", - "forward": "Vooruit", - "up": "Omhoog", - "kind": "Vriendelijk", - "localUploadingProgress": "Uploaden van {{current}} van {{total}}", - "localDownloadingProgress": "{{current}} van {{total}} downloaden", - "localTransferInFlight": "{{count}} tegelijk", - "localPreparingDownload": "Download wordt voorbereid...", - "localTransferCancelled": "Overdracht geannuleerd", - "localNothingToTransfer": "Niets over te dragen", - "localUploadComplete": "Geüploade {{count}} bestand(en)", - "localUploadFailed": "Uploaden mislukt", - "localUploadPartial": "Geüploade {{done}} bestand(en), {{failed}} mislukt", - "localDownloadComplete": "Gedownloade {{count}} bestand(en)", - "localDownloadFailed": "Download mislukt", - "localDownloadPartial": "Gedownloade {{done}} bestand(en), {{failed}} mislukt", - "localReplaceExistingPrompt": "{{count}} item(s) bestaan al in deze map. Wilt u ze vervangen?", - "localReplace": "Vervangen", - "localSkipExisting": "Overslaan", - "localSkippedExisting": "Bestaande item(s) overgeslagen {{count}}", - "localUnsafeNamesSkipped": "Er zijn {{count}} items overgeslagen waarvan de namen niet op deze computer gebruikt kunnen worden.", - "localOpen": "Open", - "localOpenFolder": "Map openen", - "localUploadToRemote": "Uploaden naar server", - "localUploadToRemoteMany": "Upload {{count}} items naar de server", - "localMoveToTrash": "Verplaats het naar de prullenbak", - "localMoveToTrashMany": "Verplaats {{count}} items naar de prullenbak", - "localTrashConfirmSingle": "\"{{name}}\" naar de prullenbak verplaatsen?", - "localTrashConfirmFolder": "Verplaats de map \"{{name}}\" en alle inhoud ervan naar de prullenbak?", - "localTrashConfirmMany": "Verplaats {{count}} items naar de prullenbak?", - "localTrashed": "Verplaatst {{count}} item(s) naar de prullenbak", - "localTrashFailed": "Kon niet naar de prullenbak verplaatst worden.", - "localRenameFailed": "Hernoemen mislukt", - "localCreateFailed": "Kon item niet aanmaken", - "goToParentFolder": "Ga naar de bovenliggende map", - "resizeSidebar": "Sleep om de zijbalk te vergroten of verkleinen (dubbelklik om terug te zetten).", - "resizeLocalPane": "Sleep om het lokale venster te vergroten of verkleinen (dubbelklikken om terug te zetten).", - "columns": "Kolommen", - "columnsHint": "Klik met de rechtermuisknop om kolommen te kiezen.", - "emptyFolder": "Deze map is leeg.", - "searchFiles": "Bestanden doorzoeken...", - "upload": "Uploaden", - "selectHostToStart": "Selecteer een host om het bestandsbeheer te starten.", - "sshRequiredForFileManager": "Bestandsbeheer vereist SSH. Op deze host is SSH niet ingeschakeld.", - "failedToConnect": "Verbinding met SSH mislukt", - "failedToLoadDirectory": "Map laden mislukt", - "noSSHConnection": "Geen SSH-verbinding beschikbaar", - "copy": "Kopiëren", - "cut": "Snee", - "paste": "Plakken", - "copyPath": "Kopieer pad", - "copyPaths": "Kopieerpaden", - "delete": "Verwijderen", - "properties": "Eigenschappen", - "refresh": "Vernieuwen", - "downloadFiles": "Download {{count}} bestanden naar de browser", - "copyFiles": "Kopieer {{count}} items", - "cutFiles": "Knip {{count}} items", - "deleteFiles": "Verwijder {{count}} items", - "filesCopiedToClipboard": "{{count}} items gekopieerd naar het klembord", - "filesCutToClipboard": "{{count}} items naar het klembord geknipt", - "pathCopiedToClipboard": "Pad gekopieerd naar klembord", - "pathsCopiedToClipboard": "{{count}} paden gekopieerd naar het klembord", - "failedToCopyPath": "Het kopiëren van het pad naar het klembord is mislukt.", - "copyFolderLink": "Kopieer de link naar de map", - "copyCurrentFolderLink": "Kopieer de link naar de huidige map", - "folderLinkCopied": "Maplink gekopieerd naar het klembord.", - "failedToCopyFolderLink": "Het kopiëren van de maplink is mislukt.", - "movedItems": "Verplaatste {{count}} items", - "failedToDeleteItem": "Het verwijderen van het item is mislukt.", - "itemRenamedSuccessfully": "{{type}} succesvol hernoemd", - "failedToRenameItem": "Het hernoemen van het item is mislukt.", - "download": "Download", - "openExternalEditor": "Extern openen", - "chooseExternalEditor": "Externe editor kiezen", - "externalEditorSelected": "Externe redacteur geselecteerd", - "externalEditorDesktopOnly": "De externe editor is alleen beschikbaar in de desktop-app.", - "externalEditorOpened": "Geopend in een externe editor. Door daar op te slaan, worden de wijzigingen naar de server geüpload.", - "failedToOpenExternalEditor": "Het openen van de externe editor is mislukt.", - "failedToSelectExternalEditor": "Externe editor selecteren mislukt", - "permissions": "Toestemmingen", - "size": "Maat", - "modified": "Gewijzigd", - "path": "Pad", - "confirmDelete": "Weet je zeker dat je {{name}} wilt verwijderen?", - "permissionDenied": "Toegang geweigerd", - "serverError": "Serverfout", - "fileSavedSuccessfully": "Bestand succesvol opgeslagen", - "failedToSaveFile": "Het opslaan van het bestand is mislukt.", - "confirmDeleteSingleItem": "Weet je zeker dat je \"{{name}} \" permanent wilt verwijderen?", - "confirmDeleteMultipleItems": "Weet je zeker dat je {{count}} items permanent wilt verwijderen?", - "confirmDeleteMultipleItemsWithFolders": "Weet je zeker dat je {{count}} items permanent wilt verwijderen? Dit omvat mappen en hun inhoud.", - "confirmDeleteFolder": "Weet je zeker dat je de map \"{{name}}\" en alle inhoud ervan permanent wilt verwijderen?", - "permanentDeleteWarning": "Deze actie kan niet ongedaan worden gemaakt. Het/de item(s) wordt/worden permanent van de server verwijderd.", - "moveToTrashWarning": "Het/de item(s) wordt/worden verplaatst naar de Termix-prullenbak en kan/kunnen worden hersteld voordat de bewaartermijn is verlopen.", - "itemsMovedToTrash": "{{count}} item(s) verplaatst naar de prullenbak", - "trash": "Afval", - "trashDescription": "Verwijderde items worden {{days}} dag(en) op deze host bewaard.", - "trashLoadFailed": "Het laden van de prullenbak is mislukt.", - "trashEmpty": "De prullenbak is leeg.", - "trashRestore": "Herstellen", - "trashRestored": "Hersteld {{name}}", - "trashRestoreFailed": "Het herstellen van het item is mislukt. Het oorspronkelijke pad bestaat mogelijk al.", - "trashDeletePermanently": "Permanent verwijderen", - "trashDeleteConfirm": "Permanent verwijderen {{name}}? Dit kan niet ongedaan gemaakt worden.", - "trashPermanentlyDeleted": "Permanent verwijderd {{name}}", - "trashDeleteFailed": "Het is niet gelukt om het item permanent te verwijderen.", - "trashEmptyAction": "Leeg de prullenbak", - "trashEmptyConfirm": "Alle items in de prullenbak permanent verwijderen? Deze actie kan niet ongedaan worden gemaakt.", - "trashEmptied": "Vuilnisbak geleegd", - "trashRetention": "Automatische opruiming na (dagen)", - "trashRetentionSaved": "Afvalretentie bijgewerkt", - "trashRetentionFailed": "Het bijwerken van de prullenbakretentie is mislukt.", - "trashUnavailableConfirm": "Dit item kan niet naar de prullenbak worden verplaatst, meestal omdat het zich op een ander bestandssysteem bevindt of verhoogde machtigingen vereist. Wilt u het in plaats daarvan permanent verwijderen?", - "recent": "Recent", - "pinned": "Vastgepind", - "folderShortcuts": "Mapsnelkoppelingen", - "failedToReconnectSSH": "Het opnieuw tot stand brengen van de SSH-sessie is mislukt.", - "openTerminalHere": "Terminal hier openen", - "run": "Loop", - "openTerminalInFolder": "Open Terminal in deze map", - "openTerminalInFileLocation": "Open Terminal op de bestandslocatie.", - "runningFile": "Hardlopen - {{file}}", - "onlyRunExecutableFiles": "Kan alleen uitvoerbare bestanden uitvoeren", - "directories": "Gidsen", - "removedFromRecentFiles": "\"{{name}}\" is verwijderd uit recente bestanden", - "removeFailed": "Verwijderen mislukt", - "unpinnedSuccessfully": "\"{{name}}\" succesvol losgemaakt", - "unpinFailed": "Ontgrendelen mislukt", - "removedShortcut": "Sneltoets \"{{name}}\" verwijderd", - "removeShortcutFailed": "Snelkoppeling verwijderen mislukt", - "clearedAllRecentFiles": "Alle recente bestanden verwijderd.", - "clearFailed": "Wissen mislukt", - "removeFromRecentFiles": "Verwijderen uit recente bestanden", - "clearAllRecentFiles": "Wis alle recente bestanden", - "unpinFile": "Maak het bestand los", - "removeShortcut": "Snelkoppeling verwijderen", - "pinFile": "Speldbestand", - "addToShortcuts": "Toevoegen aan snelkoppelingen", - "pasteFailed": "Plakken mislukt", - "noUndoableActions": "Geen onomkeerbare acties", - "undoCopySuccess": "Kopieerbewerking ongedaan gemaakt: {{count}} gekopieerde bestanden verwijderd", - "undoCopyFailedDelete": "Ongedaan maken mislukt: Er konden geen gekopieerde bestanden worden verwijderd.", - "undoCopyFailedNoInfo": "Ongedaan maken mislukt: Gekopieerde bestandsinformatie niet gevonden.", - "undoMoveSuccess": "Verplaatsingsbewerking ongedaan gemaakt: {{count}} bestanden teruggeplaatst naar de oorspronkelijke locatie", - "undoMoveFailedMove": "Ongedaan maken mislukt: Er konden geen bestanden teruggezet worden.", - "undoMoveFailedNoInfo": "Ongedaan maken mislukt: Verplaatste bestandsinformatie niet gevonden.", - "undoDeleteNotSupported": "De verwijderingsbewerking kan niet ongedaan worden gemaakt: de bestanden zijn permanent van de server verwijderd.", - "undoTypeNotSupported": "Niet-ondersteund type ongedaan maken-bewerking", - "undoOperationFailed": "De bewerking 'Ongedaan maken' is mislukt.", - "unknownError": "Onbekende fout", - "confirm": "Bevestigen", - "find": "Vinden...", - "replace": "Vervangen", - "downloadInstead": "Download in plaats daarvan", - "keyboardShortcuts": "Toetsenbord sneltoetsen", - "searchAndReplace": "Zoeken en vervangen", - "editing": "Bewerken", - "search": "Zoekopdracht", - "findNext": "Volgende zoeken", - "findPrevious": "Vorige zoeken", - "save": "Redden", - "selectAll": "Alles selecteren", - "undo": "Ongedaan maken", - "redo": "Opnieuw doen", - "moveLineUp": "Verplaats je in de rij", - "moveLineDown": "Lijn omlaag verplaatsen", - "toggleComment": "Reactie weergeven/verbergen", - "autoComplete": "Automatisch aanvullen", - "imageLoadError": "Afbeelding laden mislukt", - "startTyping": "Begin met typen...", - "unknownSize": "Onbekende grootte", - "fileIsEmpty": "Het bestand is leeg.", - "largeFileWarning": "Waarschuwing voor grote bestanden", - "largeFileWarningDesc": "Dit bestand is {{size}} groot, wat prestatieproblemen kan veroorzaken wanneer het als tekstbestand wordt geopend.", - "fileNotFoundAndRemoved": "Bestand \"{{name}}\" is niet gevonden en is verwijderd uit recente/vastgepinde bestanden.", - "failedToLoadFile": "Bestand laden mislukt: {{error}}", - "serverErrorOccurred": "Er is een serverfout opgetreden. Probeer het later opnieuw.", - "autoSaveFailed": "Automatisch opslaan is mislukt", - "fileAutoSaved": "Bestand automatisch opgeslagen", - "moveFileFailed": "Verplaatsen mislukt {{name}}", - "moveOperationFailed": "Verplaatsingsbewerking mislukt", - "canOnlyCompareFiles": "Er kunnen slechts twee bestanden vergeleken worden.", - "comparingFiles": "Bestanden vergelijken: {{file1}} en {{file2}}", - "dragFailed": "Sleepbewerking mislukt", - "filePinnedSuccessfully": "Bestand \"{{name}}\" succesvol vastgezet", - "pinFileFailed": "Het vastzetten van het bestand is mislukt.", - "fileUnpinnedSuccessfully": "Bestand \"{{name}}\" is succesvol losgemaakt.", - "unpinFileFailed": "Het losmaken van het bestand is mislukt.", - "shortcutAddedSuccessfully": "Mapsnelkoppeling \"{{name}}\" succesvol toegevoegd", - "addShortcutFailed": "Snelkoppeling toevoegen mislukt", - "operationCompletedSuccessfully": "{{operation}} {{count}} items succesvol", - "operationCompleted": "{{operation}} {{count}} items", - "downloadFileSuccess": "Bestand {{name}} succesvol gedownload", - "downloadFileFailed": "Download mislukt", - "downloadingFile": "Downloaden {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Ga naar {{name}}", - "diffCompareWith": "Vergelijk het verschil met {{name}}", - "dragOutsideToDownload": "Sleep buiten het venster om bestanden te downloaden ({{count}})", - "newFolderDefault": "Nieuwe map", - "newFileDefault": "NieuwBestand.txt", - "successfullyMovedItems": "Succesvol {{count}} items verplaatst naar {{target}}", - "move": "Beweging", - "searchInFile": "Zoeken in bestand (Ctrl+F)", - "showKeyboardShortcuts": "Toon sneltoetsen", - "decreaseFontSize": "Verklein de lettergrootte", - "increaseFontSize": "Lettergrootte vergroten", - "startWritingMarkdown": "Begin met het schrijven van je markdown-content...", - "loadingFileComparison": "Bestandsvergelijking laden...", - "reload": "Herladen", - "compare": "Vergelijken", - "sideBySide": "Naast elkaar", - "inline": "Inline", - "fileComparison": "Bestandsvergelijking: {{file1}} versus {{file2}}", - "fileTooLarge": "Bestand te groot: {{error}}", - "sshConnectionFailed": "SSH-verbinding mislukt. Controleer uw verbinding met {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Bestand laden mislukt: {{error}}", - "connecting": "Verbinden...", - "connectedSuccessfully": "Verbinding succesvol tot stand gebracht.", - "totpVerificationFailed": "TOTP-verificatie mislukt", - "warpgateVerificationFailed": "Warpgate-authenticatie mislukt", - "authenticationFailed": "Authenticatie mislukt", - "incorrectPassphrase": "Onjuiste wachtzin. Probeer het opnieuw.", - "verificationCodePrompt": "Verificatiecode:", - "changePermissions": "Machtigingen wijzigen", - "currentPermissions": "Huidige vergunningen", - "owner": "Eigenaar", - "group": "Groep", - "others": "Anderen", - "read": "Lezen", - "write": "Schrijven", - "execute": "Uitvoeren", - "permissionsChangedSuccessfully": "Machtigingen succesvol gewijzigd", - "failedToChangePermissions": "Het wijzigen van de machtigingen is mislukt.", - "name": "Naam", - "sortByName": "Naam", - "sortByDate": "Datum gewijzigd", - "sortBySize": "Maat", - "ascending": "Stijgend", - "descending": "Dalend", - "root": "Wortel", - "new": "Nieuw", - "sortBy": "Sorteren op", - "items": "Artikelen", - "selected": "Gekozen", - "editor": "Editor", - "octal": "Octaal", - "storage": "Opslag", - "disk": "Schijf", - "used": "Gebruikt", - "of": "van", - "toggleSidebar": "Zijbalk in-/uitschakelen", - "compactLayout": "Gebruik een compacte lay-out.", - "comfortableLayout": "Gebruik een comfortabele lay-out.", - "cannotLoadPdf": "Kan PDF niet laden", - "pdfLoadError": "Er is een fout opgetreden bij het laden van dit PDF-bestand.", - "loadingPdf": "PDF laden...", - "loadingPage": "Pagina laden..." - }, "transfer": { "copyToHost": "Kopiëren naar host…", "moveToHost": "Verplaats naar host…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Opnieuw proberen mislukt", "transferFailedRetryHint": "Er zijn gedeeltelijke gegevens op de bestemming bewaard gebleven. De herhaalpoging wordt hervat zodra de verbinding hersteld is." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Bestanden overdragen tussen twee SSH-hosts.", - "sourceServer": "Bronserver", - "destinationServer": "Doelserver", - "selectHost": "Selecteer host", - "noHostSelected": "Geen host geselecteerd", - "move": "Beweging", - "copy": "Kopiëren", - "upload": "Uploaden", - "itemsSelected": "{{count}} item geselecteerd", - "itemsSelected_other": "{{count}} items geselecteerd", - "ready": "Klaar", - "loadingHosts": "Hosts worden geladen...", - "name": "Naam", - "modified": "Gewijzigd", - "size": "Maat", - "noFiles": "Geen bestanden", - "open": "Open", - "up": "Omhoog", - "refresh": "Vernieuwen", - "copyToTarget": "Kopiëren naar doelmap", - "rename": "Naam wijzigen", - "delete": "Verwijderen", - "editPermissions": "Machtigingen bewerken", - "createNewFolder": "Nieuwe map maken", - "deleteSelectedItems": "Geselecteerde items verwijderen", - "deleteConfirm": "Verwijder het geselecteerde item {{count}}?", - "deleteConfirm_other": "Verwijder {{count}} geselecteerde items?", - "cancel": "Annuleren", - "save": "Redden", - "folderCreated": "Map aangemaakt", - "itemRenamed": "Item hernoemd", - "actionFailed": "Actie mislukt", - "itemsDeleted": "{{count}} item verwijderd", - "itemsDeleted_other": "{{count}} items verwijderd", - "deleteFailed": "Verwijderen mislukt", - "permissionsUpdated": "Machtigingen bijgewerkt", - "noRemoteSessionConnected": "Geen externe sessie verbonden", - "failedToListFiles": "Het lukte niet om de bestanden weer te geven.", - "failedToRefresh": "Vernieuwen mislukt", - "destinationInsideSource": "De bestemming mag zich niet binnen het geselecteerde bronpad bevinden.", - "sameHostNoOp": "De bron- en bestemmingsmap zijn dezelfde map." - }, "tunnels": { "noSshTunnels": "Geen SSH-tunnels", "createFirstTunnelMessage": "Je hebt nog geen SSH-tunnels aangemaakt. Configureer tunnelverbindingen in de Host Manager om te beginnen.", diff --git a/src/ui/locales/translated/no_NO.json b/src/ui/locales/translated/no_NO.json index f5d2184b8..2e892ef40 100644 --- a/src/ui/locales/translated/no_NO.json +++ b/src/ui/locales/translated/no_NO.json @@ -715,7 +715,6 @@ "hostManager": "Vertsadministrator", "credentials": "Legitimasjon", "portForwarding": "Videresending", - "sftp": "SFTP", "connections": "Tilkoblinger", "serial": "Seriell", "keepSideMenuOpen": "Hold menyen åpen", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tunneler", - "tabFiles": "Filer", "tabStats": "Vertsmålinger", "tabTelnet": "Telnet", "tabSharing": "Deling", "tabAuthentication": "Autentisering", "terminal": "Terminal", "tunnel": "Tunnel", - "fileManager": "Filbehandler", "serverStats": "Vertsmålinger", "status": { "available": "Tilgjengelig", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} hoppet over (ingen IP-adresse funnet)", "proxmoxStatsUrlCopied": "Proxmox-statistikk-URL kopiert til utklippstavlen", "copyProxmoxStatsUrlAction": "Kopier URL-adressen til Proxmox-statistikken", - "enableFileManagerMonitor": "Aktiver filbehandler", - "enableFileManagerMonitorDesc": "Bla gjennom og administrer filer på denne verten via SFTP", - "scpLegacyLabel": "SCP Legacy-modus", - "scpLegacyDesc": "Bruk eldre filoverføring for servere som ikke støtter SFTP-undersystemet (f.eks. innebygde eller minimale SSH-servere).", - "defaultPathLabel": "Standardsti", - "fileManagerPathHint": "Katalogen som skal åpnes når filbehandleren starter for denne verten.", "statusChecksLabel": "Statuskontroller", "enableStatusChecks": "Aktiver statuskontroller", "enableStatusChecksDesc": "Ping denne verten med jevne mellomrom for å bekrefte tilgjengelighet", @@ -1278,7 +1269,6 @@ "deletedHost": "Slettet {{name}}", "copiedToClipboard": "Kopiert til utklippstavlen", "terminalUrlCopied": "Terminal-URL kopiert", - "fileManagerUrlCopied": "URL-adressen til filbehandleren er kopiert", "tunnelUrlCopied": "Tunnel-URL kopiert", "dockerUrlCopied": "Docker-URL kopiert", "hostMetricsUrlCopied": "URL-en for vertsmålinger er kopiert", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopier adresse", "copyLink": "Kopier lenke", "copyTerminalUrlAction": "Kopier terminal-URL", - "copyFileManagerUrlAction": "Kopier URL-adressen til filbehandleren", "copyTunnelUrlAction": "Kopier tunnel-URL", "copyDockerUrlAction": "Kopier Docker-URL", "copyHostMetricsUrlAction": "Kopier URL-adressen for vertsmålinger", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Fjern fra Split" } }, - "fileManager": { - "noHostSelected": "Ingen vert valgt", - "initializingEditor": "Initialiserer redigeringsprogrammet...", - "file": "Fil", - "folder": "Mappe", - "uploadFile": "Last opp fil", - "downloadFile": "Last ned", - "extractArchive": "Utdragsarkiv", - "extractingArchive": "Uttrekker {{name}}...", - "archiveExtractedSuccessfully": "{{name}} ble pakket ut", - "extractFailed": "Utpakking mislyktes", - "compressFile": "Komprimer fil", - "compressFiles": "Komprimer filer", - "compressFilesDesc": "Komprimer {{count}} elementer til et arkiv", - "archiveName": "Arkivnavn", - "enterArchiveName": "Skriv inn arkivnavn...", - "compressionFormat": "Kompresjonsformat", - "selectedFiles": "Valgte filer", - "andMoreFiles": "og {{count}} mer...", - "compress": "Komprimere", - "compressingFiles": "Komprimerer {{count}} elementer til {{name}}...", - "filesCompressedSuccessfully": "{{name}} ble opprettet", - "compressFailed": "Komprimeringen mislyktes", - "edit": "Redigere", - "preview": "Forhåndsvisning", - "previous": "Tidligere", - "next": "Neste", - "pageXOfY": "Side {{current}} av {{total}}", - "zoomOut": "Zoom ut", - "zoomIn": "Zoom inn", - "newFile": "Ny fil", - "newFolder": "Ny mappe", - "rename": "Gi nytt navn", - "uploading": "Laster opp...", - "uploadingFile": "Laster opp {{name}}...", - "fileName": "Filnavn", - "folderName": "Mappenavn", - "fileUploadedSuccessfully": "Filen «{{name}}» ble lastet opp", - "failedToUploadFile": "Kunne ikke laste opp filen", - "uploadingFolderFiles": "Laster opp {{count}} fil(er)...", - "uploadedFolderFiles": "Lastet opp {{count}} fil(er)", - "uploadedFolderPartial": "Opplasting av {{uploaded}} fil(er), {{failed}} mislyktes", - "fileDownloadedSuccessfully": "Filen «{{name}}» er lastet ned", - "failedToDownloadFile": "Kunne ikke laste ned filen", - "fileCreatedSuccessfully": "Filen «{{name}}» er opprettet", - "folderCreatedSuccessfully": "Mappen «{{name}}» er opprettet", - "failedToCreateItem": "Kunne ikke opprette elementet", - "operationFailed": "{{operation}} operasjonen mislyktes for {{name}}: {{error}}", - "failedToResolveSymlink": "Klarte ikke å løse symbolsk lenke", - "itemsDeletedSuccessfully": "{{count}} elementer er slettet", - "failedToDeleteItems": "Kunne ikke slette elementer", - "sudoPasswordRequired": "Administratorpassord kreves", - "enterSudoPassword": "Skriv inn sudo-passordet for å fortsette denne operasjonen", - "sudoPassword": "Sudo-passord", - "sudoOperationFailed": "Sudo-operasjonen mislyktes", - "sudoAuthFailed": "Sudo-autentisering mislyktes", - "dragFilesToUpload": "Slipp filer her for å laste dem opp", - "dropToUploadHere": "Slipp for å laste opp her", - "dropToDownloadHere": "Slipp for å laste ned her", - "localFiles": "Lokalt", - "showLocalFiles": "Vis lokale filer (delt visning)", - "hideLocalFiles": "Skjul lokale filer", - "localHome": "Hjemmappe", - "localPathPlaceholder": "Lokal mappebane", - "localShowHidden": "Vis skjulte filer", - "localHideHidden": "Skjul skjulte filer", - "localRevealInFileManager": "Avslør i systemfilbehandleren", - "localCannotOpenFolder": "Kan ikke åpne mappen", - "localItemCount": "{{count}} elementer", - "localSelectedCount": "{{count}} valgt", - "back": "Tilbake", - "forward": "Framover", - "up": "Opp", - "kind": "Type", - "localUploadingProgress": "Laster opp {{current}} av {{total}}", - "localDownloadingProgress": "Laster ned {{current}} av {{total}}", - "localTransferInFlight": "{{count}} med en gang", - "localPreparingDownload": "Forbereder nedlasting ...", - "localTransferCancelled": "Overføringen er avbrutt", - "localNothingToTransfer": "Ingenting å overføre", - "localUploadComplete": "Opplastede {{count}} fil(er)", - "localUploadFailed": "Opplastingen mislyktes", - "localUploadPartial": "Opplasting av {{done}} fil(er), {{failed}} mislyktes", - "localDownloadComplete": "Lastet ned {{count}} fil(er)", - "localDownloadFailed": "Nedlastingen mislyktes", - "localDownloadPartial": "Nedlasting av {{done}} fil(er), {{failed}} mislyktes", - "localReplaceExistingPrompt": "{{count}} element(er) finnes allerede i denne mappen. Vil du erstatte dem?", - "localReplace": "Erstatt", - "localSkipExisting": "Hopp over", - "localSkippedExisting": "Hoppet over {{count}} eksisterende element(er)", - "localUnsafeNamesSkipped": "Hoppet over {{count}} elementer hvis navn ikke kan brukes på denne datamaskinen", - "localOpen": "Åpne", - "localOpenFolder": "Åpne mappen", - "localUploadToRemote": "Last opp til server", - "localUploadToRemoteMany": "Last opp {{count}} elementer til serveren", - "localMoveToTrash": "Flytt til papirkurven", - "localMoveToTrashMany": "Flytt {{count}} elementer til papirkurven", - "localTrashConfirmSingle": "Flytte «{{name}}» til papirkurven?", - "localTrashConfirmFolder": "Flytte mappen «{{name}}» og alt innholdet til papirkurven?", - "localTrashConfirmMany": "Flytte {{count}} elementer til papirkurven?", - "localTrashed": "Flyttet {{count}} element(er) til papirkurven", - "localTrashFailed": "Kunne ikke flytte til papirkurven", - "localRenameFailed": "Mislyktes i å gi nytt navn", - "localCreateFailed": "Kunne ikke opprette elementet", - "goToParentFolder": "Gå til overordnet mappe", - "resizeSidebar": "Dra for å endre størrelsen på sidefeltet (dobbeltklikk for å tilbakestille)", - "resizeLocalPane": "Dra for å endre størrelsen på den lokale ruten (dobbeltklikk for å tilbakestille)", - "columns": "Kolonner", - "columnsHint": "Høyreklikk for å velge kolonner", - "emptyFolder": "Denne mappen er tom", - "searchFiles": "Søk i filer...", - "upload": "Last opp", - "selectHostToStart": "Velg en vert for å starte filbehandling", - "sshRequiredForFileManager": "Filbehandleren krever SSH. Denne verten har ikke SSH aktivert.", - "failedToConnect": "Kunne ikke koble til SSH", - "failedToLoadDirectory": "Kunne ikke laste inn katalogen", - "noSSHConnection": "Ingen SSH-tilkobling tilgjengelig", - "copy": "Kopiere", - "cut": "Kutt", - "paste": "Lim inn", - "copyPath": "Kopier bane", - "copyPaths": "Kopier stier", - "delete": "Slett", - "properties": "Eiendommer", - "refresh": "Forfriske", - "downloadFiles": "Last ned {{count}} filer til nettleseren", - "copyFiles": "Kopier {{count}} elementer", - "cutFiles": "Klipp ut {{count}} elementer", - "deleteFiles": "Slett {{count}} elementer", - "filesCopiedToClipboard": "{{count}} elementer kopiert til utklippstavlen", - "filesCutToClipboard": "{{count}} elementer klippet til utklippstavlen", - "pathCopiedToClipboard": "Sti kopiert til utklippstavlen", - "pathsCopiedToClipboard": "{{count}} stier kopiert til utklippstavlen", - "failedToCopyPath": "Kunne ikke kopiere banen til utklippstavlen", - "copyFolderLink": "Kopier lenke til mappe", - "copyCurrentFolderLink": "Kopier lenke til gjeldende mappe", - "folderLinkCopied": "Mappelenke kopiert til utklippstavlen", - "failedToCopyFolderLink": "Kunne ikke kopiere mappelenken", - "movedItems": "Flyttet {{count}} elementer", - "failedToDeleteItem": "Kunne ikke slette elementet", - "itemRenamedSuccessfully": "{{type}} har fått nytt navn", - "failedToRenameItem": "Kunne ikke gi nytt navn til elementet", - "download": "Last ned", - "openExternalEditor": "Åpne eksternt", - "chooseExternalEditor": "Velg ekstern editor", - "externalEditorSelected": "Ekstern redigerer valgt", - "externalEditorDesktopOnly": "Ekstern editor er bare tilgjengelig i skrivebordsappen", - "externalEditorOpened": "Åpnet i ekstern editor. Hvis du lagrer der, lastes endringene opp tilbake til serveren.", - "failedToOpenExternalEditor": "Kunne ikke åpne ekstern editor", - "failedToSelectExternalEditor": "Kunne ikke velge ekstern redigeringsprogram", - "permissions": "Tillatelser", - "size": "Størrelse", - "modified": "Modifisert", - "path": "Sti", - "confirmDelete": "Er du sikker på at du vil slette {{name}}?", - "permissionDenied": "Tillatelse nektet", - "serverError": "Serverfeil", - "fileSavedSuccessfully": "Filen er lagret", - "failedToSaveFile": "Kunne ikke lagre filen", - "confirmDeleteSingleItem": "Er du sikker på at du vil slette «{{name}}» permanent?", - "confirmDeleteMultipleItems": "Er du sikker på at du vil slette {{count}} elementer permanent?", - "confirmDeleteMultipleItemsWithFolders": "Er du sikker på at du vil slette {{count}} elementer permanent? Dette inkluderer mapper og innholdet i dem.", - "confirmDeleteFolder": "Er du sikker på at du vil slette mappen «{{name}}» og alt innholdet i den permanent?", - "permanentDeleteWarning": "Denne handlingen kan ikke angres. Elementet/elementene vil bli permanent slettet fra serveren.", - "moveToTrashWarning": "Elementet/elementene vil bli flyttet til Termix-papirkurven og kan gjenopprettes før oppbevaringsperioden utløper.", - "itemsMovedToTrash": "{{count}} element(er) flyttet til papirkurven", - "trash": "Søppel", - "trashDescription": "Slettede elementer lagres i {{days}} dag(er) på denne verten.", - "trashLoadFailed": "Kunne ikke laste inn papirkurven", - "trashEmpty": "Papirkurven er tom", - "trashRestore": "Restaurere", - "trashRestored": "Gjenopprettet {{name}}", - "trashRestoreFailed": "Kunne ikke gjenopprette elementet. Den opprinnelige banen finnes kanskje allerede.", - "trashDeletePermanently": "Slett permanent", - "trashDeleteConfirm": "Vil du slette {{name}}permanent? Dette kan ikke angres.", - "trashPermanentlyDeleted": "Slettet permanent {{name}}", - "trashDeleteFailed": "Kunne ikke slette elementet permanent", - "trashEmptyAction": "Tøm søppelbøtten", - "trashEmptyConfirm": "Vil du slette alle elementene i papirkurven permanent? Dette kan ikke angres.", - "trashEmptied": "Søppelet tømt", - "trashRetention": "Automatisk opprydding etter (dager)", - "trashRetentionSaved": "Oppbevaring av søppel er oppdatert", - "trashRetentionFailed": "Kunne ikke oppdatere papirkurven", - "trashUnavailableConfirm": "Dette elementet kan ikke flyttes til papirkurven, vanligvis fordi det er på et annet filsystem eller krever utvidede tillatelser. Slette det permanent i stedet?", - "recent": "Nylig", - "pinned": "Festet", - "folderShortcuts": "Mappesnarveier", - "failedToReconnectSSH": "Kunne ikke koble til SSH-økten på nytt", - "openTerminalHere": "Åpne terminalen her", - "run": "Løp", - "openTerminalInFolder": "Åpne terminalen i denne mappen", - "openTerminalInFileLocation": "Åpne terminalen på filplassering", - "runningFile": "Løping - {{file}}", - "onlyRunExecutableFiles": "Kan bare kjøre kjørbare filer", - "directories": "Kataloger", - "removedFromRecentFiles": "Fjernet «{{name}}» fra nylige filer", - "removeFailed": "Fjerning mislyktes", - "unpinnedSuccessfully": "«{{name}}» er løsnet", - "unpinFailed": "Løsne mislyktes", - "removedShortcut": "Fjernet snarveien «{{name}}»", - "removeShortcutFailed": "Fjerning av snarvei mislyktes", - "clearedAllRecentFiles": "Slettet alle nylige filer", - "clearFailed": "Fjerning mislyktes", - "removeFromRecentFiles": "Fjern fra nylige filer", - "clearAllRecentFiles": "Fjern alle nylige filer", - "unpinFile": "Løsne filen", - "removeShortcut": "Fjern snarvei", - "pinFile": "Fest filen", - "addToShortcuts": "Legg til i snarveier", - "pasteFailed": "Lime inn mislyktes", - "noUndoableActions": "Ingen angrebare handlinger", - "undoCopySuccess": "Angret kopiering: Slettet {{count}} kopierte filer", - "undoCopyFailedDelete": "Angre mislyktes: Kunne ikke slette noen kopierte filer", - "undoCopyFailedNoInfo": "Angre mislyktes: Finner ikke informasjon om den kopierte filen", - "undoMoveSuccess": "Angret flytteoperasjon: Flyttet {{count}} filer tilbake til opprinnelig plassering", - "undoMoveFailedMove": "Angre mislyktes: Kunne ikke flytte noen filer tilbake", - "undoMoveFailedNoInfo": "Angre mislyktes: Finner ikke informasjon om den flyttede filen", - "undoDeleteNotSupported": "Sletteoperasjonen kan ikke angres: Filene er slettet permanent fra serveren", - "undoTypeNotSupported": "Ustøttet angreoperasjonstype", - "undoOperationFailed": "Angreoperasjonen mislyktes", - "unknownError": "Ukjent feil", - "confirm": "Bekrefte", - "find": "Finne...", - "replace": "Erstatt", - "downloadInstead": "Last ned i stedet", - "keyboardShortcuts": "Tastatursnarveier", - "searchAndReplace": "Søk og erstatt", - "editing": "Redigering", - "search": "Søk", - "findNext": "Finn neste", - "findPrevious": "Finn forrige", - "save": "Spare", - "selectAll": "Velg alle", - "undo": "Angre", - "redo": "Gjør om", - "moveLineUp": "Flytt linje opp", - "moveLineDown": "Flytt linje ned", - "toggleComment": "Veksle kommentar", - "autoComplete": "Autofullfør", - "imageLoadError": "Kunne ikke laste inn bildet", - "startTyping": "Begynn å skrive...", - "unknownSize": "Ukjent størrelse", - "fileIsEmpty": "Filen er tom", - "largeFileWarning": "Advarsel om stor fil", - "largeFileWarningDesc": "Denne filen er {{size}} stor, noe som kan forårsake ytelsesproblemer når den åpnes som tekst.", - "fileNotFoundAndRemoved": "Filen «{{name}}» ble ikke funnet og er fjernet fra nylige/festede filer", - "failedToLoadFile": "Kunne ikke laste inn filen: {{error}}", - "serverErrorOccurred": "Det oppsto en serverfeil. Prøv igjen senere.", - "autoSaveFailed": "Automatisk lagring mislyktes", - "fileAutoSaved": "Fil lagret automatisk", - "moveFileFailed": "Kunne ikke flytte {{name}}", - "moveOperationFailed": "Flytteoperasjonen mislyktes", - "canOnlyCompareFiles": "Kan bare sammenligne to filer", - "comparingFiles": "Sammenligning av filer: {{file1}} og {{file2}}", - "dragFailed": "Draoperasjonen mislyktes", - "filePinnedSuccessfully": "Filen «{{name}}» er låst", - "pinFileFailed": "Kunne ikke feste filen", - "fileUnpinnedSuccessfully": "Filen «{{name}}» ble løsnet", - "unpinFileFailed": "Kunne ikke løsne filen", - "shortcutAddedSuccessfully": "Mappsnarveien «{{name}}» er lagt til", - "addShortcutFailed": "Kunne ikke legge til snarvei", - "operationCompletedSuccessfully": "{{operation}} {{count}} elementer ble vellykket", - "operationCompleted": "{{operation}} {{count}} elementer", - "downloadFileSuccess": "Fil {{name}} er lastet ned", - "downloadFileFailed": "Nedlastingen mislyktes", - "downloadingFile": "Laster ned {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Flytt til {{name}}", - "diffCompareWith": "Differansesammenligning med {{name}}", - "dragOutsideToDownload": "Dra utenfor vinduet for å laste ned ({{count}} filer)", - "newFolderDefault": "Ny mappe", - "newFileDefault": "Nyfil.txt", - "successfullyMovedItems": "Flyttet {{count}} elementer til {{target}}", - "move": "Flytte", - "searchInFile": "Søk i filen (Ctrl+F)", - "showKeyboardShortcuts": "Vis hurtigtaster", - "decreaseFontSize": "Reduser skriftstørrelsen", - "increaseFontSize": "Øk skriftstørrelsen", - "startWritingMarkdown": "Begynn å skrive innholdet ditt med nedsatt pris...", - "loadingFileComparison": "Laster inn filsammenligning ...", - "reload": "Last inn på nytt", - "compare": "Sammenligne", - "sideBySide": "Side om side", - "inline": "Innebygd", - "fileComparison": "Filsammenligning: {{file1}} vs. {{file2}}", - "fileTooLarge": "Filen er for stor: {{error}}", - "sshConnectionFailed": "SSH-tilkoblingen mislyktes. Sjekk tilkoblingen din til {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Kunne ikke laste inn filen: {{error}}", - "connecting": "Kobler til...", - "connectedSuccessfully": "Tilkoblet", - "totpVerificationFailed": "TOTP-verifisering mislyktes", - "warpgateVerificationFailed": "Warpgate-autentisering mislyktes", - "authenticationFailed": "Autentisering mislyktes", - "incorrectPassphrase": "Feil passordfrase. Prøv på nytt.", - "verificationCodePrompt": "Verifiseringskode:", - "changePermissions": "Endre tillatelser", - "currentPermissions": "Gjeldende tillatelser", - "owner": "Eier", - "group": "Gruppe", - "others": "Andre", - "read": "Lese", - "write": "Skrive", - "execute": "Henrette", - "permissionsChangedSuccessfully": "Tillatelsene er endret", - "failedToChangePermissions": "Kunne ikke endre tillatelser", - "name": "Navn", - "sortByName": "Navn", - "sortByDate": "Dato endret", - "sortBySize": "Størrelse", - "ascending": "Stigende", - "descending": "Synkende", - "root": "Rot", - "new": "Ny", - "sortBy": "Sorter etter", - "items": "Elementer", - "selected": "Valgt", - "editor": "Redaktør", - "octal": "Oktal", - "storage": "Lagring", - "disk": "Disk", - "used": "Brukt", - "of": "av", - "toggleSidebar": "Slå sidefeltet av/på", - "compactLayout": "Bruk kompakt oppsett", - "comfortableLayout": "Bruk komfortabel layout", - "cannotLoadPdf": "Kan ikke laste inn PDF-filen", - "pdfLoadError": "Det oppsto en feil under lasting av denne PDF-filen.", - "loadingPdf": "Laster inn PDF-filen...", - "loadingPage": "Laster inn side..." - }, "transfer": { "copyToHost": "Kopier til vert…", "moveToHost": "Flytt til vert…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Forsøk på nytt mislyktes", "transferFailedRetryHint": "Delvise data ble lagret på destinasjonen. Forsøket gjenopptas når forbindelsen er tilbake." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Overfør filer mellom to SSH-verter.", - "sourceServer": "Kildeserver", - "destinationServer": "Destinasjonsserver", - "selectHost": "Velg vert", - "noHostSelected": "Ingen vert valgt", - "move": "Flytte", - "copy": "Kopiere", - "upload": "Last opp", - "itemsSelected": "{{count}} element valgt", - "itemsSelected_other": "{{count}} elementer valgt", - "ready": "Ferdig", - "loadingHosts": "Laster inn verter ...", - "name": "Navn", - "modified": "Modifisert", - "size": "Størrelse", - "noFiles": "Ingen filer", - "open": "Åpne", - "up": "Opp", - "refresh": "Forfriske", - "copyToTarget": "Kopier til målkatalogen", - "rename": "Gi nytt navn", - "delete": "Slett", - "editPermissions": "Rediger tillatelser", - "createNewFolder": "Opprett ny mappe", - "deleteSelectedItems": "Slett valgte elementer", - "deleteConfirm": "Slette {{count}} valgt element?", - "deleteConfirm_other": "Slette {{count}} valgte elementer?", - "cancel": "Kansellere", - "save": "Spare", - "folderCreated": "Mappe opprettet", - "itemRenamed": "Elementet har fått nytt navn", - "actionFailed": "Handlingen mislyktes", - "itemsDeleted": "{{count}} element slettet", - "itemsDeleted_other": "{{count}} elementer slettet", - "deleteFailed": "Slettingen mislyktes", - "permissionsUpdated": "Tillatelser oppdatert", - "noRemoteSessionConnected": "Ingen ekstern økt tilkoblet", - "failedToListFiles": "Kunne ikke liste opp filer", - "failedToRefresh": "Kunne ikke laste inn på nytt", - "destinationInsideSource": "Destinasjonen kan ikke være innenfor den valgte kildebanen.", - "sameHostNoOp": "Kilde og destinasjon er i samme mappe." - }, "tunnels": { "noSshTunnels": "Ingen SSH-tunneler", "createFirstTunnelMessage": "Du har ikke opprettet noen SSH-tunneler ennå. Konfigurer tunneltilkoblinger i Host Manager for å komme i gang.", diff --git a/src/ui/locales/translated/pl_PL.json b/src/ui/locales/translated/pl_PL.json index 28638a682..51bf27d5f 100644 --- a/src/ui/locales/translated/pl_PL.json +++ b/src/ui/locales/translated/pl_PL.json @@ -715,7 +715,6 @@ "hostManager": "Menedżer hostów", "credentials": "Poświadczenia", "portForwarding": "Przekierowanie", - "sftp": "SFTP", "connections": "Połączenia", "serial": "Port szeregowy", "keepSideMenuOpen": "Zachowaj otwarte menu", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tunele", - "tabFiles": "Pliki", "tabStats": "Metryki hosta", "tabTelnet": "Telnet", "tabSharing": "Udostępnianie", "tabAuthentication": "Uwierzytelnianie", "terminal": "Terminal", "tunnel": "Tunel", - "fileManager": "Menedżer plików", "serverStats": "Metryki hosta", "status": { "available": "Dostępny", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} pominięto (nie znaleziono adresu IP)", "proxmoxStatsUrlCopied": "Skopiowano adres URL statystyk Proxmox", "copyProxmoxStatsUrlAction": "Kopiuj adres URL statystyk Proxmox", - "enableFileManagerMonitor": "Włącz menedżer plików", - "enableFileManagerMonitorDesc": "Przeglądaj i zarządzaj plikami na tym hoście przez SFTP", - "scpLegacyLabel": "Tryb SCP Legacy", - "scpLegacyDesc": "Użyj starszej metody transferu plików dla serwerów, które nie obsługują podsystemu SFTP (np. wbudowanych lub minimalnych serwerów SSH).", - "defaultPathLabel": "Domyślna ścieżka", - "fileManagerPathHint": "Katalog, który zostanie otwarty po uruchomieniu menedżera plików dla tego hosta.", "statusChecksLabel": "Sprawdzanie statusu", "enableStatusChecks": "Włącz sprawdzanie statusu", "enableStatusChecksDesc": "Okresowo wysyłaj polecenie ping do tego hosta w celu sprawdzenia dostępności", @@ -1278,7 +1269,6 @@ "deletedHost": "Usunięto {{name}}", "copiedToClipboard": "Skopiowano do schowka", "terminalUrlCopied": "Skopiowano adres URL terminala", - "fileManagerUrlCopied": "Skopiowano adres URL menedżera plików", "tunnelUrlCopied": "Skopiowano adres URL tunelu", "dockerUrlCopied": "Skopiowano adres URL Dockera", "hostMetricsUrlCopied": "Skopiowano adres URL metryk hosta", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopiuj adres", "copyLink": "Kopiuj link", "copyTerminalUrlAction": "Kopiuj adres URL terminala", - "copyFileManagerUrlAction": "Kopiuj adres URL menedżera plików", "copyTunnelUrlAction": "Kopiuj adres URL tunelu", "copyDockerUrlAction": "Kopiuj adres URL Dockera", "copyHostMetricsUrlAction": "Kopiuj adres URL metryk hosta", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Usuń z podziału" } }, - "fileManager": { - "noHostSelected": "Nie wybrano hosta", - "initializingEditor": "Inicjalizacja edytora...", - "file": "Plik", - "folder": "Folder", - "uploadFile": "Prześlij plik", - "downloadFile": "Pobierz", - "extractArchive": "Wyodrębnij archiwum", - "extractingArchive": "Wyodrębnianie {{name}}...", - "archiveExtractedSuccessfully": "{{name}} wyodrębniono pomyślnie", - "extractFailed": "Wyodrębnienie nie powiodło się", - "compressFile": "Kompresuj plik", - "compressFiles": "Kompresuj pliki", - "compressFilesDesc": "Kompresuj {{count}} elementów do archiwum", - "archiveName": "Nazwa archiwum", - "enterArchiveName": "Wprowadź nazwę archiwum...", - "compressionFormat": "Format kompresji", - "selectedFiles": "Wybrane pliki", - "andMoreFiles": "i jeszcze {{count}}...", - "compress": "Kompresuj", - "compressingFiles": "Kompresowanie {{count}} elementów do {{name}}...", - "filesCompressedSuccessfully": "{{name}} utworzono pomyślnie", - "compressFailed": "Kompresja nie powiodła się", - "edit": "Edytuj", - "preview": "Podgląd", - "previous": "Poprzedni", - "next": "Następny", - "pageXOfY": "Strona {{current}} z {{total}}", - "zoomOut": "Oddal", - "zoomIn": "Powiększ", - "newFile": "Nowy plik", - "newFolder": "Nowy folder", - "rename": "Zmień nazwę", - "uploading": "Przesyłanie...", - "uploadingFile": "Przesyłanie {{name}}...", - "fileName": "Nazwa pliku", - "folderName": "Nazwa folderu", - "fileUploadedSuccessfully": "Plik „{{name}}” został pomyślnie przesłany", - "failedToUploadFile": "Nie udało się przesłać pliku", - "uploadingFolderFiles": "Przesyłanie {{count}} plików...", - "uploadedFolderFiles": "Pomyślnie przesłano {{count}} plików", - "uploadedFolderPartial": "Przesłano {{uploaded}} plików, {{failed}} nie powiodło się", - "fileDownloadedSuccessfully": "Plik „{{name}}” został pomyślnie pobrany", - "failedToDownloadFile": "Nie udało się pobrać pliku", - "fileCreatedSuccessfully": "Plik „{{name}}” został pomyślnie utworzony", - "folderCreatedSuccessfully": "Folder „{{name}}” został pomyślnie utworzony", - "failedToCreateItem": "Nie udało się utworzyć elementu", - "operationFailed": "Operacja {{operation}} nie powiodła się dla {{name}}: {{error}}", - "failedToResolveSymlink": "Nie udało się rozwiązać dowiązania symbolicznego", - "itemsDeletedSuccessfully": "Pomyślnie usunięto {{count}} elementów", - "failedToDeleteItems": "Nie udało się usunąć elementów", - "sudoPasswordRequired": "Wymagane hasło administratora", - "enterSudoPassword": "Wprowadź hasło sudo, aby kontynuować tę operację", - "sudoPassword": "Hasło sudo", - "sudoOperationFailed": "Operacja sudo nie powiodła się", - "sudoAuthFailed": "Uwierzytelnianie sudo nie powiodło się", - "dragFilesToUpload": "Upuść pliki tutaj, aby je przesłać", - "dropToUploadHere": "Upuść, aby przesłać tutaj", - "dropToDownloadHere": "Upuść, aby pobrać tutaj", - "localFiles": "Lokalny", - "showLocalFiles": "Pokaż pliki lokalne (widok podzielony)", - "hideLocalFiles": "Ukryj pliki lokalne", - "localHome": "Folder domowy", - "localPathPlaceholder": "Ścieżka do folderu lokalnego", - "localShowHidden": "Pokaż ukryte pliki", - "localHideHidden": "Ukryj ukryte pliki", - "localRevealInFileManager": "Pokaż w menedżerze plików systemowych", - "localCannotOpenFolder": "Nie można otworzyć folderu", - "localItemCount": "{{count}} elementów", - "localSelectedCount": "{{count}} wybrano", - "back": "Z powrotem", - "forward": "Do przodu", - "up": "W górę", - "kind": "Uprzejmy", - "localUploadingProgress": "Przesyłanie {{current}} z {{total}}", - "localDownloadingProgress": "Pobieranie {{current}} z {{total}}", - "localTransferInFlight": "{{count}} natychmiast", - "localPreparingDownload": "Przygotowywanie do pobrania...", - "localTransferCancelled": "Transfer anulowany", - "localNothingToTransfer": "Nic do przeniesienia", - "localUploadComplete": "Przesłano {{count}} plików", - "localUploadFailed": "Przesyłanie nie powiodło się", - "localUploadPartial": "Przesłano {{done}} plików, {{failed}} nie powiodło się", - "localDownloadComplete": "Pobrano {{count}} plików", - "localDownloadFailed": "Pobieranie nie powiodło się", - "localDownloadPartial": "Pobrano {{done}} plików, {{failed}} nie powiodło się", - "localReplaceExistingPrompt": "W tym folderze istnieje już {{count}} elementów. Czy je zastąpić?", - "localReplace": "Zastępować", - "localSkipExisting": "Pominąć", - "localSkippedExisting": "Pominięto {{count}} istniejących elementów", - "localUnsafeNamesSkipped": "Pominięto {{count}} elementów, których nazw nie można używać na tym komputerze", - "localOpen": "Otwarte", - "localOpenFolder": "Otwórz folder", - "localUploadToRemote": "Prześlij na serwer", - "localUploadToRemoteMany": "Prześlij {{count}} elementów na serwer", - "localMoveToTrash": "Przenieś do kosza", - "localMoveToTrashMany": "Przenieś {{count}} elementów do Kosza", - "localTrashConfirmSingle": "Przenieść „{{name}}” do Kosza?", - "localTrashConfirmFolder": "Czy przenieść folder „{{name}}” i całą jego zawartość do Kosza?", - "localTrashConfirmMany": "Przenieść {{count}} elementów do Kosza?", - "localTrashed": "Przeniesiono {{count}} elementów do Kosza", - "localTrashFailed": "Nie można przenieść do Kosza", - "localRenameFailed": "Zmiana nazwy nie powiodła się", - "localCreateFailed": "Nie można utworzyć elementu", - "goToParentFolder": "Przejdź do folderu nadrzędnego", - "resizeSidebar": "Przeciągnij, aby zmienić rozmiar paska bocznego (kliknij dwukrotnie, aby zresetować)", - "resizeLocalPane": "Przeciągnij, aby zmienić rozmiar panelu lokalnego (kliknij dwukrotnie, aby zresetować)", - "columns": "Kolumny", - "columnsHint": "Kliknij prawym przyciskiem myszy, aby wybrać kolumny", - "emptyFolder": "Ten folder jest pusty", - "searchFiles": "Wyszukaj pliki...", - "upload": "Prześlij", - "selectHostToStart": "Wybierz hosta, aby rozpocząć zarządzanie plikami", - "sshRequiredForFileManager": "Menedżer plików wymaga SSH. Ten host nie ma włączonego SSH.", - "failedToConnect": "Nie udało się połączyć z SSH", - "failedToLoadDirectory": "Nie udało się załadować katalogu", - "noSSHConnection": "Brak dostępnego połączenia SSH", - "copy": "Kopiuj", - "cut": "Wytnij", - "paste": "Wklej", - "copyPath": "Kopiuj ścieżkę", - "copyPaths": "Kopiuj ścieżki", - "delete": "Usuń", - "properties": "Właściwości", - "refresh": "Odśwież", - "downloadFiles": "Pobierz {{count}} plików do przeglądarki", - "copyFiles": "Skopiuj {{count}} elementów", - "cutFiles": "Wytnij {{count}} elementów", - "deleteFiles": "Usuń {{count}} elementów", - "filesCopiedToClipboard": "{{count}} elementów skopiowano do schowka", - "filesCutToClipboard": "{{count}} elementów wycięto do schowka", - "pathCopiedToClipboard": "Ścieżka skopiowana do schowka", - "pathsCopiedToClipboard": "{{count}} ścieżek skopiowano do schowka", - "failedToCopyPath": "Nie udało się skopiować ścieżki do schowka", - "copyFolderLink": "Kopiuj link do folderu", - "copyCurrentFolderLink": "Kopiuj link do bieżącego folderu", - "folderLinkCopied": "Link do folderu skopiowany do schowka", - "failedToCopyFolderLink": "Nie udało się skopiować linku do folderu", - "movedItems": "Przeniesiono {{count}} elementów", - "failedToDeleteItem": "Nie udało się usunąć elementu", - "itemRenamedSuccessfully": "Zmieniono nazwę: {{type}}", - "failedToRenameItem": "Nie udało się zmienić nazwy elementu", - "download": "Pobierz", - "openExternalEditor": "Otwórz zewnętrznie", - "chooseExternalEditor": "Wybierz edytor zewnętrzny", - "externalEditorSelected": "Wybrano edytor zewnętrzny", - "externalEditorDesktopOnly": "Edytor zewnętrzny jest dostępny tylko w aplikacji desktopowej", - "externalEditorOpened": "Otwarto w edytorze zewnętrznym. Zapisanie tam spowoduje przesłanie zmian z powrotem na serwer.", - "failedToOpenExternalEditor": "Nie udało się otworzyć edytora zewnętrznego", - "failedToSelectExternalEditor": "Nie udało się wybrać edytora zewnętrznego", - "permissions": "Uprawnienia", - "size": "Rozmiar", - "modified": "Zmodyfikowano", - "path": "Ścieżka", - "confirmDelete": "Czy na pewno chcesz usunąć {{name}}?", - "permissionDenied": "Brak uprawnień", - "serverError": "Błąd serwera", - "fileSavedSuccessfully": "Plik zapisany pomyślnie", - "failedToSaveFile": "Nie udało się zapisać pliku", - "confirmDeleteSingleItem": "Czy na pewno chcesz trwale usunąć „{{name}}”?", - "confirmDeleteMultipleItems": "Czy na pewno chcesz trwale usunąć {{count}} elementów?", - "confirmDeleteMultipleItemsWithFolders": "Czy na pewno chcesz trwale usunąć {{count}} elementów? Dotyczy to również folderów i ich zawartości.", - "confirmDeleteFolder": "Czy na pewno chcesz trwale usunąć folder „{{name}}” i całą jego zawartość?", - "permanentDeleteWarning": "Tej czynności nie można cofnąć. Elementy zostaną trwale usunięte z serwera.", - "moveToTrashWarning": "Elementy zostaną przeniesione do Kosza Termix i będzie je można przywrócić przed upływem okresu przechowywania.", - "itemsMovedToTrash": "{{count}} elementów przeniesiono do Kosza", - "trash": "Kosz", - "trashDescription": "Usunięte elementy są przechowywane na tym hoście przez {{days}} dni.", - "trashLoadFailed": "Nie udało się załadować Kosza", - "trashEmpty": "Kosz jest pusty", - "trashRestore": "Przywróć", - "trashRestored": "Przywrócono {{name}}", - "trashRestoreFailed": "Nie udało się przywrócić elementu. Oryginalna ścieżka może już istnieć.", - "trashDeletePermanently": "Usuń trwale", - "trashDeleteConfirm": "Czy trwale usunąć {{name}}? Tego nie można cofnąć.", - "trashPermanentlyDeleted": "Trwale usunięto {{name}}", - "trashDeleteFailed": "Nie udało się trwale usunąć elementu", - "trashEmptyAction": "Opróżnij kosz", - "trashEmptyConfirm": "Trwale usunąć wszystkie elementy z Kosza? Tego nie da się cofnąć.", - "trashEmptied": "Kosz opróżniony", - "trashRetention": "Automatyczne czyszczenie po (dniach)", - "trashRetentionSaved": "Zaktualizowano czas przechowywania w Koszu", - "trashRetentionFailed": "Nie udało się zaktualizować czasu przechowywania w Koszu", - "trashUnavailableConfirm": "Tego elementu nie można przenieść do Kosza, zazwyczaj dlatego, że znajduje się w innym systemie plików lub wymaga podwyższonych uprawnień. Czy zamiast tego usunąć go trwale?", - "recent": "Ostatnie", - "pinned": "Przypięte", - "folderShortcuts": "Skróty folderów", - "failedToReconnectSSH": "Nie udało się ponownie połączyć z sesją SSH", - "openTerminalHere": "Otwórz terminal tutaj", - "run": "Uruchom", - "openTerminalInFolder": "Otwórz terminal w tym folderze", - "openTerminalInFileLocation": "Otwórz terminal w lokalizacji pliku", - "runningFile": "Wykonywanie - {{file}}", - "onlyRunExecutableFiles": "Można uruchamiać tylko pliki wykonywalne", - "directories": "Katalogi", - "removedFromRecentFiles": "Usunięto „{{name}}” z ostatnich plików", - "removeFailed": "Nie udało się usunąć", - "unpinnedSuccessfully": "Pomyślnie odpięto „{{name}}”", - "unpinFailed": "Odpięcie nie powiodło się", - "removedShortcut": "Usunięto skrót „{{name}}”", - "removeShortcutFailed": "Nie udało się usunąć skrótu", - "clearedAllRecentFiles": "Wyczyszczono wszystkie ostatnie pliki", - "clearFailed": "Nie udało się wyczyścić", - "removeFromRecentFiles": "Usuń z ostatnich plików", - "clearAllRecentFiles": "Wyczyść wszystkie ostatnie pliki", - "unpinFile": "Odepnij plik", - "removeShortcut": "Usuń skrót", - "pinFile": "Przypnij plik", - "addToShortcuts": "Dodaj do skrótów", - "pasteFailed": "Wklejenie nie powiodło się", - "noUndoableActions": "Brak działań do cofnięcia", - "undoCopySuccess": "Cofnięto kopiowanie: usunięto {{count}} skopiowanych plików", - "undoCopyFailedDelete": "Nie udało się cofnąć: Nie udało się usunąć żadnych skopiowanych plików", - "undoCopyFailedNoInfo": "Nie udało się cofnąć: Nie można znaleźć informacji o skopiowanym pliku", - "undoMoveSuccess": "Cofnięto przenoszenie: przeniesiono {{count}} plików z powrotem do pierwotnej lokalizacji", - "undoMoveFailedMove": "Nie udało się cofnąć: Nie można przenieść żadnych plików z powrotem", - "undoMoveFailedNoInfo": "Nie udało się cofnąć: Nie można znaleźć informacji o przeniesionym pliku", - "undoDeleteNotSupported": "Operacji usunięcia nie można cofnąć: pliki zostały trwale usunięte z serwera", - "undoTypeNotSupported": "Nieobsługiwany typ operacji cofania", - "undoOperationFailed": "Operacja cofania nie powiodła się", - "unknownError": "Nieznany błąd", - "confirm": "Potwierdź", - "find": "Znajdź...", - "replace": "Zamień", - "downloadInstead": "Pobierz zamiast tego", - "keyboardShortcuts": "Skróty klawiaturowe", - "searchAndReplace": "Wyszukaj i zamień", - "editing": "Edycja", - "search": "Szukaj", - "findNext": "Znajdź następny", - "findPrevious": "Znajdź poprzednie", - "save": "Zapisz", - "selectAll": "Zaznacz wszystko", - "undo": "Cofnij", - "redo": "Ponów", - "moveLineUp": "Przesuń linię w górę", - "moveLineDown": "Przesuń linię w dół", - "toggleComment": "Przełącz komentarz", - "autoComplete": "Automatyczne uzupełnianie", - "imageLoadError": "Nie udało się załadować obrazu", - "startTyping": "Zacznij pisać...", - "unknownSize": "Nieznany rozmiar", - "fileIsEmpty": "Plik jest pusty", - "largeFileWarning": "Ostrzeżenie o dużym pliku", - "largeFileWarningDesc": "Ten plik ma rozmiar {{size}}, co może powodować problemy z wydajnością przy otwarciu jako tekst.", - "fileNotFoundAndRemoved": "Plik „{{name}}” nie został znaleziony i został usunięty z ostatnich/przypiętych plików", - "failedToLoadFile": "Nie udało się załadować pliku: {{error}}", - "serverErrorOccurred": "Wystąpił błąd serwera. Spróbuj ponownie później.", - "autoSaveFailed": "Automatyczne zapisywanie nie powiodło się", - "fileAutoSaved": "Plik zapisany automatycznie", - "moveFileFailed": "Nie udało się przenieść {{name}}", - "moveOperationFailed": "Operacja przenoszenia nie powiodła się", - "canOnlyCompareFiles": "Można porównać tylko dwa pliki", - "comparingFiles": "Porównywanie plików: {{file1}} i {{file2}}", - "dragFailed": "Operacja przeciągania nie powiodła się", - "filePinnedSuccessfully": "Plik „{{name}}” został pomyślnie przypięty", - "pinFileFailed": "Nie udało się przypiąć pliku", - "fileUnpinnedSuccessfully": "Plik „{{name}}” został pomyślnie odpięty", - "unpinFileFailed": "Nie udało się odpiąć pliku", - "shortcutAddedSuccessfully": "Skrót folderu „{{name}}” został pomyślnie dodany", - "addShortcutFailed": "Nie udało się dodać skrótu", - "operationCompletedSuccessfully": "{{operation}} {{count}} elementów pomyślnie", - "operationCompleted": "{{operation}} {{count}} elementów", - "downloadFileSuccess": "Plik {{name}} został pomyślnie pobrany", - "downloadFileFailed": "Pobieranie nie powiodło się", - "downloadingFile": "Pobieranie {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Przenieś do {{name}}", - "diffCompareWith": "Porównaj z {{name}}", - "dragOutsideToDownload": "Przeciągnij poza okno, aby pobrać ({{count}} plików)", - "newFolderDefault": "NowyFolder", - "newFileDefault": "NowyPlik.txt", - "successfullyMovedItems": "Pomyślnie przeniesiono {{count}} elementów do {{target}}", - "move": "Przenieś", - "searchInFile": "Wyszukaj w pliku (Ctrl+F)", - "showKeyboardShortcuts": "Pokaż skróty klawiaturowe", - "decreaseFontSize": "Zmniejsz rozmiar czcionki", - "increaseFontSize": "Zwiększ rozmiar czcionki", - "startWritingMarkdown": "Zacznij pisać treść w Markdown...", - "loadingFileComparison": "Ładowanie porównania plików...", - "reload": "Przeładuj", - "compare": "Porównaj", - "sideBySide": "Obok siebie", - "inline": "W linii", - "fileComparison": "Porównanie plików: {{file1}} vs {{file2}}", - "fileTooLarge": "Plik jest za duży: {{error}}", - "sshConnectionFailed": "Połączenie SSH nie powiodło się. Sprawdź połączenie z {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Nie udało się załadować pliku: {{error}}", - "connecting": "Łączenie...", - "connectedSuccessfully": "Połączono pomyślnie", - "totpVerificationFailed": "Weryfikacja TOTP nie powiodła się", - "warpgateVerificationFailed": "Uwierzytelnianie Warpgate nie powiodło się", - "authenticationFailed": "Uwierzytelnianie nie powiodło się", - "incorrectPassphrase": "Nieprawidłowe hasło klucza. Spróbuj ponownie.", - "verificationCodePrompt": "Kod weryfikacyjny:", - "changePermissions": "Zmień uprawnienia", - "currentPermissions": "Aktualne uprawnienia", - "owner": "Właściciel", - "group": "Grupa", - "others": "Inni", - "read": "Odczyt", - "write": "Zapis", - "execute": "Wykonywanie", - "permissionsChangedSuccessfully": "Uprawnienia zostały pomyślnie zmienione", - "failedToChangePermissions": "Nie udało się zmienić uprawnień", - "name": "Nazwa", - "sortByName": "Nazwa", - "sortByDate": "Data modyfikacji", - "sortBySize": "Rozmiar", - "ascending": "Rosnąco", - "descending": "Malejąco", - "root": "Katalog główny", - "new": "Nowy", - "sortBy": "Sortuj według", - "items": "Elementy", - "selected": "Zaznaczone", - "editor": "Edytor", - "octal": "Ósemkowy", - "storage": "Magazyn", - "disk": "Dysk", - "used": "Użyte", - "of": "z", - "toggleSidebar": "Przełącz pasek boczny", - "compactLayout": "Użyj kompaktowego układu", - "comfortableLayout": "Użyj wygodnego układu", - "cannotLoadPdf": "Nie można załadować pliku PDF", - "pdfLoadError": "Wystąpił błąd podczas ładowania pliku PDF.", - "loadingPdf": "Ładowanie pliku PDF...", - "loadingPage": "Ładowanie strony..." - }, "transfer": { "copyToHost": "Kopiuj do hosta…", "moveToHost": "Przenieś do hosta…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Ponowna próba nie powiodła się", "transferFailedRetryHint": "W miejscu docelowym zachowano częściowe dane. Ponowna próba zostanie wznowiona po przywróceniu połączenia." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Przesyłanie plików pomiędzy dwoma hostami SSH.", - "sourceServer": "Serwer źródłowy", - "destinationServer": "Serwer docelowy", - "selectHost": "Wybierz hosta", - "noHostSelected": "Nie wybrano hosta", - "move": "Przenosić", - "copy": "Kopia", - "upload": "Wgrywać", - "itemsSelected": "{{count}} wybrano element", - "itemsSelected_other": "Wybrano {{count}} elementów", - "ready": "Gotowy", - "loadingHosts": "Ładowanie hostów...", - "name": "Nazwa", - "modified": "Zmodyfikowany", - "size": "Rozmiar", - "noFiles": "Brak plików", - "open": "Otwarte", - "up": "W górę", - "refresh": "Odświeżać", - "copyToTarget": "Kopiuj do katalogu docelowego", - "rename": "Przemianować", - "delete": "Usuwać", - "editPermissions": "Edytuj uprawnienia", - "createNewFolder": "Utwórz nowy folder", - "deleteSelectedItems": "Usuń wybrane elementy", - "deleteConfirm": "Usunąć {{count}} wybrany element?", - "deleteConfirm_other": "Usunąć {{count}} wybranych elementów?", - "cancel": "Anulować", - "save": "Ratować", - "folderCreated": "Utworzono folder", - "itemRenamed": "Zmieniono nazwę elementu", - "actionFailed": "Akcja nie powiodła się", - "itemsDeleted": "{{count}} element usunięty", - "itemsDeleted_other": "Usunięto {{count}} elementów", - "deleteFailed": "Usunięcie nie powiodło się", - "permissionsUpdated": "Zaktualizowano uprawnienia", - "noRemoteSessionConnected": "Brak połączonych sesji zdalnych", - "failedToListFiles": "Nie udało się wyświetlić listy plików", - "failedToRefresh": "Nie udało się odświeżyć", - "destinationInsideSource": "Miejsce docelowe nie może znajdować się wewnątrz wybranej ścieżki źródłowej.", - "sameHostNoOp": "Źródło i cel znajdują się w tym samym folderze." - }, "tunnels": { "noSshTunnels": "Brak tuneli SSH", "createFirstTunnelMessage": "Nie utworzono jeszcze żadnych tuneli SSH. Skonfiguruj połączenia tunelowe w Menedżerze hostów, aby zacząć.", diff --git a/src/ui/locales/translated/pt_BR.json b/src/ui/locales/translated/pt_BR.json index d59433852..fd4cdedc5 100644 --- a/src/ui/locales/translated/pt_BR.json +++ b/src/ui/locales/translated/pt_BR.json @@ -715,7 +715,6 @@ "hostManager": "Gerente de Hospedagem", "credentials": "Credenciais", "portForwarding": "Encaminhamento", - "sftp": "SFTP", "connections": "Conexões", "serial": "Serial", "keepSideMenuOpen": "Mantenha o menu aberto", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Túneis", - "tabFiles": "Arquivos", "tabStats": "Métricas do host", "tabTelnet": "Telnet", "tabSharing": "Compartilhamento", "tabAuthentication": "Autenticação", "terminal": "terminal", "tunnel": "Túnel", - "fileManager": "Gerenciador de arquivos", "serverStats": "Métricas do host", "status": { "available": "Disponível", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} ignorado (nenhum IP encontrado)", "proxmoxStatsUrlCopied": "URL das estatísticas do Proxmox copiada para a área de transferência", "copyProxmoxStatsUrlAction": "Copiar URL de estatísticas do Proxmox", - "enableFileManagerMonitor": "Ativar o Gerenciador de Arquivos", - "enableFileManagerMonitorDesc": "Navegue e gerencie arquivos neste host via SFTP.", - "scpLegacyLabel": "Modo Legado SCP", - "scpLegacyDesc": "Utilize a transferência de arquivos legada para servidores que não suportam o subsistema SFTP (por exemplo, servidores SSH embutidos ou mínimos).", - "defaultPathLabel": "Caminho padrão", - "fileManagerPathHint": "O diretório a ser aberto quando o gerenciador de arquivos for iniciado para este host.", "statusChecksLabel": "Verificações de status", "enableStatusChecks": "Ativar verificações de status", "enableStatusChecksDesc": "Periodicamente, faça ping neste host para verificar a disponibilidade.", @@ -1278,7 +1269,6 @@ "deletedHost": "Excluído {{name}}", "copiedToClipboard": "Copiado para a área de transferência", "terminalUrlCopied": "URL do terminal copiada", - "fileManagerUrlCopied": "URL do Gerenciador de Arquivos copiada", "tunnelUrlCopied": "URL do túnel copiada", "dockerUrlCopied": "URL do Docker copiada", "hostMetricsUrlCopied": "URL do Host Metrics copiada", @@ -1296,7 +1286,6 @@ "copyAddress": "Copiar endereço", "copyLink": "Copiar link", "copyTerminalUrlAction": "Copiar URL do terminal", - "copyFileManagerUrlAction": "Copiar URL do Gerenciador de Arquivos", "copyTunnelUrlAction": "Copiar URL do túnel", "copyDockerUrlAction": "Copiar URL do Docker", "copyHostMetricsUrlAction": "Copiar URL de métricas do host", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Remover da divisão" } }, - "fileManager": { - "noHostSelected": "Nenhum host selecionado", - "initializingEditor": "Inicializando o editor...", - "file": "Arquivo", - "folder": "Pasta", - "uploadFile": "Carregar arquivo", - "downloadFile": "Download", - "extractArchive": "Extrair arquivo", - "extractingArchive": "Extraindo {{name}}...", - "archiveExtractedSuccessfully": "{{name}} extraído com sucesso", - "extractFailed": "A extração falhou", - "compressFile": "Compactar arquivo", - "compressFiles": "Comprimir arquivos", - "compressFilesDesc": "Comprima {{count}} itens em um arquivo", - "archiveName": "Nome do arquivo", - "enterArchiveName": "Digite o nome do arquivo...", - "compressionFormat": "Formato de compressão", - "selectedFiles": "Arquivos selecionados", - "andMoreFiles": "e {{count}} mais...", - "compress": "Compressa", - "compressingFiles": "Comprimir {{count}} itens em {{name}}...", - "filesCompressedSuccessfully": "{{name}} criado com sucesso", - "compressFailed": "A compressão falhou", - "edit": "Editar", - "preview": "Pré-visualização", - "previous": "Anterior", - "next": "Próximo", - "pageXOfY": "Página {{current}} de {{total}}", - "zoomOut": "Diminuir o zoom", - "zoomIn": "Ampliar", - "newFile": "Novo arquivo", - "newFolder": "Nova pasta", - "rename": "Renomear", - "uploading": "Carregando...", - "uploadingFile": "Carregando {{name}}...", - "fileName": "Nome do arquivo", - "folderName": "Nome da pasta", - "fileUploadedSuccessfully": "Arquivo \"{{name}}\" enviado com sucesso", - "failedToUploadFile": "Falha ao carregar o arquivo", - "uploadingFolderFiles": "Enviando {{count}} arquivo(s)...", - "uploadedFolderFiles": "Arquivo(s) carregado(s) com sucesso {{count}}", - "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", - "fileDownloadedSuccessfully": "Arquivo \"{{name}}\" baixado com sucesso", - "failedToDownloadFile": "Falha ao baixar o arquivo", - "fileCreatedSuccessfully": "Arquivo \"{{name}}\" criado com sucesso", - "folderCreatedSuccessfully": "Pasta \"{{name}}\" criada com sucesso", - "failedToCreateItem": "Falha ao criar o item", - "operationFailed": "A operação {{operation}} falhou para {{name}}: {{error}}", - "failedToResolveSymlink": "Falha ao resolver o link simbólico", - "itemsDeletedSuccessfully": "{{count}} itens excluídos com sucesso", - "failedToDeleteItems": "Falha ao excluir itens", - "sudoPasswordRequired": "Senha de administrador necessária", - "enterSudoPassword": "Digite a senha de sudo para continuar esta operação.", - "sudoPassword": "Senha de sudo", - "sudoOperationFailed": "A operação sudo falhou", - "sudoAuthFailed": "Falha na autenticação sudo", - "dragFilesToUpload": "Arraste e solte os arquivos aqui para fazer o upload.", - "dropToUploadHere": "Arraste e faça o upload aqui.", - "dropToDownloadHere": "Faça o download aqui.", - "localFiles": "Local", - "showLocalFiles": "Exibir arquivos locais (visualização dividida)", - "hideLocalFiles": "Ocultar arquivos locais", - "localHome": "Pasta inicial", - "localPathPlaceholder": "Caminho da pasta local", - "localShowHidden": "Mostrar arquivos ocultos", - "localHideHidden": "Ocultar arquivos ocultos", - "localRevealInFileManager": "Exibir no gerenciador de arquivos do sistema", - "localCannotOpenFolder": "Não é possível abrir a pasta", - "localItemCount": "{{count}} itens", - "localSelectedCount": "{{count}} selecionado", - "back": "Voltar", - "forward": "Avançar", - "up": "Acima", - "kind": "Tipo", - "localUploadingProgress": "Enviando {{current}} de {{total}}", - "localDownloadingProgress": "Baixando {{current}} de {{total}}", - "localTransferInFlight": "{{count}} imediatamente", - "localPreparingDownload": "Preparando o download...", - "localTransferCancelled": "Transferência cancelada", - "localNothingToTransfer": "Nada a transferir", - "localUploadComplete": "Uploaded {{count}} file(s)", - "localUploadFailed": "Falha no upload", - "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", - "localDownloadComplete": "Arquivo(s) baixado(s) {{count}}", - "localDownloadFailed": "Falha no download", - "localDownloadPartial": "Downloaded {{done}} file(s), {{failed}} failed", - "localReplaceExistingPrompt": "{{count}} Já existem itens nesta pasta. Deseja substituí-los?", - "localReplace": "Substituir", - "localSkipExisting": "Pular", - "localSkippedExisting": "Ignorou {{count}} item(ns) existente(s)", - "localUnsafeNamesSkipped": "Ignorado {{count}} item(ns) cujos nomes não podem ser usados neste computador", - "localOpen": "Abrir", - "localOpenFolder": "Abra a pasta", - "localUploadToRemote": "Enviar para o servidor", - "localUploadToRemoteMany": "Enviar {{count}} itens para o servidor", - "localMoveToTrash": "Mover para a Lixeira", - "localMoveToTrashMany": "Mova {{count}} itens para a Lixeira", - "localTrashConfirmSingle": "Mover \"{{name}}\" para a Lixeira?", - "localTrashConfirmFolder": "Mover a pasta \"{{name}}\" e todo o seu conteúdo para a Lixeira?", - "localTrashConfirmMany": "Mover {{count}} itens para a Lixeira?", - "localTrashed": "Movidos {{count}} itens para a Lixeira", - "localTrashFailed": "Não foi possível mover para a Lixeira", - "localRenameFailed": "A renomeação falhou", - "localCreateFailed": "Não foi possível criar o item.", - "goToParentFolder": "Acesse a pasta principal", - "resizeSidebar": "Arraste para redimensionar a barra lateral (clique duas vezes para redefinir).", - "resizeLocalPane": "Arraste para redimensionar o painel local (clique duas vezes para redefinir).", - "columns": "Colunas", - "columnsHint": "Clique com o botão direito do mouse para escolher as colunas.", - "emptyFolder": "Esta pasta está vazia", - "searchFiles": "Pesquisar arquivos...", - "upload": "Carregar", - "selectHostToStart": "Selecione um host para iniciar o gerenciamento de arquivos.", - "sshRequiredForFileManager": "O gerenciador de arquivos requer SSH. Este host não tem o SSH habilitado.", - "failedToConnect": "Falha ao conectar-se ao SSH", - "failedToLoadDirectory": "Falha ao carregar o diretório", - "noSSHConnection": "Nenhuma conexão SSH disponível", - "copy": "Cópia", - "cut": "Corte", - "paste": "Colar", - "copyPath": "Copiar caminho", - "copyPaths": "Copiar caminhos", - "delete": "Excluir", - "properties": "Propriedades", - "refresh": "Atualizar", - "downloadFiles": "Baixar {{count}} arquivos para o navegador", - "copyFiles": "Copiar {{count}} itens", - "cutFiles": "Recortar {{count}} itens", - "deleteFiles": "Excluir {{count}} itens", - "filesCopiedToClipboard": "{{count}} itens copiados para a área de transferência", - "filesCutToClipboard": "{{count}} itens copiados para a área de transferência", - "pathCopiedToClipboard": "Caminho copiado para a área de transferência", - "pathsCopiedToClipboard": "{{count}} caminhos copiados para a área de transferência", - "failedToCopyPath": "Falha ao copiar o caminho para a área de transferência", - "copyFolderLink": "Copiar link para a pasta", - "copyCurrentFolderLink": "Copiar link para a pasta atual", - "folderLinkCopied": "Link da pasta copiado para a área de transferência", - "failedToCopyFolderLink": "Falha ao copiar o link da pasta", - "movedItems": "Itens movidos {{count}}", - "failedToDeleteItem": "Falha ao excluir o item", - "itemRenamedSuccessfully": "{{type}} renomeado com sucesso", - "failedToRenameItem": "Falha ao renomear o item", - "download": "Download", - "openExternalEditor": "Abrir externamente", - "chooseExternalEditor": "Escolha um editor externo.", - "externalEditorSelected": "Editor externo selecionado", - "externalEditorDesktopOnly": "O editor externo está disponível apenas no aplicativo para desktop.", - "externalEditorOpened": "Aberto em um editor externo. Salvar aqui enviará as alterações de volta para o servidor.", - "failedToOpenExternalEditor": "Não foi possível abrir o editor externo.", - "failedToSelectExternalEditor": "Não foi possível selecionar o editor externo.", - "permissions": "Permissões", - "size": "Tamanho", - "modified": "Modificado", - "path": "Caminho", - "confirmDelete": "Tem certeza de que deseja excluir {{name}}?", - "permissionDenied": "Permissão negada", - "serverError": "Erro do servidor", - "fileSavedSuccessfully": "Arquivo salvo com sucesso", - "failedToSaveFile": "Falha ao salvar o arquivo", - "confirmDeleteSingleItem": "Tem certeza de que deseja excluir permanentemente \"{{name}}\"?", - "confirmDeleteMultipleItems": "Tem certeza de que deseja excluir permanentemente {{count}} itens?", - "confirmDeleteMultipleItemsWithFolders": "Tem certeza de que deseja excluir permanentemente {{count}} itens? Isso inclui pastas e seus conteúdos.", - "confirmDeleteFolder": "Tem certeza de que deseja excluir permanentemente a pasta \"{{name}}\" e todo o seu conteúdo?", - "permanentDeleteWarning": "Esta ação não pode ser desfeita. O(s) item(ns) será(ão) excluído(s) permanentemente do servidor.", - "moveToTrashWarning": "O(s) item(ns) será(ão) movido(s) para a Lixeira Termix e poderá(ão) ser restaurado(s) antes do término do período de retenção.", - "itemsMovedToTrash": "{{count}} item(ns) movido(s) para a Lixeira", - "trash": "Lixo", - "trashDescription": "Os itens excluídos são retidos por {{days}} dia(s) neste host.", - "trashLoadFailed": "Falha ao carregar a Lixeira", - "trashEmpty": "A lixeira está vazia.", - "trashRestore": "Restaurar", - "trashRestored": "Restaurado {{name}}", - "trashRestoreFailed": "Falha ao restaurar o item. O caminho original pode já existir.", - "trashDeletePermanently": "Excluir permanentemente", - "trashDeleteConfirm": "Excluir permanentemente {{name}}? Esta ação não pode ser desfeita.", - "trashPermanentlyDeleted": "Excluído permanentemente {{name}}", - "trashDeleteFailed": "Falha ao excluir o item permanentemente", - "trashEmptyAction": "Esvaziar o lixo", - "trashEmptyConfirm": "Excluir permanentemente todos os itens da Lixeira? Esta ação é irreversível.", - "trashEmptied": "Lixo esvaziado", - "trashRetention": "Limpeza automática após (dias)", - "trashRetentionSaved": "Retenção de lixo atualizada", - "trashRetentionFailed": "Falha na atualização da retenção de lixo", - "trashUnavailableConfirm": "Este item não pode ser movido para a Lixeira, geralmente porque está em outro sistema de arquivos ou requer permissões elevadas. Deseja excluí-lo permanentemente?", - "recent": "Recente", - "pinned": "Fixado", - "folderShortcuts": "Atalhos de pastas", - "failedToReconnectSSH": "Falha ao reconectar a sessão SSH", - "openTerminalHere": "Abra o terminal aqui", - "run": "Correr", - "openTerminalInFolder": "Abra o terminal nesta pasta.", - "openTerminalInFileLocation": "Abra o terminal no local do arquivo.", - "runningFile": "Correndo - {{file}}", - "onlyRunExecutableFiles": "Só é possível executar arquivos executáveis.", - "directories": "Diretórios", - "removedFromRecentFiles": "Removemos \"{{name}}\" dos arquivos recentes", - "removeFailed": "Remover falha", - "unpinnedSuccessfully": "Desfixado \"{{name}}\" com sucesso", - "unpinFailed": "Desafixar falhou", - "removedShortcut": "Atalho removido \"{{name}}\"", - "removeShortcutFailed": "A remoção do atalho falhou", - "clearedAllRecentFiles": "Apaguei todos os arquivos recentes.", - "clearFailed": "Limpar falhou", - "removeFromRecentFiles": "Remover dos arquivos recentes", - "clearAllRecentFiles": "Limpar todos os arquivos recentes", - "unpinFile": "Desafixar arquivo", - "removeShortcut": "Remover atalho", - "pinFile": "Arquivo PIN", - "addToShortcuts": "Adicionar aos atalhos", - "pasteFailed": "A colagem falhou", - "noUndoableActions": "Ações não podem ser desfeitas", - "undoCopySuccess": "Operação de cópia desfeita: Arquivos copiados excluídos {{count}}", - "undoCopyFailedDelete": "Falha ao desfazer: Não foi possível excluir nenhum arquivo copiado.", - "undoCopyFailedNoInfo": "A ação de desfazer falhou: Não foi possível encontrar as informações do arquivo copiado.", - "undoMoveSuccess": "Operação de movimentação desfeita: {{count}} arquivos foram movidos de volta para o local original.", - "undoMoveFailedMove": "Falha ao desfazer: Não foi possível mover nenhum arquivo de volta.", - "undoMoveFailedNoInfo": "A ação de desfazer falhou: Não foi possível encontrar as informações do arquivo movido.", - "undoDeleteNotSupported": "A operação de exclusão não pode ser desfeita: os arquivos foram excluídos permanentemente do servidor.", - "undoTypeNotSupported": "Tipo de operação de desfazer não suportado", - "undoOperationFailed": "A operação de desfazer falhou", - "unknownError": "Erro desconhecido", - "confirm": "Confirmar", - "find": "Encontrar...", - "replace": "Substituir", - "downloadInstead": "Baixe em vez de baixar.", - "keyboardShortcuts": "Atalhos de teclado", - "searchAndReplace": "Localizar e substituir", - "editing": "Edição", - "search": "Procurar", - "findNext": "Encontre a seguir", - "findPrevious": "Encontrar Anterior", - "save": "Salvar", - "selectAll": "Selecionar tudo", - "undo": "Desfazer", - "redo": "Refazer", - "moveLineUp": "Mova a fila para cima", - "moveLineDown": "Deslizar a linha para baixo", - "toggleComment": "Alternar comentário", - "autoComplete": "Preenchimento automático", - "imageLoadError": "Falha ao carregar a imagem", - "startTyping": "Comece a digitar...", - "unknownSize": "Tamanho desconhecido", - "fileIsEmpty": "O arquivo está vazio", - "largeFileWarning": "Aviso de Arquivo Grande", - "largeFileWarningDesc": "Este arquivo tem {{size}} de tamanho, o que pode causar problemas de desempenho ao ser aberto como texto.", - "fileNotFoundAndRemoved": "O arquivo \"{{name}}\" não foi encontrado e foi removido dos arquivos recentes/fixados", - "failedToLoadFile": "Falha ao carregar o arquivo: {{error}}", - "serverErrorOccurred": "Ocorreu um erro no servidor. Tente novamente mais tarde.", - "autoSaveFailed": "O salvamento automático falhou", - "fileAutoSaved": "Arquivo salvo automaticamente", - "moveFileFailed": "Falha ao mover {{name}}", - "moveOperationFailed": "A operação de movimentação falhou", - "canOnlyCompareFiles": "Só é possível comparar dois arquivos.", - "comparingFiles": "Comparando arquivos: {{file1}} e {{file2}}", - "dragFailed": "A operação de arrastar falhou", - "filePinnedSuccessfully": "Arquivo \"{{name}}\" fixado com sucesso", - "pinFileFailed": "Falha ao fixar o arquivo", - "fileUnpinnedSuccessfully": "Arquivo \"{{name}}\" desafixado com sucesso", - "unpinFileFailed": "Falha ao desafixar o arquivo", - "shortcutAddedSuccessfully": "Atalho de pasta \"{{name}}\" adicionado com sucesso", - "addShortcutFailed": "Falha ao adicionar atalho", - "operationCompletedSuccessfully": "{{operation}} {{count}} itens com sucesso", - "operationCompleted": "{{operation}} {{count}} itens", - "downloadFileSuccess": "Arquivo {{name}} baixado com sucesso", - "downloadFileFailed": "Falha no download", - "downloadingFile": "Baixando {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Mover para {{name}}", - "diffCompareWith": "Diff compare com {{name}}", - "dragOutsideToDownload": "Arraste para fora da janela para baixar ({{count}} arquivos)", - "newFolderDefault": "Nova pasta", - "newFileDefault": "NovoArquivo.txt", - "successfullyMovedItems": "Movidos com sucesso {{count}} itens para {{target}}", - "move": "Mover", - "searchInFile": "Pesquisar no arquivo (Ctrl+F)", - "showKeyboardShortcuts": "Mostrar atalhos de teclado", - "decreaseFontSize": "Diminuir o tamanho da fonte", - "increaseFontSize": "Aumentar o tamanho da fonte", - "startWritingMarkdown": "Comece a escrever seu conteúdo em Markdown...", - "loadingFileComparison": "Carregando comparação de arquivos...", - "reload": "Recarregar", - "compare": "Comparar", - "sideBySide": "Lado a lado", - "inline": "Em linha", - "fileComparison": "Comparação de arquivos: {{file1}} vs {{file2}}", - "fileTooLarge": "Arquivo muito grande: {{error}}", - "sshConnectionFailed": "A conexão SSH falhou. Verifique sua conexão com {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Falha ao carregar o arquivo: {{error}}", - "connecting": "Conectando...", - "connectedSuccessfully": "Conexão estabelecida com sucesso.", - "totpVerificationFailed": "A verificação TOTP falhou.", - "warpgateVerificationFailed": "A autenticação do portal de guerra falhou.", - "authenticationFailed": "A autenticação falhou.", - "incorrectPassphrase": "Senha incorreta. Tente novamente.", - "verificationCodePrompt": "Código de verificação:", - "changePermissions": "Alterar permissões", - "currentPermissions": "Permissões atuais", - "owner": "Proprietário", - "group": "Grupo", - "others": "Outros", - "read": "Ler", - "write": "Escrever", - "execute": "Executar", - "permissionsChangedSuccessfully": "Permissões alteradas com sucesso", - "failedToChangePermissions": "Falha ao alterar as permissões", - "name": "Nome", - "sortByName": "Nome", - "sortByDate": "Data de modificação", - "sortBySize": "Tamanho", - "ascending": "Ascendente", - "descending": "Descendente", - "root": "Raiz", - "new": "Novo", - "sortBy": "Ordenar por", - "items": "Unid", - "selected": "Selecionado", - "editor": "Editor", - "octal": "Octal", - "storage": "Armazenar", - "disk": "Disco", - "used": "Usado", - "of": "de", - "toggleSidebar": "Alternar barra lateral", - "compactLayout": "Utilize um layout compacto.", - "comfortableLayout": "Use um layout confortável", - "cannotLoadPdf": "Não foi possível carregar o PDF.", - "pdfLoadError": "Ocorreu um erro ao carregar este arquivo PDF.", - "loadingPdf": "Carregando PDF...", - "loadingPage": "Carregando página..." - }, "transfer": { "copyToHost": "Copiar para o host…", "moveToHost": "Mover para o host…", @@ -2292,50 +1951,6 @@ "retryTransferError": "A nova tentativa falhou.", "transferFailedRetryHint": "Dados parciais foram mantidos no destino. A tentativa será retomada quando a conexão for restabelecida." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Transferir arquivos entre dois hosts SSH.", - "sourceServer": "Servidor de origem", - "destinationServer": "Servidor de destino", - "selectHost": "Selecione o host", - "noHostSelected": "Nenhum host selecionado", - "move": "Mover", - "copy": "Cópia", - "upload": "Carregar", - "itemsSelected": "{{count}} item selecionado", - "itemsSelected_other": "{{count}} itens selecionados", - "ready": "Preparar", - "loadingHosts": "Carregando hosts...", - "name": "Nome", - "modified": "Modificado", - "size": "Tamanho", - "noFiles": "Nenhum arquivo", - "open": "Abrir", - "up": "Acima", - "refresh": "Atualizar", - "copyToTarget": "Copiar para o diretório de destino", - "rename": "Renomear", - "delete": "Excluir", - "editPermissions": "Editar permissões", - "createNewFolder": "Criar nova pasta", - "deleteSelectedItems": "Excluir itens selecionados", - "deleteConfirm": "Delete {{count}} selected item?", - "deleteConfirm_other": "Excluir {{count}} itens selecionados?", - "cancel": "Cancelar", - "save": "Salvar", - "folderCreated": "Pasta criada", - "itemRenamed": "Item renomeado", - "actionFailed": "A ação falhou", - "itemsDeleted": "{{count}} item excluído", - "itemsDeleted_other": "{{count}} itens excluídos", - "deleteFailed": "Falha ao excluir", - "permissionsUpdated": "Permissões atualizadas", - "noRemoteSessionConnected": "Nenhuma sessão remota conectada.", - "failedToListFiles": "Falha ao listar os arquivos", - "failedToRefresh": "Falha ao atualizar", - "destinationInsideSource": "O destino não pode estar dentro do caminho de origem selecionado.", - "sameHostNoOp": "A origem e o destino são a mesma pasta." - }, "tunnels": { "noSshTunnels": "Sem túneis SSH", "createFirstTunnelMessage": "Você ainda não criou nenhum túnel SSH. Configure as conexões de túnel no Gerenciador de Hosts para começar.", diff --git a/src/ui/locales/translated/pt_PT.json b/src/ui/locales/translated/pt_PT.json index da95ce6c9..3a0bf54e7 100644 --- a/src/ui/locales/translated/pt_PT.json +++ b/src/ui/locales/translated/pt_PT.json @@ -715,7 +715,6 @@ "hostManager": "Gestor de Máquinas", "credentials": "Credenciais", "portForwarding": "Encaminhamento", - "sftp": "SFTP", "connections": "Ligações", "serial": "Serial", "keepSideMenuOpen": "Mantenha o menu aberto", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Túneis", - "tabFiles": "Ficheiros", "tabStats": "Métricas do Host", "tabTelnet": "Telnet", "tabSharing": "Partilha", "tabAuthentication": "Autenticação", "terminal": "Terminal", "tunnel": "Túnel", - "fileManager": "Gestor de Ficheiros", "serverStats": "Métricas do Host", "status": { "available": "Disponível", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} ignorado (nenhum IP encontrado)", "proxmoxStatsUrlCopied": "URL das estatísticas do Proxmox copiado para a área de transferência", "copyProxmoxStatsUrlAction": "Copiar URL das estatísticas do Proxmox", - "enableFileManagerMonitor": "Ativar Gestor de Ficheiros", - "enableFileManagerMonitorDesc": "Explorar e gerir ficheiros neste host via SFTP", - "scpLegacyLabel": "Modo SCP Legado", - "scpLegacyDesc": "Utilizar transferência de ficheiros legada para servidores que não suportam o subsistema SFTP (ex.: servidores SSH embebidos ou mínimos)", - "defaultPathLabel": "Caminho Predefinido", - "fileManagerPathHint": "O diretório a abrir quando o gestor de ficheiros for iniciado para este host", "statusChecksLabel": "Verificações de Estado", "enableStatusChecks": "Ativar Verificações de Estado", "enableStatusChecksDesc": "Fazer ping periodicamente a este host para verificar a disponibilidade", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} eliminado", "copiedToClipboard": "Copiado para a área de transferência", "terminalUrlCopied": "URL do terminal copiada", - "fileManagerUrlCopied": "URL do gestor de ficheiros copiada", "tunnelUrlCopied": "URL do túnel copiada", "dockerUrlCopied": "URL do Docker copiada", "hostMetricsUrlCopied": "URL das métricas do host copiada", @@ -1296,7 +1286,6 @@ "copyAddress": "Copiar endereço", "copyLink": "Copiar link", "copyTerminalUrlAction": "Copiar URL do Terminal", - "copyFileManagerUrlAction": "Copiar URL do Gestor de Ficheiros", "copyTunnelUrlAction": "Copiar URL do Túnel", "copyDockerUrlAction": "Copiar URL do Docker", "copyHostMetricsUrlAction": "Copiar URL das Métricas do host", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Remover da Divisão" } }, - "fileManager": { - "noHostSelected": "Nenhum host selecionado", - "initializingEditor": "A inicializar editor...", - "file": "Ficheiro", - "folder": "Pasta", - "uploadFile": "Carregar Ficheiro", - "downloadFile": "Descarregar", - "extractArchive": "Extrair Arquivo", - "extractingArchive": "A extrair {{name}}...", - "archiveExtractedSuccessfully": "{{name}} extraído com sucesso", - "extractFailed": "Extração falhou", - "compressFile": "Comprimir Ficheiro", - "compressFiles": "Comprimir Ficheiros", - "compressFilesDesc": "Comprimir {{count}} itens num arquivo", - "archiveName": "Nome do arquivo", - "enterArchiveName": "Introduza o nome do arquivo...", - "compressionFormat": "Formato de compressão", - "selectedFiles": "Ficheiros selecionados", - "andMoreFiles": "e mais {{count}}...", - "compress": "Comprimir", - "compressingFiles": "A comprimir {{count}} itens em {{name}}...", - "filesCompressedSuccessfully": "{{name}} criado com sucesso", - "compressFailed": "Compressão falhou", - "edit": "Editar", - "preview": "Pré-visualizar", - "previous": "Anterior", - "next": "Seguinte", - "pageXOfY": "Página {{current}} de {{total}}", - "zoomOut": "Reduzir", - "zoomIn": "Ampliar", - "newFile": "Novo Ficheiro", - "newFolder": "Nova Pasta", - "rename": "Renomear", - "uploading": "A enviar...", - "uploadingFile": "A enviar {{name}}...", - "fileName": "Nome do ficheiro", - "folderName": "Nome da pasta", - "fileUploadedSuccessfully": "Ficheiro \"{{name}}\" enviado com sucesso", - "failedToUploadFile": "Falha ao enviar ficheiro", - "uploadingFolderFiles": "A enviar {{count}} ficheiro(s)...", - "uploadedFolderFiles": "Ficheiro(s) carregado(s) com sucesso {{count}}", - "uploadedFolderPartial": "Uploaded {{uploaded}} file(s), {{failed}} failed", - "fileDownloadedSuccessfully": "Ficheiro \"{{name}}\" descarregado com sucesso", - "failedToDownloadFile": "Falha ao descarregar ficheiro", - "fileCreatedSuccessfully": "Ficheiro \"{{name}}\" criado com sucesso", - "folderCreatedSuccessfully": "Pasta \"{{name}}\" criada com sucesso", - "failedToCreateItem": "Falha ao criar item", - "operationFailed": "Operação {{operation}} falhou para {{name}}: {{error}}", - "failedToResolveSymlink": "Falha ao resolver ligação simbólica", - "itemsDeletedSuccessfully": "{{count}} itens eliminados com êxito", - "failedToDeleteItems": "Falha ao eliminar itens", - "sudoPasswordRequired": "Palavra-passe de administrador necessária", - "enterSudoPassword": "Introduza a palavra-passe sudo para continuar esta operação", - "sudoPassword": "Palavra-passe sudo", - "sudoOperationFailed": "Operação sudo falhou", - "sudoAuthFailed": "Autenticação sudo falhou", - "dragFilesToUpload": "Arraste ficheiros aqui para carregar", - "dropToUploadHere": "Arraste e faça o upload aqui.", - "dropToDownloadHere": "Faça o download aqui.", - "localFiles": "Local", - "showLocalFiles": "Visualizar ficheiros locais (visualização dividida)", - "hideLocalFiles": "Ocultar ficheiros locais", - "localHome": "Pasta inicial", - "localPathPlaceholder": "Caminho da pasta local", - "localShowHidden": "Mostrar ficheiros ocultos", - "localHideHidden": "Ocultar ficheiros ocultos", - "localRevealInFileManager": "Exibir no gestor de ficheiros do sistema", - "localCannotOpenFolder": "Não é possível abrir a pasta", - "localItemCount": "{{count}} artigos", - "localSelectedCount": "{{count}} selecionado", - "back": "Costas", - "forward": "Encaminhar", - "up": "Para cima", - "kind": "Amabilidade", - "localUploadingProgress": "A enviar {{current}} de {{total}}", - "localDownloadingProgress": "A descarregar {{current}} de {{total}}", - "localTransferInFlight": "{{count}} imediatamente", - "localPreparingDownload": "A preparar o download...", - "localTransferCancelled": "Transferência cancelada", - "localNothingToTransfer": "Nada a transferir", - "localUploadComplete": "Uploaded {{count}} file(s)", - "localUploadFailed": "Falha no upload", - "localUploadPartial": "Uploaded {{done}} file(s), {{failed}} failed", - "localDownloadComplete": "Ficheiro(s) descarregado(s) {{count}}", - "localDownloadFailed": "Falha no download", - "localDownloadPartial": "Downloaded {{done}} file(s), {{failed}} failed", - "localReplaceExistingPrompt": "{{count}} Já existem itens nesta pasta. Deseja substituí-los?", - "localReplace": "Substituir", - "localSkipExisting": "Ignorar", - "localSkippedExisting": "Ignorou {{count}} item(s) existente(s)", - "localUnsafeNamesSkipped": "Ignorado {{count}} item(s) cujos nomes não podem ser utilizados neste computador", - "localOpen": "Aberto", - "localOpenFolder": "Abra a pasta", - "localUploadToRemote": "Enviar para o servidor", - "localUploadToRemoteMany": "Enviar {{count}} itens para o servidor", - "localMoveToTrash": "Mover para a Reciclagem", - "localMoveToTrashMany": "Mover {{count}} itens para a Reciclagem", - "localTrashConfirmSingle": "Deslocar \"{{name}}\" para a Reciclagem?", - "localTrashConfirmFolder": "Mover a pasta \"{{name}}\" e todo o seu conteúdo para a Reciclagem?", - "localTrashConfirmMany": "Mover {{count}} itens para a Reciclagem?", - "localTrashed": "Movidos {{count}} itens para a Reciclagem", - "localTrashFailed": "Não foi possível mover para a Reciclagem", - "localRenameFailed": "A renomeação falhou", - "localCreateFailed": "Não foi possível criar o item.", - "goToParentFolder": "Aceda à pasta principal", - "resizeSidebar": "Arraste para redimensionar a barra lateral (clique duas vezes para repor).", - "resizeLocalPane": "Arraste para redimensionar o painel local (clique duas vezes para repor).", - "columns": "Colunas", - "columnsHint": "Clique com o botão direito do rato para escolher as colunas.", - "emptyFolder": "Esta pasta está vazia", - "searchFiles": "Procurar ficheiros...", - "upload": "Carregar", - "selectHostToStart": "Selecione uma máquina para iniciar a gestão de ficheiros", - "sshRequiredForFileManager": "O gestor de ficheiros requer SSH. Esta máquina não tem SSH ativado.", - "failedToConnect": "Falha ao ligar ao SSH", - "failedToLoadDirectory": "Falha ao carregar diretório", - "noSSHConnection": "Nenhuma ligação SSH disponível", - "copy": "Copiar", - "cut": "Cortar", - "paste": "Colar", - "copyPath": "Copiar caminho", - "copyPaths": "Copiar caminhos", - "delete": "Eliminar", - "properties": "Propriedades", - "refresh": "Atualizar", - "downloadFiles": "Descarregar {{count}} ficheiros para o navegador", - "copyFiles": "Copiar {{count}} itens", - "cutFiles": "Cortar {{count}} itens", - "deleteFiles": "Eliminar {{count}} itens", - "filesCopiedToClipboard": "{{count}} itens copiados para a área de transferência", - "filesCutToClipboard": "{{count}} itens cortados para a área de transferência", - "pathCopiedToClipboard": "Caminho copiado para a área de transferência", - "pathsCopiedToClipboard": "{{count}} caminhos copiados para a área de transferência", - "failedToCopyPath": "Falha ao copiar o caminho para a área de transferência", - "copyFolderLink": "Copiar ligação para a pasta", - "copyCurrentFolderLink": "Copiar ligação para a pasta atual", - "folderLinkCopied": "Ligação da pasta copiada para a área de transferência", - "failedToCopyFolderLink": "Falha ao copiar a ligação da pasta", - "movedItems": "{{count}} itens movidos", - "failedToDeleteItem": "Falha ao eliminar o item", - "itemRenamedSuccessfully": "Renomeação de {{type}} bem-sucedida.", - "failedToRenameItem": "Falha ao renomear o item", - "download": "Transferir", - "openExternalEditor": "Abrir externamente", - "chooseExternalEditor": "Escolher editor externo", - "externalEditorSelected": "Editor externo selecionado", - "externalEditorDesktopOnly": "O editor externo está disponível apenas na aplicação de ambiente de trabalho", - "externalEditorOpened": "Aberto no editor externo. Ao guardar, as alterações serão enviadas de volta para o servidor.", - "failedToOpenExternalEditor": "Falha ao abrir o editor externo", - "failedToSelectExternalEditor": "Falha ao selecionar o editor externo", - "permissions": "Permissões", - "size": "Tamanho", - "modified": "Modificado", - "path": "Caminho", - "confirmDelete": "Tem a certeza de que pretende eliminar {{name}}?", - "permissionDenied": "Permissão negada", - "serverError": "Erro do servidor", - "fileSavedSuccessfully": "Ficheiro guardado com sucesso", - "failedToSaveFile": "Falha ao guardar o ficheiro", - "confirmDeleteSingleItem": "Tem a certeza de que pretende eliminar permanentemente \"{{name}}\"?", - "confirmDeleteMultipleItems": "Tem a certeza de que pretende eliminar permanentemente {{count}} itens?", - "confirmDeleteMultipleItemsWithFolders": "Tem a certeza de que pretende eliminar permanentemente {{count}} itens? Isto inclui pastas e o seu conteúdo.", - "confirmDeleteFolder": "Tem a certeza de que pretende eliminar permanentemente a pasta \"{{name}}\" e todo o seu conteúdo?", - "permanentDeleteWarning": "Esta ação não pode ser anulada. Os itens serão permanentemente eliminados do servidor.", - "moveToTrashWarning": "O(s) artigo(s) será(ão) movido(s) para a Reciclagem Termix e poderá(ão) ser restaurado(s) antes do fim do período de retenção.", - "itemsMovedToTrash": "{{count}} artigo(s) movido(s) para a Reciclagem", - "trash": "Lixo", - "trashDescription": "Os itens eliminados são retidos durante {{days}} dia(s) neste host.", - "trashLoadFailed": "Falha ao carregar a Reciclagem", - "trashEmpty": "A papeleira está vazia.", - "trashRestore": "Restaurar", - "trashRestored": "Restaurado {{name}}", - "trashRestoreFailed": "Falha ao restaurar o item. O caminho original pode já existir.", - "trashDeletePermanently": "Apagar permanentemente", - "trashDeleteConfirm": "Apagar permanentemente {{name}}? Esta ação não pode ser anulada.", - "trashPermanentlyDeleted": "Eliminado permanentemente {{name}}", - "trashDeleteFailed": "Falha ao eliminar o item permanentemente", - "trashEmptyAction": "Esvaziar o lixo", - "trashEmptyConfirm": "Apagar permanentemente todos os itens da Reciclagem? Esta ação é irreversível.", - "trashEmptied": "Lixo esvaziado", - "trashRetention": "Limpeza automática após (dias)", - "trashRetentionSaved": "Retenção de lixo atualizada", - "trashRetentionFailed": "Falha na atualização da retenção de lixo", - "trashUnavailableConfirm": "Este item não pode ser movido para a Reciclagem, geralmente porque está noutro sistema de ficheiros ou requer permissões elevadas. Deseja eliminá-lo permanentemente?", - "recent": "Recentes", - "pinned": "Fixados", - "folderShortcuts": "Atalhos de pastas", - "failedToReconnectSSH": "Falha ao restabelecer a sessão SSH", - "openTerminalHere": "Abrir Terminal Aqui", - "run": "Executar", - "openTerminalInFolder": "Abrir Terminal Nesta Pasta", - "openTerminalInFileLocation": "Abrir Terminal na Localização do Ficheiro", - "runningFile": "Em execução - {{file}}", - "onlyRunExecutableFiles": "Só pode executar ficheiros executáveis", - "directories": "Pastas", - "removedFromRecentFiles": "Removido \"{{name}}\" dos ficheiros recentes", - "removeFailed": "Falha ao remover", - "unpinnedSuccessfully": "\"{{name}}\" desafixado com sucesso", - "unpinFailed": "Falha ao desafixar", - "removedShortcut": "Atalho \"{{name}}\" removido", - "removeShortcutFailed": "Falha ao remover atalho", - "clearedAllRecentFiles": "Limpos todos os ficheiros recentes", - "clearFailed": "Falha ao limpar", - "removeFromRecentFiles": "Remover dos ficheiros recentes", - "clearAllRecentFiles": "Limpar todos os ficheiros recentes", - "unpinFile": "Desafixar ficheiro", - "removeShortcut": "Remover atalho", - "pinFile": "Afixar ficheiro", - "addToShortcuts": "Adicionar aos atalhos", - "pasteFailed": "Falha ao colar", - "noUndoableActions": "Sem ações para desfazer", - "undoCopySuccess": "Desfeita a operação de cópia: Eliminados {{count}} ficheiros copiados", - "undoCopyFailedDelete": "Falha ao desfazer: Não foi possível eliminar os ficheiros copiados", - "undoCopyFailedNoInfo": "Falha ao desfazer: Não foi possível encontrar a informação dos ficheiros copiados", - "undoMoveSuccess": "Desfeita a operação de mover: {{count}} ficheiros movidos de volta para o local original", - "undoMoveFailedMove": "Falha ao desfazer: Não foi possível mover nenhum ficheiro de volta", - "undoMoveFailedNoInfo": "Falha ao desfazer: Não foi possível encontrar a informação dos ficheiros movidos", - "undoDeleteNotSupported": "A operação de eliminar não pode ser desfeita: Os ficheiros foram eliminados permanentemente do servidor", - "undoTypeNotSupported": "Tipo de operação de desfazer não suportado", - "undoOperationFailed": "Falha ao desfazer a operação", - "unknownError": "Erro desconhecido", - "confirm": "Confirmar", - "find": "Procurar...", - "replace": "Substituir", - "downloadInstead": "Transferir em alternativa", - "keyboardShortcuts": "Atalhos de teclado", - "searchAndReplace": "Pesquisar e substituir", - "editing": "Edição", - "search": "Pesquisar", - "findNext": "Localizar seguinte", - "findPrevious": "Localizar anterior", - "save": "Guardar", - "selectAll": "Selecionar tudo", - "undo": "Desfazer", - "redo": "Refazer", - "moveLineUp": "Mover linha para cima", - "moveLineDown": "Mover linha para baixo", - "toggleComment": "Alternar comentário", - "autoComplete": "Autocompletar", - "imageLoadError": "Erro ao carregar a imagem", - "startTyping": "Comece a escrever...", - "unknownSize": "Tamanho desconhecido", - "fileIsEmpty": "O ficheiro está vazio", - "largeFileWarning": "Aviso de ficheiro grande", - "largeFileWarningDesc": "Este ficheiro tem {{size}} de tamanho, o que poderá causar problemas de desempenho ao ser aberto como texto.", - "fileNotFoundAndRemoved": "O ficheiro \"{{name}}\" não foi encontrado e foi removido dos ficheiros recentes/fixados", - "failedToLoadFile": "Erro ao carregar o ficheiro: {{error}}", - "serverErrorOccurred": "Ocorreu um erro no servidor. Por favor, tente novamente mais tarde.", - "autoSaveFailed": "Falha na gravação automática", - "fileAutoSaved": "Ficheiro gravado automaticamente", - "moveFileFailed": "Falha ao mover {{name}}", - "moveOperationFailed": "Falha na operação de movimentação", - "canOnlyCompareFiles": "Só é possível comparar dois ficheiros", - "comparingFiles": "A comparar ficheiros: {{file1}} e {{file2}}", - "dragFailed": "Falha ao arrastar", - "filePinnedSuccessfully": "Ficheiro \"{{name}}\" fixado com sucesso", - "pinFileFailed": "Erro ao fixar o ficheiro", - "fileUnpinnedSuccessfully": "Ficheiro \"{{name}}\" desafixado com sucesso", - "unpinFileFailed": "Falha ao desafixar ficheiro", - "shortcutAddedSuccessfully": "Atalho para a pasta \"{{name}}\" adicionado com sucesso", - "addShortcutFailed": "Falha ao adicionar atalho", - "operationCompletedSuccessfully": "{{operation}} {{count}} itens com sucesso", - "operationCompleted": "{{operation}} {{count}} itens", - "downloadFileSuccess": "Ficheiro {{name}} descarregado com sucesso", - "downloadFileFailed": "Falha ao descarregar", - "downloadingFile": "A descarregar {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Mover para {{name}}", - "diffCompareWith": "Comparar diferenças com {{name}}", - "dragOutsideToDownload": "Arraste para fora da janela para descarregar ({{count}} ficheiros)", - "newFolderDefault": "NovaPasta", - "newFileDefault": "NovoFicheiro.txt", - "successfullyMovedItems": "{{count}} itens movidos com sucesso para {{target}}", - "move": "Mover", - "searchInFile": "Procurar no ficheiro (Ctrl+F)", - "showKeyboardShortcuts": "Mostrar atalhos de teclado", - "decreaseFontSize": "Diminuir tamanho da letra", - "increaseFontSize": "Aumentar tamanho da letra", - "startWritingMarkdown": "Comece a escrever o seu conteúdo em markdown...", - "loadingFileComparison": "A carregar comparação de ficheiros...", - "reload": "Recarregar", - "compare": "Comparar", - "sideBySide": "Lado a lado", - "inline": "Em linha", - "fileComparison": "Comparação de ficheiros: {{file1}} vs {{file2}}", - "fileTooLarge": "Ficheiro demasiado grande: {{error}}", - "sshConnectionFailed": "Falha na ligação SSH. Verifique a sua ligação a {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Falha ao carregar ficheiro: {{error}}", - "connecting": "A ligar...", - "connectedSuccessfully": "Ligado com sucesso", - "totpVerificationFailed": "Falha na verificação TOTP", - "warpgateVerificationFailed": "Falha na autenticação Warpgate", - "authenticationFailed": "Falha na autenticação", - "incorrectPassphrase": "Frase-passe incorreta. Tente novamente.", - "verificationCodePrompt": "Código de verificação:", - "changePermissions": "Alterar Permissões", - "currentPermissions": "Permissões Atuais", - "owner": "Proprietário", - "group": "Grupo", - "others": "Outros", - "read": "Leitura", - "write": "Escrita", - "execute": "Execução", - "permissionsChangedSuccessfully": "Permissões alteradas com sucesso", - "failedToChangePermissions": "Falha ao alterar permissões", - "name": "Nome", - "sortByName": "Nome", - "sortByDate": "Data de modificação", - "sortBySize": "Tamanho", - "ascending": "Ascendente", - "descending": "Descendente", - "root": "Raiz", - "new": "Novo", - "sortBy": "Ordenar por", - "items": "Itens", - "selected": "Selecionados", - "editor": "Editor", - "octal": "Octal", - "storage": "Armazenamento", - "disk": "Disco", - "used": "Utilizado", - "of": "de", - "toggleSidebar": "Alternar Barra Lateral", - "compactLayout": "Utilize um layout compacto.", - "comfortableLayout": "Utilize um layout confortável", - "cannotLoadPdf": "Não é possível carregar o PDF", - "pdfLoadError": "Ocorreu um erro ao carregar este ficheiro PDF.", - "loadingPdf": "A carregar PDF...", - "loadingPage": "A carregar página..." - }, "transfer": { "copyToHost": "Copiar para o host…", "moveToHost": "Mover para o host…", @@ -2292,50 +1951,6 @@ "retryTransferError": "A repetição falhou.", "transferFailedRetryHint": "Foram mantidos dados parciais no destino. A repetição será retomada quando a ligação for restabelecida." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Transferir ficheiros entre dois hosts SSH.", - "sourceServer": "Servidor de origem", - "destinationServer": "Servidor de destino", - "selectHost": "Selecione o host", - "noHostSelected": "Nenhum host selecionado", - "move": "Mover", - "copy": "Cópia", - "upload": "Carregar", - "itemsSelected": "{{count}} item selecionado", - "itemsSelected_other": "{{count}} itens selecionados", - "ready": "Pronto", - "loadingHosts": "Carregando hosts...", - "name": "Nome", - "modified": "Modificado", - "size": "Tamanho", - "noFiles": "Nenhum ficheiro", - "open": "Aberto", - "up": "Para cima", - "refresh": "Atualizar", - "copyToTarget": "Copiar para o diretório de destino", - "rename": "Renomear", - "delete": "Eliminar", - "editPermissions": "Editar permissões", - "createNewFolder": "Criar nova pasta", - "deleteSelectedItems": "Apagar itens selecionados", - "deleteConfirm": "Delete {{count}} selected item?", - "deleteConfirm_other": "Apagar {{count}} itens selecionados?", - "cancel": "Cancelar", - "save": "Guardar", - "folderCreated": "Pasta criada", - "itemRenamed": "Artigo renomeado", - "actionFailed": "A ação falhou", - "itemsDeleted": "{{count}} item eliminado", - "itemsDeleted_other": "{{count}} itens eliminados", - "deleteFailed": "Falha ao eliminar", - "permissionsUpdated": "Permissões atualizadas", - "noRemoteSessionConnected": "Nenhuma sessão remota ligada.", - "failedToListFiles": "Falha ao listar os ficheiros", - "failedToRefresh": "Falha ao atualizar", - "destinationInsideSource": "O destino não pode estar dentro do caminho de origem selecionado.", - "sameHostNoOp": "A origem e o destino são a mesma pasta." - }, "tunnels": { "noSshTunnels": "Sem túneis SSH", "createFirstTunnelMessage": "Ainda não criou nenhum túnel SSH. Configure as ligações de túnel no Gestor de Anfitriões para começar.", diff --git a/src/ui/locales/translated/ro_RO.json b/src/ui/locales/translated/ro_RO.json index c75f4c8c4..1c6337d60 100644 --- a/src/ui/locales/translated/ro_RO.json +++ b/src/ui/locales/translated/ro_RO.json @@ -715,7 +715,6 @@ "hostManager": "Manager de gazdă", "credentials": "Acreditări", "portForwarding": "Redirecționare", - "sftp": "SFTP", "connections": "Conexiuni", "serial": "Serial", "keepSideMenuOpen": "Mențineți meniul deschis", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tuneluri", - "tabFiles": "Fișiere", "tabStats": "Metrici ale gazdei", "tabTelnet": "Telnet", "tabSharing": "Partajare", "tabAuthentication": "Autentificare", "terminal": "Terminal", "tunnel": "Tunel", - "fileManager": "Manager de fișiere", "serverStats": "Metrici ale gazdei", "status": { "available": "Disponibil", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} omis (nu a fost găsită nicio adresă IP)", "proxmoxStatsUrlCopied": "URL-ul statisticilor Proxmox a fost copiat în clipboard", "copyProxmoxStatsUrlAction": "Copiați URL-ul statisticilor Proxmox", - "enableFileManagerMonitor": "Activează Managerul de fișiere", - "enableFileManagerMonitorDesc": "Răsfoiți și gestionați fișierele de pe această gazdă prin SFTP", - "scpLegacyLabel": "Modul SCP Legacy", - "scpLegacyDesc": "Folosește transferul de fișiere vechi pentru serverele care nu acceptă subsistemul SFTP (de exemplu, servere SSH încorporate sau minimale).", - "defaultPathLabel": "Calea implicită", - "fileManagerPathHint": "Directorul care va fi deschis la lansarea managerului de fișiere pentru această gazdă.", "statusChecksLabel": "Verificări de stare", "enableStatusChecks": "Activați verificările de stare", "enableStatusChecksDesc": "Ping periodic această gazdă pentru a verifica disponibilitatea", @@ -1278,7 +1269,6 @@ "deletedHost": "Șters {{name}}", "copiedToClipboard": "Copiat în clipboard", "terminalUrlCopied": "Adresa URL a terminalului a fost copiată", - "fileManagerUrlCopied": "Adresa URL a Managerului de fișiere a fost copiată", "tunnelUrlCopied": "Adresa URL a tunelului a fost copiată", "dockerUrlCopied": "Adresa URL a Dockerului a fost copiată", "hostMetricsUrlCopied": "Adresa URL a metricilor gazdă a fost copiată", @@ -1296,7 +1286,6 @@ "copyAddress": "Copiați adresa", "copyLink": "Copiază linkul", "copyTerminalUrlAction": "Copiați adresa URL a terminalului", - "copyFileManagerUrlAction": "Copiați adresa URL a managerului de fișiere", "copyTunnelUrlAction": "Copiați URL-ul tunelului", "copyDockerUrlAction": "Copiați adresa URL a Dockerului", "copyHostMetricsUrlAction": "Copiați adresa URL a metricilor gazdă", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Eliminați din Split" } }, - "fileManager": { - "noHostSelected": "Nicio gazdă selectată", - "initializingEditor": "Se inițializează editorul...", - "file": "Fişier", - "folder": "Pliant", - "uploadFile": "Încărcați fișierul", - "downloadFile": "Descărcare", - "extractArchive": "Extrageți arhiva", - "extractingArchive": "Extragerea {{name}}...", - "archiveExtractedSuccessfully": "{{name}} extras cu succes", - "extractFailed": "Extragerea a eșuat", - "compressFile": "Comprimați fișierul", - "compressFiles": "Comprimați fișierele", - "compressFilesDesc": "Comprimați elementele {{count}} într-o arhivă", - "archiveName": "Numele arhivei", - "enterArchiveName": "Introduceți numele arhivei...", - "compressionFormat": "Format de compresie", - "selectedFiles": "Fișiere selectate", - "andMoreFiles": "și {{count}} mai multe...", - "compress": "Comprima", - "compressingFiles": "Comprimarea elementelor {{count}} în {{name}}...", - "filesCompressedSuccessfully": "{{name}} creat cu succes", - "compressFailed": "Compresia a eșuat", - "edit": "Edita", - "preview": "Previzualizare", - "previous": "Anterior", - "next": "Următorul", - "pageXOfY": "Pagina {{current}} din {{total}}", - "zoomOut": "Micșorare", - "zoomIn": "Mărire", - "newFile": "Fișier nou", - "newFolder": "Dosar nou", - "rename": "Redenumire", - "uploading": "Se încarcă...", - "uploadingFile": "Se încarcă {{name}}...", - "fileName": "Nume de fișier", - "folderName": "Nume folder", - "fileUploadedSuccessfully": "Fișierul „{{name}}” a fost încărcat cu succes", - "failedToUploadFile": "Nu s-a putut încărca fișierul", - "uploadingFolderFiles": "Se încarcă {{count}} fișier(e)...", - "uploadedFolderFiles": "Fișier(e) {{count}} încărcat(e) cu succes", - "uploadedFolderPartial": "Încărcarea fișierelor {{uploaded}} , {{failed}} a eșuat.", - "fileDownloadedSuccessfully": "Fișierul „{{name}}” a fost descărcat cu succes", - "failedToDownloadFile": "Nu s-a putut descărca fișierul", - "fileCreatedSuccessfully": "Fișierul „{{name}}” a fost creat cu succes", - "folderCreatedSuccessfully": "Dosarul „{{name}}” a fost creat cu succes", - "failedToCreateItem": "Nu s-a putut crea elementul", - "operationFailed": "Operația {{operation}} a eșuat pentru {{name}}: {{error}}", - "failedToResolveSymlink": "Nu s-a putut rezolva legătura simbolică", - "itemsDeletedSuccessfully": "{{count}} elemente șterse cu succes", - "failedToDeleteItems": "Nu s-au putut șterge elementele", - "sudoPasswordRequired": "Parolă de administrator necesară", - "enterSudoPassword": "Introduceți parola sudo pentru a continua această operațiune", - "sudoPassword": "Parolă Sudo", - "sudoOperationFailed": "Operațiunea Sudo a eșuat", - "sudoAuthFailed": "Autentificarea Sudo a eșuat", - "dragFilesToUpload": "Plasați fișierele aici pentru încărcare", - "dropToUploadHere": "Plasați aici pentru a încărca", - "dropToDownloadHere": "Plasați pentru descărcare aici", - "localFiles": "Local", - "showLocalFiles": "Afișați fișierele locale (vizualizare divizată)", - "hideLocalFiles": "Ascunde fișierele locale", - "localHome": "Dosar principal", - "localPathPlaceholder": "Calea folderului local", - "localShowHidden": "Afișează fișierele ascunse", - "localHideHidden": "Ascunde fișierele ascunse", - "localRevealInFileManager": "Afișează în managerul de fișiere al sistemului", - "localCannotOpenFolder": "Nu se poate deschide folderul", - "localItemCount": "{{count}} articole", - "localSelectedCount": "{{count}} selectat", - "back": "Spate", - "forward": "Redirecţiona", - "up": "Sus", - "kind": "Fel", - "localUploadingProgress": "Se încarcă {{current}} din {{total}}", - "localDownloadingProgress": "Se descarcă {{current}} din {{total}}", - "localTransferInFlight": "{{count}} deodată", - "localPreparingDownload": "Se pregătește descărcarea...", - "localTransferCancelled": "Transfer anulat", - "localNothingToTransfer": "Nimic de transferat", - "localUploadComplete": "Fișiere încărcate {{count}}", - "localUploadFailed": "Încărcarea a eșuat", - "localUploadPartial": "Încărcarea fișierelor {{done}} , {{failed}} a eșuat.", - "localDownloadComplete": "Fișiere descărcate {{count}}", - "localDownloadFailed": "Descărcarea a eșuat", - "localDownloadPartial": "Descărcarea fișierelor {{done}} , {{failed}} a eșuat.", - "localReplaceExistingPrompt": "{{count}} element(e) există deja în acest folder. Le înlocuiți?", - "localReplace": "Înlocui", - "localSkipExisting": "Sari peste", - "localSkippedExisting": "{{count}} element(e) existent(e) omis(e)", - "localUnsafeNamesSkipped": "{{count}} element(e) ale cărui nume nu pot fi utilizate pe acest computer au fost omise", - "localOpen": "Deschide", - "localOpenFolder": "Deschideți folderul", - "localUploadToRemote": "Încărcare pe server", - "localUploadToRemoteMany": "Încărcați {{count}} elemente pe server", - "localMoveToTrash": "Mută în coșul de gunoi", - "localMoveToTrashMany": "Mută articolele {{count}} în Coșul de gunoi", - "localTrashConfirmSingle": "Mutați „{{name}}” în coșul de gunoi?", - "localTrashConfirmFolder": "Mutați folderul „{{name}}” și tot conținutul său în Coșul de gunoi?", - "localTrashConfirmMany": "Mutați {{count}} elemente în coșul de gunoi?", - "localTrashed": "{{count}} element(e) mutat(e) în coșul de gunoi", - "localTrashFailed": "Nu s-a putut muta în coșul de gunoi", - "localRenameFailed": "Redenumirea a eșuat", - "localCreateFailed": "Nu s-a putut crea elementul", - "goToParentFolder": "Accesați folderul părinte", - "resizeSidebar": "Trageți pentru a redimensiona bara laterală (faceți dublu clic pentru a reseta)", - "resizeLocalPane": "Trageți pentru a redimensiona panoul local (faceți dublu clic pentru a reseta)", - "columns": "Coloane", - "columnsHint": "Faceți clic dreapta pentru a alege coloane", - "emptyFolder": "Acest folder este gol", - "searchFiles": "Căutați fișiere...", - "upload": "Încărcare", - "selectHostToStart": "Selectați o gazdă pentru a începe gestionarea fișierelor", - "sshRequiredForFileManager": "Managerul de fișiere necesită SSH. Această gazdă nu are SSH activat.", - "failedToConnect": "Conectarea la SSH a eșuat", - "failedToLoadDirectory": "Directorul nu a putut fi încărcat", - "noSSHConnection": "Nu este disponibilă nicio conexiune SSH", - "copy": "Copie", - "cut": "Tăiere", - "paste": "Pastă", - "copyPath": "Copiere cale", - "copyPaths": "Copiați căile", - "delete": "Şterge", - "properties": "Proprietăți", - "refresh": "Reîmprospăta", - "downloadFiles": "Descărcați fișierele {{count}} în browser", - "copyFiles": "Copiază elementele {{count}}", - "cutFiles": "Decupează elementele {{count}}", - "deleteFiles": "Ștergeți elementele {{count}}", - "filesCopiedToClipboard": "{{count}} elemente copiate în clipboard", - "filesCutToClipboard": "{{count}} elemente tăiate în clipboard", - "pathCopiedToClipboard": "Calea a fost copiată în clipboard", - "pathsCopiedToClipboard": "Căile {{count}} au fost copiate în clipboard", - "failedToCopyPath": "Nu s-a putut copia calea în clipboard", - "copyFolderLink": "Copiați linkul în folder", - "copyCurrentFolderLink": "Copiați linkul în folderul curent", - "folderLinkCopied": "Legătura la dosar a fost copiată în clipboard", - "failedToCopyFolderLink": "Nu s-a putut copia linkul dosarului", - "movedItems": "Articole mutate {{count}}", - "failedToDeleteItem": "Nu s-a putut șterge elementul", - "itemRenamedSuccessfully": "{{type}} redenumit cu succes", - "failedToRenameItem": "Nu s-a putut redenumi elementul", - "download": "Descărcare", - "openExternalEditor": "Deschis la exterior", - "chooseExternalEditor": "Alegeți un editor extern", - "externalEditorSelected": "Editor extern selectat", - "externalEditorDesktopOnly": "Editorul extern este disponibil doar în aplicația pentru desktop", - "externalEditorOpened": "Deschis într-un editor extern. Salvarea acolo va încărca modificările înapoi pe server.", - "failedToOpenExternalEditor": "Nu s-a putut deschide editorul extern", - "failedToSelectExternalEditor": "Nu s-a putut selecta editorul extern", - "permissions": "Permisiuni", - "size": "Dimensiune", - "modified": "Modificat", - "path": "Cale", - "confirmDelete": "Sigur vrei să ștergi {{name}}?", - "permissionDenied": "Acces refuzat", - "serverError": "Eroare server", - "fileSavedSuccessfully": "Fișierul a fost salvat cu succes", - "failedToSaveFile": "Nu s-a putut salva fișierul", - "confirmDeleteSingleItem": "Sigur vrei să ștergi definitiv „{{name}}”?", - "confirmDeleteMultipleItems": "Sigur vrei să ștergi definitiv elementele {{count}}?", - "confirmDeleteMultipleItemsWithFolders": "Sigur vrei să ștergi definitiv elementele {{count}} ? Aceasta include folderele și conținutul acestora.", - "confirmDeleteFolder": "Sigur doriți să ștergeți definitiv folderul „{{name}}” și tot conținutul său?", - "permanentDeleteWarning": "Această acțiune nu poate fi anulată. Elementul (elementele) va fi (vor) șters(e) definitiv de pe server.", - "moveToTrashWarning": "Elementul (elementele) va (vor) fi mutat(e) în Coșul de gunoi Termix și va (pot) fi restaurat(e) înainte de expirarea perioadei de păstrare.", - "itemsMovedToTrash": "{{count}} element(e) mutat(e) în Coșul de gunoi", - "trash": "Gunoi", - "trashDescription": "Elementele șterse sunt păstrate timp de {{days}} zi(le) pe această gazdă.", - "trashLoadFailed": "Nu s-a putut încărca Coșul de gunoi", - "trashEmpty": "Coșul de gunoi este gol", - "trashRestore": "Restabili", - "trashRestored": "Restaurat {{name}}", - "trashRestoreFailed": "Elementul nu a putut fi restaurat. Calea originală ar putea exista deja.", - "trashDeletePermanently": "Ștergeți definitiv", - "trashDeleteConfirm": "Ștergeți definitiv {{name}}? Această acțiune nu poate fi anulată.", - "trashPermanentlyDeleted": "Șters definitiv {{name}}", - "trashDeleteFailed": "Nu s-a putut șterge definitiv elementul", - "trashEmptyAction": "Goliți coșul de gunoi", - "trashEmptyConfirm": "Ștergeți definitiv toate elementele din Coșul de gunoi? Această acțiune nu poate fi anulată.", - "trashEmptied": "Coș de gunoi golit", - "trashRetention": "Curățare automată după (zile)", - "trashRetentionSaved": "Reținerea coșului de gunoi actualizată", - "trashRetentionFailed": "Nu s-a putut actualiza opțiunea „Păstrarea în coș”", - "trashUnavailableConfirm": "Acest element nu poate fi mutat în Coșul de gunoi, de obicei pentru că se află pe un alt sistem de fișiere sau necesită permisiuni ridicate. Îl ștergeți definitiv în schimb?", - "recent": "Recent", - "pinned": "Fixat", - "folderShortcuts": "Comenzi rapide pentru foldere", - "failedToReconnectSSH": "Reconectarea sesiunii SSH a eșuat.", - "openTerminalHere": "Deschideți terminalul aici", - "run": "Aleargă", - "openTerminalInFolder": "Deschideți terminalul în acest folder", - "openTerminalInFileLocation": "Deschideți terminalul la locația fișierului", - "runningFile": "Alergare - {{file}}", - "onlyRunExecutableFiles": "Poate rula doar fișiere executabile", - "directories": "Directoare", - "removedFromRecentFiles": "„{{name}}” a fost eliminat din fișierele recente", - "removeFailed": "Eliminarea a eșuat", - "unpinnedSuccessfully": "„{{name}}” a fost anulată cu succes", - "unpinFailed": "Anularea fixării a eșuat", - "removedShortcut": "S-a eliminat comanda rapidă „{{name}}”", - "removeShortcutFailed": "Eliminarea comenzii rapide a eșuat", - "clearedAllRecentFiles": "Am șters toate fișierele recente", - "clearFailed": "Ștergerea a eșuat", - "removeFromRecentFiles": "Eliminați din fișierele recente", - "clearAllRecentFiles": "Ștergeți toate fișierele recente", - "unpinFile": "Anulează fixarea fișierului", - "removeShortcut": "Eliminați comanda rapidă", - "pinFile": "Fișier fixat", - "addToShortcuts": "Adăugați la comenzi rapide", - "pasteFailed": "Lipirea a eșuat", - "noUndoableActions": "Nicio acțiune care nu poate fi anulată", - "undoCopySuccess": "Operațiune de copiere anulată: Fișierele copiate {{count}} au fost șterse", - "undoCopyFailedDelete": "Anulare eșuată: Nu s-au putut șterge fișierele copiate", - "undoCopyFailedNoInfo": "Anulare eșuată: Nu s-au găsit informațiile despre fișierul copiat", - "undoMoveSuccess": "Operațiune de mutare anulată: Fișierele {{count}} au fost mutate înapoi în locația originală.", - "undoMoveFailedMove": "Anulare eșuată: Nu s-au putut muta fișierele înapoi", - "undoMoveFailedNoInfo": "Anulare eșuată: Nu s-au găsit informații despre fișierul mutat", - "undoDeleteNotSupported": "Operațiunea de ștergere nu poate fi anulată: Fișierele au fost șterse definitiv de pe server.", - "undoTypeNotSupported": "Tip de operațiune de anulare neacceptat", - "undoOperationFailed": "Operațiunea de anulare a eșuat", - "unknownError": "Eroare necunoscută", - "confirm": "Confirma", - "find": "Găsi...", - "replace": "Înlocui", - "downloadInstead": "Descărcați în schimb", - "keyboardShortcuts": "Comenzi rapide de la tastatură", - "searchAndReplace": "Căutare și înlocuire", - "editing": "Editare", - "search": "Căutare", - "findNext": "Găsește următorul", - "findPrevious": "Găsește precedentul", - "save": "Salva", - "selectAll": "Selectați tot", - "undo": "Anula", - "redo": "Refaceți", - "moveLineUp": "Mută alinierea în sus", - "moveLineDown": "Mută linia în jos", - "toggleComment": "Comutare comentariu", - "autoComplete": "Completare automată", - "imageLoadError": "Nu s-a putut încărca imaginea", - "startTyping": "Începeți să tastați...", - "unknownSize": "Dimensiune necunoscută", - "fileIsEmpty": "Fișierul este gol", - "largeFileWarning": "Avertisment fișier mare", - "largeFileWarningDesc": "Acest fișier are dimensiunea {{size}} , ceea ce poate cauza probleme de performanță atunci când este deschis ca text.", - "fileNotFoundAndRemoved": "Fișierul „{{name}}” nu a fost găsit și a fost eliminat din fișierele recente/fixate.", - "failedToLoadFile": "Nu s-a putut încărca fișierul: {{error}}", - "serverErrorOccurred": "A apărut o eroare de server. Vă rugăm să încercați din nou mai târziu.", - "autoSaveFailed": "Salvarea automată a eșuat", - "fileAutoSaved": "Fișier salvat automat", - "moveFileFailed": "Nu s-a putut muta {{name}}", - "moveOperationFailed": "Operațiunea de mutare a eșuat", - "canOnlyCompareFiles": "Se pot compara doar două fișiere", - "comparingFiles": "Compararea fișierelor: {{file1}} și {{file2}}", - "dragFailed": "Operația de tragere a eșuat", - "filePinnedSuccessfully": "Fișierul „{{name}}” a fost fixat cu succes", - "pinFileFailed": "Fișierul nu a fost fixat", - "fileUnpinnedSuccessfully": "Fișierul „{{name}}” a fost anulat cu succes.", - "unpinFileFailed": "Nu s-a putut anula fixarea fișierului", - "shortcutAddedSuccessfully": "Comanda rapidă pentru folderul „{{name}}” a fost adăugată cu succes", - "addShortcutFailed": "Nu s-a putut adăuga comanda rapidă", - "operationCompletedSuccessfully": "{{operation}} {{count}} articole au fost selectate cu succes", - "operationCompleted": "{{operation}} {{count}} articole", - "downloadFileSuccess": "Fișierul {{name}} a fost descărcat cu succes", - "downloadFileFailed": "Descărcarea a eșuat", - "downloadingFile": "Se descarcă {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Mută la {{name}}", - "diffCompareWith": "Comparație diferențială cu {{name}}", - "dragOutsideToDownload": "Trageți în afara ferestrei pentru a descărca (fișiere{{count}})", - "newFolderDefault": "Dosar nou", - "newFileDefault": "FișierNou.txt", - "successfullyMovedItems": "{{count}} articole au fost mutate cu succes în {{target}}", - "move": "Mişcare", - "searchInFile": "Căutare în fișier (Ctrl+F)", - "showKeyboardShortcuts": "Afișați comenzile rapide de la tastatură", - "decreaseFontSize": "Micșorează dimensiunea fontului", - "increaseFontSize": "Măriți dimensiunea fontului", - "startWritingMarkdown": "Începe să scrii conținutul pentru markdown...", - "loadingFileComparison": "Se încarcă comparația fișierelor...", - "reload": "Reîncărcare", - "compare": "Comparaţie", - "sideBySide": "Alăturat", - "inline": "În linie", - "fileComparison": "Comparație fișiere: {{file1}} vs {{file2}}", - "fileTooLarge": "Fișier prea mare: {{error}}", - "sshConnectionFailed": "Conexiunea SSH a eșuat. Vă rugăm să verificați conexiunea la {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Nu s-a putut încărca fișierul: {{error}}", - "connecting": "Se conectează...", - "connectedSuccessfully": "Conectare reușită", - "totpVerificationFailed": "Verificarea TOTP a eșuat", - "warpgateVerificationFailed": "Autentificarea Warpgate a eșuat", - "authenticationFailed": "Autentificarea a eșuat", - "incorrectPassphrase": "Parolă incorectă. Vă rugăm să încercați din nou.", - "verificationCodePrompt": "Cod de verificare:", - "changePermissions": "Modificați permisiunile", - "currentPermissions": "Permisiuni actuale", - "owner": "Proprietar", - "group": "Grup", - "others": "Alţii", - "read": "Citire", - "write": "Scrie", - "execute": "Executa", - "permissionsChangedSuccessfully": "Permisiunile au fost modificate cu succes", - "failedToChangePermissions": "Nu s-au putut modifica permisiunile", - "name": "Nume", - "sortByName": "Nume", - "sortByDate": "Data modificării", - "sortBySize": "Dimensiune", - "ascending": "Ascendent", - "descending": "Descendent", - "root": "Rădăcină", - "new": "Nou", - "sortBy": "Filtrează după", - "items": "Articole", - "selected": "Selectat", - "editor": "Editor", - "octal": "Octal", - "storage": "Depozitare", - "disk": "Disc", - "used": "Folosit", - "of": "de", - "toggleSidebar": "Comutare bară laterală", - "compactLayout": "Folosește un aspect compact", - "comfortableLayout": "Folosește un aspect confortabil", - "cannotLoadPdf": "Nu se poate încărca PDF-ul", - "pdfLoadError": "A apărut o eroare la încărcarea acestui fișier PDF.", - "loadingPdf": "Se încarcă PDF-ul...", - "loadingPage": "Se încarcă pagina..." - }, "transfer": { "copyToHost": "Copiați către gazdă…", "moveToHost": "Mutare la gazdă…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Reîncercare eșuată", "transferFailedRetryHint": "Date parțiale au fost păstrate la destinație. Reîncercarea va fi reluată după restabilirea conexiunii." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Transferă fișiere între două gazde SSH.", - "sourceServer": "Server sursă", - "destinationServer": "Server de destinație", - "selectHost": "Selectați gazda", - "noHostSelected": "Nicio gazdă selectată", - "move": "Mişcare", - "copy": "Copie", - "upload": "Încărcare", - "itemsSelected": "{{count}} element selectat", - "itemsSelected_other": "{{count}} elemente selectate", - "ready": "Gata", - "loadingHosts": "Se încarcă gazdele...", - "name": "Nume", - "modified": "Modificat", - "size": "Dimensiune", - "noFiles": "Niciun fișier", - "open": "Deschide", - "up": "Sus", - "refresh": "Reîmprospăta", - "copyToTarget": "Copiați în directorul țintă", - "rename": "Redenumire", - "delete": "Şterge", - "editPermissions": "Editați permisiunile", - "createNewFolder": "Creați un folder nou", - "deleteSelectedItems": "Ștergeți elementele selectate", - "deleteConfirm": "Ștergeți elementul selectat {{count}}?", - "deleteConfirm_other": "Ștergeți {{count}} elementele selectate?", - "cancel": "Anula", - "save": "Salva", - "folderCreated": "Dosar creat", - "itemRenamed": "Element redenumit", - "actionFailed": "Acțiunea a eșuat", - "itemsDeleted": "{{count}} element șters", - "itemsDeleted_other": "{{count}} elemente șterse", - "deleteFailed": "Ștergerea a eșuat", - "permissionsUpdated": "Permisiunile au fost actualizate", - "noRemoteSessionConnected": "Nicio sesiune la distanță conectată", - "failedToListFiles": "Nu s-au putut lista fișierele", - "failedToRefresh": "Nu s-a putut actualiza", - "destinationInsideSource": "Destinația nu poate fi în interiorul căii sursă selectate.", - "sameHostNoOp": "Sursa și destinația sunt în același folder." - }, "tunnels": { "noSshTunnels": "Fără tuneluri SSH", "createFirstTunnelMessage": "Nu ați creat încă niciun tunel SSH. Configurați conexiunile la tunel în Host Manager pentru a începe.", diff --git a/src/ui/locales/translated/ru_RU.json b/src/ui/locales/translated/ru_RU.json index f09371fc7..71c28a447 100644 --- a/src/ui/locales/translated/ru_RU.json +++ b/src/ui/locales/translated/ru_RU.json @@ -715,7 +715,6 @@ "hostManager": "Менеджер хостов", "credentials": "Учетные данные", "portForwarding": "Пересылка", - "sftp": "SFTP", "connections": "Подключения", "serial": "Последовательный порт", "keepSideMenuOpen": "Оставить меню открытым", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Туннели", - "tabFiles": "Файлы", "tabStats": "Метрики хоста", "tabTelnet": "Telnet", "tabSharing": "Общий доступ", "tabAuthentication": "Аутентификация", "terminal": "Терминал", "tunnel": "Туннель", - "fileManager": "Файловый менеджер", "serverStats": "Метрики хоста", "status": { "available": "Доступный", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} пропущено (IP-адрес не найден)", "proxmoxStatsUrlCopied": "URL-адрес статистики Proxmox скопирован в буфер обмена.", "copyProxmoxStatsUrlAction": "Скопируйте URL-адрес статистики Proxmox.", - "enableFileManagerMonitor": "Включить файловый менеджер", - "enableFileManagerMonitorDesc": "Просмотр и управление файлами на этом хосте через SFTP", - "scpLegacyLabel": "Устаревший режим SCP", - "scpLegacyDesc": "Использовать устаревший протокол передачи файлов для серверов, не поддерживающих подсистему SFTP (например, встроенные или минимальные SSH-серверы).", - "defaultPathLabel": "Путь по умолчанию", - "fileManagerPathHint": "Каталог, который открывается при запуске файлового менеджера для этого хоста.", "statusChecksLabel": "Проверки состояния", "enableStatusChecks": "Включить проверки состояния", "enableStatusChecksDesc": "Периодически пинговать этот хост для проверки доступности", @@ -1278,7 +1269,6 @@ "deletedHost": "Удалён {{name}}", "copiedToClipboard": "Скопировано в буфер обмена", "terminalUrlCopied": "URL терминала скопирован", - "fileManagerUrlCopied": "URL файлового менеджера скопирован", "tunnelUrlCopied": "URL туннеля скопирован", "dockerUrlCopied": "URL Docker скопирован", "hostMetricsUrlCopied": "URL метрик хоста скопирован", @@ -1296,7 +1286,6 @@ "copyAddress": "Копировать адрес", "copyLink": "Копировать ссылку", "copyTerminalUrlAction": "Копировать URL терминала", - "copyFileManagerUrlAction": "Копировать URL файлового менеджера", "copyTunnelUrlAction": "Копировать URL туннеля", "copyDockerUrlAction": "Копировать URL Docker", "copyHostMetricsUrlAction": "Копировать URL метрик хоста", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Убрать из разделённой области" } }, - "fileManager": { - "noHostSelected": "Хост не выбран", - "initializingEditor": "Инициализация редактора...", - "file": "Файл", - "folder": "Папка", - "uploadFile": "Загрузить файл", - "downloadFile": "Скачать", - "extractArchive": "Извлечь архив", - "extractingArchive": "Распаковка {{name}}...", - "archiveExtractedSuccessfully": "{{name}} успешно распакован", - "extractFailed": "Ошибка распаковки", - "compressFile": "Сжать файл", - "compressFiles": "Сжать файлы", - "compressFilesDesc": "Сжать {{count}} элементов в архив", - "archiveName": "Имя архива", - "enterArchiveName": "Введите имя архива...", - "compressionFormat": "Формат сжатия", - "selectedFiles": "Выбранные файлы", - "andMoreFiles": "и ещё {{count}}...", - "compress": "Сжать", - "compressingFiles": "Сжатие {{count}} элементов в {{name}}...", - "filesCompressedSuccessfully": "{{name}} успешно создан", - "compressFailed": "Ошибка сжатия", - "edit": "Редактировать", - "preview": "Предпросмотр", - "previous": "Назад", - "next": "Далее", - "pageXOfY": "Страница {{current}} из {{total}}", - "zoomOut": "Уменьшить", - "zoomIn": "Увеличить", - "newFile": "Новый файл", - "newFolder": "Новая папка", - "rename": "Переименовать", - "uploading": "Загрузка...", - "uploadingFile": "Загрузка {{name}}...", - "fileName": "Имя файла", - "folderName": "Имя папки", - "fileUploadedSuccessfully": "Файл «{{name}}» успешно загружен", - "failedToUploadFile": "Не удалось загрузить файл", - "uploadingFolderFiles": "Загрузка {{count}} файла(ов)...", - "uploadedFolderFiles": "Файл(ы) {{count}} успешно загружен(ы).", - "uploadedFolderPartial": "Загрузка {{uploaded}} файла(ов), {{failed}} не удалась.", - "fileDownloadedSuccessfully": "Файл «{{name}}» успешно скачан", - "failedToDownloadFile": "Не удалось скачать файл", - "fileCreatedSuccessfully": "Файл «{{name}}» успешно создан", - "folderCreatedSuccessfully": "Папка «{{name}}» успешно создана", - "failedToCreateItem": "Не удалось создать элемент", - "operationFailed": "Операция {{operation}} не удалась для {{name}}: {{error}}", - "failedToResolveSymlink": "Не удалось разрешить символическую ссылку", - "itemsDeletedSuccessfully": "{{count}} элементов успешно удалено", - "failedToDeleteItems": "Не удалось удалить элементы", - "sudoPasswordRequired": "Требуется пароль администратора", - "enterSudoPassword": "Введите пароль sudo для продолжения операции", - "sudoPassword": "Пароль sudo", - "sudoOperationFailed": "Операция sudo не удалась", - "sudoAuthFailed": "Аутентификация sudo не удалась", - "dragFilesToUpload": "Перетащите файлы сюда для загрузки", - "dropToUploadHere": "Перетащите файл для загрузки сюда.", - "dropToDownloadHere": "Скачать можно здесь (перетащите ссылку)", - "localFiles": "Местный", - "showLocalFiles": "Отображение локальных файлов (в режиме разделенного экрана)", - "hideLocalFiles": "Скрыть локальные файлы", - "localHome": "Домашняя папка", - "localPathPlaceholder": "Путь к локальной папке", - "localShowHidden": "Показать скрытые файлы", - "localHideHidden": "Скрыть скрытые файлы", - "localRevealInFileManager": "Показать в системном файловом менеджере", - "localCannotOpenFolder": "Не удаётся открыть папку", - "localItemCount": "{{count}} предметов", - "localSelectedCount": "{{count}} выбрано", - "back": "Назад", - "forward": "Вперед", - "up": "Вверх", - "kind": "Добрый", - "localUploadingProgress": "Загрузка {{current}} из {{total}}", - "localDownloadingProgress": "Загрузка {{current}} из {{total}}", - "localTransferInFlight": "{{count}} сразу", - "localPreparingDownload": "Подготовка к загрузке...", - "localTransferCancelled": "Трансфер отменен", - "localNothingToTransfer": "Нет ничего для перевода", - "localUploadComplete": "Загружено {{count}} файлов", - "localUploadFailed": "Загрузка не удалась.", - "localUploadPartial": "Загрузка {{done}} файла(ов), {{failed}} не удалась.", - "localDownloadComplete": "Загружено {{count}} файлов", - "localDownloadFailed": "Загрузка не удалась.", - "localDownloadPartial": "Загрузка {{done}} файла(ов), {{failed}} не удалась.", - "localReplaceExistingPrompt": "{{count}} Элемент(ы) уже существуют в этой папке. Заменить их?", - "localReplace": "Заменять", - "localSkipExisting": "Пропускать", - "localSkippedExisting": "Пропущено {{count}} существующих элементов", - "localUnsafeNamesSkipped": "Пропущено {{count}} элементов, названия которых нельзя использовать на этом компьютере", - "localOpen": "Открыть", - "localOpenFolder": "Открыть папку", - "localUploadToRemote": "Загрузка на сервер", - "localUploadToRemoteMany": "Загрузите {{count}} предметов на сервер", - "localMoveToTrash": "Переместить в мусорное ведро", - "localMoveToTrashMany": "Переместите {{count}} элементов в корзину", - "localTrashConfirmSingle": "Переместить \"{{name}}\" в корзину?", - "localTrashConfirmFolder": "Переместить папку \"{{name}}\" и всё её содержимое в корзину?", - "localTrashConfirmMany": "Переместить {{count}} элементов в корзину?", - "localTrashed": "Перемещено {{count}} предметов в корзину", - "localTrashFailed": "Не удалось переместить в мусорный бак.", - "localRenameFailed": "Переименование не удалось.", - "localCreateFailed": "Не удалось создать элемент.", - "goToParentFolder": "Перейдите в родительскую папку", - "resizeSidebar": "Перетащите ползунок, чтобы изменить размер боковой панели (двойной щелчок для сброса).", - "resizeLocalPane": "Перетащите курсор, чтобы изменить размер локальной панели (двойной щелчок для сброса).", - "columns": "Колонки", - "columnsHint": "Щелкните правой кнопкой мыши, чтобы выбрать столбцы.", - "emptyFolder": "Эта папка пуста", - "searchFiles": "Поиск файлов...", - "upload": "Загрузить", - "selectHostToStart": "Выберите хост для начала управления файлами", - "sshRequiredForFileManager": "Файловому менеджеру требуется SSH. У этого хоста SSH не включен.", - "failedToConnect": "Не удалось подключиться к SSH", - "failedToLoadDirectory": "Не удалось загрузить каталог", - "noSSHConnection": "Нет доступного SSH-подключения", - "copy": "Копировать", - "cut": "Вырезать", - "paste": "Вставить", - "copyPath": "Копировать путь", - "copyPaths": "Копировать пути", - "delete": "Удалить", - "properties": "Свойства", - "refresh": "Обновить", - "downloadFiles": "Скачать {{count}} файлов в браузер", - "copyFiles": "Копировать {{count}} элементов", - "cutFiles": "Вырезать {{count}} элементов", - "deleteFiles": "Удалить {{count}} элементов", - "filesCopiedToClipboard": "{{count}} элементов скопировано в буфер обмена", - "filesCutToClipboard": "{{count}} элементов вырезано в буфер обмена", - "pathCopiedToClipboard": "Путь скопирован в буфер обмена", - "pathsCopiedToClipboard": "{{count}} путей скопировано в буфер обмена", - "failedToCopyPath": "Не удалось скопировать путь в буфер обмена", - "copyFolderLink": "Скопировать ссылку на папку", - "copyCurrentFolderLink": "Скопировать ссылку на текущую папку", - "folderLinkCopied": "Ссылка на папку скопирована в буфер обмена", - "failedToCopyFolderLink": "Не удалось скопировать ссылку на папку", - "movedItems": "Перемещено {{count}} элементов", - "failedToDeleteItem": "Не удалось удалить элемент", - "itemRenamedSuccessfully": "{{type}} успешно переименован(а)", - "failedToRenameItem": "Не удалось переименовать элемент", - "download": "Скачать", - "openExternalEditor": "Открыть во внешнем редакторе", - "chooseExternalEditor": "Выбрать внешний редактор", - "externalEditorSelected": "Внешний редактор выбран", - "externalEditorDesktopOnly": "Внешний редактор доступен только в настольном приложении", - "externalEditorOpened": "Открыт во внешнем редакторе. Сохранение в нём загрузит изменения обратно на сервер.", - "failedToOpenExternalEditor": "Не удалось открыть внешний редактор", - "failedToSelectExternalEditor": "Не удалось выбрать внешний редактор", - "permissions": "Права доступа", - "size": "Размер", - "modified": "Изменён", - "path": "Путь", - "confirmDelete": "Вы уверены, что хотите удалить {{name}}?", - "permissionDenied": "Доступ запрещён", - "serverError": "Ошибка сервера", - "fileSavedSuccessfully": "Файл успешно сохранён", - "failedToSaveFile": "Не удалось сохранить файл", - "confirmDeleteSingleItem": "Вы уверены, что хотите безвозвратно удалить \"{{name}}\"?", - "confirmDeleteMultipleItems": "Вы уверены, что хотите безвозвратно удалить {{count}} элементов?", - "confirmDeleteMultipleItemsWithFolders": "Вы уверены, что хотите безвозвратно удалить {{count}} элементов? Включая папки и их содержимое.", - "confirmDeleteFolder": "Вы уверены, что хотите безвозвратно удалить папку \"{{name}}\" и всё её содержимое?", - "permanentDeleteWarning": "Это действие необратимо. Элемент(ы) будут безвозвратно удалены с сервера.", - "moveToTrashWarning": "Изделие(а) будет перемещено в мусорный контейнер Termix и может быть возвращено в рабочее состояние до истечения срока хранения.", - "itemsMovedToTrash": "{{count}} элемент(ы) перемещены в корзину", - "trash": "Мусор", - "trashDescription": "Удаленные элементы сохраняются на этом хосте в течение {{days}} дней.", - "trashLoadFailed": "Не удалось загрузить мусор.", - "trashEmpty": "Мусорное ведро пусто.", - "trashRestore": "Восстановить", - "trashRestored": "Восстановлено {{name}}", - "trashRestoreFailed": "Не удалось восстановить элемент. Возможно, исходный путь уже существует.", - "trashDeletePermanently": "Удалить навсегда", - "trashDeleteConfirm": "Удалить {{name}}навсегда? Это действие необратимо.", - "trashPermanentlyDeleted": "Удалено навсегда {{name}}", - "trashDeleteFailed": "Не удалось удалить элемент навсегда.", - "trashEmptyAction": "Опустошите мусорный бак", - "trashEmptyConfirm": "Удалить все элементы из корзины навсегда? Это действие необратимо.", - "trashEmptied": "Мусор вывезен", - "trashRetention": "Автоматическая очистка через (дней)", - "trashRetentionSaved": "Обновлена информация о хранении мусора.", - "trashRetentionFailed": "Не удалось обновить срок хранения корзины.", - "trashUnavailableConfirm": "Этот элемент нельзя переместить в корзину, как правило, потому что он находится в другой файловой системе или требует повышенных прав доступа. Может, лучше удалить его навсегда?", - "recent": "Недавние", - "pinned": "Закреплённые", - "folderShortcuts": "Ярлыки папок", - "failedToReconnectSSH": "Не удалось переподключить SSH-сессию", - "openTerminalHere": "Открыть терминал здесь", - "run": "Запустить", - "openTerminalInFolder": "Открыть терминал в этой папке", - "openTerminalInFileLocation": "Открыть терминал в расположении файла", - "runningFile": "Выполняется - {{file}}", - "onlyRunExecutableFiles": "Можно запускать только исполняемые файлы", - "directories": "Каталоги", - "removedFromRecentFiles": "Удалено \"{{name}}\" из недавних файлов", - "removeFailed": "Не удалось удалить", - "unpinnedSuccessfully": "Успешно откреплено \"{{name}}\"", - "unpinFailed": "Не удалось открепить", - "removedShortcut": "Ярлык \"{{name}}\" удалён", - "removeShortcutFailed": "Не удалось удалить ярлык", - "clearedAllRecentFiles": "Очищен список недавних файлов", - "clearFailed": "Очистка не удалась", - "removeFromRecentFiles": "Удалить из списка недавних файлов", - "clearAllRecentFiles": "Очистить список недавних файлов", - "unpinFile": "Открепить файл", - "removeShortcut": "Удалить ярлык", - "pinFile": "Закрепить файл", - "addToShortcuts": "Добавить в ярлыки", - "pasteFailed": "Вставка не удалась", - "noUndoableActions": "Нет действий для отмены", - "undoCopySuccess": "Отмена копирования: удалено {{count}} скопированных файлов", - "undoCopyFailedDelete": "Отмена не удалась: не удалось удалить скопированные файлы", - "undoCopyFailedNoInfo": "Отмена не удалась: не найдена информация о скопированных файлах", - "undoMoveSuccess": "Отмена перемещения: {{count}} файлов возвращено в исходное расположение", - "undoMoveFailedMove": "Отмена не удалась: не удалось вернуть файлы", - "undoMoveFailedNoInfo": "Отмена не удалась: не найдена информация о перемещённых файлах", - "undoDeleteNotSupported": "Операцию удаления нельзя отменить: файлы навсегда удалены с сервера", - "undoTypeNotSupported": "Неподдерживаемый тип операции отмены", - "undoOperationFailed": "Операция отмены не удалась", - "unknownError": "Неизвестная ошибка", - "confirm": "Подтвердить", - "find": "Поиск...", - "replace": "Заменить", - "downloadInstead": "Скачать вместо", - "keyboardShortcuts": "Горячие клавиши", - "searchAndReplace": "Поиск и замена", - "editing": "Редактирование", - "search": "Поиск", - "findNext": "Найти далее", - "findPrevious": "Найти предыдущий", - "save": "Сохранить", - "selectAll": "Выделить всё", - "undo": "Отменить", - "redo": "Повторить", - "moveLineUp": "Переместить строку вверх", - "moveLineDown": "Переместить строку вниз", - "toggleComment": "Переключить комментарий", - "autoComplete": "Автодополнение", - "imageLoadError": "Не удалось загрузить изображение", - "startTyping": "Начните вводить...", - "unknownSize": "Неизвестный размер", - "fileIsEmpty": "Файл пуст", - "largeFileWarning": "Предупреждение о большом файле", - "largeFileWarningDesc": "Размер этого файла {{size}}, что может вызвать проблемы с производительностью при открытии в текстовом виде.", - "fileNotFoundAndRemoved": "Файл \"{{name}}\" не найден и был удалён из недавних/закреплённых файлов", - "failedToLoadFile": "Не удалось загрузить файл: {{error}}", - "serverErrorOccurred": "Произошла ошибка сервера. Попробуйте позже.", - "autoSaveFailed": "Автосохранение не удалось", - "fileAutoSaved": "Файл автосохранён", - "moveFileFailed": "Не удалось переместить {{name}}", - "moveOperationFailed": "Операция перемещения не удалась", - "canOnlyCompareFiles": "Можно сравнивать только два файла", - "comparingFiles": "Сравнение файлов: {{file1}} и {{file2}}", - "dragFailed": "Операция перетаскивания не удалась", - "filePinnedSuccessfully": "Файл \"{{name}}\" успешно закреплён", - "pinFileFailed": "Не удалось закрепить файл", - "fileUnpinnedSuccessfully": "Файл \"{{name}}\" успешно откреплён", - "unpinFileFailed": "Не удалось открепить файл", - "shortcutAddedSuccessfully": "Ярлык папки \"{{name}}\" успешно добавлен", - "addShortcutFailed": "Не удалось добавить ярлык", - "operationCompletedSuccessfully": "{{operation}} {{count}} элементов успешно", - "operationCompleted": "{{operation}} {{count}} элементов", - "downloadFileSuccess": "Файл {{name}} успешно загружен", - "downloadFileFailed": "Не удалось загрузить", - "downloadingFile": "Загрузка {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Переместить в {{name}}", - "diffCompareWith": "Сравнить с {{name}}", - "dragOutsideToDownload": "Перетащите за пределы окна для загрузки ({{count}} файлов)", - "newFolderDefault": "Новая папка", - "newFileDefault": "Новый файл.txt", - "successfullyMovedItems": "Успешно перемещено {{count}} элементов в {{target}}", - "move": "Переместить", - "searchInFile": "Поиск в файле (Ctrl+F)", - "showKeyboardShortcuts": "Показать сочетания клавиш", - "decreaseFontSize": "Уменьшить размер шрифта", - "increaseFontSize": "Увеличить размер шрифта", - "startWritingMarkdown": "Начните писать Markdown-контент...", - "loadingFileComparison": "Загрузка сравнения файлов...", - "reload": "Перезагрузить", - "compare": "Сравнить", - "sideBySide": "Бок о бок", - "inline": "Встроенный", - "fileComparison": "Сравнение файлов: {{file1}} и {{file2}}", - "fileTooLarge": "Файл слишком большой: {{error}}", - "sshConnectionFailed": "Ошибка SSH-подключения. Проверьте подключение к {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Не удалось загрузить файл: {{error}}", - "connecting": "Подключение...", - "connectedSuccessfully": "Подключено успешно", - "totpVerificationFailed": "Ошибка проверки TOTP", - "warpgateVerificationFailed": "Ошибка аутентификации Warpgate", - "authenticationFailed": "Ошибка аутентификации", - "incorrectPassphrase": "Неверная парольная фраза. Попробуйте еще раз.", - "verificationCodePrompt": "Код подтверждения:", - "changePermissions": "Изменить права доступа", - "currentPermissions": "Текущие права доступа", - "owner": "Владелец", - "group": "Группа", - "others": "Остальные", - "read": "Чтение", - "write": "Запись", - "execute": "Выполнение", - "permissionsChangedSuccessfully": "Права доступа успешно изменены", - "failedToChangePermissions": "Не удалось изменить права доступа", - "name": "Имя", - "sortByName": "По имени", - "sortByDate": "По дате изменения", - "sortBySize": "По размеру", - "ascending": "По возрастанию", - "descending": "По убыванию", - "root": "Корень", - "new": "Создать", - "sortBy": "Сортировка", - "items": "Элементов", - "selected": "Выбрано", - "editor": "Редактор", - "octal": "Восьмеричный", - "storage": "Хранилище", - "disk": "Диск", - "used": "Занято", - "of": "из", - "toggleSidebar": "Переключить боковую панель", - "compactLayout": "Используйте компактную компоновку.", - "comfortableLayout": "Используйте удобную планировку", - "cannotLoadPdf": "Не удаётся загрузить PDF", - "pdfLoadError": "Ошибка при загрузке этого PDF-файла.", - "loadingPdf": "Загрузка PDF...", - "loadingPage": "Загрузка страницы..." - }, "transfer": { "copyToHost": "Копировать на хост…", "moveToHost": "Переместить на хост…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Повтор не удался", "transferFailedRetryHint": "Частичные данные сохранены в месте назначения. Повтор возобновится, когда соединение восстановится." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Передача файлов между двумя SSH-хостами.", - "sourceServer": "Исходный сервер", - "destinationServer": "Сервер назначения", - "selectHost": "Выберите хост", - "noHostSelected": "Хост не выбран", - "move": "Двигаться", - "copy": "Копия", - "upload": "Загрузить", - "itemsSelected": "{{count}} выбранный элемент", - "itemsSelected_other": "{{count}} выбранные элементы", - "ready": "Готовый", - "loadingHosts": "Загрузка хостов...", - "name": "Имя", - "modified": "Модифицированный", - "size": "Размер", - "noFiles": "Нет файлов", - "open": "Открыть", - "up": "Вверх", - "refresh": "Обновить", - "copyToTarget": "Скопировать в целевую директорию", - "rename": "Переименовать", - "delete": "Удалить", - "editPermissions": "Изменить права доступа", - "createNewFolder": "Создать новую папку", - "deleteSelectedItems": "Удалить выбранные элементы", - "deleteConfirm": "Удалить {{count}} выбранный элемент?", - "deleteConfirm_other": "Удалить {{count}} выбранные элементы?", - "cancel": "Отмена", - "save": "Сохранять", - "folderCreated": "Создана папка", - "itemRenamed": "Элемент переименован", - "actionFailed": "Действие не удалось.", - "itemsDeleted": "{{count}} элемент удален", - "itemsDeleted_other": "{{count}} удаленные элементы", - "deleteFailed": "Удаление не удалось.", - "permissionsUpdated": "Права доступа обновлены.", - "noRemoteSessionConnected": "Удалённая сессия не подключена", - "failedToListFiles": "Не удалось отобразить список файлов.", - "failedToRefresh": "Не удалось обновить", - "destinationInsideSource": "Место назначения не может находиться внутри выбранного пути источника.", - "sameHostNoOp": "Исходная и целевая папки — одна и та же." - }, "tunnels": { "noSshTunnels": "Нет SSH-туннелей", "createFirstTunnelMessage": "Вы ещё не создали ни одного SSH-туннеля. Настройте туннельные соединения в Менеджере хостов, чтобы начать.", diff --git a/src/ui/locales/translated/sr_SP.json b/src/ui/locales/translated/sr_SP.json index 88e1f57ad..f0904c1ad 100644 --- a/src/ui/locales/translated/sr_SP.json +++ b/src/ui/locales/translated/sr_SP.json @@ -715,7 +715,6 @@ "hostManager": "Менаџер домаћина", "credentials": "Акредитиви", "portForwarding": "Преусмеравање", - "sftp": "СФТП", "connections": "Везе", "serial": "Серијски", "keepSideMenuOpen": "Остави мени отвореним", @@ -767,14 +766,12 @@ "tabRdp": "РДП", "tabVnc": "VNC", "tabTunnels": "Тунели", - "tabFiles": "Датотеке", "tabStats": "Метрике хоста", "tabTelnet": "Телнет", "tabSharing": "Дељење", "tabAuthentication": "Аутентификација", "terminal": "Терминал", "tunnel": "Тунел", - "fileManager": "Менаџер датотека", "serverStats": "Метрике хоста", "status": { "available": "Доступно", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} прескочено (није пронађена IP адреса)", "proxmoxStatsUrlCopied": "URL Proxmox статистике је копиран у међуспремник", "copyProxmoxStatsUrlAction": "Копирај URL статистике Proxmox-а", - "enableFileManagerMonitor": "Омогући менаџер датотека", - "enableFileManagerMonitorDesc": "Прегледајте и управљајте датотекама на овом хосту преко SFTP-а", - "scpLegacyLabel": "SCP Legacy Mode", - "scpLegacyDesc": "Користите старији пренос датотека за сервере који не подржавају SFTP подсистем (нпр. уграђени или минимални SSH сервери).", - "defaultPathLabel": "Подразумевана путања", - "fileManagerPathHint": "Директоријум који треба отворити када се покрене менаџер датотека за овај хост.", "statusChecksLabel": "Провере статуса", "enableStatusChecks": "Омогући провере статуса", "enableStatusChecksDesc": "Периодично пингујте овај хост да бисте проверили доступност", @@ -1278,7 +1269,6 @@ "deletedHost": "Обрисано {{name}}", "copiedToClipboard": "Копирано у међуспремник", "terminalUrlCopied": "URL терминала је копиран", - "fileManagerUrlCopied": "URL менаџера датотека је копиран", "tunnelUrlCopied": "URL тунела је копиран", "dockerUrlCopied": "URL адреса Докера је копирана", "hostMetricsUrlCopied": "URL метрика хоста је копиран", @@ -1296,7 +1286,6 @@ "copyAddress": "Копирај адресу", "copyLink": "Копирај линк", "copyTerminalUrlAction": "Копирај URL терминала", - "copyFileManagerUrlAction": "Копирај URL менаџера датотека", "copyTunnelUrlAction": "Копирај URL тунела", "copyDockerUrlAction": "Копирај Докер УРЛ", "copyHostMetricsUrlAction": "Копирај URL метрика хоста", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Уклони из Сплита" } }, - "fileManager": { - "noHostSelected": "Није изабран домаћин", - "initializingEditor": "Иницијализација уређивача...", - "file": "Датотека", - "folder": "Фасцикла", - "uploadFile": "Отпреми датотеку", - "downloadFile": "Преузми", - "extractArchive": "Издвоји архиву", - "extractingArchive": "Издвајање {{name}}...", - "archiveExtractedSuccessfully": "{{name}} је успешно извучено", - "extractFailed": "Издвајање није успело", - "compressFile": "Компресуј датотеку", - "compressFiles": "Компресуј датотеке", - "compressFilesDesc": "Компресуј {{count}} ставки у архиву", - "archiveName": "Назив архиве", - "enterArchiveName": "Унесите име архиве...", - "compressionFormat": "Формат компресије", - "selectedFiles": "Изабране датотеке", - "andMoreFiles": "и још {{count}}...", - "compress": "Компресуј", - "compressingFiles": "Компресија {{count}} ставки у {{name}}...", - "filesCompressedSuccessfully": "{{name}} је успешно креирано", - "compressFailed": "Компресија није успела", - "edit": "Измени", - "preview": "Преглед", - "previous": "Претходно", - "next": "Следеће", - "pageXOfY": "Страница {{current}} од {{total}}", - "zoomOut": "Умањи", - "zoomIn": "Увећај", - "newFile": "Нова датотека", - "newFolder": "Нова фасцикла", - "rename": "Преименуј", - "uploading": "Отпремање...", - "uploadingFile": "Отпремање {{name}}...", - "fileName": "Име датотеке", - "folderName": "Назив фасцикле", - "fileUploadedSuccessfully": "Датотека „{{name}}“ је успешно отпремљена", - "failedToUploadFile": "Отпремање датотеке није успело", - "uploadingFolderFiles": "Отпремање {{count}} датотека...", - "uploadedFolderFiles": "Успешно отпремљено {{count}} датотека", - "uploadedFolderPartial": "Отпремљено {{uploaded}} датотека, {{failed}} није успело", - "fileDownloadedSuccessfully": "Датотека „{{name}}“ је успешно преузета", - "failedToDownloadFile": "Преузимање датотеке није успело", - "fileCreatedSuccessfully": "Датотека „{{name}}“ је успешно креирана", - "folderCreatedSuccessfully": "Фасцикла „{{name}}“ је успешно креирана", - "failedToCreateItem": "Није успело креирање ставке", - "operationFailed": "Операција {{operation}} није успела за {{name}}: {{error}}", - "failedToResolveSymlink": "Није успело решавање симболичке везе", - "itemsDeletedSuccessfully": "{{count}} ставке су успешно обрисане", - "failedToDeleteItems": "Брисање ставки није успело", - "sudoPasswordRequired": "Потребна је администраторска лозинка", - "enterSudoPassword": "Унесите лозинку за судо да бисте наставили ову операцију", - "sudoPassword": "Sudo лозинка", - "sudoOperationFailed": "Sudo операција није успела", - "sudoAuthFailed": "Судо аутентификација није успела", - "dragFilesToUpload": "Отпустите датотеке овде да бисте их отпремили", - "dropToUploadHere": "Отпустите да бисте отпремили овде", - "dropToDownloadHere": "Отпустите да бисте преузели овде", - "localFiles": "Локално", - "showLocalFiles": "Прикажи локалне датотеке (подељени приказ)", - "hideLocalFiles": "Сакриј локалне датотеке", - "localHome": "Почетна фасцикла", - "localPathPlaceholder": "Путања локалне фасцикле", - "localShowHidden": "Прикажи скривене датотеке", - "localHideHidden": "Сакриј скривене датотеке", - "localRevealInFileManager": "Откриј у системском менаџеру датотека", - "localCannotOpenFolder": "Не могу да отворим фасциклу", - "localItemCount": "{{count}} ставке", - "localSelectedCount": "{{count}} изабрано", - "back": "Назад", - "forward": "Напред", - "up": "Горе", - "kind": "Врста", - "localUploadingProgress": "Отпремање {{current}} од {{total}}", - "localDownloadingProgress": "Преузимање {{current}} од {{total}}", - "localTransferInFlight": "{{count}} одједном", - "localPreparingDownload": "Припрема преузимања...", - "localTransferCancelled": "Трансфер отказан", - "localNothingToTransfer": "Ништа за пренос", - "localUploadComplete": "Отпремљено {{count}} датотека", - "localUploadFailed": "Отпремање није успело", - "localUploadPartial": "Отпремљено {{done}} датотека, {{failed}} није успело", - "localDownloadComplete": "Преузето {{count}} датотека", - "localDownloadFailed": "Преузимање није успело", - "localDownloadPartial": "Преузимање {{done}} датотека, {{failed}} није успело", - "localReplaceExistingPrompt": "{{count}} став(е) већ постоје у овој фасцикли. Заменити их?", - "localReplace": "Замени", - "localSkipExisting": "Прескочи", - "localSkippedExisting": "Прескочено {{count}} постојећих ставки", - "localUnsafeNamesSkipped": "Прескочено је {{count}} ставки чија имена се не могу користити на овом рачунару", - "localOpen": "Отворено", - "localOpenFolder": "Отвори фасциклу", - "localUploadToRemote": "Отпреми на сервер", - "localUploadToRemoteMany": "Отпреми {{count}} ставки на сервер", - "localMoveToTrash": "Премести у отпад", - "localMoveToTrashMany": "Премести {{count}} ставки у Отпад", - "localTrashConfirmSingle": "Преместити „{{name}}“ у Отпад?", - "localTrashConfirmFolder": "Преместити фасциклу „{{name}}“ и сав њен садржај у корпу за отпад?", - "localTrashConfirmMany": "Преместити {{count}} ставки у Отпад?", - "localTrashed": "Премештено {{count}} ставки у Отпад", - "localTrashFailed": "Није могуће преместити у Отпад", - "localRenameFailed": "Преименовање није успело", - "localCreateFailed": "Није могуће креирати ставку", - "goToParentFolder": "Иди у надређени фолдер", - "resizeSidebar": "Превуците да бисте променили величину бочне траке (двапут кликните да бисте ресетовали)", - "resizeLocalPane": "Превуците да бисте променили величину локалног окна (двапут кликните да бисте ресетовали)", - "columns": "Колоне", - "columnsHint": "Кликните десним тастером миша да бисте изабрали колоне", - "emptyFolder": "Ова фасцикла је празна", - "searchFiles": "Претражи датотеке...", - "upload": "Отпреми", - "selectHostToStart": "Изаберите хост да бисте започели управљање датотекама", - "sshRequiredForFileManager": "Менаџер датотека захтева SSH. Овај хост нема омогућен SSH.", - "failedToConnect": "Повезивање са SSH-ом није успело", - "failedToLoadDirectory": "Учитавање директоријума није успело", - "noSSHConnection": "SSH веза није доступна", - "copy": "Копија", - "cut": "Исеци", - "paste": "Залепи", - "copyPath": "Копирај путању", - "copyPaths": "Копирај путање", - "delete": "Обриши", - "properties": "Некретнине", - "refresh": "Освежи", - "downloadFiles": "Преузмите {{count}} датотека у прегледач", - "copyFiles": "Копирај {{count}} ставке", - "cutFiles": "Исеци {{count}} ставки", - "deleteFiles": "Обриши {{count}} ставки", - "filesCopiedToClipboard": "{{count}} ставке копиране у међуспремник", - "filesCutToClipboard": "{{count}} ставке исечене у међуспремник", - "pathCopiedToClipboard": "Путања је копирана у међуспремник", - "pathsCopiedToClipboard": "{{count}} путање копиране у међуспремник", - "failedToCopyPath": "Копирање путање у међуспремник није успело", - "copyFolderLink": "Копирај везу у фасциклу", - "copyCurrentFolderLink": "Копирај везу у тренутну фасциклу", - "folderLinkCopied": "Линк фасцикле копиран у међуспремник", - "failedToCopyFolderLink": "Копирање линка фасцикле није успело", - "movedItems": "Премештено {{count}} ставки", - "failedToDeleteItem": "Брисање ставке није успело", - "itemRenamedSuccessfully": "{{type}} је успешно преименован", - "failedToRenameItem": "Преименовање ставке није успело", - "download": "Преузми", - "openExternalEditor": "Отвори споља", - "chooseExternalEditor": "Изаберите спољни уређивач", - "externalEditorSelected": "Изабран је спољни уредник", - "externalEditorDesktopOnly": "Спољни уређивач је доступан само у десктоп апликацији", - "externalEditorOpened": "Отворено у екстерном уређивачу. Чувањем тамо ће се измене отпремити назад на сервер.", - "failedToOpenExternalEditor": "Отварање екстерног уређивача није успело", - "failedToSelectExternalEditor": "Није успело бирање спољног уређивача", - "permissions": "Дозволе", - "size": "Величина", - "modified": "Измењено", - "path": "Путања", - "confirmDelete": "Да ли сте сигурни да желите да обришете {{name}}?", - "permissionDenied": "Дозвола одбијена", - "serverError": "Грешка сервера", - "fileSavedSuccessfully": "Датотека је успешно сачувана", - "failedToSaveFile": "Чување датотеке није успело", - "confirmDeleteSingleItem": "Да ли сте сигурни да желите трајно да избришете „{{name}}“?", - "confirmDeleteMultipleItems": "Да ли сте сигурни да желите трајно да избришете {{count}} ставке?", - "confirmDeleteMultipleItemsWithFolders": "Да ли сте сигурни да желите трајно да избришете {{count}} ставке? Ово укључује фасцикле и њихов садржај.", - "confirmDeleteFolder": "Да ли сте сигурни да желите трајно да избришете фасциклу „{{name}}“ и сав њен садржај?", - "permanentDeleteWarning": "Ова радња се не може поништити. Ставка(е) ће бити трајно избрисане са сервера.", - "moveToTrashWarning": "Ставка(е) ће бити премештена(е) у смеће Термикса и може(ју) се вратити пре истека периода чувања.", - "itemsMovedToTrash": "{{count}} ставке премештено у Отпад", - "trash": "Смеће", - "trashDescription": "Обрисане ставке се чувају {{days}} дана на овом хосту.", - "trashLoadFailed": "Учитавање Отпада није успело", - "trashEmpty": "Отпад је празан", - "trashRestore": "Врати", - "trashRestored": "Враћено {{name}}", - "trashRestoreFailed": "Враћање ставке није успело. Оригинална путања можда већ постоји.", - "trashDeletePermanently": "Трајно избриши", - "trashDeleteConfirm": "Трајно избрисати {{name}}? Ово се не може поништити.", - "trashPermanentlyDeleted": "Трајно обрисано {{name}}", - "trashDeleteFailed": "Трајно брисање ставке није успело", - "trashEmptyAction": "Испразни отпад", - "trashEmptyConfirm": "Трајно избрисати сваку ставку у Отпаду? Ово се не може поништити.", - "trashEmptied": "Отпад је испражњен", - "trashRetention": "Аутоматско чишћење после (дана)", - "trashRetentionSaved": "Задржавање отпада је ажурирано", - "trashRetentionFailed": "Ажурирање задржавања у отпаду није успело", - "trashUnavailableConfirm": "Ова ставка не може бити премештена у Отпад, обично зато што се налази на другом систему датотека или захтева повишена права. Желите ли да је трајно избришете?", - "recent": "Недавно", - "pinned": "Закачено", - "folderShortcuts": "Пречице за фасцикле", - "failedToReconnectSSH": "Поновно повезивање SSH сесије није успело", - "openTerminalHere": "Отвори терминал овде", - "run": "Трчи", - "openTerminalInFolder": "Отвори терминал у овој фасцикли", - "openTerminalInFileLocation": "Отвори терминал на локацији датотеке", - "runningFile": "Трчање - {{file}}", - "onlyRunExecutableFiles": "Може покренути само извршне датотеке", - "directories": "Директоријуми", - "removedFromRecentFiles": "Уклоњено је „{{name}}“ из недавних датотека", - "removeFailed": "Уклањање није успело", - "unpinnedSuccessfully": "Откачено „{{name}}“ успешно", - "unpinFailed": "Откачивање није успело", - "removedShortcut": "Уклоњена пречица „{{name}}“", - "removeShortcutFailed": "Уклањање пречице није успело", - "clearedAllRecentFiles": "Обрисани су сви недавно објављени фајлови", - "clearFailed": "Брисање није успело", - "removeFromRecentFiles": "Уклони из недавних датотека", - "clearAllRecentFiles": "Обриши све недавно објављене датотеке", - "unpinFile": "Откачи датотеку", - "removeShortcut": "Уклони пречицу", - "pinFile": "Закачи датотеку", - "addToShortcuts": "Додај у пречице", - "pasteFailed": "Лепљење није успело", - "noUndoableActions": "Нема радњи које се могу поништити", - "undoCopySuccess": "Поништена операција копирања: Обрисано {{count}} копираних датотека", - "undoCopyFailedDelete": "Поништавање није успело: Није могуће обрисати ниједну копирану датотеку", - "undoCopyFailedNoInfo": "Поништавање није успело: Нису пронађене информације о копираној датотеци", - "undoMoveSuccess": "Поништена операција премештања: Премештено {{count}} датотека назад на оригиналну локацију", - "undoMoveFailedMove": "Поништавање није успело: Није могуће вратити датотеке", - "undoMoveFailedNoInfo": "Поништавање није успело: Нису пронађене информације о премештеној датотеци", - "undoDeleteNotSupported": "Брисање се не може поништити: Датотеке су трајно обрисане са сервера", - "undoTypeNotSupported": "Неподржани тип операције поништавања", - "undoOperationFailed": "Поништавање није успело", - "unknownError": "Непозната грешка", - "confirm": "Потврди", - "find": "Пронађи...", - "replace": "Замени", - "downloadInstead": "Преузми уместо тога", - "keyboardShortcuts": "Пречице на тастатури", - "searchAndReplace": "Претражи и замени", - "editing": "Уређивање", - "search": "Претрага", - "findNext": "Пронађи следеће", - "findPrevious": "Пронађи претходно", - "save": "Сачувај", - "selectAll": "Изабери све", - "undo": "Поништи", - "redo": "Понови", - "moveLineUp": "Помери ред нагоре", - "moveLineDown": "Помери ред надоле", - "toggleComment": "Укључи/искључи коментар", - "autoComplete": "Аутоматско довршавање", - "imageLoadError": "Учитавање слике није успело", - "startTyping": "Почни да куцаш...", - "unknownSize": "Непозната величина", - "fileIsEmpty": "Датотека је празна", - "largeFileWarning": "Упозорење о великој датотеци", - "largeFileWarningDesc": "Ова датотека је величине {{size}} , што може изазвати проблеме са перформансама када се отвори као текст.", - "fileNotFoundAndRemoved": "Датотека „{{name}}“ није пронађена и уклоњена је из недавно коришћених/закачених датотека", - "failedToLoadFile": "Учитавање датотеке није успело: {{error}}", - "serverErrorOccurred": "Дошло је до грешке сервера. Молимо покушајте поново касније.", - "autoSaveFailed": "Аутоматско чување није успело", - "fileAutoSaved": "Датотека је аутоматски сачувана", - "moveFileFailed": "Премештање није успело {{name}}", - "moveOperationFailed": "Операција премештања није успела", - "canOnlyCompareFiles": "Могуће је упоредити само две датотеке", - "comparingFiles": "Поређење датотека: {{file1}} и {{file2}}", - "dragFailed": "Превлачење није успело", - "filePinnedSuccessfully": "Датотека „{{name}}“ је успешно закачена", - "pinFileFailed": "Качење датотеке није успело", - "fileUnpinnedSuccessfully": "Датотека „{{name}}“ је успешно откачена", - "unpinFileFailed": "Откачивање датотеке није успело", - "shortcutAddedSuccessfully": "Пречица за фасциклу „{{name}}“ је успешно додата", - "addShortcutFailed": "Додавање пречице није успело", - "operationCompletedSuccessfully": "{{operation}} {{count}} ставке успешно", - "operationCompleted": "{{operation}} {{count}} ставке", - "downloadFileSuccess": "Датотека {{name}} је успешно преузета", - "downloadFileFailed": "Преузимање није успело", - "downloadingFile": "Преузимање {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Премести на {{name}}", - "diffCompareWith": "Разлика у поређењу са {{name}}", - "dragOutsideToDownload": "Превуците спољашњост прозора да бисте преузели ({{count}} датотека)", - "newFolderDefault": "Нови фолдер", - "newFileDefault": "НоваДатотека.txt", - "successfullyMovedItems": "Успешно премештено {{count}} ставки у {{target}}", - "move": "Помери", - "searchInFile": "Претражи у датотеци (Ctrl+F)", - "showKeyboardShortcuts": "Прикажи пречице на тастатури", - "decreaseFontSize": "Смањи величину фонта", - "increaseFontSize": "Повећај величину фонта", - "startWritingMarkdown": "Почните да пишете свој садржај у markdown формату...", - "loadingFileComparison": "Учитавање поређења датотека...", - "reload": "Поново учитај", - "compare": "Упореди", - "sideBySide": "Један поред другог", - "inline": "Уграђено", - "fileComparison": "Поређење датотека: {{file1}} наспрам {{file2}}", - "fileTooLarge": "Датотека је превелика: {{error}}", - "sshConnectionFailed": "SSH веза није успела. Молимо вас да проверите везу са {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Учитавање датотеке није успело: {{error}}", - "connecting": "Повезивање...", - "connectedSuccessfully": "Успешно повезано", - "totpVerificationFailed": "Верификација TOTP-а није успела", - "warpgateVerificationFailed": "Аутентификација Варпгејта није успела", - "authenticationFailed": "Аутентификација није успела", - "incorrectPassphrase": "Нетачна лозинка. Молимо покушајте поново.", - "verificationCodePrompt": "Верификациони код:", - "changePermissions": "Промени дозволе", - "currentPermissions": "Тренутне дозволе", - "owner": "Власник", - "group": "Група", - "others": "Други", - "read": "Читај", - "write": "Пиши", - "execute": "Изврши", - "permissionsChangedSuccessfully": "Дозволе су успешно промењене", - "failedToChangePermissions": "Промена дозвола није успела", - "name": "Име", - "sortByName": "Име", - "sortByDate": "Датум измене", - "sortBySize": "Величина", - "ascending": "Растуће", - "descending": "Опадајући", - "root": "Корен", - "new": "Ново", - "sortBy": "Сортирај по", - "items": "Ставке", - "selected": "Изабрано", - "editor": "Уредник", - "octal": "Октално", - "storage": "Складиштење", - "disk": "Диск", - "used": "Коришћено", - "of": "од", - "toggleSidebar": "Укључи/искључи бочну траку", - "compactLayout": "Користите компактни распоред", - "comfortableLayout": "Користите удобан распоред", - "cannotLoadPdf": "Није могуће учитати PDF", - "pdfLoadError": "Дошло је до грешке при учитавању ове PDF датотеке.", - "loadingPdf": "Учитавање PDF-а...", - "loadingPage": "Учитавање странице..." - }, "transfer": { "copyToHost": "Копирај на хост…", "moveToHost": "Премести на хост…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Поновни покушај није успео", "transferFailedRetryHint": "Делимични подаци су сачувани на одредишту. Покушај ће бити настављен када се веза поново успостави." }, - "sftpTransfer": { - "title": "СФТП", - "description": "Пренос датотека између два SSH хоста.", - "sourceServer": "Изворни сервер", - "destinationServer": "Одредишни сервер", - "selectHost": "Изаберите хоста", - "noHostSelected": "Није изабран домаћин", - "move": "Помери", - "copy": "Копија", - "upload": "Отпреми", - "itemsSelected": "{{count}} ставка је изабрана", - "itemsSelected_other": "Изабрано је {{count}} ставки", - "ready": "Спреман", - "loadingHosts": "Учитавање хостова...", - "name": "Име", - "modified": "Измењено", - "size": "Величина", - "noFiles": "Нема датотека", - "open": "Отворено", - "up": "Горе", - "refresh": "Освежи", - "copyToTarget": "Копирај у циљни директоријум", - "rename": "Преименуј", - "delete": "Обриши", - "editPermissions": "Измени дозволе", - "createNewFolder": "Направи нову фасциклу", - "deleteSelectedItems": "Обриши изабране ставке", - "deleteConfirm": "Обришите {{count}} изабрану ставку?", - "deleteConfirm_other": "Обришите {{count}} изабраних ставки?", - "cancel": "Откажи", - "save": "Сачувај", - "folderCreated": "Фасцикла је направљена", - "itemRenamed": "Ставка је преименована", - "actionFailed": "Акција није успела", - "itemsDeleted": "{{count}} ставка је обрисана", - "itemsDeleted_other": "{{count}} ставки су обрисане", - "deleteFailed": "Брисање није успело", - "permissionsUpdated": "Дозволе су ажуриране", - "noRemoteSessionConnected": "Није повезана удаљена сесија", - "failedToListFiles": "Навођење датотека није успело", - "failedToRefresh": "Освежавање није успело", - "destinationInsideSource": "Одредиште не може бити унутар изабране изворне путање.", - "sameHostNoOp": "Извор и одредиште су иста фасцикла." - }, "tunnels": { "noSshTunnels": "Нема SSH тунела", "createFirstTunnelMessage": "Још увек нисте креирали ниједан SSH тунел. Конфигуришите тунелске везе у Host Manager-у да бисте започели.", diff --git a/src/ui/locales/translated/sv_SE.json b/src/ui/locales/translated/sv_SE.json index 76fdbadee..7490a90da 100644 --- a/src/ui/locales/translated/sv_SE.json +++ b/src/ui/locales/translated/sv_SE.json @@ -715,7 +715,6 @@ "hostManager": "Värdhanterare", "credentials": "Referenser", "portForwarding": "Spedition", - "sftp": "SFTP", "connections": "Anslutningar", "serial": "Serie", "keepSideMenuOpen": "Håll menyn öppen", @@ -767,14 +766,12 @@ "tabRdp": "Landsbygdsutveckling", "tabVnc": "VNC", "tabTunnels": "Tunnlar", - "tabFiles": "Filer", "tabStats": "Värdstatistik", "tabTelnet": "Telnet", "tabSharing": "Delning", "tabAuthentication": "Autentisering", "terminal": "Terminal", "tunnel": "Tunnel", - "fileManager": "Filhanteraren", "serverStats": "Värdstatistik", "status": { "available": "Tillgänglig", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} hoppades över (ingen IP-adress hittades)", "proxmoxStatsUrlCopied": "Proxmox Stats URL kopierad till urklipp", "copyProxmoxStatsUrlAction": "Kopiera Proxmox Stats-URL", - "enableFileManagerMonitor": "Aktivera filhanteraren", - "enableFileManagerMonitorDesc": "Bläddra bland och hantera filer på den här värden via SFTP", - "scpLegacyLabel": "SCP Legacy-läge", - "scpLegacyDesc": "Använd äldre filöverföring för servrar som inte stöder SFTP-undersystemet (t.ex. inbäddade eller minimala SSH-servrar).", - "defaultPathLabel": "Standardsökväg", - "fileManagerPathHint": "Katalogen som ska öppnas när filhanteraren startas för den här värden.", "statusChecksLabel": "Statuskontroller", "enableStatusChecks": "Aktivera statuskontroller", "enableStatusChecksDesc": "Pinga den här värden regelbundet för att verifiera tillgänglighet", @@ -1278,7 +1269,6 @@ "deletedHost": "Borttagen {{name}}", "copiedToClipboard": "Kopierad till urklipp", "terminalUrlCopied": "Terminal-URL kopierad", - "fileManagerUrlCopied": "Filhanterarens URL kopierad", "tunnelUrlCopied": "Tunnel-URL kopierad", "dockerUrlCopied": "Docker-URL kopierad", "hostMetricsUrlCopied": "URL för värdstatistik kopierad", @@ -1296,7 +1286,6 @@ "copyAddress": "Kopiera adress", "copyLink": "Kopiera länk", "copyTerminalUrlAction": "Kopiera terminal-URL", - "copyFileManagerUrlAction": "Kopiera filhanterarens URL", "copyTunnelUrlAction": "Kopiera tunnel-URL", "copyDockerUrlAction": "Kopiera Docker-URL", "copyHostMetricsUrlAction": "Kopiera URL för värdstatistik", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Ta bort från Split" } }, - "fileManager": { - "noHostSelected": "Ingen värd vald", - "initializingEditor": "Initierar redigeraren...", - "file": "Fil", - "folder": "Mapp", - "uploadFile": "Ladda upp fil", - "downloadFile": "Ladda ner", - "extractArchive": "Utdragsarkiv", - "extractingArchive": "Extraherar {{name}}...", - "archiveExtractedSuccessfully": "{{name}} extraherades", - "extractFailed": "Extraheringen misslyckades", - "compressFile": "Komprimera fil", - "compressFiles": "Komprimera filer", - "compressFilesDesc": "Komprimera {{count}} objekt till ett arkiv", - "archiveName": "Arkivnamn", - "enterArchiveName": "Ange arkivnamn...", - "compressionFormat": "Komprimeringsformat", - "selectedFiles": "Valda filer", - "andMoreFiles": "och {{count}} mer...", - "compress": "Komprimera", - "compressingFiles": "Komprimerar {{count}} objekt till {{name}}...", - "filesCompressedSuccessfully": "{{name}} skapades", - "compressFailed": "Komprimeringen misslyckades", - "edit": "Redigera", - "preview": "Förhandsvisning", - "previous": "Tidigare", - "next": "Nästa", - "pageXOfY": "Sida {{current}} av {{total}}", - "zoomOut": "Zooma ut", - "zoomIn": "Zooma in", - "newFile": "Ny fil", - "newFolder": "Ny mapp", - "rename": "Döpa om", - "uploading": "Laddar upp...", - "uploadingFile": "Laddar upp {{name}}...", - "fileName": "Filnamn", - "folderName": "Mappnamn", - "fileUploadedSuccessfully": "Filen \"{{name}}\" har laddats upp", - "failedToUploadFile": "Misslyckades med att ladda upp filen", - "uploadingFolderFiles": "Laddar upp {{count}} fil(er)...", - "uploadedFolderFiles": "Uppladdade {{count}} fil(er) framgångsrikt", - "uploadedFolderPartial": "Uppladdade {{uploaded}} fil(er), {{failed}} misslyckades", - "fileDownloadedSuccessfully": "Filen \"{{name}}\" har laddats ner", - "failedToDownloadFile": "Misslyckades med att ladda ner filen", - "fileCreatedSuccessfully": "Filen \"{{name}}\" skapades", - "folderCreatedSuccessfully": "Mappen \"{{name}}\" skapades", - "failedToCreateItem": "Misslyckades med att skapa objektet", - "operationFailed": "{{operation}} -åtgärden misslyckades för {{name}}: {{error}}", - "failedToResolveSymlink": "Misslyckades med att lösa symbolisk länk", - "itemsDeletedSuccessfully": "{{count}} objekt har raderats", - "failedToDeleteItems": "Misslyckades med att ta bort objekt", - "sudoPasswordRequired": "Administratörslösenord krävs", - "enterSudoPassword": "Ange sudo-lösenordet för att fortsätta den här operationen", - "sudoPassword": "Sudo-lösenord", - "sudoOperationFailed": "Sudo-operationen misslyckades", - "sudoAuthFailed": "Sudo-autentisering misslyckades", - "dragFilesToUpload": "Släpp filer här för att ladda upp", - "dropToUploadHere": "Släpp för att ladda upp här", - "dropToDownloadHere": "Släpp för att ladda ner här", - "localFiles": "Lokal", - "showLocalFiles": "Visa lokala filer (delad vy)", - "hideLocalFiles": "Dölj lokala filer", - "localHome": "Hemmappen", - "localPathPlaceholder": "Sökväg till lokal mapp", - "localShowHidden": "Visa dolda filer", - "localHideHidden": "Dölj dolda filer", - "localRevealInFileManager": "Visa i systemets filhanterare", - "localCannotOpenFolder": "Kan inte öppna mappen", - "localItemCount": "{{count}} objekt", - "localSelectedCount": "{{count}} vald", - "back": "Tillbaka", - "forward": "Fram", - "up": "Upp", - "kind": "Slag", - "localUploadingProgress": "Laddar upp {{current}} av {{total}}", - "localDownloadingProgress": "Laddar ner {{current}} av {{total}}", - "localTransferInFlight": "{{count}} på en gång", - "localPreparingDownload": "Förbereder nedladdning...", - "localTransferCancelled": "Överföringen avbruten", - "localNothingToTransfer": "Inget att överföra", - "localUploadComplete": "Uppladdade {{count}} fil(er)", - "localUploadFailed": "Uppladdningen misslyckades", - "localUploadPartial": "Uppladdade {{done}} fil(er), {{failed}} misslyckades", - "localDownloadComplete": "Nedladdade {{count}} fil(er)", - "localDownloadFailed": "Nedladdningen misslyckades", - "localDownloadPartial": "Nedladdning av {{done}} fil(er), {{failed}} misslyckades", - "localReplaceExistingPrompt": "{{count}} objekt finns redan i den här mappen. Vill du ersätta dem?", - "localReplace": "Ersätta", - "localSkipExisting": "Hoppa", - "localSkippedExisting": "Hoppade över {{count}} befintliga objekt", - "localUnsafeNamesSkipped": "Hoppade över {{count}} objekt vars namn inte kan användas på den här datorn", - "localOpen": "Öppna", - "localOpenFolder": "Öppna mappen", - "localUploadToRemote": "Ladda upp till servern", - "localUploadToRemoteMany": "Ladda upp {{count}} objekt till servern", - "localMoveToTrash": "Flytta till papperskorgen", - "localMoveToTrashMany": "Flytta {{count}} objekt till papperskorgen", - "localTrashConfirmSingle": "Flytta \"{{name}}\" till papperskorgen?", - "localTrashConfirmFolder": "Flytta mappen \"{{name}}\" och allt dess innehåll till papperskorgen?", - "localTrashConfirmMany": "Flytta {{count}} objekt till papperskorgen?", - "localTrashed": "Flyttade {{count}} objekt till papperskorgen", - "localTrashFailed": "Kunde inte flytta till papperskorgen", - "localRenameFailed": "Byte av namn misslyckades", - "localCreateFailed": "Kunde inte skapa objektet", - "goToParentFolder": "Gå till överordnad mapp", - "resizeSidebar": "Dra för att ändra storlek på sidofältet (dubbelklicka för att återställa)", - "resizeLocalPane": "Dra för att ändra storlek på den lokala rutan (dubbelklicka för att återställa)", - "columns": "Kolumner", - "columnsHint": "Högerklicka för att välja kolumner", - "emptyFolder": "Den här mappen är tom", - "searchFiles": "Sök filer...", - "upload": "Ladda upp", - "selectHostToStart": "Välj en värd för att starta filhanteringen", - "sshRequiredForFileManager": "Filhanteraren kräver SSH. Den här värden har inte SSH aktiverat.", - "failedToConnect": "Misslyckades med att ansluta till SSH", - "failedToLoadDirectory": "Misslyckades med att ladda katalogen", - "noSSHConnection": "Ingen SSH-anslutning tillgänglig", - "copy": "Kopiera", - "cut": "Skära", - "paste": "Klistra", - "copyPath": "Kopiera sökväg", - "copyPaths": "Kopiera sökvägar", - "delete": "Radera", - "properties": "Fastigheter", - "refresh": "Uppdatera", - "downloadFiles": "Ladda ner {{count}} filer till webbläsaren", - "copyFiles": "Kopiera {{count}} objekt", - "cutFiles": "Klipp ut {{count}} objekt", - "deleteFiles": "Ta bort {{count}} objekt", - "filesCopiedToClipboard": "{{count}} objekt kopierade till urklipp", - "filesCutToClipboard": "{{count}} objekt klippta till urklipp", - "pathCopiedToClipboard": "Sökväg kopierad till urklipp", - "pathsCopiedToClipboard": "{{count}} sökvägar kopierade till urklipp", - "failedToCopyPath": "Misslyckades med att kopiera sökvägen till urklipp", - "copyFolderLink": "Kopiera länk till mapp", - "copyCurrentFolderLink": "Kopiera länk till aktuell mapp", - "folderLinkCopied": "Mapplänk kopierad till urklipp", - "failedToCopyFolderLink": "Misslyckades med att kopiera mapplänken", - "movedItems": "Flyttade {{count}} objekt", - "failedToDeleteItem": "Misslyckades med att ta bort objektet", - "itemRenamedSuccessfully": "{{type}} har bytt namn", - "failedToRenameItem": "Misslyckades med att byta namn på objektet", - "download": "Ladda ner", - "openExternalEditor": "Öppna externt", - "chooseExternalEditor": "Välj extern redigerare", - "externalEditorSelected": "Extern redigerare vald", - "externalEditorDesktopOnly": "Extern redigerare är endast tillgänglig i skrivbordsappen", - "externalEditorOpened": "Öppnad i extern redigerare. Om du sparar där laddas ändringarna upp tillbaka till servern.", - "failedToOpenExternalEditor": "Misslyckades med att öppna den externa redigeraren", - "failedToSelectExternalEditor": "Misslyckades med att välja extern redigerare", - "permissions": "Behörigheter", - "size": "Storlek", - "modified": "Ändrad", - "path": "Väg", - "confirmDelete": "Är du säker på att du vill ta bort {{name}}?", - "permissionDenied": "Tillstånd nekad", - "serverError": "Serverfel", - "fileSavedSuccessfully": "Filen har sparats", - "failedToSaveFile": "Misslyckades med att spara filen", - "confirmDeleteSingleItem": "Är du säker på att du vill ta bort \"{{name}} \" permanent?", - "confirmDeleteMultipleItems": "Är du säker på att du vill ta bort {{count}} objekt permanent?", - "confirmDeleteMultipleItemsWithFolders": "Är du säker på att du vill ta bort {{count}} objekt permanent? Detta inkluderar mappar och deras innehåll.", - "confirmDeleteFolder": "Är du säker på att du vill permanent ta bort mappen \"{{name}}\" och allt dess innehåll?", - "permanentDeleteWarning": "Den här åtgärden kan inte ångras. Objektet/objekten kommer att raderas permanent från servern.", - "moveToTrashWarning": "Objektet/objekten kommer att flyttas till Termix-papperskorgen och kan återställas innan lagringsperioden löper ut.", - "itemsMovedToTrash": "{{count}} objekt flyttade till papperskorgen", - "trash": "Skräp", - "trashDescription": "Borttagna objekt sparas i {{days}} dag(ar) på den här värden.", - "trashLoadFailed": "Misslyckades med att ladda papperskorgen", - "trashEmpty": "Papperskorgen är tom", - "trashRestore": "Återställa", - "trashRestored": "Återställd {{name}}", - "trashRestoreFailed": "Det gick inte att återställa objektet. Den ursprungliga sökvägen kan redan finnas.", - "trashDeletePermanently": "Ta bort permanent", - "trashDeleteConfirm": "Vill du permanent radera {{name}}? Detta kan inte ångras.", - "trashPermanentlyDeleted": "Permanent borttagen {{name}}", - "trashDeleteFailed": "Misslyckades med att ta bort objektet permanent", - "trashEmptyAction": "Töm papperskorgen", - "trashEmptyConfirm": "Vill du permanent radera alla objekt i papperskorgen? Det här kan inte ångras.", - "trashEmptied": "Papperskorgen tömd", - "trashRetention": "Automatisk rensning efter (dagar)", - "trashRetentionSaved": "Papperskorgen har lagrats uppdaterad", - "trashRetentionFailed": "Misslyckades med att uppdatera papperskorgen", - "trashUnavailableConfirm": "Det här objektet kan inte flyttas till papperskorgen, vanligtvis för att det finns på ett annat filsystem eller kräver utökade behörigheter. Vill du ta bort det permanent istället?", - "recent": "Ny", - "pinned": "Fäst", - "folderShortcuts": "Mappgenvägar", - "failedToReconnectSSH": "Misslyckades med att återansluta SSH-sessionen", - "openTerminalHere": "Öppna terminalen här", - "run": "Sikt", - "openTerminalInFolder": "Öppna terminalen i den här mappen", - "openTerminalInFileLocation": "Öppna terminalen på filplatsen", - "runningFile": "Löpning - {{file}}", - "onlyRunExecutableFiles": "Kan bara köra körbara filer", - "directories": "Kataloger", - "removedFromRecentFiles": "Tog bort \"{{name}}\" från senaste filer", - "removeFailed": "Borttagning misslyckades", - "unpinnedSuccessfully": "\"{{name}}\" har lossats", - "unpinFailed": "Misslyckades med att lossa", - "removedShortcut": "Tog bort genvägen \"{{name}}\"", - "removeShortcutFailed": "Misslyckades med att ta bort genvägen", - "clearedAllRecentFiles": "Rensade alla senaste filer", - "clearFailed": "Rensa misslyckades", - "removeFromRecentFiles": "Ta bort från senaste filer", - "clearAllRecentFiles": "Rensa alla senaste filer", - "unpinFile": "Lossa filen", - "removeShortcut": "Ta bort genväg", - "pinFile": "Fäst fil", - "addToShortcuts": "Lägg till i genvägar", - "pasteFailed": "Klistra in misslyckades", - "noUndoableActions": "Inga ångerbara åtgärder", - "undoCopySuccess": "Ångrad kopiering: Raderade {{count}} kopierade filer", - "undoCopyFailedDelete": "Ångra misslyckades: Kunde inte ta bort några kopierade filer", - "undoCopyFailedNoInfo": "Ångra misslyckades: Kunde inte hitta informationen om den kopierade filen", - "undoMoveSuccess": "Ångrade flyttåtgärd: Flyttade {{count}} filer tillbaka till ursprunglig plats", - "undoMoveFailedMove": "Ångra misslyckades: Kunde inte flytta tillbaka några filer", - "undoMoveFailedNoInfo": "Ångra misslyckades: Kunde inte hitta informationen om den flyttade filen", - "undoDeleteNotSupported": "Raderingen kan inte ångras: Filerna har raderats permanent från servern", - "undoTypeNotSupported": "Ångra-åtgärdstypen stöds inte", - "undoOperationFailed": "Ångraåtgärden misslyckades", - "unknownError": "Okänt fel", - "confirm": "Bekräfta", - "find": "Hitta...", - "replace": "Ersätta", - "downloadInstead": "Ladda ner istället", - "keyboardShortcuts": "Kortkommandon", - "searchAndReplace": "Sök och ersätt", - "editing": "Redigering", - "search": "Söka", - "findNext": "Hitta nästa", - "findPrevious": "Hitta föregående", - "save": "Spara", - "selectAll": "Markera alla", - "undo": "Ångra", - "redo": "Göra om", - "moveLineUp": "Flytta upp raden", - "moveLineDown": "Flytta raden nedåt", - "toggleComment": "Växla kommentar", - "autoComplete": "Autoslutför", - "imageLoadError": "Misslyckades med att ladda bilden", - "startTyping": "Börja skriva...", - "unknownSize": "Okänd storlek", - "fileIsEmpty": "Filen är tom", - "largeFileWarning": "Varning för stora filer", - "largeFileWarningDesc": "Den här filen är {{size}} stor, vilket kan orsaka prestandaproblem när den öppnas som text.", - "fileNotFoundAndRemoved": "Filen \"{{name}}\" hittades inte och har tagits bort från senaste/fästa filer", - "failedToLoadFile": "Misslyckades med att ladda filen: {{error}}", - "serverErrorOccurred": "Serverfel uppstod. Försök igen senare.", - "autoSaveFailed": "Automatisk sparning misslyckades", - "fileAutoSaved": "Filen sparades automatiskt", - "moveFileFailed": "Misslyckades med att flytta {{name}}", - "moveOperationFailed": "Flyttoperationen misslyckades", - "canOnlyCompareFiles": "Kan bara jämföra två filer", - "comparingFiles": "Jämför filer: {{file1}} och {{file2}}", - "dragFailed": "Dragningsåtgärden misslyckades", - "filePinnedSuccessfully": "Filen \"{{name}}\" har fästs", - "pinFileFailed": "Misslyckades med att fästa filen", - "fileUnpinnedSuccessfully": "Filen \"{{name}}\" har lossats", - "unpinFileFailed": "Misslyckades med att lossa filen", - "shortcutAddedSuccessfully": "Mappgenvägen \"{{name}}\" har lagts till", - "addShortcutFailed": "Misslyckades med att lägga till genväg", - "operationCompletedSuccessfully": "{{operation}} {{count}} objekt lyckades", - "operationCompleted": "{{operation}} {{count}} objekt", - "downloadFileSuccess": "Filen {{name}} har laddats ner", - "downloadFileFailed": "Nedladdningen misslyckades", - "downloadingFile": "Laddar ner {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Flytta till {{name}}", - "diffCompareWith": "Differensjämförelse med {{name}}", - "dragOutsideToDownload": "Dra utanför fönstret för att ladda ner ({{count}} filer)", - "newFolderDefault": "Nymapp", - "newFileDefault": "Nyfil.txt", - "successfullyMovedItems": "Flyttade {{count}} objekt till {{target}}", - "move": "Flytta", - "searchInFile": "Sök i filen (Ctrl+F)", - "showKeyboardShortcuts": "Visa kortkommandon", - "decreaseFontSize": "Minska teckenstorleken", - "increaseFontSize": "Öka teckenstorleken", - "startWritingMarkdown": "Börja skriva ditt nedskrivningsinnehåll...", - "loadingFileComparison": "Laddar filjämförelse...", - "reload": "Ladda om", - "compare": "Jämföra", - "sideBySide": "Sida vid sida", - "inline": "Inline", - "fileComparison": "Filjämförelse: {{file1}} vs {{file2}}", - "fileTooLarge": "Filen är för stor: {{error}}", - "sshConnectionFailed": "SSH-anslutningen misslyckades. Kontrollera din anslutning till {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Misslyckades med att ladda filen: {{error}}", - "connecting": "Ansluter...", - "connectedSuccessfully": "Ansluten", - "totpVerificationFailed": "TOTP-verifiering misslyckades", - "warpgateVerificationFailed": "Warpgate-autentisering misslyckades", - "authenticationFailed": "Autentiseringen misslyckades", - "incorrectPassphrase": "Felaktigt lösenord. Försök igen.", - "verificationCodePrompt": "Verifieringskod:", - "changePermissions": "Ändra behörigheter", - "currentPermissions": "Nuvarande behörigheter", - "owner": "Ägare", - "group": "Grupp", - "others": "Andra", - "read": "Läsa", - "write": "Skriva", - "execute": "Utföra", - "permissionsChangedSuccessfully": "Behörigheter har ändrats", - "failedToChangePermissions": "Misslyckades med att ändra behörigheter", - "name": "Namn", - "sortByName": "Namn", - "sortByDate": "Datum ändrat", - "sortBySize": "Storlek", - "ascending": "Stigande", - "descending": "Fallande", - "root": "Rot", - "new": "Ny", - "sortBy": "Sortera efter", - "items": "Föremål", - "selected": "Vald", - "editor": "Redaktör", - "octal": "Oktal", - "storage": "Lagring", - "disk": "Disk", - "used": "Begagnad", - "of": "av", - "toggleSidebar": "Växla sidofält", - "compactLayout": "Använd kompakt layout", - "comfortableLayout": "Använd bekväm layout", - "cannotLoadPdf": "Kan inte ladda PDF-filen", - "pdfLoadError": "Det uppstod ett fel när PDF-filen laddades.", - "loadingPdf": "Laddar PDF...", - "loadingPage": "Laddar sida..." - }, "transfer": { "copyToHost": "Kopiera till värd…", "moveToHost": "Flytta till värd…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Återförsök misslyckades", "transferFailedRetryHint": "Delvis data sparades på destinationen. Försöket återupptas när anslutningen är tillbaka." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Överför filer mellan två SSH-värdar.", - "sourceServer": "Källserver", - "destinationServer": "Destinationsserver", - "selectHost": "Välj värd", - "noHostSelected": "Ingen värd vald", - "move": "Flytta", - "copy": "Kopiera", - "upload": "Ladda upp", - "itemsSelected": "{{count}} objekt valt", - "itemsSelected_other": "{{count}} objekt valda", - "ready": "Redo", - "loadingHosts": "Laddar värdar...", - "name": "Namn", - "modified": "Ändrad", - "size": "Storlek", - "noFiles": "Inga filer", - "open": "Öppna", - "up": "Upp", - "refresh": "Uppdatera", - "copyToTarget": "Kopiera till målkatalog", - "rename": "Döpa om", - "delete": "Radera", - "editPermissions": "Redigera behörigheter", - "createNewFolder": "Skapa ny mapp", - "deleteSelectedItems": "Ta bort valda objekt", - "deleteConfirm": "Ta bort {{count}} markerat objekt?", - "deleteConfirm_other": "Ta bort {{count}} markerade objekt?", - "cancel": "Avboka", - "save": "Spara", - "folderCreated": "Mapp skapad", - "itemRenamed": "Artikeln har bytt namn", - "actionFailed": "Åtgärden misslyckades", - "itemsDeleted": "{{count}} objekt borttaget", - "itemsDeleted_other": "{{count}} objekt raderade", - "deleteFailed": "Raderingen misslyckades", - "permissionsUpdated": "Behörigheter uppdaterade", - "noRemoteSessionConnected": "Ingen fjärrsession ansluten", - "failedToListFiles": "Misslyckades med att lista filer", - "failedToRefresh": "Misslyckades med att uppdatera", - "destinationInsideSource": "Destinationen får inte vara inom den valda källsökvägen.", - "sameHostNoOp": "Källa och destination är samma mapp." - }, "tunnels": { "noSshTunnels": "Inga SSH-tunnlar", "createFirstTunnelMessage": "Du har inte skapat några SSH-tunnlar än. Konfigurera tunnelanslutningar i Host Manager för att komma igång.", diff --git a/src/ui/locales/translated/th_TH.json b/src/ui/locales/translated/th_TH.json index 0365400f4..9a4bb030b 100644 --- a/src/ui/locales/translated/th_TH.json +++ b/src/ui/locales/translated/th_TH.json @@ -715,7 +715,6 @@ "hostManager": "ผู้จัดการโฮสต์", "credentials": "คุณสมบัติ", "portForwarding": "การส่งต่อ", - "sftp": "เอสเอฟทีพี", "connections": "การเชื่อมต่อ", "serial": "ซีเรียล", "keepSideMenuOpen": "เปิดเมนูค้างไว้", @@ -767,14 +766,12 @@ "tabRdp": "อาร์ดีพี", "tabVnc": "วีเอ็นซี", "tabTunnels": "อุโมงค์", - "tabFiles": "ไฟล์", "tabStats": "เมตริกโฮสต์", "tabTelnet": "เทลเน็ต", "tabSharing": "การแบ่งปัน", "tabAuthentication": "การตรวจสอบสิทธิ์", "terminal": "เทอร์มินัล", "tunnel": "อุโมงค์", - "fileManager": "ตัวจัดการไฟล์", "serverStats": "เมตริกโฮสต์", "status": { "available": "มีอยู่", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} ข้ามไป (ไม่พบ IP)", "proxmoxStatsUrlCopied": "คัดลอก URL สถิติ Proxmox ไปยังคลิปบอร์ดแล้ว", "copyProxmoxStatsUrlAction": "คัดลอก URL สถิติของ Proxmox", - "enableFileManagerMonitor": "เปิดใช้งานตัวจัดการไฟล์", - "enableFileManagerMonitorDesc": "เรียกดูและจัดการไฟล์บนโฮสต์นี้ผ่านทาง SFTP", - "scpLegacyLabel": "โหมด SCP ดั้งเดิม", - "scpLegacyDesc": "สำหรับเซิร์ฟเวอร์ที่ไม่รองรับระบบย่อย SFTP (เช่น เซิร์ฟเวอร์ SSH แบบฝังตัวหรือแบบขั้นต่ำ) ให้ใช้การถ่ายโอนไฟล์แบบเดิม", - "defaultPathLabel": "เส้นทางเริ่มต้น", - "fileManagerPathHint": "โฟลเดอร์ที่จะเปิดเมื่อโปรแกรมจัดการไฟล์เริ่มทำงานสำหรับโฮสต์นี้", "statusChecksLabel": "การตรวจสอบสถานะ", "enableStatusChecks": "เปิดใช้งานการตรวจสอบสถานะ", "enableStatusChecksDesc": "ตรวจสอบความพร้อมใช้งานของโฮสต์นี้เป็นระยะโดยการ ping ไปยังโฮสต์นี้", @@ -1278,7 +1269,6 @@ "deletedHost": "ลบแล้ว {{name}}", "copiedToClipboard": "คัดลอกไปยังคลิปบอร์ดแล้ว", "terminalUrlCopied": "คัดลอก URL ของเทอร์มินัลแล้ว", - "fileManagerUrlCopied": "คัดลอก URL ของตัวจัดการไฟล์แล้ว", "tunnelUrlCopied": "คัดลอก URL ของอุโมงค์แล้ว", "dockerUrlCopied": "คัดลอก URL ของ Docker แล้ว", "hostMetricsUrlCopied": "คัดลอก URL ของ Host Metrics แล้ว", @@ -1296,7 +1286,6 @@ "copyAddress": "คัดลอกที่อยู่", "copyLink": "คัดลอกลิงก์", "copyTerminalUrlAction": "คัดลอก URL ของเทอร์มินัล", - "copyFileManagerUrlAction": "คัดลอก URL ของตัวจัดการไฟล์", "copyTunnelUrlAction": "คัดลอก URL อุโมงค์", "copyDockerUrlAction": "คัดลอก URL ของ Docker", "copyHostMetricsUrlAction": "คัดลอก URL ของ Host Metrics", @@ -1857,336 +1846,6 @@ "removeFromSplit": "ลบออกจากการแบ่ง" } }, - "fileManager": { - "noHostSelected": "ไม่ได้เลือกโฮสต์", - "initializingEditor": "กำลังเริ่มต้นโปรแกรมแก้ไข...", - "file": "ไฟล์", - "folder": "โฟลเดอร์", - "uploadFile": "อัปโหลดไฟล์", - "downloadFile": "ดาวน์โหลด", - "extractArchive": "แตกไฟล์เก็บถาวร", - "extractingArchive": "กำลังดึงข้อมูล {{name}}...", - "archiveExtractedSuccessfully": "{{name}} ดึงข้อมูลสำเร็จแล้ว", - "extractFailed": "การดึงข้อมูลล้มเหลว", - "compressFile": "บีบอัดไฟล์", - "compressFiles": "บีบอัดไฟล์", - "compressFilesDesc": "บีบอัดรายการ {{count}} รายการลงในไฟล์เก็บถาวร", - "archiveName": "ชื่อไฟล์เก็บถาวร", - "enterArchiveName": "ป้อนชื่อไฟล์เก็บถาวร...", - "compressionFormat": "รูปแบบการบีบอัด", - "selectedFiles": "ไฟล์ที่เลือก", - "andMoreFiles": "และ {{count}} เพิ่มเติม...", - "compress": "บีบอัด", - "compressingFiles": "บีบอัดรายการ {{count}} รายการลงใน {{name}} รายการ ...", - "filesCompressedSuccessfully": "{{name}} สร้างสำเร็จแล้ว", - "compressFailed": "การบีบอัดล้มเหลว", - "edit": "แก้ไข", - "preview": "ตัวอย่าง", - "previous": "ก่อนหน้า", - "next": "ต่อไป", - "pageXOfY": "หน้า {{current}} จาก {{total}}", - "zoomOut": "ซูมออก", - "zoomIn": "ซูมเข้า", - "newFile": "ไฟล์ใหม่", - "newFolder": "โฟลเดอร์ใหม่", - "rename": "เปลี่ยนชื่อ", - "uploading": "กำลังอัปโหลด...", - "uploadingFile": "กำลังอัปโหลด {{name}}...", - "fileName": "ชื่อไฟล์", - "folderName": "ชื่อโฟลเดอร์", - "fileUploadedSuccessfully": "ไฟล์ \"{{name}}\" อัปโหลดสำเร็จแล้ว", - "failedToUploadFile": "ไม่สามารถอัปโหลดไฟล์ได้", - "uploadingFolderFiles": "กำลังอัปโหลดไฟล์ {{count}} ไฟล์...", - "uploadedFolderFiles": "อัปโหลดไฟล์ {{count}} ไฟล์สำเร็จแล้ว", - "uploadedFolderPartial": "อัปโหลดไฟล์ {{uploaded}} ไฟล์, {{failed}} ล้มเหลว", - "fileDownloadedSuccessfully": "ดาวน์โหลดไฟล์ \"{{name}}\" สำเร็จแล้ว", - "failedToDownloadFile": "ไม่สามารถดาวน์โหลดไฟล์ได้", - "fileCreatedSuccessfully": "สร้างไฟล์ \"{{name}}\" สำเร็จแล้ว", - "folderCreatedSuccessfully": "สร้างโฟลเดอร์ \"{{name}}\" สำเร็จแล้ว", - "failedToCreateItem": "ไม่สามารถสร้างรายการได้", - "operationFailed": "การดำเนินการ {{operation}} ล้มเหลวสำหรับ {{name}}: {{error}}", - "failedToResolveSymlink": "ไม่สามารถแก้ไขลิงก์สัญลักษณ์ได้", - "itemsDeletedSuccessfully": "รายการ {{count}} ถูกลบสำเร็จแล้ว", - "failedToDeleteItems": "ไม่สามารถลบรายการได้", - "sudoPasswordRequired": "ต้องใช้รหัสผ่านผู้ดูแลระบบ", - "enterSudoPassword": "ป้อนรหัสผ่าน sudo เพื่อดำเนินการต่อ", - "sudoPassword": "รหัสผ่าน Sudo", - "sudoOperationFailed": "การดำเนินการ Sudo ล้มเหลว", - "sudoAuthFailed": "การตรวจสอบสิทธิ์ Sudo ล้มเหลว", - "dragFilesToUpload": "ลากไฟล์มาวางที่นี่เพื่ออัปโหลด", - "dropToUploadHere": "ลากเส้นเพื่ออัปโหลดที่นี่", - "dropToDownloadHere": "คลิกที่นี่เพื่อดาวน์โหลด", - "localFiles": "ท้องถิ่น", - "showLocalFiles": "แสดงไฟล์ในเครื่อง (มุมมองแบบแบ่งหน้าจอ)", - "hideLocalFiles": "ซ่อนไฟล์ในเครื่อง", - "localHome": "โฟลเดอร์บ้าน", - "localPathPlaceholder": "เส้นทางโฟลเดอร์ในเครื่อง", - "localShowHidden": "แสดงไฟล์ที่ซ่อนอยู่", - "localHideHidden": "ซ่อนไฟล์ที่ซ่อนไว้", - "localRevealInFileManager": "เปิดเผยในตัวจัดการไฟล์ระบบ", - "localCannotOpenFolder": "ไม่สามารถเปิดโฟลเดอร์ได้", - "localItemCount": "{{count}} รายการ", - "localSelectedCount": "{{count}} เลือกแล้ว", - "back": "กลับ", - "forward": "ซึ่งไปข้างหน้า", - "up": "ขึ้น", - "kind": "ใจดี", - "localUploadingProgress": "กำลังอัปโหลด {{current}} จาก {{total}}", - "localDownloadingProgress": "กำลังดาวน์โหลด {{current}} จาก {{total}}", - "localTransferInFlight": "{{count}} ทันที", - "localPreparingDownload": "กำลังเตรียมดาวน์โหลด...", - "localTransferCancelled": "การโอนเงินถูกยกเลิก", - "localNothingToTransfer": "ไม่มีอะไรต้องโอน", - "localUploadComplete": "อัปโหลดไฟล์ {{count}} ไฟล์แล้ว", - "localUploadFailed": "การอัปโหลดล้มเหลว", - "localUploadPartial": "อัปโหลดไฟล์ {{done}} ไฟล์, {{failed}} ล้มเหลว", - "localDownloadComplete": "ดาวน์โหลดไฟล์ {{count}} ไฟล์แล้ว", - "localDownloadFailed": "การดาวน์โหลดล้มเหลว", - "localDownloadPartial": "ดาวน์โหลดไฟล์ {{done}} ไฟล์, {{failed}} ไฟล์ล้มเหลว", - "localReplaceExistingPrompt": "{{count}} รายการมีอยู่แล้วในโฟลเดอร์นี้ ต้องการแทนที่หรือไม่?", - "localReplace": "แทนที่", - "localSkipExisting": "ข้าม", - "localSkippedExisting": "ข้ามรายการที่มีอยู่ {{count}} รายการ", - "localUnsafeNamesSkipped": "ข้ามรายการ {{count}} รายการ เนื่องจากไม่สามารถใช้ชื่อรายการดังกล่าวในคอมพิวเตอร์เครื่องนี้ได้", - "localOpen": "เปิด", - "localOpenFolder": "เปิดโฟลเดอร์", - "localUploadToRemote": "อัปโหลดไปยังเซิร์ฟเวอร์", - "localUploadToRemoteMany": "อัปโหลด {{count}} รายการไปยังเซิร์ฟเวอร์", - "localMoveToTrash": "ย้ายไปที่ถังขยะ", - "localMoveToTrashMany": "ย้ายรายการ {{count}} รายการไปยังถังขยะ", - "localTrashConfirmSingle": "ย้าย \"{{name}}\" ไปที่ถังขยะ?", - "localTrashConfirmFolder": "ย้ายโฟลเดอร์ \"{{name}}\" และเนื้อหาทั้งหมดไปที่ถังขยะ?", - "localTrashConfirmMany": "ย้ายรายการ {{count}} รายการไปยังถังขยะหรือไม่?", - "localTrashed": "ย้ายรายการ {{count}} รายการไปยังถังขยะ", - "localTrashFailed": "ไม่สามารถย้ายไปถังขยะได้", - "localRenameFailed": "การเปลี่ยนชื่อล้มเหลว", - "localCreateFailed": "ไม่สามารถสร้างรายการได้", - "goToParentFolder": "ไปที่โฟลเดอร์หลัก", - "resizeSidebar": "ลากเพื่อปรับขนาดแถบด้านข้าง (ดับเบิ้ลคลิกเพื่อรีเซ็ต)", - "resizeLocalPane": "ลากเพื่อปรับขนาดบานหน้าต่างเฉพาะที่ (ดับเบิ้ลคลิกเพื่อรีเซ็ต)", - "columns": "คอลัมน์", - "columnsHint": "คลิกขวาเพื่อเลือกคอลัมน์", - "emptyFolder": "โฟลเดอร์นี้ว่างเปล่า", - "searchFiles": "ค้นหาไฟล์...", - "upload": "อัปโหลด", - "selectHostToStart": "เลือกโฮสต์เพื่อเริ่มการจัดการไฟล์", - "sshRequiredForFileManager": "โปรแกรมจัดการไฟล์ต้องการการเชื่อมต่อ SSH แต่เครื่องนี้ไม่ได้เปิดใช้งาน SSH", - "failedToConnect": "ไม่สามารถเชื่อมต่อ SSH ได้", - "failedToLoadDirectory": "ไม่สามารถโหลดไดเร็กทอรีได้", - "noSSHConnection": "ไม่มีการเชื่อมต่อ SSH ให้ใช้งาน", - "copy": "สำเนา", - "cut": "ตัด", - "paste": "แปะ", - "copyPath": "คัดลอกเส้นทาง", - "copyPaths": "คัดลอกเส้นทาง", - "delete": "ลบ", - "properties": "คุณสมบัติ", - "refresh": "รีเฟรช", - "downloadFiles": "ดาวน์โหลดไฟล์ {{count}} ไปยังเบราว์เซอร์", - "copyFiles": "คัดลอกรายการ {{count}} รายการ", - "cutFiles": "ตัด {{count}} รายการ", - "deleteFiles": "ลบรายการ {{count}} รายการ", - "filesCopiedToClipboard": "{{count}} รายการที่คัดลอกไปยังคลิปบอร์ด", - "filesCutToClipboard": "{{count}} รายการที่ถูกตัดไปยังคลิปบอร์ด", - "pathCopiedToClipboard": "คัดลอกเส้นทางไปยังคลิปบอร์ดแล้ว", - "pathsCopiedToClipboard": "{{count}} คัดลอกเส้นทางไปยังคลิปบอร์ดแล้ว", - "failedToCopyPath": "ไม่สามารถคัดลอกเส้นทางไปยังคลิปบอร์ดได้", - "copyFolderLink": "คัดลink ไปยังโฟลเดอร์", - "copyCurrentFolderLink": "คัดลink ไปยังโฟลเดอร์ปัจจุบัน", - "folderLinkCopied": "คัดลอกลิงก์โฟลเดอร์ไปยังคลิปบอร์ดแล้ว", - "failedToCopyFolderLink": "ไม่สามารถคัดลอกลิงก์โฟลเดอร์ได้", - "movedItems": "ย้ายรายการ {{count}} รายการ", - "failedToDeleteItem": "ไม่สามารถลบรายการได้", - "itemRenamedSuccessfully": "{{type}} เปลี่ยนชื่อสำเร็จแล้ว", - "failedToRenameItem": "ไม่สามารถเปลี่ยนชื่อรายการได้", - "download": "ดาวน์โหลด", - "openExternalEditor": "เปิดจากภายนอก", - "chooseExternalEditor": "เลือกตัวแก้ไขภายนอก", - "externalEditorSelected": "เลือกบรรณาธิการภายนอก", - "externalEditorDesktopOnly": "โปรแกรมแก้ไขภายนอกใช้งานได้เฉพาะในแอปพลิเคชันบนเดสก์ท็อปเท่านั้น", - "externalEditorOpened": "เปิดในโปรแกรมแก้ไขภายนอก การบันทึกในโปรแกรมแก้ไขนั้นจะอัปโหลดการเปลี่ยนแปลงกลับไปยังเซิร์ฟเวอร์", - "failedToOpenExternalEditor": "ไม่สามารถเปิดโปรแกรมแก้ไขภายนอกได้", - "failedToSelectExternalEditor": "ไม่สามารถเลือกตัวแก้ไขภายนอกได้", - "permissions": "สิทธิ์การเข้าถึง", - "size": "ขนาด", - "modified": "แก้ไขแล้ว", - "path": "เส้นทาง", - "confirmDelete": "คุณแน่ใจหรือไม่ว่าต้องการลบ {{name}}?", - "permissionDenied": "ไม่ได้รับอนุญาต", - "serverError": "ข้อผิดพลาดของเซิร์ฟเวอร์", - "fileSavedSuccessfully": "บันทึกไฟล์สำเร็จแล้ว", - "failedToSaveFile": "ไม่สามารถบันทึกไฟล์ได้", - "confirmDeleteSingleItem": "คุณแน่ใจหรือไม่ว่าต้องการลบ \"{{name}} \" อย่างถาวร?", - "confirmDeleteMultipleItems": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการ {{count}} อย่างถาวร?", - "confirmDeleteMultipleItemsWithFolders": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการ {{count}} รายการอย่างถาวร ซึ่งรวมถึงโฟลเดอร์และเนื้อหาภายในด้วย", - "confirmDeleteFolder": "คุณแน่ใจหรือไม่ว่าต้องการลบโฟลเดอร์ \"{{name}}\" และเนื้อหาทั้งหมดในนั้นอย่างถาวร?", - "permanentDeleteWarning": "การดำเนินการนี้ไม่สามารถยกเลิกได้ รายการดังกล่าวจะถูกลบออกจากเซิร์ฟเวอร์อย่างถาวร", - "moveToTrashWarning": "ไฟล์ดังกล่าวจะถูกย้ายไปยังถังขยะของ Termix และสามารถกู้คืนได้ก่อนที่ระยะเวลาการเก็บรักษาจะหมดลง", - "itemsMovedToTrash": "{{count}} รายการถูกย้ายไปยังถังขยะ", - "trash": "ขยะ", - "trashDescription": "รายการที่ถูกลบจะยังคงอยู่เป็นเวลา {{days}} วันบนโฮสต์นี้", - "trashLoadFailed": "ไม่สามารถโหลดถังขยะได้", - "trashEmpty": "ถังขยะว่างเปล่า", - "trashRestore": "คืนค่า", - "trashRestored": "กู้คืนแล้ว {{name}}", - "trashRestoreFailed": "ไม่สามารถกู้คืนรายการได้ เส้นทางเดิมอาจมีอยู่แล้ว", - "trashDeletePermanently": "ลบอย่างถาวร", - "trashDeleteConfirm": "ลบ {{name}}อย่างถาวรหรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้", - "trashPermanentlyDeleted": "ลบถาวร {{name}}", - "trashDeleteFailed": "ไม่สามารถลบรายการอย่างถาวรได้", - "trashEmptyAction": "ขยะทิ้ง", - "trashEmptyConfirm": "ต้องการลบทุกอย่างในถังขยะอย่างถาวรหรือไม่? การกระทำนี้ไม่สามารถย้อนกลับได้", - "trashEmptied": "ขยะถูกเก็บแล้ว", - "trashRetention": "ล้างข้อมูลอัตโนมัติหลังจาก (วัน)", - "trashRetentionSaved": "การปรับปรุงการเก็บรักษาขยะ", - "trashRetentionFailed": "ไม่สามารถอัปเดตการเก็บรักษาข้อมูลถังขยะได้", - "trashUnavailableConfirm": "ไม่สามารถย้ายรายการนี้ไปยังถังขยะได้ เนื่องจากไฟล์นี้อยู่ในระบบไฟล์อื่น หรือต้องใช้สิทธิ์ระดับสูง ต้องการลบอย่างถาวรแทนหรือไม่?", - "recent": "ล่าสุด", - "pinned": "ปักหมุด", - "folderShortcuts": "ทางลัดโฟลเดอร์", - "failedToReconnectSSH": "ไม่สามารถเชื่อมต่อเซสชัน SSH ใหม่ได้", - "openTerminalHere": "เปิดเทอร์มินัลที่นี่", - "run": "วิ่ง", - "openTerminalInFolder": "เปิดเทอร์มินัลในโฟลเดอร์นี้", - "openTerminalInFileLocation": "เปิดเทอร์มินัลที่ตำแหน่งไฟล์", - "runningFile": "การวิ่ง - {{file}}", - "onlyRunExecutableFiles": "สามารถเรียกใช้งานได้เฉพาะไฟล์ปฏิบัติการเท่านั้น", - "directories": "รายชื่อ", - "removedFromRecentFiles": "ลบ \"{{name}}\" ออกจากไฟล์ล่าสุดแล้ว", - "removeFailed": "ลบสิ่งที่ล้มเหลว", - "unpinnedSuccessfully": "ยกเลิกการตรึง \"{{name}}\" สำเร็จแล้ว", - "unpinFailed": "ยกเลิกการตรึงไม่สำเร็จ", - "removedShortcut": "ลบทางลัด \"{{name}} \" ออกแล้ว", - "removeShortcutFailed": "การลบทางลัดล้มเหลว", - "clearedAllRecentFiles": "ลบไฟล์ล่าสุดทั้งหมดแล้ว", - "clearFailed": "เคลียร์ล้มเหลว", - "removeFromRecentFiles": "ลบออกจากไฟล์ล่าสุด", - "clearAllRecentFiles": "ลบไฟล์ล่าสุดทั้งหมด", - "unpinFile": "ยกเลิกการตรึงไฟล์", - "removeShortcut": "ลบทางลัด", - "pinFile": "ไฟล์พิน", - "addToShortcuts": "เพิ่มไปยังทางลัด", - "pasteFailed": "การวางล้มเหลว", - "noUndoableActions": "ไม่มีการกระทำใดที่ย้อนกลับไม่ได้", - "undoCopySuccess": "ยกเลิกการคัดลอก: ลบไฟล์ที่คัดลอก {{count}} ไฟล์", - "undoCopyFailedDelete": "การยกเลิกการกระทำล้มเหลว: ไม่สามารถลบไฟล์ที่คัดลอกไว้ได้", - "undoCopyFailedNoInfo": "การยกเลิกการกระทำล้มเหลว: ไม่พบข้อมูลไฟล์ที่คัดลอกไว้", - "undoMoveSuccess": "ยกเลิกการย้าย: ย้ายไฟล์ {{count}} ไฟล์กลับไปยังตำแหน่งเดิม", - "undoMoveFailedMove": "การยกเลิกไม่สำเร็จ: ไม่สามารถย้ายไฟล์ใดๆ กลับได้", - "undoMoveFailedNoInfo": "การยกเลิกไม่สำเร็จ: ไม่พบข้อมูลไฟล์ที่ถูกย้าย", - "undoDeleteNotSupported": "ไม่สามารถยกเลิกการลบได้: ไฟล์ถูกลบออกจากเซิร์ฟเวอร์อย่างถาวรแล้ว", - "undoTypeNotSupported": "ประเภทการดำเนินการยกเลิกที่ไม่รองรับ", - "undoOperationFailed": "การยกเลิกการดำเนินการล้มเหลว", - "unknownError": "ข้อผิดพลาดที่ไม่ทราบสาเหตุ", - "confirm": "ยืนยัน", - "find": "หา...", - "replace": "แทนที่", - "downloadInstead": "ดาวน์โหลดแทน", - "keyboardShortcuts": "แป้นพิมพ์ลัด", - "searchAndReplace": "ค้นหาและแทนที่", - "editing": "การแก้ไข", - "search": "ค้นหา", - "findNext": "ค้นหาถัดไป", - "findPrevious": "ค้นหาก่อนหน้า", - "save": "บันทึก", - "selectAll": "เลือกทั้งหมด", - "undo": "เลิกทำ", - "redo": "ทำซ้ำ", - "moveLineUp": "เคลื่อนแถว", - "moveLineDown": "เลื่อนเส้นลง", - "toggleComment": "สลับการแสดงความคิดเห็น", - "autoComplete": "การเติมข้อความอัตโนมัติ", - "imageLoadError": "ไม่สามารถโหลดรูปภาพได้", - "startTyping": "เริ่มพิมพ์...", - "unknownSize": "ขนาดไม่ทราบแน่ชัด", - "fileIsEmpty": "ไฟล์ว่างเปล่า", - "largeFileWarning": "คำเตือนเกี่ยวกับไฟล์ขนาดใหญ่", - "largeFileWarningDesc": "ไฟล์นี้มีขนาด {{size}} ซึ่งอาจทำให้เกิดปัญหาด้านประสิทธิภาพเมื่อเปิดเป็นข้อความ", - "fileNotFoundAndRemoved": "ไม่พบไฟล์ \"{{name}}\" และไฟล์ดังกล่าวถูกลบออกจากไฟล์ล่าสุด/ไฟล์ปักหมุดแล้ว", - "failedToLoadFile": "ไม่สามารถโหลดไฟล์ได้: {{error}}", - "serverErrorOccurred": "เกิดข้อผิดพลาดของเซิร์ฟเวอร์ โปรดลองใหม่อีกครั้งในภายหลัง", - "autoSaveFailed": "การบันทึกอัตโนมัติล้มเหลว", - "fileAutoSaved": "ไฟล์ถูกบันทึกอัตโนมัติ", - "moveFileFailed": "การย้ายล้มเหลว {{name}}", - "moveOperationFailed": "การดำเนินการย้ายล้มเหลว", - "canOnlyCompareFiles": "สามารถเปรียบเทียบไฟล์ได้เพียงสองไฟล์เท่านั้น", - "comparingFiles": "เปรียบเทียบไฟล์: {{file1}} และ {{file2}}", - "dragFailed": "การดำเนินการลากล้มเหลว", - "filePinnedSuccessfully": "ไฟล์ \"{{name}}\" ถูกปักหมุดสำเร็จแล้ว", - "pinFileFailed": "ไม่สามารถตรึงไฟล์ได้", - "fileUnpinnedSuccessfully": "ไฟล์ \"{{name}}\" ถูกยกเลิกการตรึงสำเร็จแล้ว", - "unpinFileFailed": "ไม่สามารถยกเลิกการตรึงไฟล์ได้", - "shortcutAddedSuccessfully": "เพิ่มทางลัดโฟลเดอร์ \"{{name}}\" สำเร็จแล้ว", - "addShortcutFailed": "ไม่สามารถเพิ่มทางลัดได้", - "operationCompletedSuccessfully": "{{operation}} {{count}} รายการสำเร็จ", - "operationCompleted": "{{operation}} {{count}} รายการ", - "downloadFileSuccess": "ดาวน์โหลดไฟล์ {{name}} สำเร็จแล้ว", - "downloadFileFailed": "การดาวน์โหลดล้มเหลว", - "downloadingFile": "กำลังดาวน์โหลด {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "ย้ายไปที่ {{name}}", - "diffCompareWith": "เปรียบเทียบความแตกต่างกับ {{name}}", - "dragOutsideToDownload": "ลากออกนอกหน้าต่างเพื่อดาวน์โหลดไฟล์ ({{count}} ไฟล์)", - "newFolderDefault": "โฟลเดอร์ใหม่", - "newFileDefault": "ไฟล์ใหม่.txt", - "successfullyMovedItems": "ย้ายรายการ {{count}} รายการไปยัง {{target}} สำเร็จแล้ว", - "move": "เคลื่อนไหว", - "searchInFile": "ค้นหาในไฟล์ (Ctrl+F)", - "showKeyboardShortcuts": "แสดงทางลัดแป้นพิมพ์", - "decreaseFontSize": "ลดขนาดตัวอักษร", - "increaseFontSize": "เพิ่มขนาดตัวอักษร", - "startWritingMarkdown": "เริ่มเขียนเนื้อหา Markdown ของคุณได้เลย...", - "loadingFileComparison": "กำลังโหลดการเปรียบเทียบไฟล์...", - "reload": "โหลดซ้ำ", - "compare": "เปรียบเทียบ", - "sideBySide": "เคียงข้างกัน", - "inline": "อินไลน์", - "fileComparison": "การเปรียบเทียบไฟล์: {{file1}} กับ {{file2}}", - "fileTooLarge": "ไฟล์มีขนาดใหญ่เกินไป: {{error}}", - "sshConnectionFailed": "การเชื่อมต่อ SSH ล้มเหลว โปรดตรวจสอบการเชื่อมต่อของคุณกับ {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "ไม่สามารถโหลดไฟล์ได้: {{error}}", - "connecting": "กำลังเชื่อมต่อ...", - "connectedSuccessfully": "เชื่อมต่อสำเร็จแล้ว", - "totpVerificationFailed": "การตรวจสอบ TOTP ล้มเหลว", - "warpgateVerificationFailed": "การตรวจสอบสิทธิ์วาร์ปไม่สำเร็จ", - "authenticationFailed": "การตรวจสอบสิทธิ์ล้มเหลว", - "incorrectPassphrase": "รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง", - "verificationCodePrompt": "รหัสยืนยัน:", - "changePermissions": "เปลี่ยนสิทธิ์การเข้าถึง", - "currentPermissions": "สิทธิ์การเข้าถึงปัจจุบัน", - "owner": "เจ้าของ", - "group": "กลุ่ม", - "others": "คนอื่น", - "read": "อ่าน", - "write": "เขียน", - "execute": "ดำเนินการ", - "permissionsChangedSuccessfully": "สิทธิ์การเข้าถึงเปลี่ยนแปลงสำเร็จแล้ว", - "failedToChangePermissions": "ไม่สามารถเปลี่ยนสิทธิ์ได้", - "name": "ชื่อ", - "sortByName": "ชื่อ", - "sortByDate": "วันที่แก้ไข", - "sortBySize": "ขนาด", - "ascending": "ขึ้นไป", - "descending": "ลง", - "root": "ราก", - "new": "ใหม่", - "sortBy": "เรียงลำดับตาม", - "items": "รายการ", - "selected": "เลือกแล้ว", - "editor": "บรรณาธิการ", - "octal": "แปด", - "storage": "พื้นที่จัดเก็บ", - "disk": "ดิสก์", - "used": "ใช้แล้ว", - "of": "ของ", - "toggleSidebar": "สลับแถบด้านข้าง", - "compactLayout": "ใช้รูปแบบการจัดวางที่กะทัดรัด", - "comfortableLayout": "ใช้รูปแบบการจัดวางที่สะดวกสบาย", - "cannotLoadPdf": "ไม่สามารถโหลดไฟล์ PDF ได้", - "pdfLoadError": "เกิดข้อผิดพลาดในการโหลดไฟล์ PDF นี้", - "loadingPdf": "กำลังโหลดไฟล์ PDF...", - "loadingPage": "กำลังโหลดหน้าเว็บ..." - }, "transfer": { "copyToHost": "คัดลอกไปยังโฮสต์…", "moveToHost": "ย้ายไปยังโฮสต์…", @@ -2292,50 +1951,6 @@ "retryTransferError": "การลองใหม่ล้มเหลว", "transferFailedRetryHint": "ข้อมูลบางส่วนถูกเก็บไว้ในปลายทาง การลองใหม่จะดำเนินการต่อเมื่อการเชื่อมต่อกลับมาใช้งานได้" }, - "sftpTransfer": { - "title": "เอสเอฟทีพี", - "description": "โอนไฟล์ระหว่างโฮสต์ SSH สองเครื่อง", - "sourceServer": "เซิร์ฟเวอร์ต้นทาง", - "destinationServer": "เซิร์ฟเวอร์ปลายทาง", - "selectHost": "เลือกโฮสต์", - "noHostSelected": "ไม่ได้เลือกโฮสต์", - "move": "เคลื่อนไหว", - "copy": "สำเนา", - "upload": "อัปโหลด", - "itemsSelected": "{{count}} รายการที่เลือก", - "itemsSelected_other": "รายการที่เลือก {{count}} รายการ", - "ready": "พร้อม", - "loadingHosts": "กำลังโหลดโฮสต์...", - "name": "ชื่อ", - "modified": "แก้ไขแล้ว", - "size": "ขนาด", - "noFiles": "ไม่มีไฟล์", - "open": "เปิด", - "up": "ขึ้น", - "refresh": "รีเฟรช", - "copyToTarget": "คัดลอกไปยังไดเร็กทอรีเป้าหมาย", - "rename": "เปลี่ยนชื่อ", - "delete": "ลบ", - "editPermissions": "แก้ไขสิทธิ์การเข้าถึง", - "createNewFolder": "สร้างโฟลเดอร์ใหม่", - "deleteSelectedItems": "ลบรายการที่เลือก", - "deleteConfirm": "ลบรายการที่เลือก {{count}} รายการหรือไม่?", - "deleteConfirm_other": "ลบรายการที่เลือก {{count}} รายการหรือไม่?", - "cancel": "ยกเลิก", - "save": "บันทึก", - "folderCreated": "สร้างโฟลเดอร์แล้ว", - "itemRenamed": "เปลี่ยนชื่อรายการ", - "actionFailed": "การดำเนินการล้มเหลว", - "itemsDeleted": "{{count}} รายการถูกลบ", - "itemsDeleted_other": "รายการที่ถูกลบ {{count}} รายการ", - "deleteFailed": "การลบล้มเหลว", - "permissionsUpdated": "สิทธิ์การเข้าถึงได้รับการอัปเดตแล้ว", - "noRemoteSessionConnected": "ไม่มีการเชื่อมต่อเซสชันระยะไกล", - "failedToListFiles": "ไม่สามารถแสดงรายการไฟล์ได้", - "failedToRefresh": "ไม่สามารถรีเฟรชได้", - "destinationInsideSource": "ปลายทางต้องไม่อยู่ภายในเส้นทางต้นทางที่เลือกไว้", - "sameHostNoOp": "ต้นทางและปลายทางเป็นโฟลเดอร์เดียวกัน" - }, "tunnels": { "noSshTunnels": "ไม่มีอุโมงค์ SSH", "createFirstTunnelMessage": "คุณยังไม่ได้สร้างอุโมงค์ SSH ใดๆ เลย กำหนดค่าการเชื่อมต่ออุโมงค์ใน Host Manager เพื่อเริ่มต้นใช้งาน", diff --git a/src/ui/locales/translated/tr_TR.json b/src/ui/locales/translated/tr_TR.json index 3eb42e22f..21197e24d 100644 --- a/src/ui/locales/translated/tr_TR.json +++ b/src/ui/locales/translated/tr_TR.json @@ -715,7 +715,6 @@ "hostManager": "Ana Bilgisayar Yöneticisi", "credentials": "Kimlik Bilgileri", "portForwarding": "İletme", - "sftp": "SFTP", "connections": "Bağlantılar", "serial": "Seri", "keepSideMenuOpen": "Menüyü Açık Tutun", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Tüneller", - "tabFiles": "Dosyalar", "tabStats": "Sunucu Metrikleri", "tabTelnet": "Telnet", "tabSharing": "Paylaşım", "tabAuthentication": "Kimlik Doğrulama", "terminal": "Terminal", "tunnel": "Tünel", - "fileManager": "Dosya Yöneticisi", "serverStats": "Sunucu Metrikleri", "status": { "available": "Mevcut", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} atlandı (IP adresi bulunamadı)", "proxmoxStatsUrlCopied": "Proxmox İstatistikleri URL'si panoya kopyalandı", "copyProxmoxStatsUrlAction": "Proxmox İstatistikleri URL'sini Kopyala", - "enableFileManagerMonitor": "Dosya Yöneticisini Etkinleştir", - "enableFileManagerMonitorDesc": "Bu sunucudaki dosyalara SFTP üzerinden göz atın ve yönetin", - "scpLegacyLabel": "SCP Eski Modu", - "scpLegacyDesc": "SFTP alt sistemini desteklemeyen sunucular için eski dosya aktarımını kullan (ör. gömülü veya minimal SSH sunucuları).", - "defaultPathLabel": "Varsayılan Yol", - "fileManagerPathHint": "Dosya yöneticisi bu sunucu için başlatıldığında açılacak dizin.", "statusChecksLabel": "Durum Denetimleri", "enableStatusChecks": "Durum Denetimlerini Etkinleştir", "enableStatusChecksDesc": "Kullanılabilirliği doğrulamak için bu sunucuya düzenli olarak ping at", @@ -1278,7 +1269,6 @@ "deletedHost": "{{name}} silindi", "copiedToClipboard": "Panoya kopyalandı", "terminalUrlCopied": "Terminal URL'si kopyalandı", - "fileManagerUrlCopied": "Dosya Yöneticisi URL'si kopyalandı", "tunnelUrlCopied": "Tünel URL'si kopyalandı", "dockerUrlCopied": "Docker URL'si kopyalandı", "hostMetricsUrlCopied": "Ana makine metrikleri URL'si kopyalandı", @@ -1296,7 +1286,6 @@ "copyAddress": "Adresi Kopyala", "copyLink": "Bağlantıyı Kopyala", "copyTerminalUrlAction": "Terminal URL'sini Kopyala", - "copyFileManagerUrlAction": "Dosya Yöneticisi URL'sini Kopyala", "copyTunnelUrlAction": "Tünel URL'sini Kopyala", "copyDockerUrlAction": "Docker URL'sini Kopyala", "copyHostMetricsUrlAction": "Sunucu Metrikleri URL'sini Kopyala", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Bölünmüşten Kaldır" } }, - "fileManager": { - "noHostSelected": "Hiçbir sunucu seçilmedi", - "initializingEditor": "Düzenleyici başlatılıyor...", - "file": "Dosya", - "folder": "Klasör", - "uploadFile": "Dosya Yükle", - "downloadFile": "İndir", - "extractArchive": "Arşivi Çıkar", - "extractingArchive": "{{name}} çıkartılıyor...", - "archiveExtractedSuccessfully": "{{name}} başarıyla çıkartıldı.", - "extractFailed": "Çıkartma başarısız.", - "compressFile": "Dosyayı Sıkıştır", - "compressFiles": "Dosyaları Sıkıştır", - "compressFilesDesc": "{{count}} öğeyi arşive sıkıştır", - "archiveName": "Arşiv Adı", - "enterArchiveName": "Arşiv adını girin...", - "compressionFormat": "Sıkıştırma Biçimi", - "selectedFiles": "Seçili dosyalar", - "andMoreFiles": "ve {{count}} tane daha...", - "compress": "Sıkıştır", - "compressingFiles": "{{count}} öğe {{name}} içine sıkıştırılıyor...", - "filesCompressedSuccessfully": "{{name}} başarıyla oluşturuldu.", - "compressFailed": "Sıkıştırma başarısız.", - "edit": "Düzenle", - "preview": "Önizle", - "previous": "Önceki", - "next": "Sonraki", - "pageXOfY": "Sayfa {{current}} / {{total}}", - "zoomOut": "Uzaklaştır", - "zoomIn": "Yakınlaştır", - "newFile": "Yeni Dosya", - "newFolder": "Yeni Klasör", - "rename": "Yeniden Adlandır", - "uploading": "Yükleniyor...", - "uploadingFile": "{{name}} yükleniyor...", - "fileName": "Dosya Adı", - "folderName": "Klasör Adı", - "fileUploadedSuccessfully": "\"{{name}}\" dosyası başarıyla yüklendi.", - "failedToUploadFile": "Dosya yüklenemedi.", - "uploadingFolderFiles": "{{count}} dosya yükleniyor...", - "uploadedFolderFiles": "{{count}} dosya başarıyla yüklendi", - "uploadedFolderPartial": "{{uploaded}} dosya yüklendi, {{failed}} başarısız oldu", - "fileDownloadedSuccessfully": "\"{{name}}\" dosyası başarıyla indirildi.", - "failedToDownloadFile": "Dosya indirilemedi.", - "fileCreatedSuccessfully": "\"{{name}}\" dosyası başarıyla oluşturuldu.", - "folderCreatedSuccessfully": "\"{{name}}\" klasörü başarıyla oluşturuldu.", - "failedToCreateItem": "Öğe oluşturulamadı", - "operationFailed": "{{operation}} işlemi {{name}} için başarısız: {{error}}", - "failedToResolveSymlink": "Sembolik bağlantı çözümlenemedi", - "itemsDeletedSuccessfully": "{{count}} öğe başarıyla silindi", - "failedToDeleteItems": "Öğeler silinemedi", - "sudoPasswordRequired": "Yönetici Parolası Gerekli", - "enterSudoPassword": "Bu işleme devam etmek için sudo parolasını girin", - "sudoPassword": "Sudo parolası", - "sudoOperationFailed": "Sudo işlemi başarısız", - "sudoAuthFailed": "Sudo kimlik doğrulaması başarısız", - "dragFilesToUpload": "Yüklemek için dosyaları buraya bırakın", - "dropToUploadHere": "Yüklemek için sürükle bırak özelliğini kullanın.", - "dropToDownloadHere": "İndirmek için buraya tıklayın", - "localFiles": "Yerel", - "showLocalFiles": "Yerel dosyaları göster (bölünmüş görünüm)", - "hideLocalFiles": "Yerel dosyaları gizle", - "localHome": "Ana klasör", - "localPathPlaceholder": "Yerel klasör yolu", - "localShowHidden": "Gizli dosyaları göster", - "localHideHidden": "Gizli dosyaları gizle", - "localRevealInFileManager": "Sistem dosya yöneticisinde göster", - "localCannotOpenFolder": "Klasör açılamıyor.", - "localItemCount": "{{count}} öğe", - "localSelectedCount": "{{count}} seçildi", - "back": "Geri", - "forward": "İleri", - "up": "Yukarı", - "kind": "Tür", - "localUploadingProgress": "{{total}}'ten {{current}} yükleniyor", - "localDownloadingProgress": "{{total}}'ten {{current}} indiriliyor", - "localTransferInFlight": "{{count}} hemen", - "localPreparingDownload": "İndirme işlemi hazırlanıyor...", - "localTransferCancelled": "Transfer iptal edildi.", - "localNothingToTransfer": "Aktarılacak bir şey yok.", - "localUploadComplete": "{{count}} dosya yüklendi", - "localUploadFailed": "Yükleme başarısız oldu", - "localUploadPartial": "{{done}} dosya yüklendi, {{failed}} başarısız oldu", - "localDownloadComplete": "{{count}} dosya indirildi", - "localDownloadFailed": "İndirme başarısız oldu", - "localDownloadPartial": "{{done}} dosya indirildi, {{failed}} başarısız oldu", - "localReplaceExistingPrompt": "Bu klasörde zaten {{count}} öğe mevcut. Bunların yerine yenilerini mi koymak istiyorsunuz?", - "localReplace": "Yer değiştirmek", - "localSkipExisting": "Atlamak", - "localSkippedExisting": "{{count}} mevcut öğe atlandı", - "localUnsafeNamesSkipped": "Bu bilgisayarda adları kullanılamayan {{count}} öğe atlandı.", - "localOpen": "Açık", - "localOpenFolder": "Klasörü aç", - "localUploadToRemote": "Sunucuya yükle", - "localUploadToRemoteMany": "Sunucuya {{count}} öğe yükle", - "localMoveToTrash": "Çöp Kutusuna Taşı", - "localMoveToTrashMany": "{{count}} öğeyi Çöp Kutusuna Taşı", - "localTrashConfirmSingle": "\"{{name}}\" öğesini Çöp Kutusuna taşı?", - "localTrashConfirmFolder": "\"{{name}}\" klasörünü ve tüm içeriğini Çöp Kutusu'na taşı?", - "localTrashConfirmMany": "{{count}} öğeyi Çöp Kutusuna taşı?", - "localTrashed": "{{count}} öğe Çöp Kutusu'na taşındı", - "localTrashFailed": "Çöp kutusuna taşınamadı", - "localRenameFailed": "Yeniden adlandırma başarısız oldu.", - "localCreateFailed": "Öğe oluşturulamadı.", - "goToParentFolder": "Üst klasöre git", - "resizeSidebar": "Kenar çubuğunu yeniden boyutlandırmak için sürükleyin (sıfırlamak için çift tıklayın)", - "resizeLocalPane": "Yerel bölmeyi yeniden boyutlandırmak için sürükleyin (sıfırlamak için çift tıklayın).", - "columns": "Sütunlar", - "columnsHint": "Sütunları seçmek için sağ tıklayın.", - "emptyFolder": "Bu klasör boş", - "searchFiles": "Dosya ara...", - "upload": "Yükle", - "selectHostToStart": "Dosya yönetimini başlatmak için bir sunucu seçin", - "sshRequiredForFileManager": "Dosya yöneticisi SSH gerektirir. Bu sunucuda SSH etkin değil.", - "failedToConnect": "SSH'ye bağlanılamadı", - "failedToLoadDirectory": "Dizin yüklenemedi", - "noSSHConnection": "SSH bağlantısı yok", - "copy": "Kopyala", - "cut": "Kes", - "paste": "Yapıştır", - "copyPath": "Yolu Kopyala", - "copyPaths": "Yolları Kopyala", - "delete": "Sil", - "properties": "Özellikler", - "refresh": "Yenile", - "downloadFiles": "{{count}} dosyayı tarayıcıya indir", - "copyFiles": "{{count}} öğeyi kopyala", - "cutFiles": "{{count}} öğeyi kes", - "deleteFiles": "{{count}} öğeyi sil", - "filesCopiedToClipboard": "{{count}} öğe panoya kopyalandı", - "filesCutToClipboard": "{{count}} öğe panoya kesildi", - "pathCopiedToClipboard": "Yol panoya kopyalandı", - "pathsCopiedToClipboard": "{{count}} yol panoya kopyalandı", - "failedToCopyPath": "Yol panoya kopyalanamadı", - "copyFolderLink": "Klasör Bağlantısını Kopyala", - "copyCurrentFolderLink": "Geçerli Klasörün Bağlantısını Kopyala", - "folderLinkCopied": "Klasör bağlantısı panoya kopyalandı", - "failedToCopyFolderLink": "Klasör bağlantısı kopyalanamadı", - "movedItems": "{{count}} öğe taşındı", - "failedToDeleteItem": "Öğe silinemedi", - "itemRenamedSuccessfully": "{{type}} başarıyla yeniden adlandırıldı", - "failedToRenameItem": "Öğe yeniden adlandırılamadı", - "download": "İndir", - "openExternalEditor": "Harici Olarak Aç", - "chooseExternalEditor": "Harici düzenleyici seç", - "externalEditorSelected": "Harici düzenleyici seçildi", - "externalEditorDesktopOnly": "Harici düzenleyici yalnızca masaüstü uygulamasında kullanılabilir", - "externalEditorOpened": "Harici düzenleyicide açıldı. Orada kaydetmek değişiklikleri sunucuya yükler.", - "failedToOpenExternalEditor": "Harici düzenleyici açılamadı", - "failedToSelectExternalEditor": "Harici düzenleyici seçilemedi", - "permissions": "İzinler", - "size": "Boyut", - "modified": "Değiştirilme", - "path": "Yol", - "confirmDelete": "{{name}} öğesini silmek istediğinize emin misiniz?", - "permissionDenied": "İzin reddedildi", - "serverError": "Sunucu Hatası", - "fileSavedSuccessfully": "Dosya başarıyla kaydedildi", - "failedToSaveFile": "Dosya kaydedilemedi", - "confirmDeleteSingleItem": "\"{{name}}\" öğesini kalıcı olarak silmek istediğinize emin misiniz?", - "confirmDeleteMultipleItems": "{{count}} öğeyi kalıcı olarak silmek istediğinize emin misiniz?", - "confirmDeleteMultipleItemsWithFolders": "{{count}} öğeyi kalıcı olarak silmek istediğinize emin misiniz? Bu, klasörleri ve içeriklerini de kapsar.", - "confirmDeleteFolder": "\"{{name}}\" klasörünü ve tüm içeriğini kalıcı olarak silmek istediğinize emin misiniz?", - "permanentDeleteWarning": "Bu işlem geri alınamaz. Öğe(ler) sunucudan kalıcı olarak silinecek.", - "moveToTrashWarning": "Öğeler Termix Çöp Kutusu'na taşınacak ve saklama süresi dolmadan önce geri yüklenebilecektir.", - "itemsMovedToTrash": "{{count}} öğe Çöp Kutusuna taşındı", - "trash": "Çöp", - "trashDescription": "Silinen öğeler bu sunucuda {{days}} gün boyunca saklanır.", - "trashLoadFailed": "Çöp kutusu yüklenemedi.", - "trashEmpty": "Çöp kutusu boş.", - "trashRestore": "Eski haline getirmek", - "trashRestored": "Geri Yüklendi {{name}}", - "trashRestoreFailed": "Öğeyi geri yükleme başarısız oldu. Orijinal yol zaten mevcut olabilir.", - "trashDeletePermanently": "Kalıcı olarak sil", - "trashDeleteConfirm": "{{name}}öğesini kalıcı olarak silmek istiyor musunuz? Bu işlem geri alınamaz.", - "trashPermanentlyDeleted": "Kalıcı olarak silindi {{name}}", - "trashDeleteFailed": "Öğeyi kalıcı olarak silme işlemi başarısız oldu.", - "trashEmptyAction": "Çöpü Boşaltın", - "trashEmptyConfirm": "Çöp kutusundaki tüm öğeleri kalıcı olarak silmek mi istiyorsunuz? Bu işlem geri alınamaz.", - "trashEmptied": "Çöp boşaltıldı.", - "trashRetention": "Otomatik temizleme süresi (gün)", - "trashRetentionSaved": "Çöp saklama süresi güncellendi", - "trashRetentionFailed": "Çöp kutusu saklama süresi güncellenemedi.", - "trashUnavailableConfirm": "Bu öğe Çöp Kutusu'na taşınamaz, genellikle başka bir dosya sisteminde olduğu veya yükseltilmiş izinler gerektirdiği içindir. Bunun yerine kalıcı olarak silin.", - "recent": "Son Kullanılanlar", - "pinned": "Sabitlenmiş", - "folderShortcuts": "Klasör Kısayolları", - "failedToReconnectSSH": "SSH oturumuna yeniden bağlanılamadı", - "openTerminalHere": "Terminali Burada Aç", - "run": "Çalıştır", - "openTerminalInFolder": "Bu Klasörde Terminal Aç", - "openTerminalInFileLocation": "Dosya Konumunda Terminal Aç", - "runningFile": "Çalıştırılıyor - {{file}}", - "onlyRunExecutableFiles": "Yalnızca çalıştırılabilir dosyalar çalıştırılabilir", - "directories": "Dizinler", - "removedFromRecentFiles": "\"{{name}}\" son dosyalardan kaldırıldı", - "removeFailed": "Kaldırma başarısız", - "unpinnedSuccessfully": "\"{{name}}\" sabitlemesi başarıyla kaldırıldı", - "unpinFailed": "Sabitleme kaldırma başarısız", - "removedShortcut": "\"{{name}}\" kısayolu kaldırıldı", - "removeShortcutFailed": "Kısayol kaldırma başarısız", - "clearedAllRecentFiles": "Tüm son dosyalar temizlendi", - "clearFailed": "Temizleme başarısız", - "removeFromRecentFiles": "Son dosyalardan kaldır", - "clearAllRecentFiles": "Tüm son dosyaları temizle", - "unpinFile": "Dosya sabitlemesini kaldır", - "removeShortcut": "Kısayolu kaldır", - "pinFile": "Dosyayı sabitle", - "addToShortcuts": "Kısayollara ekle", - "pasteFailed": "Yapıştırma başarısız", - "noUndoableActions": "Geri alınabilir işlem yok", - "undoCopySuccess": "Kopyalama işlemi geri alındı: {{count}} kopyalanmış dosya silindi", - "undoCopyFailedDelete": "Geri alma başarısız: Hiçbir kopyalanmış dosya silinemedi", - "undoCopyFailedNoInfo": "Geri alma başarısız: Kopyalanmış dosya bilgisi bulunamadı", - "undoMoveSuccess": "Taşıma işlemi geri alındı: {{count}} dosya orijinal konumuna geri taşındı", - "undoMoveFailedMove": "Geri alma başarısız: Hiçbir dosya geri taşınamadı", - "undoMoveFailedNoInfo": "Geri alma başarısız: Taşınmış dosya bilgisi bulunamadı", - "undoDeleteNotSupported": "Silme işlemi geri alınamaz: Dosyalar sunucudan kalıcı olarak silindi", - "undoTypeNotSupported": "Desteklenmeyen geri alma işlem türü", - "undoOperationFailed": "Geri alma işlemi başarısız", - "unknownError": "Bilinmeyen hata", - "confirm": "Onayla", - "find": "Bul...", - "replace": "Değiştir", - "downloadInstead": "Bunun Yerine İndir", - "keyboardShortcuts": "Klavye Kısayolları", - "searchAndReplace": "Ara ve Değiştir", - "editing": "Düzenleme", - "search": "Ara", - "findNext": "Sonrakini Bul", - "findPrevious": "Öncekini Bul", - "save": "Kaydet", - "selectAll": "Tümünü Seç", - "undo": "Geri Al", - "redo": "Yinele", - "moveLineUp": "Satırı Yukarı Taşı", - "moveLineDown": "Satırı Aşağı Taşı", - "toggleComment": "Yorumu Aç/Kapat", - "autoComplete": "Otomatik Tamamla", - "imageLoadError": "Görüntü yüklenemedi", - "startTyping": "Yazmaya başlayın...", - "unknownSize": "Bilinmeyen boyut", - "fileIsEmpty": "Dosya boş", - "largeFileWarning": "Büyük Dosya Uyarısı", - "largeFileWarningDesc": "Bu dosya {{size}} boyutunda, metin olarak açıldığında performans sorunlarına neden olabilir.", - "fileNotFoundAndRemoved": "\"{{name}}\" dosyası bulunamadı ve son kullanılanlar/sabitlenmiş dosyalardan kaldırıldı.", - "failedToLoadFile": "Dosya yüklenemedi: {{error}}", - "serverErrorOccurred": "Sunucu hatası oluştu. Lütfen daha sonra tekrar deneyin.", - "autoSaveFailed": "Otomatik kaydetme başarısız oldu", - "fileAutoSaved": "Dosya otomatik kaydedildi", - "moveFileFailed": "{{name}} taşınamadı", - "moveOperationFailed": "Taşıma işlemi başarısız oldu", - "canOnlyCompareFiles": "Yalnızca iki dosya karşılaştırılabilir", - "comparingFiles": "Dosyalar karşılaştırılıyor: {{file1}} ve {{file2}}", - "dragFailed": "Sürükleme işlemi başarısız oldu", - "filePinnedSuccessfully": "\"{{name}}\" dosyası başarıyla sabitlendi", - "pinFileFailed": "Dosya sabitleme başarısız oldu", - "fileUnpinnedSuccessfully": "\"{{name}}\" dosyasının sabitlemesi başarıyla kaldırıldı", - "unpinFileFailed": "Sabitlenmiş dosya kaldırılamadı", - "shortcutAddedSuccessfully": "Klasör kısayolu \"{{name}}\" başarıyla eklendi", - "addShortcutFailed": "Kısayol eklenemedi", - "operationCompletedSuccessfully": "{{count}} öğe başarıyla {{operation}}", - "operationCompleted": "{{count}} öğe {{operation}}", - "downloadFileSuccess": "{{name}} dosyası başarıyla indirildi", - "downloadFileFailed": "İndirme başarısız oldu", - "downloadingFile": "{{name}}… indiriliyor", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "{{name}} konumuna taşı", - "diffCompareWith": "{{name}} ile diff karşılaştır", - "dragOutsideToDownload": "İndirmek için pencere dışına sürükleyin ({{count}} dosya)", - "newFolderDefault": "Yeni Klasör", - "newFileDefault": "Yeni Dosya.txt", - "successfullyMovedItems": "{{count}} öğe başarıyla {{target}} konumuna taşındı", - "move": "Taşı", - "searchInFile": "Dosyada ara (Ctrl+F)", - "showKeyboardShortcuts": "Klavye kısayollarını göster", - "decreaseFontSize": "Yazı tipi boyutunu küçült", - "increaseFontSize": "Yazı tipi boyutunu büyüt", - "startWritingMarkdown": "Markdown içeriğinizi yazmaya başlayın...", - "loadingFileComparison": "Dosya karşılaştırması yükleniyor...", - "reload": "Yeniden yükle", - "compare": "Karşılaştır", - "sideBySide": "Yan Yana", - "inline": "Satır İçi", - "fileComparison": "Dosya Karşılaştırması: {{file1}} vs {{file2}}", - "fileTooLarge": "Dosya çok büyük: {{error}}", - "sshConnectionFailed": "SSH bağlantısı başarısız oldu. Lütfen {{name}} ({{ip}}:{{port}}) ile bağlantınızı kontrol edin", - "loadFileFailed": "Dosya yüklenemedi: {{error}}", - "connecting": "Bağlanıyor...", - "connectedSuccessfully": "Bağlantı başarıyla kuruldu", - "totpVerificationFailed": "TOTP doğrulaması başarısız oldu", - "warpgateVerificationFailed": "Warpgate kimlik doğrulaması başarısız oldu", - "authenticationFailed": "Kimlik doğrulama başarısız oldu", - "incorrectPassphrase": "Yanlış parola. Lütfen tekrar deneyin.", - "verificationCodePrompt": "Doğrulama kodu:", - "changePermissions": "İzinleri Değiştir", - "currentPermissions": "Mevcut İzinler", - "owner": "Sahip", - "group": "Grup", - "others": "Diğerleri", - "read": "Okuma", - "write": "Yazma", - "execute": "Çalıştırma", - "permissionsChangedSuccessfully": "İzinler başarıyla değiştirildi", - "failedToChangePermissions": "İzinler değiştirilemedi", - "name": "Ad", - "sortByName": "Ad", - "sortByDate": "Değiştirilme Tarihi", - "sortBySize": "Boyut", - "ascending": "Artan", - "descending": "Azalan", - "root": "Kök", - "new": "Yeni", - "sortBy": "Sırala", - "items": "Öğeler", - "selected": "Seçili", - "editor": "Düzenleyici", - "octal": "Sekizlik", - "storage": "Depolama", - "disk": "Disk", - "used": "Kullanılan", - "of": " / ", - "toggleSidebar": "Kenar Çubuğunu Göster/Gizle", - "compactLayout": "Kompakt düzeni kullanın.", - "comfortableLayout": "Rahat bir düzen kullanın.", - "cannotLoadPdf": "PDF yüklenemiyor", - "pdfLoadError": "Bu PDF dosyası yüklenirken bir hata oluştu.", - "loadingPdf": "PDF yükleniyor...", - "loadingPage": "Sayfa yükleniyor..." - }, "transfer": { "copyToHost": "Sunucuya kopyala…", "moveToHost": "Sunucuya taşı…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Yeniden deneme başarısız oldu", "transferFailedRetryHint": "Hedefte kısmi veri tutuldu. Bağlantı geri geldiğinde yeniden deneme devam edecek." }, - "sftpTransfer": { - "title": "SFTP", - "description": "İki SSH sunucusu arasında dosya aktarımı.", - "sourceServer": "Kaynak Sunucusu", - "destinationServer": "Hedef Sunucu", - "selectHost": "Sunucu seçin", - "noHostSelected": "Sunucu seçilmedi.", - "move": "Taşınmak", - "copy": "Kopyala", - "upload": "Yüklemek", - "itemsSelected": "{{count}} seçilen öğe", - "itemsSelected_other": "{{count}} öğe seçildi", - "ready": "Hazır", - "loadingHosts": "Sunucular yükleniyor...", - "name": "İsim", - "modified": "Değiştirildi", - "size": "Boyut", - "noFiles": "Dosya yok", - "open": "Açık", - "up": "Yukarı", - "refresh": "Yenile", - "copyToTarget": "Hedef Dizine Kopyala", - "rename": "Yeniden isimlendirmek", - "delete": "Silmek", - "editPermissions": "Düzenleme İzinleri", - "createNewFolder": "Yeni Klasör Oluştur", - "deleteSelectedItems": "Seçili Öğeleri Sil", - "deleteConfirm": "Delete {{count}} selected item?", - "deleteConfirm_other": "Delete {{count}} selected items?", - "cancel": "İptal etmek", - "save": "Kaydetmek", - "folderCreated": "Klasör oluşturuldu", - "itemRenamed": "Öğe yeniden adlandırıldı.", - "actionFailed": "İşlem başarısız oldu", - "itemsDeleted": "{{count}} öğe silindi", - "itemsDeleted_other": "{{count}} öğe silindi", - "deleteFailed": "Silme işlemi başarısız oldu.", - "permissionsUpdated": "İzinler güncellendi", - "noRemoteSessionConnected": "Bağlantı kurulmuş uzaktan oturum yok.", - "failedToListFiles": "Dosyaları listeleme başarısız oldu.", - "failedToRefresh": "Yenileme başarısız oldu", - "destinationInsideSource": "Hedef, seçilen kaynak yolunun içinde olamaz.", - "sameHostNoOp": "Kaynak ve hedef aynı klasördedir." - }, "tunnels": { "noSshTunnels": "SSH Tüneli yok", "createFirstTunnelMessage": "Henüz hiç SSH tüneli oluşturmadınız. Başlamak için Ana Bilgisayar Yöneticisi'nde tünel bağlantılarını yapılandırın.", diff --git a/src/ui/locales/translated/uk_UA.json b/src/ui/locales/translated/uk_UA.json index 327459d09..f35af346b 100644 --- a/src/ui/locales/translated/uk_UA.json +++ b/src/ui/locales/translated/uk_UA.json @@ -715,7 +715,6 @@ "hostManager": "Менеджер хостингу", "credentials": "Облікові дані", "portForwarding": "Переадресація", - "sftp": "SFTP", "connections": "З'єднання", "serial": "Серійний", "keepSideMenuOpen": "Залишити меню відкритим", @@ -767,14 +766,12 @@ "tabRdp": "ПРП", "tabVnc": "VNC", "tabTunnels": "Тунелі", - "tabFiles": "Файли", "tabStats": "Метрики хоста", "tabTelnet": "Телнет", "tabSharing": "Спільне використання", "tabAuthentication": "Автентифікація", "terminal": "Термінал", "tunnel": "Тунель", - "fileManager": "Файловий менеджер", "serverStats": "Метрики хоста", "status": { "available": "Доступно", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} пропущено (IP-адресу не знайдено)", "proxmoxStatsUrlCopied": "URL-адресу статистики Proxmox скопійовано в буфер обміну", "copyProxmoxStatsUrlAction": "Копіювати URL-адресу статистики Proxmox", - "enableFileManagerMonitor": "Увімкнути файловий менеджер", - "enableFileManagerMonitorDesc": "Переглядайте та керуйте файлами на цьому хості через SFTP", - "scpLegacyLabel": "Застарілий режим SCP", - "scpLegacyDesc": "Використовуйте застарілу передачу файлів для серверів, які не підтримують підсистему SFTP (наприклад, вбудовані або мінімальні SSH-сервери).", - "defaultPathLabel": "Шлях за замовчуванням", - "fileManagerPathHint": "Каталог, який потрібно відкрити під час запуску файлового менеджера для цього хоста.", "statusChecksLabel": "Перевірки статусу", "enableStatusChecks": "Увімкнути перевірки стану", "enableStatusChecksDesc": "Періодично перевіряти доступність цього хоста за допомогою ping-повідомлення", @@ -1278,7 +1269,6 @@ "deletedHost": "Видалено {{name}}", "copiedToClipboard": "Скопійовано в буфер обміну", "terminalUrlCopied": "URL-адресу термінала скопійовано", - "fileManagerUrlCopied": "URL-адресу файлового менеджера скопійовано", "tunnelUrlCopied": "URL-адресу тунелю скопійовано", "dockerUrlCopied": "URL-адресу Docker скопійовано", "hostMetricsUrlCopied": "URL-адресу показників хоста скопійовано", @@ -1296,7 +1286,6 @@ "copyAddress": "Копіювати адресу", "copyLink": "Копіювати посилання", "copyTerminalUrlAction": "Копіювати URL-адресу термінала", - "copyFileManagerUrlAction": "Копіювати URL-адресу файлового менеджера", "copyTunnelUrlAction": "Копіювати URL-адресу тунелю", "copyDockerUrlAction": "Копіювати URL-адресу Docker", "copyHostMetricsUrlAction": "Копіювати URL-адресу показників хоста", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Видалити зі Спліта" } }, - "fileManager": { - "noHostSelected": "Хост не вибрано", - "initializingEditor": "Ініціалізація редактора...", - "file": "Файл", - "folder": "Папка", - "uploadFile": "Завантажити файл", - "downloadFile": "Завантажити", - "extractArchive": "Витягти архів", - "extractingArchive": "Видобування {{name}}...", - "archiveExtractedSuccessfully": "{{name}} успішно видобуто", - "extractFailed": "Не вдалося витягти", - "compressFile": "Стиснути файл", - "compressFiles": "Стиснути файли", - "compressFilesDesc": "Стиснути {{count}} елементів в архів", - "archiveName": "Назва архіву", - "enterArchiveName": "Введіть назву архіву...", - "compressionFormat": "Формат стиснення", - "selectedFiles": "Вибрані файли", - "andMoreFiles": "та {{count}} більше...", - "compress": "Стиснути", - "compressingFiles": "Стискання {{count}} елементів у {{name}}...", - "filesCompressedSuccessfully": "{{name}} успішно створено", - "compressFailed": "Стиснення не вдалося", - "edit": "Редагувати", - "preview": "Попередній перегляд", - "previous": "Попередній", - "next": "Далі", - "pageXOfY": "Сторінка {{current}} з {{total}}", - "zoomOut": "Зменшити масштаб", - "zoomIn": "Збільшити масштаб", - "newFile": "Новий файл", - "newFolder": "Нова папка", - "rename": "Перейменувати", - "uploading": "Завантаження...", - "uploadingFile": "Завантаження {{name}}...", - "fileName": "Ім'я файлу", - "folderName": "Назва папки", - "fileUploadedSuccessfully": "Файл «{{name}}» успішно завантажено", - "failedToUploadFile": "Не вдалося завантажити файл", - "uploadingFolderFiles": "Завантаження {{count}} файлів...", - "uploadedFolderFiles": "Успішно завантажено {{count}} файл(ів)", - "uploadedFolderPartial": "Завантажено {{uploaded}} файл(ів), {{failed}} не вдалося", - "fileDownloadedSuccessfully": "Файл «{{name}}» успішно завантажено", - "failedToDownloadFile": "Не вдалося завантажити файл", - "fileCreatedSuccessfully": "Файл «{{name}}» успішно створено", - "folderCreatedSuccessfully": "Папка «{{name}}» успішно створена", - "failedToCreateItem": "Не вдалося створити елемент", - "operationFailed": "Операція {{operation}} не вдалася для {{name}}: {{error}}", - "failedToResolveSymlink": "Не вдалося вирішити проблему з символічним посиланням", - "itemsDeletedSuccessfully": "{{count}} елементів успішно видалено", - "failedToDeleteItems": "Не вдалося видалити елементи", - "sudoPasswordRequired": "Потрібен пароль адміністратора", - "enterSudoPassword": "Введіть пароль sudo, щоб продовжити цю операцію", - "sudoPassword": "Пароль Sudo", - "sudoOperationFailed": "Операція Sudo не вдалася", - "sudoAuthFailed": "Не вдалося виконати автентифікацію Sudo", - "dragFilesToUpload": "Перетягніть сюди файли для завантаження", - "dropToUploadHere": "Перетягніть сюди, щоб завантажити", - "dropToDownloadHere": "Перетягніть сюди, щоб завантажити", - "localFiles": "Місцевий", - "showLocalFiles": "Показати локальні файли (розділений вигляд)", - "hideLocalFiles": "Приховати локальні файли", - "localHome": "Домашня папка", - "localPathPlaceholder": "Шлях до локальної папки", - "localShowHidden": "Показати приховані файли", - "localHideHidden": "Приховати приховані файли", - "localRevealInFileManager": "Показати в системному файловому менеджері", - "localCannotOpenFolder": "Не вдається відкрити папку", - "localItemCount": "{{count}} елементів", - "localSelectedCount": "{{count}} вибрано", - "back": "Назад", - "forward": "Вперед", - "up": "Вгору", - "kind": "Вид", - "localUploadingProgress": "Завантаження {{current}} з {{total}}", - "localDownloadingProgress": "Завантаження {{current}} з {{total}}", - "localTransferInFlight": "{{count}} одразу", - "localPreparingDownload": "Підготовка завантаження...", - "localTransferCancelled": "Переказ скасовано", - "localNothingToTransfer": "Нічого для перенесення", - "localUploadComplete": "Завантажено {{count}} файл(ів)", - "localUploadFailed": "Не вдалося завантажити", - "localUploadPartial": "Завантажено {{done}} файл(ів), {{failed}} не вдалося", - "localDownloadComplete": "Завантажено {{count}} файл(ів)", - "localDownloadFailed": "Завантаження не вдалося", - "localDownloadPartial": "Завантажено {{done}} файл(ів), {{failed}} не вдалося", - "localReplaceExistingPrompt": "{{count}} елементів вже є в цій папці. Замінити їх?", - "localReplace": "Замінити", - "localSkipExisting": "Пропустити", - "localSkippedExisting": "Пропущено {{count}} існуючих елементів", - "localUnsafeNamesSkipped": "Пропущено {{count}} елементів, назви яких не можна використовувати на цьому комп’ютері", - "localOpen": "ВІДЧИНЕНО", - "localOpenFolder": "Відкрити папку", - "localUploadToRemote": "Завантажити на сервер", - "localUploadToRemoteMany": "Завантажити {{count}} елементів на сервер", - "localMoveToTrash": "Перемістити в кошик", - "localMoveToTrashMany": "Перемістити {{count}} елементів до кошика", - "localTrashConfirmSingle": "Перемістити \"{{name}}\" до кошика?", - "localTrashConfirmFolder": "Перемістити папку «{{name}}» та весь її вміст до кошика?", - "localTrashConfirmMany": "Перемістити {{count}} елементів до кошика?", - "localTrashed": "Переміщено {{count}} елементів до кошика", - "localTrashFailed": "Не вдалося перемістити в кошик", - "localRenameFailed": "Не вдалося перейменувати", - "localCreateFailed": "Не вдалося створити елемент", - "goToParentFolder": "Перейти до батьківської папки", - "resizeSidebar": "Перетягніть, щоб змінити розмір бічної панелі (двічі клацніть, щоб скинути налаштування)", - "resizeLocalPane": "Перетягніть, щоб змінити розмір локальної панелі (двічі клацніть, щоб скинути налаштування)", - "columns": "Колонки", - "columnsHint": "Клацніть правою кнопкою миші, щоб вибрати стовпці", - "emptyFolder": "Ця папка порожня", - "searchFiles": "Пошук файлів...", - "upload": "Завантажити", - "selectHostToStart": "Виберіть хост, щоб розпочати керування файлами", - "sshRequiredForFileManager": "Файловий менеджер вимагає SSH. На цьому хості SSH не ввімкнено.", - "failedToConnect": "Не вдалося підключитися до SSH", - "failedToLoadDirectory": "Не вдалося завантажити каталог", - "noSSHConnection": "SSH-з'єднання недоступне", - "copy": "Копіювати", - "cut": "Вирізати", - "paste": "Вставити", - "copyPath": "Копіювати шлях", - "copyPaths": "Копіювати шляхи", - "delete": "Видалити", - "properties": "Властивості", - "refresh": "Оновити", - "downloadFiles": "Завантажити {{count}} файлів у браузер", - "copyFiles": "Копіювати {{count}} елементів", - "cutFiles": "Вирізати {{count}} елементів", - "deleteFiles": "Видалити {{count}} елементів", - "filesCopiedToClipboard": "{{count}} елементів, скопійованих у буфер обміну", - "filesCutToClipboard": "{{count}} елементів, вирізаних у буфер обміну", - "pathCopiedToClipboard": "Шлях скопійовано в буфер обміну", - "pathsCopiedToClipboard": "{{count}} шляхів скопійовано в буфер обміну", - "failedToCopyPath": "Не вдалося скопіювати шлях до буфера обміну", - "copyFolderLink": "Копіювати посилання до папки", - "copyCurrentFolderLink": "Копіювати посилання до поточної папки", - "folderLinkCopied": "Посилання на папку скопійовано в буфер обміну", - "failedToCopyFolderLink": "Не вдалося скопіювати посилання на папку", - "movedItems": "Переміщено {{count}} елементів", - "failedToDeleteItem": "Не вдалося видалити елемент", - "itemRenamedSuccessfully": "{{type}} успішно перейменовано", - "failedToRenameItem": "Не вдалося перейменувати елемент", - "download": "Завантажити", - "openExternalEditor": "Відкрити зовні", - "chooseExternalEditor": "Виберіть зовнішнього редактора", - "externalEditorSelected": "Вибрано зовнішнього редактора", - "externalEditorDesktopOnly": "Зовнішній редактор доступний лише в настільній програмі", - "externalEditorOpened": "Відкрито в зовнішньому редакторі. Збереження там завантажить зміни назад на сервер.", - "failedToOpenExternalEditor": "Не вдалося відкрити зовнішній редактор", - "failedToSelectExternalEditor": "Не вдалося вибрати зовнішній редактор", - "permissions": "Дозволи", - "size": "Розмір", - "modified": "Змінено", - "path": "Шлях", - "confirmDelete": "Ви впевнені, що хочете видалити {{name}}?", - "permissionDenied": "Дозвіл відхилено", - "serverError": "Помилка сервера", - "fileSavedSuccessfully": "Файл успішно збережено", - "failedToSaveFile": "Не вдалося зберегти файл", - "confirmDeleteSingleItem": "Ви впевнені, що хочете остаточно видалити \"{{name}}\"?", - "confirmDeleteMultipleItems": "Ви впевнені, що хочете остаточно видалити {{count}} елементів?", - "confirmDeleteMultipleItemsWithFolders": "Ви впевнені, що хочете остаточно видалити {{count}} елементів? Це стосується папок та їхнього вмісту.", - "confirmDeleteFolder": "Ви впевнені, що хочете остаточно видалити папку «{{name}}» та весь її вміст?", - "permanentDeleteWarning": "Цю дію неможливо скасувати. Елемент(и) буде назавжди видалено із сервера.", - "moveToTrashWarning": "Елемент(и) буде переміщено до кошика Termix і може бути відновлено до закінчення терміну зберігання.", - "itemsMovedToTrash": "{{count}} елементів переміщено до кошика", - "trash": "Сміття", - "trashDescription": "Видалені елементи зберігаються на цьому хості протягом {{days}} днів.", - "trashLoadFailed": "Не вдалося завантажити кошик", - "trashEmpty": "Кошик порожній", - "trashRestore": "Відновити", - "trashRestored": "Відновлено {{name}}", - "trashRestoreFailed": "Не вдалося відновити елемент. Можливо, початковий шлях уже існує.", - "trashDeletePermanently": "Видалити назавжди", - "trashDeleteConfirm": "Видалити {{name}}назавжди? Цю дію не можна скасувати.", - "trashPermanentlyDeleted": "Видалено назавжди {{name}}", - "trashDeleteFailed": "Не вдалося остаточно видалити елемент", - "trashEmptyAction": "Очистити кошик", - "trashEmptyConfirm": "Видалити всі елементи з кошика назавжди? Цю дію не можна скасувати.", - "trashEmptied": "Кошик очищено", - "trashRetention": "Автоматичне очищення через (дні)", - "trashRetentionSaved": "Зберігання в кошику оновлено", - "trashRetentionFailed": "Не вдалося оновити налаштування зберігання в кошику", - "trashUnavailableConfirm": "Цей елемент не можна перемістити до кошика, зазвичай тому, що він знаходиться в іншій файловій системі або потребує підвищених прав. Видалити його назавжди?", - "recent": "Нещодавні", - "pinned": "Закріплено", - "folderShortcuts": "Ярлики папок", - "failedToReconnectSSH": "Не вдалося відновити сеанс SSH", - "openTerminalHere": "Відкрити термінал тут", - "run": "Бігти", - "openTerminalInFolder": "Відкрити термінал у цій папці", - "openTerminalInFileLocation": "Відкрити термінал у місці розташування файлу", - "runningFile": "Біг – {{file}}", - "onlyRunExecutableFiles": "Можна запускати лише виконувані файли", - "directories": "Довідники", - "removedFromRecentFiles": "Видалено \"{{name}}\" з останніх файлів", - "removeFailed": "Видалити не вдалося", - "unpinnedSuccessfully": "Відкріплено \"{{name}}\" успішно", - "unpinFailed": "Не вдалося відкріпити", - "removedShortcut": "Видалено ярлик «{{name}}»", - "removeShortcutFailed": "Не вдалося видалити ярлик", - "clearedAllRecentFiles": "Очищено всі нещодавні файли", - "clearFailed": "Очистити не вдалося", - "removeFromRecentFiles": "Видалити з останніх файлів", - "clearAllRecentFiles": "Очистити всі нещодавні файли", - "unpinFile": "Відкріпити файл", - "removeShortcut": "Видалити ярлик", - "pinFile": "Закріпити файл", - "addToShortcuts": "Додати до ярликів", - "pasteFailed": "Вставити не вдалося", - "noUndoableActions": "Немає дій, які можна скасувати", - "undoCopySuccess": "Скасовано копіювання: Видалено {{count}} скопійованих файлів", - "undoCopyFailedDelete": "Не вдалося скасувати: не вдалося видалити жодного скопійованого файлу", - "undoCopyFailedNoInfo": "Не вдалося скасувати: не вдалося знайти інформацію про скопійований файл", - "undoMoveSuccess": "Скасовано операцію переміщення: {{count}} файлів переміщено назад у початкове розташування", - "undoMoveFailedMove": "Не вдалося скасувати: не вдалося перемістити жодного файлу назад", - "undoMoveFailedNoInfo": "Не вдалося скасувати: не вдалося знайти інформацію про переміщений файл", - "undoDeleteNotSupported": "Операцію видалення неможливо скасувати: файли остаточно видалено із сервера", - "undoTypeNotSupported": "Непідтримуваний тип операції скасування", - "undoOperationFailed": "Не вдалося скасувати операцію", - "unknownError": "Невідома помилка", - "confirm": "Підтвердити", - "find": "Знайти...", - "replace": "Замінити", - "downloadInstead": "Завантажити замість цього", - "keyboardShortcuts": "Комбінації клавіш", - "searchAndReplace": "Пошук і заміна", - "editing": "Редагування", - "search": "Пошук", - "findNext": "Знайти далі", - "findPrevious": "Знайти попередній", - "save": "Зберегти", - "selectAll": "Вибрати все", - "undo": "Скасувати", - "redo": "Повторити", - "moveLineUp": "Перемістити лінію вгору", - "moveLineDown": "Перемістити рядок вниз", - "toggleComment": "Перемикач коментарів", - "autoComplete": "Автоматичне завершення", - "imageLoadError": "Не вдалося завантажити зображення", - "startTyping": "Почніть друкувати...", - "unknownSize": "Невідомий розмір", - "fileIsEmpty": "Файл порожній", - "largeFileWarning": "Попередження про великий файл", - "largeFileWarningDesc": "Розмір цього файлу становить {{size}} , що може спричинити проблеми з продуктивністю під час відкриття його як тексту.", - "fileNotFoundAndRemoved": "Файл «{{name}}» не знайдено та видалено з нещодавніх/закріплених файлів", - "failedToLoadFile": "Не вдалося завантажити файл: {{error}}", - "serverErrorOccurred": "Сталася помилка сервера. Будь ласка, спробуйте пізніше.", - "autoSaveFailed": "Не вдалося автоматично зберегти", - "fileAutoSaved": "Файл автоматично збережено", - "moveFileFailed": "Не вдалося перемістити {{name}}", - "moveOperationFailed": "Не вдалося перемістити", - "canOnlyCompareFiles": "Можна порівняти лише два файли", - "comparingFiles": "Порівняння файлів: {{file1}} та {{file2}}", - "dragFailed": "Не вдалося перетягнути", - "filePinnedSuccessfully": "Файл «{{name}}» успішно закріплено", - "pinFileFailed": "Не вдалося закріпити файл", - "fileUnpinnedSuccessfully": "Файл «{{name}}» успішно відкріплено", - "unpinFileFailed": "Не вдалося відкріпити файл", - "shortcutAddedSuccessfully": "Ярлик папки «{{name}}» успішно додано", - "addShortcutFailed": "Не вдалося додати ярлик", - "operationCompletedSuccessfully": "{{operation}} {{count}} елементів успішно", - "operationCompleted": "{{operation}} {{count}} елементів", - "downloadFileSuccess": "Файл {{name}} успішно завантажено", - "downloadFileFailed": "Завантаження не вдалося", - "downloadingFile": "Завантаження {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Перейти до {{name}}", - "diffCompareWith": "Різниця у порівнянні з {{name}}", - "dragOutsideToDownload": "Перетягніть за межі вікна, щоб завантажити ({{count}} файлів)", - "newFolderDefault": "НоваПапка", - "newFileDefault": "НовийФайл.txt", - "successfullyMovedItems": "Елементів {{count}} успішно переміщено до {{target}}", - "move": "Перемістити", - "searchInFile": "Пошук у файлі (Ctrl+F)", - "showKeyboardShortcuts": "Показати комбінації клавіш", - "decreaseFontSize": "Зменшити розмір шрифту", - "increaseFontSize": "Збільшити розмір шрифту", - "startWritingMarkdown": "Почніть писати свій контент у форматі Markdown...", - "loadingFileComparison": "Завантаження порівняння файлів...", - "reload": "Перезавантажити", - "compare": "Порівняти", - "sideBySide": "Пліч-о-пліч", - "inline": "Вбудований", - "fileComparison": "Порівняння файлів: {{file1}} проти {{file2}}", - "fileTooLarge": "Файл занадто великий: {{error}}", - "sshConnectionFailed": "Помилка SSH-з’єднання. Будь ласка, перевірте ваше з’єднання з {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Не вдалося завантажити файл: {{error}}", - "connecting": "Підключення...", - "connectedSuccessfully": "Підключено успішно", - "totpVerificationFailed": "Не вдалося перевірити TOTP", - "warpgateVerificationFailed": "Автентифікація Warpgate не вдалася", - "authenticationFailed": "Помилка автентифікації", - "incorrectPassphrase": "Неправильна парольна фраза. Спробуйте ще раз.", - "verificationCodePrompt": "Код підтвердження:", - "changePermissions": "Змінити дозволи", - "currentPermissions": "Поточні дозволи", - "owner": "Власник", - "group": "Група", - "others": "Інші", - "read": "Читати", - "write": "Написати", - "execute": "Виконати", - "permissionsChangedSuccessfully": "Дозволи успішно змінено", - "failedToChangePermissions": "Не вдалося змінити дозволи", - "name": "Ім'я", - "sortByName": "Ім'я", - "sortByDate": "Дата зміни", - "sortBySize": "Розмір", - "ascending": "Зростаючий", - "descending": "Спадання", - "root": "Корінь", - "new": "Новий", - "sortBy": "Сортувати за", - "items": "Елементи", - "selected": "Вибрано", - "editor": "Редактор", - "octal": "Вісімкове", - "storage": "Зберігання", - "disk": "Диск", - "used": "Використано", - "of": "з", - "toggleSidebar": "Перемикання бічної панелі", - "compactLayout": "Використовуйте компактний макет", - "comfortableLayout": "Використовуйте зручний макет", - "cannotLoadPdf": "Не вдається завантажити PDF", - "pdfLoadError": "Під час завантаження цього PDF-файлу сталася помилка.", - "loadingPdf": "Завантаження PDF-файлу...", - "loadingPage": "Завантаження сторінки..." - }, "transfer": { "copyToHost": "Копіювати на хост…", "moveToHost": "Перемістити на хост…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Повторна спроба не вдалася", "transferFailedRetryHint": "На місці призначення збережено частку даних. Повторна спроба відновиться після відновлення з’єднання." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Передача файлів між двома SSH-хостами.", - "sourceServer": "Вихідний сервер", - "destinationServer": "Цільовий сервер", - "selectHost": "Виберіть хоста", - "noHostSelected": "Хост не вибрано", - "move": "Перемістити", - "copy": "Копіювати", - "upload": "Завантажити", - "itemsSelected": "Вибрано елемент {{count}}", - "itemsSelected_other": "Вибрано {{count}} елементів", - "ready": "Готовий", - "loadingHosts": "Завантаження хостів...", - "name": "Ім'я", - "modified": "Змінено", - "size": "Розмір", - "noFiles": "Немає файлів", - "open": "ВІДЧИНЕНО", - "up": "Вгору", - "refresh": "Оновити", - "copyToTarget": "Копіювати до цільового каталогу", - "rename": "Перейменувати", - "delete": "Видалити", - "editPermissions": "Редагувати дозволи", - "createNewFolder": "Створити нову папку", - "deleteSelectedItems": "Видалити вибрані елементи", - "deleteConfirm": "Видалити {{count}} вибраний елемент?", - "deleteConfirm_other": "Видалити {{count}} вибраних елементів?", - "cancel": "Скасувати", - "save": "Зберегти", - "folderCreated": "Папку створено", - "itemRenamed": "Елемент перейменовано", - "actionFailed": "Дія не вдалася", - "itemsDeleted": "{{count}} елемент видалено", - "itemsDeleted_other": "{{count}} елементів видалено", - "deleteFailed": "Видалити не вдалося", - "permissionsUpdated": "Дозволи оновлено", - "noRemoteSessionConnected": "Немає підключеного віддаленого сеансу", - "failedToListFiles": "Не вдалося переглянути файли", - "failedToRefresh": "Не вдалося оновити", - "destinationInsideSource": "Пункт призначення не може бути всередині вибраного вихідного шляху.", - "sameHostNoOp": "Джерело та призначення – це одна й та сама папка." - }, "tunnels": { "noSshTunnels": "Без SSH-тунелів", "createFirstTunnelMessage": "Ви ще не створили жодного SSH-тунелю. Налаштуйте тунельні з’єднання в Host Manager, щоб розпочати.", diff --git a/src/ui/locales/translated/vi_VN.json b/src/ui/locales/translated/vi_VN.json index fc3a09697..986f90ce3 100644 --- a/src/ui/locales/translated/vi_VN.json +++ b/src/ui/locales/translated/vi_VN.json @@ -715,7 +715,6 @@ "hostManager": "Quản lý máy chủ", "credentials": "Thông tin đăng nhập", "portForwarding": "Chuyển tiếp", - "sftp": "SFTP", "connections": "Kết nối", "serial": "Nối tiếp", "keepSideMenuOpen": "Giữ menu luôn mở", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "Đường hầm", - "tabFiles": "Tệp", "tabStats": "Số liệu máy chủ", "tabTelnet": "Telnet", "tabSharing": "Chia sẻ", "tabAuthentication": "Xác thực", "terminal": "Terminal", "tunnel": "Đường hầm", - "fileManager": "Trình quản lý tệp", "serverStats": "Số liệu máy chủ", "status": { "available": "Có sẵn", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} đã bỏ qua (không tìm thấy địa chỉ IP)", "proxmoxStatsUrlCopied": "URL Thống kê Proxmox đã được sao chép vào clipboard", "copyProxmoxStatsUrlAction": "Sao chép URL Thống kê Proxmox", - "enableFileManagerMonitor": "Bật Trình quản lý tập tin", - "enableFileManagerMonitorDesc": "Duyệt và quản lý tập tin trên máy này qua SFTP", - "scpLegacyLabel": "Chế độ SCP cũ", - "scpLegacyDesc": "Sử dụng truyền tập tin kiểu cũ cho các máy chủ không hỗ trợ hệ thống con SFTP (ví dụ: máy chủ SSH nhúng hoặc tối thiểu).", - "defaultPathLabel": "Đường dẫn mặc định", - "fileManagerPathHint": "Thư mục sẽ mở khi khởi chạy Trình quản lý tập tin cho máy này.", "statusChecksLabel": "Kiểm tra trạng thái", "enableStatusChecks": "Bật Kiểm tra trạng thái", "enableStatusChecksDesc": "Định kỳ ping máy này để xác minh tính khả dụng", @@ -1278,7 +1269,6 @@ "deletedHost": "Đã xóa {{name}}", "copiedToClipboard": "Đã sao chép vào bộ nhớ tạm", "terminalUrlCopied": "Đã sao chép URL Terminal", - "fileManagerUrlCopied": "Đã sao chép URL Trình quản lý tệp", "tunnelUrlCopied": "Đã sao chép URL Đường hầm", "dockerUrlCopied": "Đã sao chép URL Docker", "hostMetricsUrlCopied": "Đã sao chép URL Chỉ số máy chủ", @@ -1296,7 +1286,6 @@ "copyAddress": "Sao chép địa chỉ", "copyLink": "Sao chép liên kết", "copyTerminalUrlAction": "Sao chép URL Terminal", - "copyFileManagerUrlAction": "Sao chép URL File Manager", "copyTunnelUrlAction": "Sao chép URL Tunnel", "copyDockerUrlAction": "Sao chép URL Docker", "copyHostMetricsUrlAction": "Sao chép URL Host Metrics", @@ -1857,336 +1846,6 @@ "removeFromSplit": "Xóa khỏi phần chia" } }, - "fileManager": { - "noHostSelected": "Chưa chọn máy chủ", - "initializingEditor": "Đang khởi tạo trình soạn thảo...", - "file": "Tệp", - "folder": "Thư mục", - "uploadFile": "Tải tệp lên", - "downloadFile": "Tải xuống", - "extractArchive": "Giải nén lưu trữ", - "extractingArchive": "Đang giải nén {{name}}...", - "archiveExtractedSuccessfully": "{{name}} đã được giải nén thành công", - "extractFailed": "Giải nén thất bại", - "compressFile": "Nén tập tin", - "compressFiles": "Nén tập tin", - "compressFilesDesc": "Nén {{count}} mục vào một tệp nén", - "archiveName": "Tên tệp lưu trữ", - "enterArchiveName": "Nhập tên tệp lưu trữ...", - "compressionFormat": "Định dạng nén", - "selectedFiles": "Tập tin đã chọn", - "andMoreFiles": "và {{count}} tập tin khác...", - "compress": "Nén", - "compressingFiles": "Đang nén {{count}} mục vào {{name}}...", - "filesCompressedSuccessfully": "{{name}} đã được tạo thành công", - "compressFailed": "Nén thất bại", - "edit": "Chỉnh sửa", - "preview": "Xem trước", - "previous": "Trước", - "next": "Tiếp", - "pageXOfY": "Trang {{current}} / {{total}}", - "zoomOut": "Thu nhỏ", - "zoomIn": "Phóng to", - "newFile": "Tập tin mới", - "newFolder": "Thư mục mới", - "rename": "Đổi tên", - "uploading": "Đang tải lên...", - "uploadingFile": "Đang tải lên {{name}}...", - "fileName": "Tên tập tin", - "folderName": "Tên thư mục", - "fileUploadedSuccessfully": "Tập tin \"{{name}}\" đã được tải lên thành công", - "failedToUploadFile": "Tải tập tin lên thất bại", - "uploadingFolderFiles": "Đang tải lên {{count}} tập tin...", - "uploadedFolderFiles": "Đã tải lên {{count}} tập tin thành công", - "uploadedFolderPartial": "Đã tải lên {{uploaded}} tập tin, {{failed}} thất bại", - "fileDownloadedSuccessfully": "Tập tin \"{{name}}\" đã được tải xuống thành công", - "failedToDownloadFile": "Tải tập tin xuống thất bại", - "fileCreatedSuccessfully": "Tập tin \"{{name}}\" đã được tạo thành công", - "folderCreatedSuccessfully": "Thư mục \"{{name}}\" đã được tạo thành công", - "failedToCreateItem": "Không thể tạo mục", - "operationFailed": "Thao tác {{operation}} thất bại đối với {{name}}: {{error}}", - "failedToResolveSymlink": "Không thể theo dõi symlink", - "itemsDeletedSuccessfully": "Đã xóa thành công {{count}} mục", - "failedToDeleteItems": "Không thể xóa các mục", - "sudoPasswordRequired": "Yêu cầu mật khẩu quản trị", - "enterSudoPassword": "Nhập mật khẩu sudo để tiếp tục thao tác này", - "sudoPassword": "Mật khẩu sudo", - "sudoOperationFailed": "Thao tác sudo thất bại", - "sudoAuthFailed": "Xác thực sudo thất bại", - "dragFilesToUpload": "Kéo tệp vào đây để tải lên", - "dropToUploadHere": "Kéo thả để tải lên tại đây", - "dropToDownloadHere": "Kéo thả để tải xuống tại đây", - "localFiles": "Địa phương", - "showLocalFiles": "Hiển thị các tệp cục bộ (chế độ chia đôi màn hình)", - "hideLocalFiles": "Ẩn các tệp cục bộ", - "localHome": "Thư mục trang chủ", - "localPathPlaceholder": "Đường dẫn thư mục cục bộ", - "localShowHidden": "Hiển thị các tệp ẩn", - "localHideHidden": "Ẩn các tệp tin ẩn", - "localRevealInFileManager": "Hiển thị trong trình quản lý tệp hệ thống", - "localCannotOpenFolder": "Không thể mở thư mục", - "localItemCount": "{{count}} mục", - "localSelectedCount": "{{count}} đã chọn", - "back": "Mặt sau", - "forward": "Phía trước", - "up": "Hướng lên", - "kind": "Loại", - "localUploadingProgress": "Đang tải lên {{current}} trong số {{total}}", - "localDownloadingProgress": "Đang tải xuống {{current}} của {{total}}", - "localTransferInFlight": "{{count}} ngay lập tức", - "localPreparingDownload": "Đang chuẩn bị tải xuống...", - "localTransferCancelled": "Giao dịch bị hủy", - "localNothingToTransfer": "Không có gì để chuyển", - "localUploadComplete": "Đã tải lên {{count}} tập tin", - "localUploadFailed": "Tải lên thất bại", - "localUploadPartial": "Đã tải lên {{done}} tập tin, {{failed}} thất bại", - "localDownloadComplete": "Đã tải xuống {{count}} tập tin", - "localDownloadFailed": "Tải xuống thất bại", - "localDownloadPartial": "Đã tải xuống {{done}} tập tin, {{failed}} thất bại", - "localReplaceExistingPrompt": "{{count}} mục đã tồn tại trong thư mục này. Bạn có muốn thay thế chúng không?", - "localReplace": "Thay thế", - "localSkipExisting": "Nhảy", - "localSkippedExisting": "Đã bỏ qua {{count}} mục hiện có", - "localUnsafeNamesSkipped": "Đã bỏ qua {{count}} mục mà tên của chúng không thể sử dụng trên máy tính này.", - "localOpen": "Mở", - "localOpenFolder": "Mở thư mục", - "localUploadToRemote": "Tải lên máy chủ", - "localUploadToRemoteMany": "Tải lên {{count}} mục lên máy chủ", - "localMoveToTrash": "Chuyển vào thùng rác", - "localMoveToTrashMany": "Di chuyển {{count}} mục vào Thùng rác", - "localTrashConfirmSingle": "Di chuyển \"{{name}}\" vào Thùng rác?", - "localTrashConfirmFolder": "Di chuyển thư mục \"{{name}}\" và tất cả nội dung bên trong vào Thùng rác?", - "localTrashConfirmMany": "Di chuyển {{count}} mục vào Thùng rác?", - "localTrashed": "Đã chuyển {{count}} mục vào Thùng rác", - "localTrashFailed": "Không thể chuyển vào Thùng rác", - "localRenameFailed": "Đổi tên không thành công", - "localCreateFailed": "Không thể tạo mục", - "goToParentFolder": "Đi đến thư mục cha", - "resizeSidebar": "Kéo để thay đổi kích thước thanh bên (nhấp đúp để đặt lại về kích thước ban đầu)", - "resizeLocalPane": "Kéo chuột để thay đổi kích thước khung cục bộ (nhấp đúp chuột để đặt lại về kích thước ban đầu)", - "columns": "Cột", - "columnsHint": "Nhấp chuột phải để chọn cột", - "emptyFolder": "Thư mục này trống", - "searchFiles": "Tìm kiếm tệp...", - "upload": "Tải lên", - "selectHostToStart": "Chọn máy chủ để bắt đầu quản lý tệp", - "sshRequiredForFileManager": "Trình quản lý tệp yêu cầu SSH. Máy chủ này không bật SSH.", - "failedToConnect": "Kết nối SSH thất bại", - "failedToLoadDirectory": "Không thể tải thư mục", - "noSSHConnection": "Không có kết nối SSH", - "copy": "Sao chép", - "cut": "Cắt", - "paste": "Dán", - "copyPath": "Sao chép đường dẫn", - "copyPaths": "Sao chép các đường dẫn", - "delete": "Xóa", - "properties": "Thuộc tính", - "refresh": "Làm mới", - "downloadFiles": "Tải {{count}} tệp xuống trình duyệt", - "copyFiles": "Sao chép {{count}} mục", - "cutFiles": "Cắt {{count}} mục", - "deleteFiles": "Xóa {{count}} mục", - "filesCopiedToClipboard": "Đã sao chép {{count}} mục vào clipboard", - "filesCutToClipboard": "Đã cắt {{count}} mục vào clipboard", - "pathCopiedToClipboard": "Đã sao chép đường dẫn vào clipboard", - "pathsCopiedToClipboard": "Đã sao chép {{count}} đường dẫn vào clipboard", - "failedToCopyPath": "Không thể sao chép đường dẫn vào clipboard", - "copyFolderLink": "Sao chép liên kết đến thư mục", - "copyCurrentFolderLink": "Sao chép liên kết đến thư mục hiện tại", - "folderLinkCopied": "Đã sao chép liên kết thư mục vào clipboard", - "failedToCopyFolderLink": "Không thể sao chép liên kết thư mục", - "movedItems": "Đã di chuyển {{count}} mục", - "failedToDeleteItem": "Không thể xóa mục", - "itemRenamedSuccessfully": "Đã đổi tên {{type}} thành công", - "failedToRenameItem": "Không thể đổi tên mục", - "download": "Tải xuống", - "openExternalEditor": "Mở bên ngoài", - "chooseExternalEditor": "Chọn trình soạn thảo bên ngoài", - "externalEditorSelected": "Đã chọn trình soạn thảo bên ngoài", - "externalEditorDesktopOnly": "Trình soạn thảo bên ngoài chỉ khả dụng trong ứng dụng desktop", - "externalEditorOpened": "Đã mở bằng trình soạn thảo bên ngoài. Lưu ở đó sẽ tải các thay đổi lên máy chủ.", - "failedToOpenExternalEditor": "Không thể mở trình soạn thảo bên ngoài", - "failedToSelectExternalEditor": "Không thể chọn trình soạn thảo bên ngoài", - "permissions": "Quyền", - "size": "Kích thước", - "modified": "Đã sửa", - "path": "Đường dẫn", - "confirmDelete": "Bạn có chắc chắn muốn xóa {{name}}?", - "permissionDenied": "Quyền bị từ chối", - "serverError": "Lỗi máy chủ", - "fileSavedSuccessfully": "Đã lưu tệp thành công", - "failedToSaveFile": "Không thể lưu tệp", - "confirmDeleteSingleItem": "Bạn có chắc chắn muốn xóa vĩnh viễn \"{{name}}\"?", - "confirmDeleteMultipleItems": "Bạn có chắc chắn muốn xóa vĩnh viễn {{count}} mục?", - "confirmDeleteMultipleItemsWithFolders": "Bạn có chắc chắn muốn xóa vĩnh viễn {{count}} mục? Điều này bao gồm các thư mục và nội dung của chúng.", - "confirmDeleteFolder": "Bạn có chắc chắn muốn xóa vĩnh viễn thư mục \"{{name}}\" và tất cả nội dung của nó?", - "permanentDeleteWarning": "Hành động này không thể hoàn tác. (Các) mục sẽ bị xóa vĩnh viễn khỏi máy chủ.", - "moveToTrashWarning": "Các mục này sẽ được chuyển vào Thùng rác Termix và có thể được khôi phục trước khi thời hạn lưu giữ hết hạn.", - "itemsMovedToTrash": "{{count}} mục đã được chuyển vào Thùng rác", - "trash": "Rác", - "trashDescription": "Các mục đã xóa sẽ được lưu giữ trong {{days}} ngày trên máy chủ này.", - "trashLoadFailed": "Không thể tải Thùng rác", - "trashEmpty": "Thùng rác trống", - "trashRestore": "Khôi phục", - "trashRestored": "Đã khôi phục {{name}}", - "trashRestoreFailed": "Không thể khôi phục mục. Đường dẫn gốc có thể đã tồn tại.", - "trashDeletePermanently": "Xóa vĩnh viễn", - "trashDeleteConfirm": "Xóa vĩnh viễn {{name}}? Thao tác này không thể hoàn tác.", - "trashPermanentlyDeleted": "Đã xóa vĩnh viễn {{name}}", - "trashDeleteFailed": "Không thể xóa mục vĩnh viễn", - "trashEmptyAction": "Đổ rác", - "trashEmptyConfirm": "Xóa vĩnh viễn mọi mục trong Thùng rác? Thao tác này không thể hoàn tác.", - "trashEmptied": "Thùng rác đã được đổ", - "trashRetention": "Tự động dọn dẹp sau (ngày)", - "trashRetentionSaved": "Cập nhật thời gian lưu trữ rác", - "trashRetentionFailed": "Không thể cập nhật thời gian lưu trữ thùng rác.", - "trashUnavailableConfirm": "Không thể chuyển mục này vào Thùng rác, thường là do nó nằm trên hệ thống tệp khác hoặc yêu cầu quyền quản trị. Thay vào đó, hãy xóa vĩnh viễn mục này?", - "recent": "Gần đây", - "pinned": "Đã ghim", - "folderShortcuts": "Lối tắt thư mục", - "failedToReconnectSSH": "Không thể kết nối lại phiên SSH", - "openTerminalHere": "Mở terminal tại đây", - "run": "Chạy", - "openTerminalInFolder": "Mở terminal trong thư mục này", - "openTerminalInFileLocation": "Mở terminal tại vị trí tệp", - "runningFile": "Đang chạy - {{file}}", - "onlyRunExecutableFiles": "Chỉ chạy được tệp thực thi", - "directories": "Thư mục", - "removedFromRecentFiles": "Đã xóa \"{{name}}\" khỏi tệp gần đây", - "removeFailed": "Xóa thất bại", - "unpinnedSuccessfully": "Đã bỏ ghim \"{{name}}\" thành công", - "unpinFailed": "Bỏ ghim thất bại", - "removedShortcut": "Đã xóa lối tắt \"{{name}}\"", - "removeShortcutFailed": "Xóa lối tắt thất bại", - "clearedAllRecentFiles": "Đã xóa toàn bộ tệp gần đây", - "clearFailed": "Xóa thất bại", - "removeFromRecentFiles": "Xóa khỏi tệp gần đây", - "clearAllRecentFiles": "Xóa toàn bộ tệp gần đây", - "unpinFile": "Bỏ ghim tệp", - "removeShortcut": "Xóa lối tắt", - "pinFile": "Ghim tệp", - "addToShortcuts": "Thêm vào lối tắt", - "pasteFailed": "Dán thất bại", - "noUndoableActions": "Không có hành động hoàn tác được", - "undoCopySuccess": "Đã hoàn tác sao chép: Đã xóa {{count}} tệp đã sao chép", - "undoCopyFailedDelete": "Hoàn tác thất bại: Không thể xóa tệp đã sao chép", - "undoCopyFailedNoInfo": "Hoàn tác thất bại: Không tìm thấy thông tin tệp đã sao chép", - "undoMoveSuccess": "Đã hoàn tác di chuyển: Đã di chuyển {{count}} tệp về vị trí ban đầu", - "undoMoveFailedMove": "Hoàn tác thất bại: Không thể di chuyển tệp về vị trí cũ", - "undoMoveFailedNoInfo": "Hoàn tác thất bại: Không tìm thấy thông tin tệp đã di chuyển", - "undoDeleteNotSupported": "Không thể hoàn tác xóa: Tệp đã bị xóa vĩnh viễn khỏi máy chủ", - "undoTypeNotSupported": "Loại thao tác hoàn tác không được hỗ trợ", - "undoOperationFailed": "Thao tác hoàn tác thất bại", - "unknownError": "Lỗi không xác định", - "confirm": "Xác nhận", - "find": "Tìm kiếm...", - "replace": "Thay thế", - "downloadInstead": "Tải xuống thay thế", - "keyboardShortcuts": "Phím tắt", - "searchAndReplace": "Tìm & Thay thế", - "editing": "Chỉnh sửa", - "search": "Tìm kiếm", - "findNext": "Tìm tiếp", - "findPrevious": "Tìm trước", - "save": "Lưu", - "selectAll": "Chọn tất cả", - "undo": "Hoàn tác", - "redo": "Làm lại", - "moveLineUp": "Di chuyển dòng lên", - "moveLineDown": "Di chuyển dòng xuống", - "toggleComment": "Chuyển đổi chú thích", - "autoComplete": "Tự động hoàn thành", - "imageLoadError": "Lỗi tải ảnh", - "startTyping": "Bắt đầu nhập...", - "unknownSize": "Kích thước không xác định", - "fileIsEmpty": "Tệp trống", - "largeFileWarning": "Cảnh báo tệp lớn", - "largeFileWarningDesc": "Tệp này có kích thước {{size}}, có thể gây ra vấn đề hiệu suất khi mở dưới dạng văn bản.", - "fileNotFoundAndRemoved": "Tệp \"{{name}}\" không tìm thấy và đã bị xóa khỏi tệp gần đây/đã ghim", - "failedToLoadFile": "Lỗi tải tệp: {{error}}", - "serverErrorOccurred": "Đã xảy ra lỗi máy chủ. Vui lòng thử lại sau.", - "autoSaveFailed": "Tự động lưu không thành công", - "fileAutoSaved": "Tệp đã được tự động lưu", - "moveFileFailed": "Không thể di chuyển {{name}}", - "moveOperationFailed": "Thao tác di chuyển không thành công", - "canOnlyCompareFiles": "Chỉ có thể so sánh hai tệp", - "comparingFiles": "Đang so sánh tệp: {{file1}} và {{file2}}", - "dragFailed": "Thao tác kéo không thành công", - "filePinnedSuccessfully": "Tệp \"{{name}}\" đã được ghim thành công", - "pinFileFailed": "Không thể ghim tệp", - "fileUnpinnedSuccessfully": "Tệp \"{{name}}\" đã được bỏ ghim thành công", - "unpinFileFailed": "Không thể bỏ ghim tệp", - "shortcutAddedSuccessfully": "Đã thêm lối tắt thư mục \"{{name}}\" thành công", - "addShortcutFailed": "Không thể thêm lối tắt", - "operationCompletedSuccessfully": "Đã {{operation}} thành công {{count}} mục", - "operationCompleted": "Đã {{operation}} {{count}} mục", - "downloadFileSuccess": "Đã tải xuống tệp {{name}} thành công", - "downloadFileFailed": "Tải xuống thất bại", - "downloadingFile": "Đang tải xuống {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "Di chuyển tới {{name}}", - "diffCompareWith": "So sánh khác biệt với {{name}}", - "dragOutsideToDownload": "Kéo ra ngoài cửa sổ để tải xuống ({{count}} tệp)", - "newFolderDefault": "Thư mục mới", - "newFileDefault": "Tệp mới.txt", - "successfullyMovedItems": "Đã di chuyển thành công {{count}} mục tới {{target}}", - "move": "Di chuyển", - "searchInFile": "Tìm trong tệp (Ctrl+F)", - "showKeyboardShortcuts": "Hiển thị phím tắt", - "decreaseFontSize": "Giảm cỡ chữ", - "increaseFontSize": "Tăng cỡ chữ", - "startWritingMarkdown": "Bắt đầu viết nội dung markdown...", - "loadingFileComparison": "Đang tải so sánh tệp...", - "reload": "Tải lại", - "compare": "So sánh", - "sideBySide": "Song song", - "inline": "Trong dòng", - "fileComparison": "So sánh tệp: {{file1}} vs {{file2}}", - "fileTooLarge": "Tệp quá lớn: {{error}}", - "sshConnectionFailed": "Kết nối SSH thất bại. Vui lòng kiểm tra kết nối tới {{name}} ({{ip}}:{{port}})", - "loadFileFailed": "Không thể tải tệp: {{error}}", - "connecting": "Đang kết nối...", - "connectedSuccessfully": "Đã kết nối thành công", - "totpVerificationFailed": "Xác minh TOTP thất bại", - "warpgateVerificationFailed": "Xác thực Warpgate thất bại", - "authenticationFailed": "Xác thực thất bại", - "incorrectPassphrase": "Cụm mật khẩu không đúng. Vui lòng thử lại.", - "verificationCodePrompt": "Mã xác minh:", - "changePermissions": "Thay đổi quyền", - "currentPermissions": "Quyền hiện tại", - "owner": "Chủ sở hữu", - "group": "Nhóm", - "others": "Khác", - "read": "Đọc", - "write": "Ghi", - "execute": "Thực thi", - "permissionsChangedSuccessfully": "Thay đổi quyền thành công", - "failedToChangePermissions": "Không thể thay đổi quyền", - "name": "Tên", - "sortByName": "Tên", - "sortByDate": "Ngày sửa đổi", - "sortBySize": "Kích thước", - "ascending": "Tăng dần", - "descending": "Giảm dần", - "root": "Thư mục gốc", - "new": "Mới", - "sortBy": "Sắp xếp theo", - "items": "Mục", - "selected": "Đã chọn", - "editor": "Soạn thảo", - "octal": "Bát phân", - "storage": "Lưu trữ", - "disk": "Đĩa", - "used": "Đã dùng", - "of": "của", - "toggleSidebar": "Ẩn/Hiện thanh bên", - "compactLayout": "Sử dụng bố cục nhỏ gọn", - "comfortableLayout": "Sử dụng bố cục thoải mái", - "cannotLoadPdf": "Không thể tải PDF", - "pdfLoadError": "Đã xảy ra lỗi khi tải tệp PDF này.", - "loadingPdf": "Đang tải PDF...", - "loadingPage": "Đang tải trang..." - }, "transfer": { "copyToHost": "Sao chép đến máy chủ…", "moveToHost": "Di chuyển đến máy chủ…", @@ -2292,50 +1951,6 @@ "retryTransferError": "Thử lại thất bại", "transferFailedRetryHint": "Dữ liệu chưa hoàn chỉnh được giữ ở đích. Thử lại sẽ tiếp tục khi kết nối được khôi phục." }, - "sftpTransfer": { - "title": "SFTP", - "description": "Truyền tệp giữa hai máy chủ SSH.", - "sourceServer": "Máy chủ nguồn", - "destinationServer": "Máy chủ đích", - "selectHost": "Chọn máy chủ", - "noHostSelected": "Chưa chọn máy chủ nào", - "move": "Di chuyển", - "copy": "Sao chép", - "upload": "Tải lên", - "itemsSelected": "{{count}} mục đã chọn", - "itemsSelected_other": "{{count}} mục đã chọn", - "ready": "Sẵn sàng", - "loadingHosts": "Đang tải máy chủ...", - "name": "Tên", - "modified": "Đã sửa đổi", - "size": "Kích cỡ", - "noFiles": "Không có tệp nào", - "open": "Mở", - "up": "Hướng lên", - "refresh": "Làm cho khỏe lại", - "copyToTarget": "Sao chép vào thư mục đích", - "rename": "Đổi tên", - "delete": "Xóa bỏ", - "editPermissions": "Chỉnh sửa quyền", - "createNewFolder": "Tạo thư mục mới", - "deleteSelectedItems": "Xóa các mục đã chọn", - "deleteConfirm": "Xóa mục đã chọn {{count}}?", - "deleteConfirm_other": "Xóa {{count}} mục đã chọn?", - "cancel": "Hủy bỏ", - "save": "Cứu", - "folderCreated": "Thư mục đã được tạo", - "itemRenamed": "Mục đã được đổi tên", - "actionFailed": "Hành động thất bại", - "itemsDeleted": "{{count}} mục đã bị xóa", - "itemsDeleted_other": "{{count}} mục đã bị xóa", - "deleteFailed": "Xóa không thành công", - "permissionsUpdated": "Quyền hạn đã được cập nhật", - "noRemoteSessionConnected": "Không có phiên kết nối từ xa nào được thiết lập.", - "failedToListFiles": "Không thể liệt kê các tệp.", - "failedToRefresh": "Không thể làm mới", - "destinationInsideSource": "Đường dẫn đích không được nằm bên trong đường dẫn nguồn đã chọn.", - "sameHostNoOp": "Thư mục nguồn và thư mục đích đều nằm trong cùng một thư mục." - }, "tunnels": { "noSshTunnels": "Không có đường hầm SSH", "createFirstTunnelMessage": "Bạn chưa tạo đường hầm SSH nào. Hãy cấu hình kết nối đường hầm trong Trình quản lý máy chủ để bắt đầu.", diff --git a/src/ui/locales/translated/zh_CN.json b/src/ui/locales/translated/zh_CN.json index 85f1992ca..f6437344b 100644 --- a/src/ui/locales/translated/zh_CN.json +++ b/src/ui/locales/translated/zh_CN.json @@ -715,7 +715,6 @@ "hostManager": "主机管理器", "credentials": "凭据", "portForwarding": "转发", - "sftp": "SFTP", "connections": "连接", "serial": "串口", "keepSideMenuOpen": "保持菜单打开", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "隧道", - "tabFiles": "文件", "tabStats": "主机指标", "tabTelnet": "Telnet", "tabSharing": "共享", "tabAuthentication": "认证", "terminal": "终端", "tunnel": "隧道", - "fileManager": "文件管理器", "serverStats": "主机指标", "status": { "available": "可用的", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} 已跳过(未找到 IP )", "proxmoxStatsUrlCopied": "Proxmox 统计信息 URL 已复制到剪贴板", "copyProxmoxStatsUrlAction": "复制 Proxmox 统计信息 URL", - "enableFileManagerMonitor": "启用文件管理器", - "enableFileManagerMonitorDesc": "通过 SFTP 浏览和管理此主机上的文件", - "scpLegacyLabel": "SCP 传统模式", - "scpLegacyDesc": "对不支持 SFTP 子系统的服务器使用传统文件传输(例如嵌入式或最小化 SSH 服务器)。", - "defaultPathLabel": "默认路径", - "fileManagerPathHint": "为此主机启动文件管理器时要打开的目录。", "statusChecksLabel": "状态检查", "enableStatusChecks": "启用状态检查", "enableStatusChecksDesc": "定期 ping 此主机以验证可用性", @@ -1278,7 +1269,6 @@ "deletedHost": "已删除 {{name}}", "copiedToClipboard": "已复制到剪贴板", "terminalUrlCopied": "终端 URL 已复制", - "fileManagerUrlCopied": "文件管理器 URL 已复制", "tunnelUrlCopied": "隧道 URL 已复制", "dockerUrlCopied": "Docker URL 已复制", "hostMetricsUrlCopied": "主机监控 URL 已复制", @@ -1296,7 +1286,6 @@ "copyAddress": "复制地址", "copyLink": "复制链接", "copyTerminalUrlAction": "复制终端 URL", - "copyFileManagerUrlAction": "复制文件管理器 URL", "copyTunnelUrlAction": "复制隧道 URL", "copyDockerUrlAction": "复制 Docker URL", "copyHostMetricsUrlAction": "复制主机监控 URL", @@ -1857,336 +1846,6 @@ "removeFromSplit": "从拆分中移除" } }, - "fileManager": { - "noHostSelected": "未选择主机", - "initializingEditor": "正在初始化编辑器...", - "file": "文件", - "folder": "文件夹", - "uploadFile": "上传文件", - "downloadFile": "下载", - "extractArchive": "解压归档文件", - "extractingArchive": "正在解压 {{name}}...", - "archiveExtractedSuccessfully": "{{name}} 解压成功", - "extractFailed": "解压失败", - "compressFile": "压缩文件", - "compressFiles": "压缩文件", - "compressFilesDesc": "将 {{count}} 个项目压缩成一个归档文件", - "archiveName": "归档名称", - "enterArchiveName": "输入归档名称...", - "compressionFormat": "压缩格式", - "selectedFiles": "已选文件", - "andMoreFiles": "还有 {{count}} 个...", - "compress": "压缩", - "compressingFiles": "正在将 {{count}} 个项目压缩成 {{name}}...", - "filesCompressedSuccessfully": "{{name}} 创建成功", - "compressFailed": "压缩失败", - "edit": "编辑", - "preview": "预览", - "previous": "上一个", - "next": "下一个", - "pageXOfY": "第 {{current}} 页,共 {{total}} 页", - "zoomOut": "缩小", - "zoomIn": "放大", - "newFile": "新建文件", - "newFolder": "新建文件夹", - "rename": "重命名", - "uploading": "上传中...", - "uploadingFile": "正在上传 {{name}}...", - "fileName": "文件名", - "folderName": "文件夹名称", - "fileUploadedSuccessfully": "文件 \"{{name}}\" 上传成功", - "failedToUploadFile": "上传文件失败", - "uploadingFolderFiles": "正在上传 {{count}} 文件...", - "uploadedFolderFiles": "已成功上传 {{count}} 个文件", - "uploadedFolderPartial": "上传了 {{uploaded}} 个文件, {{failed}} 个文件失败", - "fileDownloadedSuccessfully": "文件 \"{{name}}\" 下载成功", - "failedToDownloadFile": "下载文件失败", - "fileCreatedSuccessfully": "文件 \"{{name}}\" 创建成功", - "folderCreatedSuccessfully": "文件夹 \"{{name}}\" 创建成功", - "failedToCreateItem": "创建项目失败", - "operationFailed": "对 {{name}} 的 {{operation}} 操作失败:{{error}}", - "failedToResolveSymlink": "解析符号链接失败", - "itemsDeletedSuccessfully": "{{count}} 个项目删除成功", - "failedToDeleteItems": "删除项目失败", - "sudoPasswordRequired": "需要管理员密码", - "enterSudoPassword": "输入 sudo 密码以继续此操作", - "sudoPassword": "Sudo 密码", - "sudoOperationFailed": "Sudo 操作失败", - "sudoAuthFailed": "Sudo 认证失败", - "dragFilesToUpload": "将文件拖放到此处以上传", - "dropToUploadHere": "在此处上传", - "dropToDownloadHere": "点击此处下载", - "localFiles": "当地的", - "showLocalFiles": "显示本地文件(分屏显示)", - "hideLocalFiles": "隐藏本地文件", - "localHome": "主页", - "localPathPlaceholder": "本地文件夹路径", - "localShowHidden": "显示隐藏文件", - "localHideHidden": "隐藏隐藏文件", - "localRevealInFileManager": "在系统文件管理器中显示", - "localCannotOpenFolder": "无法打开文件夹", - "localItemCount": "{{count}} 项目", - "localSelectedCount": "{{count}} 已选择", - "back": "后退", - "forward": "向前", - "up": "向上", - "kind": "种类", - "localUploadingProgress": "正在上传 {{current}} 中的 {{total}}", - "localDownloadingProgress": "正在下载 {{current}} 中的 {{total}}", - "localTransferInFlight": "{{count}} 立刻", - "localPreparingDownload": "正在准备下载……", - "localTransferCancelled": "转账已取消", - "localNothingToTransfer": "无需转移", - "localUploadComplete": "已上传 {{count}} 个文件", - "localUploadFailed": "上传失败", - "localUploadPartial": "上传了 {{done}} 个文件, {{failed}} 个文件失败", - "localDownloadComplete": "已下载 {{count}} 个文件", - "localDownloadFailed": "下载失败", - "localDownloadPartial": "下载了 {{done}} 个文件, {{failed}} 个文件失败", - "localReplaceExistingPrompt": "此文件夹中已存在 {{count}} 个项目。是否替换它们?", - "localReplace": "代替", - "localSkipExisting": "跳过", - "localSkippedExisting": "已跳过 {{count}} 个现有项目", - "localUnsafeNamesSkipped": "已跳过 {{count}} 个名称无法在此计算机上使用的项目。", - "localOpen": "打开", - "localOpenFolder": "打开文件夹", - "localUploadToRemote": "上传到服务器", - "localUploadToRemoteMany": "上传 {{count}} 项到服务器", - "localMoveToTrash": "移至垃圾桶", - "localMoveToTrashMany": "将 {{count}} 个物品移至垃圾桶", - "localTrashConfirmSingle": "将“{{name}}”移至回收站?", - "localTrashConfirmFolder": "将文件夹“{{name}}”及其所有内容移至回收站?", - "localTrashConfirmMany": "将 {{count}} 个项目移至回收站?", - "localTrashed": "已将 {{count}} 个项目移至回收站", - "localTrashFailed": "无法移至垃圾桶", - "localRenameFailed": "重命名失败", - "localCreateFailed": "无法创建项目", - "goToParentFolder": "转到上文件夹", - "resizeSidebar": "拖动即可调整侧边栏大小(双击即可重置)", - "resizeLocalPane": "拖动即可调整本地窗格大小(双击可重置)", - "columns": "列", - "columnsHint": "右键单击选择列", - "emptyFolder": "此文件夹为空", - "searchFiles": "搜索文件...", - "upload": "上传", - "selectHostToStart": "选择主机以开始文件管理", - "sshRequiredForFileManager": "文件管理器需要 SSH。此主机未启用 SSH。", - "failedToConnect": "连接 SSH 失败", - "failedToLoadDirectory": "加载目录失败", - "noSSHConnection": "没有可用的 SSH 连接", - "copy": "复制", - "cut": "剪切", - "paste": "粘贴", - "copyPath": "复制路径", - "copyPaths": "复制路径", - "delete": "删除", - "properties": "属性", - "refresh": "刷新", - "downloadFiles": "将 {{count}} 个文件下载到浏览器", - "copyFiles": "复制 {{count}} 个项目", - "cutFiles": "剪切 {{count}} 个项目", - "deleteFiles": "删除 {{count}} 个项目", - "filesCopiedToClipboard": "已将 {{count}} 个项目复制到剪贴板", - "filesCutToClipboard": "已将 {{count}} 个项目剪切到剪贴板", - "pathCopiedToClipboard": "路径已复制到剪贴板", - "pathsCopiedToClipboard": "已将 {{count}} 条路径复制到剪贴板", - "failedToCopyPath": "无法将路径复制到剪贴板", - "copyFolderLink": "复制文件夹链接", - "copyCurrentFolderLink": "复制当前文件夹链接", - "folderLinkCopied": "文件夹链接已复制到剪贴板", - "failedToCopyFolderLink": "无法复制文件夹链接", - "movedItems": "已移动 {{count}} 个项目", - "failedToDeleteItem": "删除项目失败", - "itemRenamedSuccessfully": "{{type}} 重命名成功", - "failedToRenameItem": "重命名项目失败", - "download": "下载", - "openExternalEditor": "在外部打开", - "chooseExternalEditor": "选择外部编辑器", - "externalEditorSelected": "已选择外部编辑器", - "externalEditorDesktopOnly": "外部编辑器仅在桌面应用中可用", - "externalEditorOpened": "已在外部编辑器中打开。在其中保存文件会将更改上传回服务器。", - "failedToOpenExternalEditor": "无法打开外部编辑器", - "failedToSelectExternalEditor": "无法选择外部编辑器", - "permissions": "权限", - "size": "大小", - "modified": "修改时间", - "path": "路径", - "confirmDelete": "确定要删除 {{name}} 吗?", - "permissionDenied": "权限被拒绝", - "serverError": "服务器错误", - "fileSavedSuccessfully": "文件已成功保存", - "failedToSaveFile": "保存文件失败", - "confirmDeleteSingleItem": "确定要永久删除“{{name}}”吗?", - "confirmDeleteMultipleItems": "确定要永久删除{{count}}个项目吗?", - "confirmDeleteMultipleItemsWithFolders": "确定要永久删除{{count}}个项目吗?这包括文件夹及其内容。", - "confirmDeleteFolder": "确定要永久删除文件夹“{{name}}”及其所有内容吗?", - "permanentDeleteWarning": "此操作无法撤销。项目将从服务器永久删除。", - "moveToTrashWarning": "该项目将被移至 Termix 回收站,可在保留期到期前恢复。", - "itemsMovedToTrash": "{{count}} 项已移至回收站", - "trash": "回收站", - "trashDescription": "已删除的项目将在本主机上保留 {{days}} 天。", - "trashLoadFailed": "加载回收站失败", - "trashEmpty": "回收站是空的", - "trashRestore": "恢复", - "trashRestored": "已恢复 {{name}}", - "trashRestoreFailed": "恢复项目失败。原始路径可能已存在。", - "trashDeletePermanently": "永久删除", - "trashDeleteConfirm": "永久删除 {{name}}?此操作无法撤销。", - "trashPermanentlyDeleted": "永久删除 {{name}}", - "trashDeleteFailed": "无法永久删除项目", - "trashEmptyAction": "清空回收站", - "trashEmptyConfirm": "永久删除回收站中的所有项目?此操作无法撤销。", - "trashEmptied": "回收站已清空", - "trashRetention": "自动清理(N 天后)", - "trashRetentionSaved": "回收站保留设置已更新", - "trashRetentionFailed": "更新回收站保留设置失败", - "trashUnavailableConfirm": "此项目无法移至回收站,通常是因为它位于其他文件系统或需要更高的权限。是否将其永久删除?", - "recent": "最近", - "pinned": "已固定", - "folderShortcuts": "文件夹快捷方式", - "failedToReconnectSSH": "重新连接SSH会话失败", - "openTerminalHere": "在此处打开终端", - "run": "运行", - "openTerminalInFolder": "在此文件夹中打开终端", - "openTerminalInFileLocation": "在文件位置打开终端", - "runningFile": "正在运行 - {{file}}", - "onlyRunExecutableFiles": "只能运行可执行文件", - "directories": "目录", - "removedFromRecentFiles": "已从最近文件中移除“{{name}}”", - "removeFailed": "移除失败", - "unpinnedSuccessfully": "已成功取消固定“{{name}}”", - "unpinFailed": "取消固定失败", - "removedShortcut": "已移除快捷方式“{{name}}”", - "removeShortcutFailed": "移除快捷方式失败", - "clearedAllRecentFiles": "已清空所有最近文件", - "clearFailed": "清空失败", - "removeFromRecentFiles": "从最近文件中移除", - "clearAllRecentFiles": "清空所有最近文件", - "unpinFile": "取消固定文件", - "removeShortcut": "移除快捷方式", - "pinFile": "固定文件", - "addToShortcuts": "添加到快捷方式", - "pasteFailed": "粘贴失败", - "noUndoableActions": "无可撤销的操作", - "undoCopySuccess": "已撤销复制操作:已删除{{count}}个已复制的文件", - "undoCopyFailedDelete": "撤销失败:无法删除任何已复制的文件", - "undoCopyFailedNoInfo": "撤销失败:找不到已复制的文件信息", - "undoMoveSuccess": "已撤销移动操作:已将{{count}}个文件移回原始位置", - "undoMoveFailedMove": "撤销失败:无法将任何文件移回", - "undoMoveFailedNoInfo": "撤销失败:找不到移动的文件信息", - "undoDeleteNotSupported": "删除操作无法撤销:文件已从服务器永久删除", - "undoTypeNotSupported": "不支持的撤销操作类型", - "undoOperationFailed": "撤销操作失败", - "unknownError": "未知错误", - "confirm": "确认", - "find": "查找...", - "replace": "替换", - "downloadInstead": "改为下载", - "keyboardShortcuts": "键盘快捷键", - "searchAndReplace": "查找和替换", - "editing": "编辑", - "search": "搜索", - "findNext": "查找下一个", - "findPrevious": "查找上一个", - "save": "保存", - "selectAll": "全选", - "undo": "撤销", - "redo": "重做", - "moveLineUp": "上移一行", - "moveLineDown": "下移一行", - "toggleComment": "切换注释", - "autoComplete": "自动补全", - "imageLoadError": "加载图像失败", - "startTyping": "开始键入...", - "unknownSize": "未知大小", - "fileIsEmpty": "文件为空", - "largeFileWarning": "大文件警告", - "largeFileWarningDesc": "该文件大小为 {{size}},以文本方式打开可能会导致性能问题。", - "fileNotFoundAndRemoved": "文件 \"{{name}}\" 未找到,已从最近/固定文件中移除", - "failedToLoadFile": "加载文件失败:{{error}}", - "serverErrorOccurred": "服务器发生错误,请稍后重试。", - "autoSaveFailed": "自动保存失败", - "fileAutoSaved": "文件已自动保存", - "moveFileFailed": "移动 {{name}} 失败", - "moveOperationFailed": "移动操作失败", - "canOnlyCompareFiles": "只能比较两个文件", - "comparingFiles": "正在比较文件:{{file1}} 和 {{file2}}", - "dragFailed": "拖动操作失败", - "filePinnedSuccessfully": "文件 \"{{name}}\" 固定成功", - "pinFileFailed": "固定文件失败", - "fileUnpinnedSuccessfully": "文件 \"{{name}}\" 取消固定成功", - "unpinFileFailed": "取消固定文件失败", - "shortcutAddedSuccessfully": "文件夹快捷方式 \"{{name}}\" 添加成功", - "addShortcutFailed": "添加快捷方式失败", - "operationCompletedSuccessfully": "成功{{operation}}了{{count}}个项目", - "operationCompleted": "{{operation}}了{{count}}个项目", - "downloadFileSuccess": "文件 {{name}} 下载成功", - "downloadFileFailed": "下载失败", - "downloadingFile": "正在下载 {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "移动到 {{name}}", - "diffCompareWith": "与 {{name}} 对比差异", - "dragOutsideToDownload": "拖到窗口外以下载 ({{count}} 个文件)", - "newFolderDefault": "新建文件夹", - "newFileDefault": "新建文件.txt", - "successfullyMovedItems": "成功移动 {{count}} 个项目到 {{target}}", - "move": "移动", - "searchInFile": "在文件中搜索 (Ctrl+F)", - "showKeyboardShortcuts": "显示键盘快捷键", - "decreaseFontSize": "缩小字体", - "increaseFontSize": "放大字体", - "startWritingMarkdown": "开始编写Markdown内容...", - "loadingFileComparison": "加载文件比较中...", - "reload": "重新加载", - "compare": "比较", - "sideBySide": "并排", - "inline": "内联", - "fileComparison": "文件比较: {{file1}} 对比 {{file2}}", - "fileTooLarge": "文件过大: {{error}}", - "sshConnectionFailed": "SSH 连接失败。请检查您到 {{name}} ({{ip}}:{{port}}) 的连接", - "loadFileFailed": "加载文件失败: {{error}}", - "connecting": "连接中...", - "connectedSuccessfully": "连接成功", - "totpVerificationFailed": "TOTP 验证失败", - "warpgateVerificationFailed": "Warpgate 认证失败", - "authenticationFailed": "认证失败", - "incorrectPassphrase": "密码错误。请重试。", - "verificationCodePrompt": "验证码:", - "changePermissions": "更改权限", - "currentPermissions": "当前权限", - "owner": "所有者", - "group": "组", - "others": "其他", - "read": "读取", - "write": "写入", - "execute": "执行", - "permissionsChangedSuccessfully": "权限修改成功", - "failedToChangePermissions": "权限修改失败", - "name": "名称", - "sortByName": "名称", - "sortByDate": "修改日期", - "sortBySize": "大小", - "ascending": "升序", - "descending": "降序", - "root": "根目录", - "new": "新建", - "sortBy": "排序方式", - "items": "项目", - "selected": "已选择", - "editor": "编辑器", - "octal": "八进制", - "storage": "存储", - "disk": "磁盘", - "used": "已用", - "of": "/", - "toggleSidebar": "切换侧边栏", - "compactLayout": "采用紧凑型布局", - "comfortableLayout": "采用舒适的布局", - "cannotLoadPdf": "无法加载 PDF", - "pdfLoadError": "加载此 PDF 文件时出错。", - "loadingPdf": "正在加载 PDF...", - "loadingPage": "正在加载页面..." - }, "transfer": { "copyToHost": "复制到主机…", "moveToHost": "移动到主机…", @@ -2292,50 +1951,6 @@ "retryTransferError": "重试失败", "transferFailedRetryHint": "部分数据已保留在目标位置。连接恢复后重试将继续。" }, - "sftpTransfer": { - "title": "SFTP", - "description": "在两台SSH主机之间传输文件。", - "sourceServer": "源服务器", - "destinationServer": "目标服务器", - "selectHost": "选择主机", - "noHostSelected": "未选择主机", - "move": "移动", - "copy": "复制", - "upload": "上传", - "itemsSelected": "{{count}} 已选择项目", - "itemsSelected_other": "{{count}} 已选择项目", - "ready": "准备好", - "loadingHosts": "正在加载主机...", - "name": "姓名", - "modified": "修改的", - "size": "尺寸", - "noFiles": "没有文件", - "open": "打开", - "up": "向上", - "refresh": "刷新", - "copyToTarget": "复制到目标目录", - "rename": "重命名", - "delete": "删除", - "editPermissions": "编辑权限", - "createNewFolder": "创建新文件夹", - "deleteSelectedItems": "删除选定项目", - "deleteConfirm": "删除 {{count}} 选定项目?", - "deleteConfirm_other": "删除 {{count}} 选定项目?", - "cancel": "取消", - "save": "节省", - "folderCreated": "已创建文件夹", - "itemRenamed": "项目已重命名", - "actionFailed": "操作失败", - "itemsDeleted": "{{count}} 项目已删除", - "itemsDeleted_other": "{{count}} 项已删除", - "deleteFailed": "删除失败", - "permissionsUpdated": "权限已更新", - "noRemoteSessionConnected": "未连接远程会话", - "failedToListFiles": "文件列表失败", - "failedToRefresh": "刷新失败", - "destinationInsideSource": "目标路径不能位于所选源路径内。", - "sameHostNoOp": "源文件夹和目标文件夹是同一个文件夹。" - }, "tunnels": { "noSshTunnels": "无 SSH 隧道", "createFirstTunnelMessage": "您尚未创建任何 SSH 隧道。在主机管理器中配置隧道连接以开始使用。", diff --git a/src/ui/locales/translated/zh_TW.json b/src/ui/locales/translated/zh_TW.json index f490aeed8..c04231c7b 100644 --- a/src/ui/locales/translated/zh_TW.json +++ b/src/ui/locales/translated/zh_TW.json @@ -715,7 +715,6 @@ "hostManager": "主機管理員", "credentials": "憑證", "portForwarding": "轉發", - "sftp": "SFTP", "connections": "連線", "serial": "序列", "keepSideMenuOpen": "保持菜單打開", @@ -767,14 +766,12 @@ "tabRdp": "RDP", "tabVnc": "VNC", "tabTunnels": "通道", - "tabFiles": "檔案", "tabStats": "主機指標", "tabTelnet": "Telnet", "tabSharing": "分享", "tabAuthentication": "驗證", "terminal": "終端機", "tunnel": "通道", - "fileManager": "檔案管理員", "serverStats": "主機指標", "status": { "available": "可用的", @@ -1118,12 +1115,6 @@ "proxmoxResultSkippedNoIp": "{{count}} 已跳過(找不到 IP 位址)", "proxmoxStatsUrlCopied": "Proxmox 統計資料 URL 已複製到剪貼簿", "copyProxmoxStatsUrlAction": "複製 Proxmox 統計資料 URL", - "enableFileManagerMonitor": "啟用檔案管理員", - "enableFileManagerMonitorDesc": "透過 SFTP 瀏覽並管理此主機上的檔案", - "scpLegacyLabel": "SCP 舊版模式", - "scpLegacyDesc": "對不支援 SFTP 子系統的伺服器(例如嵌入式或精簡型 SSH 伺服器)使用舊版檔案傳輸。", - "defaultPathLabel": "預設路徑", - "fileManagerPathHint": "啟動此主機的檔案管理員時開啟的目錄。", "statusChecksLabel": "狀態檢查", "enableStatusChecks": "啟用狀態檢查", "enableStatusChecksDesc": "定期 ping 此主機以驗證可用性", @@ -1278,7 +1269,6 @@ "deletedHost": "已刪除 {{name}}", "copiedToClipboard": "已複製到剪貼簿", "terminalUrlCopied": "終端機 URL 已複製", - "fileManagerUrlCopied": "檔案管理員 URL 已複製", "tunnelUrlCopied": "通道 URL 已複製", "dockerUrlCopied": "Docker URL 已複製", "hostMetricsUrlCopied": "主機指標 URL 已複製", @@ -1296,7 +1286,6 @@ "copyAddress": "複製位址", "copyLink": "複製連結", "copyTerminalUrlAction": "複製終端機 URL", - "copyFileManagerUrlAction": "複製檔案管理員 URL", "copyTunnelUrlAction": "複製通道 URL", "copyDockerUrlAction": "複製 Docker URL", "copyHostMetricsUrlAction": "複製主機指標 URL", @@ -1857,336 +1846,6 @@ "removeFromSplit": "從分割中移除" } }, - "fileManager": { - "noHostSelected": "未選取主機", - "initializingEditor": "正在初始化編輯器…", - "file": "檔案", - "folder": "資料夾", - "uploadFile": "上傳檔案", - "downloadFile": "下載", - "extractArchive": "解壓縮", - "extractingArchive": "正在解壓縮 {{name}}...", - "archiveExtractedSuccessfully": "{{name}} 解壓縮成功", - "extractFailed": "解壓縮失敗", - "compressFile": "壓縮檔案", - "compressFiles": "壓縮檔案", - "compressFilesDesc": "將 {{count}} 個項目壓縮為一個壓縮檔", - "archiveName": "壓縮檔名稱", - "enterArchiveName": "輸入壓縮檔名稱...", - "compressionFormat": "壓縮格式", - "selectedFiles": "已選取的檔案", - "andMoreFiles": "還有 {{count}} 個...", - "compress": "壓縮", - "compressingFiles": "正在將 {{count}} 個項目壓縮為 {{name}}...", - "filesCompressedSuccessfully": "{{name}} 建立成功", - "compressFailed": "壓縮失敗", - "edit": "編輯", - "preview": "預覽", - "previous": "上一個", - "next": "下一個", - "pageXOfY": "第 {{current}} 頁,共 {{total}} 頁", - "zoomOut": "縮小", - "zoomIn": "放大", - "newFile": "新增檔案", - "newFolder": "新增資料夾", - "rename": "重新命名", - "uploading": "上傳中...", - "uploadingFile": "正在上傳 {{name}}...", - "fileName": "檔案名稱", - "folderName": "資料夾名稱", - "fileUploadedSuccessfully": "檔案「{{name}}」上傳成功", - "failedToUploadFile": "上傳檔案失敗", - "uploadingFolderFiles": "正在上傳 {{count}} 檔案...", - "uploadedFolderFiles": "已成功上傳 {{count}} 個文件", - "uploadedFolderPartial": "上傳了 {{uploaded}} 個文件, {{failed}} 個文件失敗", - "fileDownloadedSuccessfully": "檔案「{{name}}」下載成功", - "failedToDownloadFile": "下載檔案失敗", - "fileCreatedSuccessfully": "檔案「{{name}}」建立成功", - "folderCreatedSuccessfully": "資料夾「{{name}}」建立成功", - "failedToCreateItem": "建立項目失敗", - "operationFailed": "{{operation}} 操作在 {{name}} 上失敗:{{error}}", - "failedToResolveSymlink": "無法解析符號連結", - "itemsDeletedSuccessfully": "成功刪除 {{count}} 個項目", - "failedToDeleteItems": "刪除項目失敗", - "sudoPasswordRequired": "需要管理員密碼", - "enterSudoPassword": "請輸入 sudo 密碼以繼續此操作", - "sudoPassword": "Sudo 密碼", - "sudoOperationFailed": "Sudo 操作失敗", - "sudoAuthFailed": "Sudo 驗證失敗", - "dragFilesToUpload": "拖放檔案至此以上傳", - "dropToUploadHere": "在此處上傳", - "dropToDownloadHere": "點此下載", - "localFiles": "當地的", - "showLocalFiles": "顯示本地文件(分割畫面顯示)", - "hideLocalFiles": "隱藏本地文件", - "localHome": "首頁", - "localPathPlaceholder": "本機資料夾路徑", - "localShowHidden": "顯示隱藏文件", - "localHideHidden": "隱藏隱藏文件", - "localRevealInFileManager": "在系統檔案管理器中顯示", - "localCannotOpenFolder": "無法開啟資料夾", - "localItemCount": "{{count}} 項目", - "localSelectedCount": "{{count}} 已選擇", - "back": "後退", - "forward": "向前", - "up": "向上", - "kind": "種類", - "localUploadingProgress": "正在上傳 {{current}} 中的 {{total}}", - "localDownloadingProgress": "正在下載 {{current}} 中的 {{total}}", - "localTransferInFlight": "{{count}} 立刻", - "localPreparingDownload": "正在準備下載…", - "localTransferCancelled": "轉帳已取消", - "localNothingToTransfer": "無需轉移", - "localUploadComplete": "已上傳 {{count}} 個文件", - "localUploadFailed": "上傳失敗", - "localUploadPartial": "上傳了 {{done}} 個文件, {{failed}} 個文件失敗", - "localDownloadComplete": "已下載 {{count}} 個文件", - "localDownloadFailed": "下載失敗", - "localDownloadPartial": "下載了 {{done}} 個文件, {{failed}} 個文件失敗", - "localReplaceExistingPrompt": "此資料夾中已存在 {{count}} 個項目。是否替換它們?", - "localReplace": "代替", - "localSkipExisting": "跳過", - "localSkippedExisting": "已跳過 {{count}} 個現有項目", - "localUnsafeNamesSkipped": "已跳過 {{count}} 個名稱無法在此電腦上使用的項目。", - "localOpen": "打開", - "localOpenFolder": "打開資料夾", - "localUploadToRemote": "上傳到伺服器", - "localUploadToRemoteMany": "上傳 {{count}} 項目到伺服器", - "localMoveToTrash": "移至垃圾桶", - "localMoveToTrashMany": "將 {{count}} 個物品移至垃圾桶", - "localTrashConfirmSingle": "將「{{name}}」移至回收站?", - "localTrashConfirmFolder": "將資料夾「{{name}}」及其所有內容移至回收站?", - "localTrashConfirmMany": "將 {{count}} 個項目移至回收站?", - "localTrashed": "已將 {{count}} 個項目移至回收站", - "localTrashFailed": "無法移至垃圾桶", - "localRenameFailed": "重新命名失敗", - "localCreateFailed": "無法建立專案", - "goToParentFolder": "轉到上資料夾", - "resizeSidebar": "拖曳即可調整側邊欄大小(雙擊即可重設)", - "resizeLocalPane": "拖曳即可調整本機窗格大小(雙擊可重設)", - "columns": "列", - "columnsHint": "右鍵單擊選擇列", - "emptyFolder": "此資料夾為空", - "searchFiles": "搜尋檔案...", - "upload": "上傳", - "selectHostToStart": "選擇主機以開始檔案管理", - "sshRequiredForFileManager": "檔案管理員需要 SSH。此主機未啟用 SSH。", - "failedToConnect": "無法連線至 SSH", - "failedToLoadDirectory": "載入目錄失敗", - "noSSHConnection": "沒有可用的 SSH 連線", - "copy": "複製", - "cut": "剪下", - "paste": "貼上", - "copyPath": "複製路徑", - "copyPaths": "複製路徑", - "delete": "刪除", - "properties": "內容", - "refresh": "重新整理", - "downloadFiles": "下載 {{count}} 個檔案到瀏覽器", - "copyFiles": "複製 {{count}} 個項目", - "cutFiles": "剪下 {{count}} 個項目", - "deleteFiles": "刪除 {{count}} 個項目", - "filesCopiedToClipboard": "{{count}} 個項目已複製到剪貼簿", - "filesCutToClipboard": "{{count}} 個項目已剪下到剪貼簿", - "pathCopiedToClipboard": "路徑已複製到剪貼簿", - "pathsCopiedToClipboard": "{{count}} 個路徑已複製到剪貼簿", - "failedToCopyPath": "無法複製路徑到剪貼簿", - "copyFolderLink": "複製資料夾連結", - "copyCurrentFolderLink": "複製目前資料夾連結", - "folderLinkCopied": "資料夾連結已複製到剪貼簿", - "failedToCopyFolderLink": "無法複製資料夾連結", - "movedItems": "已移動 {{count}} 個項目", - "failedToDeleteItem": "無法刪除項目", - "itemRenamedSuccessfully": "{{type}} 重新命名成功", - "failedToRenameItem": "無法重新命名項目", - "download": "下載", - "openExternalEditor": "以外部編輯器開啟", - "chooseExternalEditor": "選擇外部編輯器", - "externalEditorSelected": "已選擇外部編輯器", - "externalEditorDesktopOnly": "外部編輯器僅限桌面版應用程式使用", - "externalEditorOpened": "已於外部編輯器開啟。在此儲存將上傳變更回伺服器。", - "failedToOpenExternalEditor": "無法開啟外部編輯器", - "failedToSelectExternalEditor": "無法選擇外部編輯器", - "permissions": "權限", - "size": "大小", - "modified": "修改時間", - "path": "路徑", - "confirmDelete": "確定要刪除 {{name}} 嗎?", - "permissionDenied": "權限不足", - "serverError": "伺服器錯誤", - "fileSavedSuccessfully": "檔案儲存成功", - "failedToSaveFile": "無法儲存檔案", - "confirmDeleteSingleItem": "確定要永久刪除「{{name}}」嗎?", - "confirmDeleteMultipleItems": "確定要永久刪除 {{count}} 個項目嗎?", - "confirmDeleteMultipleItemsWithFolders": "確定要永久刪除 {{count}} 個項目嗎?這包含資料夾及其內容。", - "confirmDeleteFolder": "確定要永久刪除資料夾「{{name}}」及其所有內容嗎?", - "permanentDeleteWarning": "此動作無法復原。項目將從伺服器永久刪除。", - "moveToTrashWarning": "該物品將移至 Termix 回收站,可在保留期到期前恢復。", - "itemsMovedToTrash": "{{count}} 項已移至回收站", - "trash": "垃圾", - "trashDescription": "已刪除的項目將保留在本主機上 {{days}} 天。", - "trashLoadFailed": "載入回收站失敗", - "trashEmpty": "垃圾桶是空的", - "trashRestore": "恢復", - "trashRestored": "已恢復 {{name}}", - "trashRestoreFailed": "恢復專案失敗。原始路徑可能已存在。", - "trashDeletePermanently": "永久刪除", - "trashDeleteConfirm": "永久刪除 {{name}}?此操作無法撤銷。", - "trashPermanentlyDeleted": "永久刪除 {{name}}", - "trashDeleteFailed": "無法永久刪除項目", - "trashEmptyAction": "清空垃圾桶", - "trashEmptyConfirm": "永久刪除回收站中的所有項目?此操作無法撤銷。", - "trashEmptied": "垃圾已清空", - "trashRetention": "自動清理(天數)後", - "trashRetentionSaved": "垃圾保留狀況更新", - "trashRetentionFailed": "更新垃圾回收站保留策略失敗", - "trashUnavailableConfirm": "此項目無法移至回收站,通常是因為它位於其他檔案系統或需要更高的權限。是否將其永久刪除?", - "recent": "最近", - "pinned": "已釘選", - "folderShortcuts": "資料夾捷徑", - "failedToReconnectSSH": "無法重新連線 SSH 工作階段", - "openTerminalHere": "在此處開啟終端機", - "run": "執行", - "openTerminalInFolder": "在此資料夾中開啟終端機", - "openTerminalInFileLocation": "在檔案位置開啟終端機", - "runningFile": "執行中 - {{file}}", - "onlyRunExecutableFiles": "只能執行可執行檔案", - "directories": "目錄", - "removedFromRecentFiles": "已從最近檔案中移除 \"{{name}}\"", - "removeFailed": "移除失敗", - "unpinnedSuccessfully": "已成功取消釘選 \"{{name}}\"", - "unpinFailed": "取消釘選失敗", - "removedShortcut": "已移除捷徑 \"{{name}}\"", - "removeShortcutFailed": "移除捷徑失敗", - "clearedAllRecentFiles": "已清除所有最近檔案", - "clearFailed": "清除失敗", - "removeFromRecentFiles": "從最近檔案中移除", - "clearAllRecentFiles": "清除所有最近檔案", - "unpinFile": "取消釘選檔案", - "removeShortcut": "移除捷徑", - "pinFile": "釘選檔案", - "addToShortcuts": "加入捷徑", - "pasteFailed": "貼上失敗", - "noUndoableActions": "沒有可復原的動作", - "undoCopySuccess": "已復原複製操作:已刪除 {{count}} 個複製的檔案", - "undoCopyFailedDelete": "復原失敗:無法刪除任何複製的檔案", - "undoCopyFailedNoInfo": "復原失敗:找不到複製檔案的資訊", - "undoMoveSuccess": "已復原移動操作:已將 {{count}} 個檔案移回原始位置", - "undoMoveFailedMove": "復原失敗:無法移回任何檔案", - "undoMoveFailedNoInfo": "復原失敗:找不到移動檔案的資訊", - "undoDeleteNotSupported": "刪除操作無法復原:檔案已從伺服器永久刪除", - "undoTypeNotSupported": "不支援的復原操作類型", - "undoOperationFailed": "復原操作失敗", - "unknownError": "未知錯誤", - "confirm": "確認", - "find": "尋找...", - "replace": "取代", - "downloadInstead": "改為下載", - "keyboardShortcuts": "鍵盤快捷鍵", - "searchAndReplace": "搜尋與取代", - "editing": "編輯", - "search": "搜尋", - "findNext": "尋找下一個", - "findPrevious": "尋找上一個", - "save": "儲存", - "selectAll": "全選", - "undo": "復原", - "redo": "重做", - "moveLineUp": "將行上移", - "moveLineDown": "將行下移", - "toggleComment": "切換註解", - "autoComplete": "自動完成", - "imageLoadError": "無法載入圖片", - "startTyping": "開始輸入...", - "unknownSize": "未知大小", - "fileIsEmpty": "檔案為空", - "largeFileWarning": "大檔案警告", - "largeFileWarningDesc": "此檔案大小為 {{size}},以文字模式開啟可能會影響效能。", - "fileNotFoundAndRemoved": "找不到檔案 \"{{name}}\",已從最近使用的/已釘選的檔案中移除", - "failedToLoadFile": "無法載入檔案:{{error}}", - "serverErrorOccurred": "伺服器發生錯誤,請稍後再試。", - "autoSaveFailed": "自動儲存失敗", - "fileAutoSaved": "檔案已自動儲存", - "moveFileFailed": "無法移動 {{name}}", - "moveOperationFailed": "移動操作失敗", - "canOnlyCompareFiles": "只能比較兩個檔案", - "comparingFiles": "正在比較檔案:{{file1}} 與 {{file2}}", - "dragFailed": "拖曳操作失敗", - "filePinnedSuccessfully": "已成功釘選檔案 \"{{name}}\"", - "pinFileFailed": "釘選檔案失敗", - "fileUnpinnedSuccessfully": "已成功取消釘選檔案 \"{{name}}\"", - "unpinFileFailed": "取消釘選檔案失敗", - "shortcutAddedSuccessfully": "資料夾捷徑 \"{{name}}\" 已成功新增", - "addShortcutFailed": "新增捷徑失敗", - "operationCompletedSuccessfully": "已成功{{operation}} {{count}} 個項目", - "operationCompleted": "{{operation}} {{count}} 個項目", - "downloadFileSuccess": "檔案 {{name}} 已成功下載", - "downloadFileFailed": "下載失敗", - "downloadingFile": "正在下載 {{name}}…", - "downloadProgressBytes": "{{transferred}} / {{total}}", - "moveTo": "移動至 {{name}}", - "diffCompareWith": "與 {{name}} 進行差異比對", - "dragOutsideToDownload": "拖曳至視窗外以下載 ({{count}} 個檔案)", - "newFolderDefault": "新資料夾", - "newFileDefault": "新檔案.txt", - "successfullyMovedItems": "已成功移動 {{count}} 個項目至 {{target}}", - "move": "移動", - "searchInFile": "在檔案中搜尋 (Ctrl+F)", - "showKeyboardShortcuts": "顯示鍵盤快捷鍵", - "decreaseFontSize": "縮小字型大小", - "increaseFontSize": "放大字型大小", - "startWritingMarkdown": "開始撰寫您的 Markdown 內容...", - "loadingFileComparison": "載入檔案比對中...", - "reload": "重新載入", - "compare": "比對", - "sideBySide": "並排", - "inline": "內嵌", - "fileComparison": "檔案比對:{{file1}} vs {{file2}}", - "fileTooLarge": "檔案過大:{{error}}", - "sshConnectionFailed": "SSH 連線失敗。請檢查與 {{name}} ({{ip}}:{{port}}) 的連線", - "loadFileFailed": "載入檔案失敗:{{error}}", - "connecting": "連線中...", - "connectedSuccessfully": "已成功連線", - "totpVerificationFailed": "TOTP 驗證失敗", - "warpgateVerificationFailed": "Warpgate 驗證失敗", - "authenticationFailed": "驗證失敗", - "incorrectPassphrase": "密碼片語錯誤。請再試一次。", - "verificationCodePrompt": "驗證碼:", - "changePermissions": "變更權限", - "currentPermissions": "目前權限", - "owner": "擁有者", - "group": "群組", - "others": "其他人", - "read": "讀取", - "write": "寫入", - "execute": "執行", - "permissionsChangedSuccessfully": "權限已成功變更", - "failedToChangePermissions": "無法變更權限", - "name": "名稱", - "sortByName": "名稱", - "sortByDate": "修改日期", - "sortBySize": "大小", - "ascending": "遞增", - "descending": "遞減", - "root": "根目錄", - "new": "新增", - "sortBy": "排序依據", - "items": "項目", - "selected": "已選取", - "editor": "編輯器", - "octal": "八進位", - "storage": "儲存空間", - "disk": "磁碟", - "used": "已使用", - "of": " / ", - "toggleSidebar": "切換側邊欄", - "compactLayout": "採用緊湊型佈局", - "comfortableLayout": "採用舒適的佈局", - "cannotLoadPdf": "無法載入 PDF", - "pdfLoadError": "載入此 PDF 檔案時發生錯誤。", - "loadingPdf": "正在載入 PDF...", - "loadingPage": "正在載入頁面..." - }, "transfer": { "copyToHost": "複製到主機…", "moveToHost": "移動到主機…", @@ -2292,50 +1951,6 @@ "retryTransferError": "重試失敗", "transferFailedRetryHint": "部分資料已保留在目的地。重新連線後,重試將從斷點繼續。" }, - "sftpTransfer": { - "title": "SFTP", - "description": "在兩台SSH主機之間傳輸檔案。", - "sourceServer": "來源伺服器", - "destinationServer": "目標伺服器", - "selectHost": "選擇主機", - "noHostSelected": "未選擇主機", - "move": "移動", - "copy": "複製", - "upload": "上傳", - "itemsSelected": "{{count}} 已選擇項目", - "itemsSelected_other": "{{count}} 已選擇項目", - "ready": "準備好", - "loadingHosts": "正在加載主機...", - "name": "姓名", - "modified": "修改的", - "size": "尺寸", - "noFiles": "沒有文件", - "open": "打開", - "up": "向上", - "refresh": "重新整理", - "copyToTarget": "複製到目標目錄", - "rename": "重新命名", - "delete": "刪除", - "editPermissions": "編輯權限", - "createNewFolder": "建立新資料夾", - "deleteSelectedItems": "刪除選定項目", - "deleteConfirm": "刪除 {{count}} 選取項目?", - "deleteConfirm_other": "刪除 {{count}} 選取項目?", - "cancel": "取消", - "save": "節省", - "folderCreated": "已建立資料夾", - "itemRenamed": "項目已重新命名", - "actionFailed": "操作失敗", - "itemsDeleted": "{{count}} 項目已刪除", - "itemsDeleted_other": "{{count}} 項目已刪除", - "deleteFailed": "刪除失敗", - "permissionsUpdated": "權限已更新", - "noRemoteSessionConnected": "未連接遠端會話", - "failedToListFiles": "文件列表失敗", - "failedToRefresh": "刷新失敗", - "destinationInsideSource": "目標路徑不能位於所選來源路徑內。", - "sameHostNoOp": "來源資料夾和目標資料夾是同一個資料夾。" - }, "tunnels": { "noSshTunnels": "無 SSH 通道", "createFirstTunnelMessage": "您尚未建立任何 SSH 通道。請在主機管理員中設定通道連線以開始使用。", diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 274eb4600..7adc41838 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -843,7 +843,9 @@ let remoteGuacamoleApi: AxiosInstance | null = null; export function getRemoteFileManagerApi(): AxiosInstance { if (!remoteFileManagerApi) { - remoteFileManagerApi = createRemoteOriginApiInstance("/ssh/file_manager"); + remoteFileManagerApi = createRemoteOriginApiInstance( + "/plugin-api/file-manager", + ); } return remoteFileManagerApi; } @@ -931,9 +933,10 @@ function initializeApiInstances() { // Tunnel Management API (port 30003) tunnelApi = createApiInstance(getApiUrl("/ssh", 30003), "TUNNEL"); - // File Manager Operations API (port 30004) + // File Manager Operations API - the file-manager plugin, mounted on the + // main backend. fileManagerApi = createApiInstance( - getApiUrl("/ssh/file_manager", 30004), + getApiUrl("/plugin-api/file-manager", 30001), "FILE_MANAGER", ); @@ -978,7 +981,7 @@ export let sshHostApi: AxiosInstance; // Tunnel Management API (port 30003) export let tunnelApi: AxiosInstance; -// File Manager Operations API (port 30004) +// File Manager Operations API (file-manager plugin) export let fileManagerApi: AxiosInstance; // Host Metrics API (host-metrics plugin) @@ -1203,7 +1206,7 @@ export async function getTransferMethodPreview( methodPreference?: TransferMethodPreference, ): Promise { try { - const response = await fileManagerApi.post("/ssh/transferMethodPreview", { + const response = await fileManagerApi.post("/transferMethodPreview", { sourceSessionId, sourcePaths, destSessionId, @@ -1383,7 +1386,7 @@ export async function transferToHost( parallelSegmentCount, }); - const response = await fileManagerApi.post("/ssh/transferToHost", { + const response = await fileManagerApi.post("/transferToHost", { sourceSessionId, sourcePaths, destSessionId, @@ -1410,9 +1413,7 @@ export async function getTransferStatus( transferId: string, ): Promise { try { - const response = await fileManagerApi.get( - `/ssh/transferStatus/${transferId}`, - ); + const response = await fileManagerApi.get(`/transferStatus/${transferId}`); return response.data; } catch (error) { handleApiError(error, "get transfer status"); @@ -1424,7 +1425,7 @@ export async function listActiveTransfers(): Promise<{ transfers: TransferProgressResponse[]; }> { try { - const response = await fileManagerApi.get("/ssh/activeTransfers"); + const response = await fileManagerApi.get("/activeTransfers"); return response.data; } catch (error) { handleApiError(error, "list active transfers"); @@ -1434,7 +1435,7 @@ export async function listActiveTransfers(): Promise<{ export async function cancelTransferToHost(transferId: string): Promise { try { - await fileManagerApi.post(`/ssh/transferCancel/${transferId}`); + await fileManagerApi.post(`/transferCancel/${transferId}`); } catch (error) { fileLogger.warn("Transfer cancel request failed (non-fatal)", { operation: "host_transfer", @@ -1449,7 +1450,7 @@ export async function cleanupCancelledTransfer( ): Promise<{ removedPaths: string[]; failedPaths: string[] }> { try { const response = await fileManagerApi.post( - `/ssh/transferCleanup/${transferId}`, + `/transferCleanup/${transferId}`, ); return response.data; } catch (error) { @@ -1462,9 +1463,7 @@ export async function retryTransferToHost( transferId: string, ): Promise<{ ok: boolean; transferId: string }> { try { - const response = await fileManagerApi.post( - `/ssh/transferRetry/${transferId}`, - ); + const response = await fileManagerApi.post(`/transferRetry/${transferId}`); return response.data; } catch (error) { handleApiError(error, "retry transfer"); @@ -1586,72 +1585,6 @@ export { deleteC2STunnelPreset, } from "@/api/tunnel-api"; -export { - getFileManagerRecent, - addFileManagerRecent, - removeFileManagerRecent, - getFileManagerPinned, - addFileManagerPinned, - removeFileManagerPinned, - getFileManagerShortcuts, - addFileManagerShortcut, - removeFileManagerShortcut, -} from "@/api/file-manager-metadata-api"; - -export { - connectSSH, - disconnectSSH, - verifySSHTOTP, - verifySSHWarpgate, - quickConnect, - getSSHStatus, - keepSSHAlive, - listSSHFiles, - identifySSHSymlink, - resolveSSHPath, - readSSHFile, - writeSSHFile, - uploadSSHFile, - downloadSSHFile, - downloadSSHFileStream, - createSSHFile, - createSSHFolder, - deleteSSHItem, - setSudoPassword, - copySSHItem, - renameSSHItem, - moveSSHItem, - changeSSHPermissions, - extractSSHArchive, - compressSSHFiles, - ensureSSHSessionForHost, - browseSSHDirectory, - type HostConnectionState, - type EnsureSSHSessionResult, - type BrowseSSHDirectoryResult, -} from "@/api/ssh-file-operations-api"; - -export { - getRecentFiles, - addRecentFile, - removeRecentFile, - getPinnedFiles, - addPinnedFile, - removePinnedFile, - getFolderShortcuts, - addFolderShortcut, - removeFolderShortcut, -} from "@/api/file-manager-data-api"; - -// Desktop-only local disk <-> remote transfers (dual-pane file manager). -export { - uploadLocalFileToSession, - downloadSessionFileToLocal, - cancelLocalTransfer, - createLocalTransferId, - type LocalTransferProgressEvent, -} from "@/api/local-transfer-api"; - export { getAllServerStatuses, getServerStatusById, diff --git a/src/ui/onboarding/steps/FeaturesStep.tsx b/src/ui/onboarding/steps/FeaturesStep.tsx index 63e89cb04..3b0db037c 100644 --- a/src/ui/onboarding/steps/FeaturesStep.tsx +++ b/src/ui/onboarding/steps/FeaturesStep.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { FolderTree, Plug, type LucideIcon } from "lucide-react"; +import { Plug, type LucideIcon } from "lucide-react"; import { useActionSlot } from "@/hooks/use-action-slot"; /** @@ -9,7 +9,6 @@ import { useActionSlot } from "@/hooks/use-action-slot"; * through the "onboarding.features" slot. */ const FEATURES: { icon: LucideIcon; key: string }[] = [ - { icon: FolderTree, key: "files" }, { icon: Plug, key: "tunnels" }, ]; diff --git a/src/ui/plugin-host/shell-bridge.ts b/src/ui/plugin-host/shell-bridge.ts index dd0fc0092..8a3b74c13 100644 --- a/src/ui/plugin-host/shell-bridge.ts +++ b/src/ui/plugin-host/shell-bridge.ts @@ -89,8 +89,6 @@ export const shell: TabShellCallbacks = { openSingletonTab: (...args) => callbacks?.openSingletonTab(...args), closeTab: (...args) => callbacks?.closeTab(...args), renameTab: (...args) => callbacks?.renameTab(...args), - openFileInEditor: (...args) => callbacks?.openFileInEditor(...args), - openFileManager: (...args) => callbacks?.openFileManager(...args), openTerminalTab: (...args) => callbacks?.openTerminalTab(...args), openRailView: (...args) => callbacks?.openRailView(...args), closeRailView: (...args) => callbacks?.closeRailView(...args), diff --git a/src/ui/plugin-host/testing-host.tsx b/src/ui/plugin-host/testing-host.tsx index 13ca30fe8..86c0fbe7d 100644 --- a/src/ui/plugin-host/testing-host.tsx +++ b/src/ui/plugin-host/testing-host.tsx @@ -68,8 +68,6 @@ function recordingShell(calls: ShellCall[]): TabShellCallbacks { openSingletonTab: record("openSingletonTab"), closeTab: record("closeTab"), renameTab: record("renameTab"), - openFileInEditor: record("openFileInEditor"), - openFileManager: record("openFileManager"), openTerminalTab: record("openTerminalTab"), openRailView: record("openRailView"), closeRailView: record("closeRailView"), diff --git a/src/ui/shell/CommandPalette.tsx b/src/ui/shell/CommandPalette.tsx index c91154513..32caab3fc 100644 --- a/src/ui/shell/CommandPalette.tsx +++ b/src/ui/shell/CommandPalette.tsx @@ -14,7 +14,6 @@ import { Server, Settings, Terminal, - FolderSearch, Globe, Plus, MessagesSquare, @@ -67,11 +66,6 @@ function getSshActions(host: Host): { label: string; }[] { return [ - host.enableFileManager && { - type: "files", - icon: FolderSearch, - label: "Files", - }, // --- tmux-monitor --- opt-in per host, off by default host.enableTerminal !== false && host.enableTmuxMonitor && { diff --git a/src/ui/shell/shell-layout.ts b/src/ui/shell/shell-layout.ts index f130ca01a..92de96ca8 100644 --- a/src/ui/shell/shell-layout.ts +++ b/src/ui/shell/shell-layout.ts @@ -19,7 +19,6 @@ import { getTabType } from "./tab-registry"; * not a meaningful part of an arrangement. */ const CORE_CAPTURABLE = new Set([ - "files", "tunnel", "tmux_monitor", "serial", @@ -51,7 +50,6 @@ const CORE_UNSAVED = new Set([ "user-profile", "admin-settings", "split-screen", - "sftp", ]); /** diff --git a/src/ui/shell/tab-registry.ts b/src/ui/shell/tab-registry.ts index 6c5a0481c..ca87ef5be 100644 --- a/src/ui/shell/tab-registry.ts +++ b/src/ui/shell/tab-registry.ts @@ -23,8 +23,6 @@ export interface TabShellCallbacks { ) => void; closeTab: (tabId: string) => void; renameTab: (tabId: string, label: string) => void; - openFileInEditor: (host: Host, path: string) => void; - openFileManager: (host: Host, path?: string) => void; openTerminalTab: (host: Host, path?: string) => void; openRailView: (id: string) => void; /** Closes a rail view wherever it is shown, e.g. when its feature goes away. */ @@ -85,7 +83,7 @@ export const useTabTypes = registry.useList; export const resetTabTypes = registry.reset; /** Core tab types that are saved and reopened after login. */ -const CORE_PERSISTENT = new Set(["files", "tunnel"]); +const CORE_PERSISTENT = new Set(["tunnel"]); /** Core tab types that hold a live session (close confirm, refresh). */ const CORE_SESSION = new Set(["serial"]); /** Core tab types that restore without a host. */ diff --git a/src/ui/shell/tabUtils.tsx b/src/ui/shell/tabUtils.tsx index f08446cae..f23134df7 100644 --- a/src/ui/shell/tabUtils.tsx +++ b/src/ui/shell/tabUtils.tsx @@ -1,12 +1,10 @@ /* eslint-disable react-refresh/only-export-components */ import { Braces, - FolderSearch, LayoutDashboard, LayoutGrid, LayoutPanelLeft, Network, - ArrowLeftRight, Server, Settings, Usb, @@ -46,11 +44,6 @@ const LocalTerminal = lazy(() => default: m.LocalTerminal, })), ); -const loadFileManager = () => - import("@/features/file-manager/FileManager").then((m) => ({ - default: m.FileManager, - })); -const FileManager = lazy(loadFileManager); const loadTmuxMonitor = () => import("@/features/tmux-monitor/TmuxMonitor").then((m) => ({ default: m.TmuxMonitor, @@ -71,11 +64,6 @@ const loadTunnelTab = () => default: m.TunnelTab, })); const TunnelTab = lazy(loadTunnelTab); -const SftpTransferTab = lazy(() => - import("@/features/sftp/SftpTransferTab").then((m) => ({ - default: m.SftpTransferTab, - })), -); const Serial = lazy(() => import("@/features/serial/Serial").then((m) => ({ default: m.Serial, @@ -100,7 +88,6 @@ const SshToolsPanel = lazy(() => import("@/sidebar/SshToolsPanel").then((m) => ({ default: m.SshToolsPanel })), ); const tabSurfaceLoaders: Partial Promise>> = { - files: loadFileManager, tmux_monitor: loadTmuxMonitor, tunnel: loadTunnelTab, }; @@ -168,8 +155,6 @@ export function tabIcon(type: TabType) { return ; case "local-terminal": return ; - case "files": - return ; case "host-manager": return ; case "user-profile": @@ -178,8 +163,6 @@ export function tabIcon(type: TabType) { return ; case "tunnel": return ; - case "sftp": - return ; // --- tmux-monitor --- case "tmux_monitor": return ; @@ -293,32 +276,11 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) { , ); - case "files": - if (!host) - return ( - - ); - return withTabSuspense( - shell.openTerminalTab(host, path)} - />, - ); - case "tunnel": return withTabSuspense( , ); - case "sftp": - return withTabSuspense(); - // --- tmux-monitor --- case "tmux_monitor": return withTabSuspense( diff --git a/src/ui/sidebar/AppRail.tsx b/src/ui/sidebar/AppRail.tsx index 0c4d367bd..b7d952703 100644 --- a/src/ui/sidebar/AppRail.tsx +++ b/src/ui/sidebar/AppRail.tsx @@ -20,7 +20,6 @@ export type CoreRailView = | "hosts" | "credentials" | "port-forwarding" - | "sftp" | "termix-id" | "quick-connect" | "serial" diff --git a/src/ui/sidebar/HostEditor.tsx b/src/ui/sidebar/HostEditor.tsx index 6c7f6c73a..96c6954ee 100644 --- a/src/ui/sidebar/HostEditor.tsx +++ b/src/ui/sidebar/HostEditor.tsx @@ -80,7 +80,6 @@ import { type HostProtocols, } from "./HostEditorData"; import { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext"; -import { HostFilesTab } from "./HostEditorFilesTab"; import { HostEditorGeneralTab } from "./HostEditorGeneralTab"; import { canEditHost } from "./host-permissions"; import { VaultProfileManager } from "./VaultProfileManager"; @@ -2517,10 +2516,6 @@ export function HostEditor({ )} - {activeTab === "files" && ( - - )} - {(() => { const section = getHostEditorSection(activeTab); if (!section) return null; diff --git a/src/ui/sidebar/HostManagerTabs.tsx b/src/ui/sidebar/HostManagerTabs.tsx index d7d1c94bb..f2eacb56a 100644 --- a/src/ui/sidebar/HostManagerTabs.tsx +++ b/src/ui/sidebar/HostManagerTabs.tsx @@ -1,7 +1,6 @@ /* eslint-disable react-refresh/only-export-components */ import { useEffect, useRef, type ComponentType, type ReactNode } from "react"; import { - Folder, KeyRound, Network, Settings, @@ -11,8 +10,7 @@ import { import { byOrderThenId, createRegistry } from "@/lib/registry"; /** Core host editor tabs. Plugins add theirs through registerHostEditorSection. */ -export type CoreHostTabId = - "general" | "ssh" | "terminal" | "tunnels" | "files"; +export type CoreHostTabId = "general" | "ssh" | "terminal" | "tunnels"; export type HostTabId = CoreHostTabId | (string & {}); export type CredentialTabId = "general" | "auth"; @@ -68,7 +66,7 @@ export const hostEditorSectionList = sections.list; export const useHostEditorSections = sections.useList; export const resetHostEditorSections = sections.reset; -const CORE_SSH_GROUP = new Set(["ssh", "terminal", "tunnels", "files"]); +const CORE_SSH_GROUP = new Set(["ssh", "terminal", "tunnels"]); /** Whether a tab lives in the SSH group's second strip. */ export function isSshGroupTab(id: string): boolean { @@ -167,12 +165,6 @@ export function makeHostSshSubTabs( icon: , order: 20, }, - { - id: "files", - label: t("hosts.tabFiles"), - icon: , - order: 60, - }, ], "ssh", t, diff --git a/src/ui/sidebar/UserProfilePanel.tsx b/src/ui/sidebar/UserProfilePanel.tsx index 32f13d360..58d116d02 100644 --- a/src/ui/sidebar/UserProfilePanel.tsx +++ b/src/ui/sidebar/UserProfilePanel.tsx @@ -38,12 +38,6 @@ import { shouldForceLocalPreferenceStorage } from "@/settings/remote-sync-state" import { C2STunnelPresetManager } from "@/user/C2STunnelPresetManager"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; -import { - MAX_TRANSFER_CONCURRENCY, - TRANSFER_CONCURRENCY_STORAGE_KEY, - getTransferConcurrency, - setTransferConcurrency, -} from "@/features/file-manager/local-transfer-utils"; import { VersionBadge } from "@/components/version-badge"; import { Dialog, @@ -213,6 +207,40 @@ function apiErrorMessage(error: unknown, fallback: string) { return (error as ApiErrorLike).response?.data?.error || fallback; } +// Local transfer concurrency is the file-manager plugin's own setting; this +// panel just offers the control, using the same storage key the plugin reads. +const TRANSFER_CONCURRENCY_STORAGE_KEY = + "termix:file-manager:transfer-concurrency"; +const DEFAULT_TRANSFER_CONCURRENCY = 4; +const MAX_TRANSFER_CONCURRENCY = 8; + +function clampTransferConcurrency(value: unknown): number { + const n = Math.floor(Number(value)); + if (!Number.isFinite(n)) return DEFAULT_TRANSFER_CONCURRENCY; + return Math.min(MAX_TRANSFER_CONCURRENCY, Math.max(1, n)); +} + +function getTransferConcurrency(): number { + try { + const raw = localStorage.getItem(TRANSFER_CONCURRENCY_STORAGE_KEY); + return raw === null + ? DEFAULT_TRANSFER_CONCURRENCY + : clampTransferConcurrency(raw); + } catch { + return DEFAULT_TRANSFER_CONCURRENCY; + } +} + +function setTransferConcurrency(value: number): number { + const clamped = clampTransferConcurrency(value); + try { + localStorage.setItem(TRANSFER_CONCURRENCY_STORAGE_KEY, String(clamped)); + } catch { + // storage unavailable + } + return clamped; +} + type CreatedProfileApiKey = { id: string; name: string; diff --git a/src/ui/sidebar/rail-items.ts b/src/ui/sidebar/rail-items.ts index afd7ccd07..fdc239af2 100644 --- a/src/ui/sidebar/rail-items.ts +++ b/src/ui/sidebar/rail-items.ts @@ -1,5 +1,4 @@ import { - ArrowLeftRight, Braces, Clock, Fingerprint, @@ -125,12 +124,6 @@ export const RAIL_ITEMS: RailItemDef[] = [ promotable: true, rightDockable: true, }, - { - id: "sftp", - icon: ArrowLeftRight, - labelKey: "nav.sftp", - separatorAfter: true, - }, { id: "macros", icon: Braces, diff --git a/src/ui/sidebar/tree/HostItem/HostItem.tsx b/src/ui/sidebar/tree/HostItem/HostItem.tsx index 74977b7eb..99d6509d0 100644 --- a/src/ui/sidebar/tree/HostItem/HostItem.tsx +++ b/src/ui/sidebar/tree/HostItem/HostItem.tsx @@ -8,7 +8,6 @@ import { Copy, CopyPlus, Cpu, - FolderSearch, GripVertical, Key, KeyRound, @@ -126,13 +125,6 @@ export function getSshActions(host: Host): { order: number; }[] { return [ - host.enableSsh && - host.enableFileManager && { - type: "files" as TabType, - icon: FolderSearch, - label: "Files", - order: 20, - }, host.enableSsh && host.enableTunnel && { type: "tunnel" as TabType, @@ -814,20 +806,6 @@ export function HostItem({ {t("hosts.copyLink")} - {host.enableSsh && host.enableFileManager && ( - { - e.stopPropagation(); - writeClipboardText( - `${window.location.origin}?view=file-manager&hostId=${host.id}`, - ); - toast.success(t("hosts.fileManagerUrlCopied")); - }} - > - - {t("hosts.copyFileManagerUrlAction")} - - )} {host.enableSsh && host.enableTunnel && ( { diff --git a/src/ui/tests/lib/useConnectionRetry.test.ts b/src/ui/tests/lib/useConnectionRetry.test.ts deleted file mode 100644 index 572af4162..000000000 --- a/src/ui/tests/lib/useConnectionRetry.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { renderHook, waitFor } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; -import { - computeReconnectDelay, - useConnectionRetry, -} from "../../lib/useConnectionRetry.ts"; - -describe("connection retry delay", () => { - it("uses exponential backoff with bounded jitter", () => { - expect(computeReconnectDelay(1, 2000, 8000, () => 0.5)).toBe(2000); - expect(computeReconnectDelay(2, 2000, 8000, () => 0)).toBe(3600); - expect(computeReconnectDelay(2, 2000, 8000, () => 1)).toBe(4400); - }); - - it("never exceeds the configured cap", () => { - expect(computeReconnectDelay(8, 2000, 8000, () => 1)).toBe(8000); - }); -}); - -describe("useConnectionRetry", () => { - it("turns a rejected async connection into a failed state", async () => { - const connect = vi.fn().mockRejectedValue(new Error("offline")); - const { result } = renderHook(() => - useConnectionRetry({ connect, enabled: false }), - ); - - await waitFor(() => expect(result.current.status).toBe("error")); - expect(connect).toHaveBeenCalledOnce(); - }); -}); diff --git a/src/ui/tests/shell/shell-layout.test.ts b/src/ui/tests/shell/shell-layout.test.ts index 3aa79c4f9..6d8b954c9 100644 --- a/src/ui/tests/shell/shell-layout.test.ts +++ b/src/ui/tests/shell/shell-layout.test.ts @@ -253,14 +253,27 @@ describe("resolveWorkspaceTabTarget", () => { expect((result as { host?: Host }).host?.syncId).toBe("sync-web-01"); }); - it("skips a non-singleton, non-serial type with no resolvable host", () => { + it("skips a non-singleton, non-serial, non-plugin type with no resolvable host", () => { + const snapshot: WorkspaceTabSnapshot = { + slotId: "s1", + type: "host-manager", + label: "host-manager", + }; + expect(resolveWorkspaceTabTarget(snapshot, hosts)).toEqual({ + kind: "skip", + }); + }); + + it("keeps a saved tab whose owning plugin is not running as a placeholder", () => { + // "files" is the file-manager plugin's tab type. Nothing registered it in + // this test, matching the plugin being disabled, failed or still loading. const snapshot: WorkspaceTabSnapshot = { slotId: "s1", type: "files", label: "files", }; expect(resolveWorkspaceTabTarget(snapshot, hosts)).toEqual({ - kind: "skip", + kind: "singleton", }); }); }); diff --git a/src/ui/tests/sidebar/rail-items.test.ts b/src/ui/tests/sidebar/rail-items.test.ts index 52418465b..3852e54a5 100644 --- a/src/ui/tests/sidebar/rail-items.test.ts +++ b/src/ui/tests/sidebar/rail-items.test.ts @@ -48,6 +48,7 @@ describe("RAIL_ITEMS", () => { // Guards against a destination silently disappearing now that the rail, // the settings toggles, the sidebar titles and the mobile bar all read // from this one list. + // "sftp" moved to the file-manager plugin's own registerRailItem. expect(RAIL_ITEMS.map((item) => item.id)).toEqual([ "hosts", "credentials", @@ -58,7 +59,6 @@ describe("RAIL_ITEMS", () => { "quick-connect", "serial", "ssh-tools", - "sftp", "macros", "history", "session-logs", diff --git a/src/ui/tests/sidebar/tree/HostItem/HostItem.test.tsx b/src/ui/tests/sidebar/tree/HostItem/HostItem.test.tsx index d42ec5999..711c3f7e6 100644 --- a/src/ui/tests/sidebar/tree/HostItem/HostItem.test.tsx +++ b/src/ui/tests/sidebar/tree/HostItem/HostItem.test.tsx @@ -219,16 +219,16 @@ describe("HostItem density parity", () => { it("learns repeated local actions and preloads the preferred host tool", () => { renderHostItem("comfortable"); - const filesButton = screen.getByTitle("Files"); - fireEvent.click(filesButton); - fireEvent.click(filesButton); - fireEvent.click(filesButton); + const tunnelButton = screen.getByTitle("Tunnel"); + fireEvent.click(tunnelButton); + fireEvent.click(tunnelButton); + fireEvent.click(tunnelButton); const hostRow = screen.getByText("web-01").closest(".cursor-pointer"); expect(hostRow).toBeTruthy(); fireEvent.pointerEnter(hostRow!); expect(preloadTabSurfaceMock).toHaveBeenCalledWith("terminal"); - expect(preloadTabSurfaceMock).toHaveBeenCalledWith("files"); + expect(preloadTabSurfaceMock).toHaveBeenCalledWith("tunnel"); }); });