Compare commits

..
Author SHA1 Message Date
LukeGus 62047dee5f chore: run prettier 2026-07-23 23:52:42 -05:00
Brad BakerCopilot Autofix powered by AIcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
db0dea08cf fix tmux-monitor tailscale issue (#1076)
* Initial plan

* fix(tmux-monitor): explicitly handle tailscale auth in PanePreview hostConfig

For Tailscale-auth hosts the pane-preview attach path was building the
Terminal hostConfig with only the generic spread of host fields.  This
could omit or mismap auth-critical details and trigger a plain TCP/SSH
reachability path that doesn't work with Tailscale-only SSH endpoints.

The fix branches on `host.authType === "tailscale"` and:
- Carries `authType: "tailscale"` explicitly so the backend always selects
  the Tailscale-aware PTY path regardless of how the host object evolves.
- Derives `port` from `host.sshPort ?? host.port` so Tailscale SSH
  endpoints on a non-default SSH port are reached correctly.
- Leaves all non-tailscale auth types on the unchanged code path.

Reattach (bumping instanceId + attachNonce) continues to work because
terminalHostConfig is recomputed on every render with the latest
instanceIdRef.current value.

* refactor(tmux-monitor): simplify tailscale port logic with extracted variable

Address code review feedback: extract resolvedPort into a local variable
to avoid the duplicated `host.sshPort ?? host.port` expression that was
assigned to both `port` and `sshPort`.  Restructure as an if/else block
instead of an IIFE for readability.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-23 18:40:02 -05:00
Brad Baker 096db5c636 fix(alerts): send channel config as object payload instead of JSON string (#1075) 2026-07-23 13:45:33 -05:00
9b7f52b629 Fix Guacamole tab visibility lifecycle (#1074)
Co-authored-by: default-student <default-student@github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
2026-07-22 14:37:44 -05:00
LukeGus 08825c256d feat: rework Electron desktop app to run standalone-first with optional two-way sync to a remote Termix server 2026-07-21 01:27:03 -05:00
LukeGus f44d09eef7 feat: add multiplayer/shared sessions for terminal and guacd 2026-07-20 22:18:54 -05:00
LukeGus 36ab7e4872 fix: incorrect version sent to posthog 2026-07-20 14:13:03 -05:00
LukeGus c1c06272d7 fix: general qol additions and new analytics/telemetrics feature 2026-07-20 13:29:03 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 00c0fe7cab chore(deps): bump the major-updates group with 2 updates (#1072)
Bumps the major-updates group with 2 updates: [nanoid](https://github.com/ai/nanoid) and [typescript](https://github.com/microsoft/TypeScript).


Updates `nanoid` from 5.1.16 to 6.0.0
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.16...6.0.0)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/commits)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 02:11:36 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 09aa75b51b chore(deps): bump ws in the prod-patch-updates group (#1071)
Bumps the prod-patch-updates group with 1 update: [ws](https://github.com/websockets/ws).


Updates `ws` from 8.21.0 to 8.21.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.21.0...8.21.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.21.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 02:10:57 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1441ffef99 chore(deps-dev): bump the dev-patch-updates group with 28 updates (#1069)
Bumps the dev-patch-updates group with 28 updates:

| Package | From | To |
| --- | --- | --- |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.5.2` | `2.5.4` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.5` | `6.43.6` |
| [@radix-ui/react-accordion](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/accordion) | `1.2.15` | `1.2.17` |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/alert-dialog) | `1.1.18` | `1.1.20` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.6` | `1.3.8` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.18` | `1.1.20` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.19` | `2.1.21` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.11` | `2.1.12` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.18` | `1.1.20` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.11` | `1.1.13` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.13` | `1.2.15` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.3.2` | `2.3.4` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.11` | `1.1.12` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.4.2` | `1.4.4` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.3.2` | `1.3.4` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.16` | `1.1.18` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.11` | `1.2.13` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.2` | `4.3.3` |
| [@uiw/codemirror-extensions-langs](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` |
| [@uiw/codemirror-theme-github](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` |
| [@uiw/react-codemirror](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `4.1.9` | `4.1.10` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.9` | `4.1.10` |
| [i18next](https://github.com/i18next/i18next) | `26.3.4` | `26.3.6` |
| [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) | `1.6.1` | `1.6.3` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.8` | `17.0.10` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.2` | `4.3.3` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.9` | `4.1.10` |


Updates `@biomejs/biome` from 2.5.2 to 2.5.4
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.4/packages/@biomejs/biome)

Updates `@codemirror/view` from 6.43.5 to 6.43.6
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)

Updates `@radix-ui/react-accordion` from 1.2.15 to 1.2.17
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/accordion/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/accordion)

Updates `@radix-ui/react-alert-dialog` from 1.1.18 to 1.1.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/alert-dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/alert-dialog)

Updates `@radix-ui/react-checkbox` from 1.3.6 to 1.3.8
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/checkbox)

Updates `@radix-ui/react-dialog` from 1.1.18 to 1.1.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)

Updates `@radix-ui/react-dropdown-menu` from 2.1.19 to 2.1.21
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu)

Updates `@radix-ui/react-label` from 2.1.11 to 2.1.12
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label)

Updates `@radix-ui/react-popover` from 1.1.18 to 1.1.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover)

Updates `@radix-ui/react-progress` from 1.1.11 to 1.1.13
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/progress/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/progress)

Updates `@radix-ui/react-scroll-area` from 1.2.13 to 1.2.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/scroll-area/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/scroll-area)

Updates `@radix-ui/react-select` from 2.3.2 to 2.3.4
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select)

Updates `@radix-ui/react-separator` from 1.1.11 to 1.1.12
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/separator/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/separator)

Updates `@radix-ui/react-slider` from 1.4.2 to 1.4.4
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slider/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slider)

Updates `@radix-ui/react-switch` from 1.3.2 to 1.3.4
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch)

Updates `@radix-ui/react-tabs` from 1.1.16 to 1.1.18
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs)

Updates `@radix-ui/react-tooltip` from 1.2.11 to 1.2.13
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip)

Updates `@tailwindcss/vite` from 4.3.2 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-vite)

Updates `@uiw/codemirror-extensions-langs` from 4.25.10 to 4.25.11
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11)

Updates `@uiw/codemirror-theme-github` from 4.25.10 to 4.25.11
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11)

Updates `@uiw/react-codemirror` from 4.25.10 to 4.25.11
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11)

Updates `@vitest/coverage-v8` from 4.1.9 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/coverage-v8)

Updates `@vitest/ui` from 4.1.9 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/ui)

Updates `i18next` from 26.3.4 to 26.3.6
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.3.4...v26.3.6)

Updates `radix-ui` from 1.6.1 to 1.6.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui)

Updates `react-i18next` from 17.0.8 to 17.0.10
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.8...v17.0.10)

Updates `tailwindcss` from 4.3.2 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss)

Updates `vitest` from 4.1.9 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/vitest)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@codemirror/view"
  dependency-version: 6.43.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-accordion"
  dependency-version: 1.2.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-alert-dialog"
  dependency-version: 1.1.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-checkbox"
  dependency-version: 1.3.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-dialog"
  dependency-version: 1.1.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-version: 2.1.21
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-label"
  dependency-version: 2.1.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-popover"
  dependency-version: 1.1.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-progress"
  dependency-version: 1.1.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-version: 1.2.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-separator"
  dependency-version: 1.1.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-slider"
  dependency-version: 1.4.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-switch"
  dependency-version: 1.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-tabs"
  dependency-version: 1.1.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-tooltip"
  dependency-version: 1.2.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@uiw/codemirror-extensions-langs"
  dependency-version: 4.25.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@uiw/codemirror-theme-github"
  dependency-version: 4.25.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@uiw/react-codemirror"
  dependency-version: 4.25.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitest/ui"
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: i18next
  dependency-version: 26.3.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: radix-ui
  dependency-version: 1.6.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: react-i18next
  dependency-version: 17.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: vitest
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 02:09:53 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 0f671c6f4a ci(deps): bump actions/setup-node in the github-actions group (#1068)
Bumps the github-actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-node` from 6 to 7
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 02:09:15 -05:00
LukeGus 7ba45969f6 fix: general qol additions 2026-07-20 01:28:17 -05:00
LukeGus 8da7b25c81 fix: general bug fixes 2026-07-20 00:38:13 -05:00
111 changed files with 29962 additions and 37651 deletions
+1 -1
View File
@@ -121,7 +121,7 @@ jobs:
CHANGES=$(git log --oneline --no-merges "${{ steps.prev.outputs.sha }}..${{ needs.prep.outputs.sha }}" -- . ':!package-lock.json' | sed 's/^/- /')
fi
if [ -z "$CHANGES" ]; then
CHANGES="- No new commits since the last beta."
CHANGES="- No new commits since the last beta (or this is the first beta build)."
fi
cat > BETA_RELEASE_BODY.md << EOF
-14
View File
@@ -189,20 +189,6 @@ SSH sessions and tabs stay open across devices/refreshes if enabled in user prof
**Languages:**
Built-in support ~30 languages (managed by [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Session Sharing:**
Share a live terminal, RDP, VNC, or Telnet session with others in real time. Share via a link (joined anonymously, no account needed) or with a specific Termix user, and choose read-only or read-write access. Shares can expire automatically or be revoked at any time, and session sharing can be toggled globally or per-host.
</td>
<td width="50%" valign="top">
**Desktop Standalone + 2-Way Sync:**
The Electron desktop app runs fully standalone with its own local backend and database, no server required. Optionally connect it to a remote Termix server for automatic two-way sync of hosts, credentials, snippets, and more, and choose whether SSH connections are started locally or through the remote server.
</td>
</tr>
</table>
+53 -56
View File
@@ -1,73 +1,70 @@
<!-- SUMMARY -->
Standalone-first Electron desktop app with optional remote sync, shared/multiplayer terminal and remote desktop sessions, improved SSH MFA support, custom key shortcuts, bug fixes across terminal, RDP/VNC, mobile, and auth.
Revamped RBAC/sharing, session recording & replay, Vault auth for monitors, API key host enrollment, Proxmox guest auto sync, database refactor, plus 30+ bug fixes across terminal, file manager, RDP/VNC, and auth. DO NOT DOWNGRADE FROM THIS VERSION.
<!-- /SUMMARY -->
<!-- YOUTUBE -->
https://youtu.be/g0QjNdV3YYY
https://youtu.be/c3UD4q2jW_8
<!-- /YOUTUBE -->
<!-- UPDATE_LOG -->
- Added simple telemetrics to PostHog (user count, total hosts across users, and version metrics).
- Reworked Electron desktop app to run standalone-first with a now optional sync to a remote Termix server.
- Added support for starting connections locally or from the remote server on desktop app.
- Added support for custom key shortcuts.
- Added support for more MFA types (SSH-only).
- Added multiplayer/shared sessions for terminals and remote desktop (share via link or user).
- Added support for logging into SSH hosts that require multi-factor authentication (like Duo or JumpCloud push/TOTP prompts).
- Added the option to convert a Quick Connect session into a saved host after connecting.
- Added an export option for sharing host entries without credentials, so a host list can be shared without leaking passwords or keys.
- Unified the folder picker across hosts, credentials, and snippets so they all use the same searchable, create-in-place selector.
- Allowed pasting into the key recording field from the clipboard.
- Allowed sharing hosts that use authentication type "none".
- Allowed setting authentication type "none" on RDP hosts.
- Added the option to show two or more sidebar panels open at the same time.
- Added global custom themes that can be applied across all hosts instead of per host.
- Added a button to quickly create a Credentials entry from a host's existing authentication details.
- Added persistent split screen, so your layout and assigned tabs are restored after closing and reopening the app.
- Added tag matching to host search, so searching now matches tags as well as hostnames.
- Brought back the ability to collapse snippets.
- Added the ability to assign login credentials to an entire folder of hosts instead of one at a time.
- Added the ability to share terminal, VNC, and RDP sessions with other users, including read-only and read-write modes.
- Added the ability to share entire folders of hosts with other users instead of sharing hosts one by one.
- Added the ability to make folders of hosts available to specific users instead of everyone recreating them.
- Reworked SSH credentials to support both a password and an SSH key on the same credential, with an option to auto fill the password when prompted.
- Added a custom group claim option for OIDC login, useful for identity providers like Zitadel that don't use a plain "groups" claim.
- Revamped RBAC/sharing system (new UI, all auth types and host protocols now supported)
- Complete admin control over user information (manage all users hosts, credentials, and snippets)
- Support Vault auth for monitors
- API key host enrollment endpoint
- Allow pinned hosts with name sorting
- Session recording and replay
- Terminal font size shortcuts (ctrl + / -)
- Open File Manager to tab right-click menu
- Proxmox guest auto sync
- Complete database refactor
- 30-day donation reminder and new donation milestones that support research: (donate.termix.site)
- Improve site performance with cache and poll pauses
- Save quick connect sessions as hosts
<!-- /UPDATE_LOG -->
<!-- BUG_FIXES -->
- Fixed the Add Channel dialog failing with "config is required" when adding Webhook or ntfy alert channels.
- Fixed font size and UI scaling being too small even at the largest setting on high resolution displays.
- Fixed the Docker integration not working on hosts using authentication type "none".
- Fixed the latest Russian translation updates from Crowdin not being included in the app.
- Fixed missing Nerd Font symbol support in the Android app.
- Fixed credential changes on RDP hosts not saving properly.
- Fixed credential folders not showing up in the folder dropdown when editing a credential.
- Fixed RDP hosts not using their stored credential and falling back to a direct connection.
- Fixed Cmd + scroll on Mac resizing the terminal instead of scrolling.
- Fixed text repeating itself in the terminal when typing with a wireless keyboard on Android.
- Fixed RDP connections failing when going through a jump host.
- Fixed SSH connections failing when going through a jump host in some setups.
- Fixed OIDC login failing with a database error when the identity provider didn't return a client ID.
- Fixed SSH client-to-server tunnels failing with an authentication error.
- Fixed Host Metrics disk usage only showing the root filesystem and ignoring other mounted disks.
- Fixed Android navigation buttons covering the terminal's top bar keys.
- Fixed Proxmox discovery importing DHCP LXC containers with an IP of 0.0.0.0 instead of their real address.
- Fixed VNC connections to macOS Screen Sharing hanging after the handshake instead of connecting.
- Fixed File Manager delete still failing on Windows hosts running PowerShell 5.1.
- Fixed the terminal dropping characters while typing on iOS.
- Fixed SSH lines like "[username@host]" being wrongly highlighted as a log level and breaking output formatting.
- Fixed RDP touch mode on Android not registering taps as clicks.
- Fixed VNC connections still failing due to a guacd protocol version mismatch.
- Guacamole tab showing "connecting" instead of rendering the desktop.
- Fixed tmux not using Tailscale when starting connections.
- Fixed an invalid websocket frame from causing code 10006 crash triggering restart loop.
- Remove chacha20-poly1305 without native ssh2 binding.
- Corrected SSRF blocklist from false-positive on all IPv4.
- Fixed terminal background image incorrectly displaying.
- Syntax highlighting artifacts
- Filter dashboard status hosts
- Persist dashboard service link changes
- Snippet text overflow
- Persist remote desktop credential auth
- Guard language switching failures
- Resolve tunnel source credentials
- Windows file delete command
- Artifact release checkout ref
- Command palette escape in fullscreen
- Alerts and audit log normalization
- macOS VNC protocol negotiation
- Port knocking before SSH connect
- Allow escape to close link confirmation
- Prevent Electron modifier wheel zoom
- Credential auth optional password
- Retry transient terminal DNS lookups
- OIDC redirect forwarded port handling
- Preserve recent open tabs on startup
- Terminal font selection
- Poor font legibility in multiple places
- File manager uploads failing
- Tmux detection for non-POSTIX shells
- OPKSSH js-yaml ESM import
- Android Vietnamese IME input
- Firefox RDP clipboard paste
- Proxmox discovery over HTTPS
- External editor actions in file preview
- Firefox desktop OIDC callback
- Status checks through jump hosts
- Restore sudo password auto fill settings
- Preserve file editor position on save
- Sync cloud preference storage mode
- Render RDP sessions at native pixel density
- Restore database import in embedded desktop mode
- Command autocomplete dropdown poor contrast
- Allow clipboard paste in key recording field
- Fix GitHub/google SSO "not defined" errors
<!-- /BUG_FIXES -->
-14
View File
@@ -189,20 +189,6 @@ Termix هي منصة مفتوحة المصدر ومجانية للأبد وذا
**اللغات:**
دعم مدمج لحوالي 30 لغة (تُدار بواسطة [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**مشاركة الجلسة:**
شارك جلسة طرفية أو RDP أو VNC أو Telnet مباشرة مع الآخرين في الوقت الفعلي. شارك عبر رابط (الانضمام بشكل مجهول، بدون الحاجة لحساب) أو مع مستخدم Termix محدد، واختر الوصول للقراءة فقط أو للقراءة والكتابة. يمكن أن تنتهي صلاحية المشاركات تلقائيًا أو يتم إلغاؤها في أي وقت، ويمكن تبديل مشاركة الجلسة عالميًا أو لكل مضيف على حدة.
</td>
<td width="50%" valign="top">
**تطبيق سطح مكتب مستقل + مزامنة ثنائية الاتجاه:**
يعمل تطبيق سطح المكتب Electron بشكل مستقل تمامًا مع خلفية وقاعدة بيانات محلية خاصة به، دون الحاجة لخادم. يمكن اختياريًا توصيله بخادم Termix عن بُعد للمزامنة التلقائية ثنائية الاتجاه للمضيفين وبيانات الاعتماد والمقتطفات والمزيد، واختيار ما إذا كانت اتصالات SSH تبدأ محليًا أو عبر الخادم البعيد.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Termix 是一个开源、永久免费、自托管的一体化服务器管理平
**语言:**
内置支持约 30 种语言(由 [Crowdin](https://docs.termix.site/translations) 管理)。
</td>
</tr>
<tr>
<td width="50%" valign="top">
**会话共享:**
与他人实时共享终端、RDP、VNC 或 Telnet 会话。通过链接分享(匿名加入,无需帐户)或与特定的 Termix 用户分享,并选择只读或读写权限。共享可以自动过期或随时撤销,会话共享可以全局或按主机切换。
</td>
<td width="50%" valign="top">
**桌面独立运行 + 双向同步:**
Electron 桌面应用可完全独立运行,拥有自己的本地后端和数据库,无需服务器。也可以选择连接到远程 Termix 服务器,实现主机、凭据、代码片段等的自动双向同步,并选择 SSH 连接是在本地启动还是通过远程服务器启动。
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ SSH-Sitzungen und Tabs bleiben uber Gerate/Aktualisierungen hinweg offen, wenn i
**Sprachen:**
Integrierte Unterstutzung fur ca. 30 Sprachen (verwaltet uber [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Sitzungsfreigabe:**
Teilen Sie eine Live-Terminal-, RDP-, VNC- oder Telnet-Sitzung in Echtzeit mit anderen. Freigabe uber einen Link (anonymer Beitritt, kein Konto erforderlich) oder mit einem bestimmten Termix-Benutzer, mit Wahl zwischen Nur-Lese- oder Lese-/Schreibzugriff. Freigaben konnen automatisch ablaufen oder jederzeit widerrufen werden, und die Sitzungsfreigabe kann global oder pro Host umgeschaltet werden.
</td>
<td width="50%" valign="top">
**Eigenstandiger Desktop + bidirektionale Synchronisierung:**
Die Electron-Desktop-App lauft vollstandig eigenstandig mit eigenem lokalem Backend und eigener Datenbank, kein Server erforderlich. Optional mit einem entfernten Termix-Server verbinden fur automatische bidirektionale Synchronisierung von Hosts, Zugangsdaten, Snippets und mehr, mit der Wahl, ob SSH-Verbindungen lokal oder uber den entfernten Server gestartet werden.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Las sesiones SSH y pestanas permanecen abiertas entre dispositivos/actualizacion
**Idiomas:**
Soporte integrado para aproximadamente 30 idiomas (gestionado por [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Uso compartido de sesion:**
Comparte una sesion en vivo de terminal, RDP, VNC o Telnet con otras personas en tiempo real. Comparte mediante un enlace (se une de forma anonima, sin necesidad de cuenta) o con un usuario especifico de Termix, y elige acceso de solo lectura o de lectura y escritura. Las comparticiones pueden expirar automaticamente o revocarse en cualquier momento, y el uso compartido de sesiones se puede activar globalmente o por host.
</td>
<td width="50%" valign="top">
**Aplicacion de escritorio independiente + sincronizacion bidireccional:**
La aplicacion de escritorio Electron funciona de forma totalmente independiente con su propio backend y base de datos locales, sin necesidad de servidor. Opcionalmente, conectala a un servidor Termix remoto para sincronizacion bidireccional automatica de hosts, credenciales, fragmentos y mas, y elige si las conexiones SSH se inician localmente o a traves del servidor remoto.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Les sessions SSH et les onglets restent ouverts sur tous les appareils/actualisa
**Langues:**
Support integre d'environ 30 langues (gere par [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Partage de session:**
Partagez une session de terminal, RDP, VNC ou Telnet en direct avec d'autres personnes en temps reel. Partagez via un lien (rejoint anonymement, sans compte necessaire) ou avec un utilisateur Termix specifique, et choisissez un acces en lecture seule ou en lecture-ecriture. Les partages peuvent expirer automatiquement ou etre revoques a tout moment, et le partage de session peut etre active globalement ou par hote.
</td>
<td width="50%" valign="top">
**Application de bureau autonome + synchronisation bidirectionnelle:**
L'application de bureau Electron fonctionne de maniere totalement autonome avec son propre backend et sa propre base de donnees locale, sans serveur requis. Connectez-la eventuellement a un serveur Termix distant pour une synchronisation bidirectionnelle automatique des hotes, des identifiants, des extraits de code et plus encore, et choisissez si les connexions SSH sont demarrees localement ou via le serveur distant.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Termix एक ओपन-सोर्स, हमेशा के लिए मु
**भाषाएँ:**
लगभग 30 भाषाओं का बिल्ट-इन सपोर्ट ([Crowdin](https://docs.termix.site/translations) द्वारा प्रबंधित)।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**सेशन शेयरिंग:**
लाइव टर्मिनल, RDP, VNC, या Telnet सेशन को दूसरों के साथ रीयल टाइम में शेयर करें। लिंक के जरिए शेयर करें (गुमनाम रूप से जुड़ें, अकाउंट की जरूरत नहीं) या किसी खास Termix यूजर के साथ, और रीड-ओनली या रीड-राइट एक्सेस चुनें। शेयर अपने आप एक्सपायर हो सकते हैं या कभी भी रद्द किए जा सकते हैं, और सेशन शेयरिंग को ग्लोबली या प्रति होस्ट टॉगल किया जा सकता है।
</td>
<td width="50%" valign="top">
**डेस्कटॉप स्टैंडअलोन + 2-वे सिंक:**
Electron डेस्कटॉप ऐप अपने खुद के लोकल बैकएंड और डेटाबेस के साथ पूरी तरह से स्टैंडअलोन चलता है, किसी सर्वर की जरूरत नहीं। चाहें तो इसे किसी रिमोट Termix सर्वर से कनेक्ट करें ताकि होस्ट्स, क्रेडेंशियल्स, स्निपेट्स और अन्य चीज़ों का ऑटोमैटिक 2-वे सिंक हो सके, और चुनें कि SSH कनेक्शन लोकली शुरू हों या रिमोट सर्वर के जरिए।
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Le sessioni SSH e le schede rimangono aperte tra dispositivi/aggiornamenti se ab
**Lingue:**
Supporto integrato per circa 30 lingue (gestito da [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Condivisione Sessione:**
Condividi una sessione di terminale, RDP, VNC o Telnet dal vivo con altri in tempo reale. Condividi tramite un link (accesso anonimo, nessun account necessario) o con un utente Termix specifico, e scegli l'accesso in sola lettura o lettura/scrittura. Le condivisioni possono scadere automaticamente o essere revocate in qualsiasi momento, e la condivisione della sessione puo essere attivata globalmente o per singolo host.
</td>
<td width="50%" valign="top">
**App Desktop Standalone + Sincronizzazione Bidirezionale:**
L'app desktop Electron funziona in modo completamente autonomo con il proprio backend e database locali, senza bisogno di un server. Facoltativamente, collegala a un server Termix remoto per la sincronizzazione bidirezionale automatica di host, credenziali, snippet e altro, scegliendo se le connessioni SSH vengono avviate localmente o tramite il server remoto.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Tailnetのデバイスをリストしてホストとしてすばやく追加し
**多言語対応:**
約30言語の組み込みサポート([Crowdin](https://docs.termix.site/translations)で管理されています)。
</td>
</tr>
<tr>
<td width="50%" valign="top">
**セッション共有:**
ターミナル、RDP、VNC、Telnetのライブセッションを他のユーザーとリアルタイムで共有できます。リンクで共有(匿名参加、アカウント不要)するか、特定のTermixユーザーと共有し、読み取り専用または読み取り/書き込みアクセスを選択できます。共有は自動的に期限切れになるか、いつでも取り消すことができ、セッション共有はグローバルまたはホストごとに切り替えられます。
</td>
<td width="50%" valign="top">
**デスクトップスタンドアロン + 双方向同期:**
Electronデスクトップアプリは、独自のローカルバックエンドとデータベースを使用して完全にスタンドアロンで動作し、サーバーは不要です。オプションでリモートのTermixサーバーに接続し、ホスト、認証情報、スニペットなどの自動双方向同期を行い、SSH接続をローカルで開始するかリモートサーバー経由で開始するかを選択できます。
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Tailscale 네트워크의 기기를 나열하여 호스트로 빠르게 추가
**다국어 지원:**
약 30개 언어 내장 지원([Crowdin](https://docs.termix.site/translations)으로 관리).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**세션 공유:**
터미널, RDP, VNC, Telnet 세션을 다른 사람과 실시간으로 공유하세요. 링크를 통해 공유(계정 없이 익명으로 참여)하거나 특정 Termix 사용자와 공유할 수 있으며, 읽기 전용 또는 읽기/쓰기 권한을 선택할 수 있습니다. 공유는 자동으로 만료되거나 언제든지 취소될 수 있으며, 세션 공유는 전역 또는 호스트별로 전환할 수 있습니다.
</td>
<td width="50%" valign="top">
**데스크톱 독립 실행 + 양방향 동기화:**
Electron 데스크톱 앱은 자체 로컬 백엔드와 데이터베이스를 사용하여 서버 없이 완전히 독립적으로 실행됩니다. 선택적으로 원격 Termix 서버에 연결하여 호스트, 자격 증명, 스니펫 등을 자동으로 양방향 동기화하고, SSH 연결을 로컬에서 시작할지 원격 서버를 통해 시작할지 선택할 수 있습니다.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Sessoes SSH e abas permanecem abertas entre dispositivos/atualizacoes se habilit
**Idiomas:**
Suporte integrado para aproximadamente 30 idiomas (gerenciado pelo [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Compartilhamento de sessao:**
Compartilhe uma sessao de terminal, RDP, VNC ou Telnet ao vivo com outras pessoas em tempo real. Compartilhe por meio de um link (entrada anonima, sem necessidade de conta) ou com um usuario especifico do Termix, e escolha acesso somente leitura ou leitura/gravacao. Os compartilhamentos podem expirar automaticamente ou ser revogados a qualquer momento, e o compartilhamento de sessao pode ser ativado globalmente ou por host.
</td>
<td width="50%" valign="top">
**Aplicativo de desktop autonomo + sincronizacao bidirecional:**
O aplicativo de desktop Electron funciona de forma totalmente autonoma com seu proprio backend e banco de dados locais, sem necessidade de servidor. Opcionalmente, conecte-o a um servidor Termix remoto para sincronizacao bidirecional automatica de hosts, credenciais, snippets e muito mais, e escolha se as conexoes SSH sao iniciadas localmente ou por meio do servidor remoto.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ SSH-сессии и вкладки остаются открытыми на вс
**Языки:**
Встроенная поддержка около 30 языков (управляется через [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Общий доступ к сеансу:**
Делитесь сеансом терминала, RDP, VNC или Telnet с другими в режиме реального времени. Делитесь по ссылке (анонимное присоединение, учетная запись не требуется) или с конкретным пользователем Termix, выбирая доступ только для чтения или для чтения и записи. Общий доступ может автоматически истекать или быть отозван в любое время, а общий доступ к сеансам можно включать глобально или для отдельного хоста.
</td>
<td width="50%" valign="top">
**Автономное настольное приложение + двусторонняя синхронизация:**
Настольное приложение на Electron полностью автономно, с собственным локальным бэкендом и базой данных, сервер не требуется. При желании подключите его к удаленному серверу Termix для автоматической двусторонней синхронизации хостов, учетных данных, сниппетов и прочего, и выберите, запускаются ли SSH-соединения локально или через удаленный сервер.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Kullanici profilinde etkinlestirilmisse SSH oturumlari ve sekmeler cihazlar/yeni
**Diller:**
Yaklasik 30 dil icin yerlesik destek ([Crowdin](https://docs.termix.site/translations) tarafindan yonetilir).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Oturum Paylasimi:**
Canli bir terminal, RDP, VNC veya Telnet oturumunu baskalariyla gercek zamanli olarak paylasin. Bir baglanti uzerinden (anonim olarak katilir, hesap gerekmez) veya belirli bir Termix kullanicisiyla paylasin ve salt okunur veya okuma/yazma erisimi secin. Paylasimlar otomatik olarak sona erebilir veya istediginiz zaman iptal edilebilir; oturum paylasimi genel olarak veya sunucu bazinda acilip kapatilabilir.
</td>
<td width="50%" valign="top">
**Bagimsiz Masaustu + Cift Yonlu Senkronizasyon:**
Electron masaustu uygulamasi, kendi yerel arka ucu ve veritabaniyla tamamen bagimsiz calisir, sunucu gerekmez. Istege bagli olarak sunucular, kimlik bilgileri, kod parcaciklari ve daha fazlasinin otomatik cift yonlu senkronizasyonu icin uzak bir Termix sunucusuna baglayin ve SSH baglantilarinin yerel olarak mi yoksa uzak sunucu uzerinden mi baslatilacagini secin.
</td>
</tr>
</table>
-14
View File
@@ -189,20 +189,6 @@ Cac phien SSH va tab van mo tren cac thiet bi/lan lam moi neu duoc bat trong ho
**Ngon Ngu:**
Ho tro tich hop khoang 30 ngon ngu (duoc quan ly boi [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Chia Se Phien:**
Chia se mot phien terminal, RDP, VNC, hoac Telnet truc tiep voi nguoi khac theo thoi gian thuc. Chia se qua lien ket (tham gia an danh, khong can tai khoan) hoac voi mot nguoi dung Termix cu the, va chon quyen truy cap chi doc hoac doc/ghi. Cac lien ket chia se co the tu dong het han hoac bi thu hoi bat cu luc nao, va tinh nang chia se phien co the duoc bat/tat toan cuc hoac theo tung host.
</td>
<td width="50%" valign="top">
**Ung Dung Desktop Doc Lap + Dong Bo 2 Chieu:**
Ung dung desktop Electron chay hoan toan doc lap voi backend va co so du lieu cuc bo rieng, khong can may chu. Tuy chon ket noi voi may chu Termix tu xa de tu dong dong bo 2 chieu cac host, thong tin dang nhap, doan ma va nhieu hon nua, va chon xem cac ket noi SSH duoc khoi tao cuc bo hay thong qua may chu tu xa.
</td>
</tr>
</table>
+101 -139
View File
@@ -442,10 +442,7 @@ function isInvalidCertificateAllowedForUrl(url) {
// fall through
}
// The only remaining "connected remote server" a self-signed/invalid
// certificate could legitimately apply to is the Remote Sync server
// (also used for C2S tunnel relaying, see getC2SRelayUrl).
const config = remoteSync.getRemoteSyncConfig();
const config = getServerConfigSync();
if (!config?.allowInvalidCertificate || !config?.serverUrl) return false;
return getOrigin(url) === getOrigin(config.serverUrl);
@@ -820,62 +817,7 @@ function getBackendDataDir() {
return dataDir;
}
function getBackendPidFilePath() {
return path.join(app.getPath("userData"), "backend.pid");
}
// If the app was previously killed abnormally (crash, force-quit, Task
// Manager) rather than through the normal quit flow, will-quit never fires
// and stopBackendServer() never runs -- the forked backend child is a
// genuinely separate OS process on Windows/mac/Linux, so it keeps running
// and holding every port the backend binds (30001, 30003-30008, 30010,
// 30012...). Every subsequent launch's own backend then fails outright
// with EADDRINUSE and the app is stuck until something manually kills the
// orphan. Reap any such leftover process, identified by PID file, before
// spawning a new one.
function reapOrphanedBackendProcess() {
const pidFilePath = getBackendPidFilePath();
let recordedPid;
try {
recordedPid = parseInt(fs.readFileSync(pidFilePath, "utf8").trim(), 10);
} catch {
return;
}
if (!Number.isInteger(recordedPid) || recordedPid <= 0) return;
try {
// Signal 0 does not kill the process -- it only checks whether a
// process with this PID exists and is signalable, throwing ESRCH if
// not. This avoids killing an unrelated process that happens to have
// reused the same PID since the last run.
process.kill(recordedPid, 0);
} catch {
// No live process at that PID; nothing to reap.
try {
fs.unlinkSync(pidFilePath);
} catch {
// already absent
}
return;
}
logToFile(
`Found orphaned backend process from a previous session (pid ${recordedPid}), terminating it before starting a new one`,
);
try {
process.kill(recordedPid, "SIGKILL");
} catch {
// already gone
}
try {
fs.unlinkSync(pidFilePath);
} catch {
// already absent
}
}
function startBackendServer() {
reapOrphanedBackendProcess();
return new Promise((resolve) => {
const { entryPath, backendCwd } = getBackendPaths();
@@ -917,11 +859,6 @@ function startBackendServer() {
});
logToFile("Backend process spawned, pid:", backendProcess.pid);
try {
fs.writeFileSync(getBackendPidFilePath(), String(backendProcess.pid));
} catch {
// Non-fatal: only means a future crash won't self-heal via reap.
}
let resolved = false;
const readyTimeout = setTimeout(() => {
@@ -953,7 +890,6 @@ function startBackendServer() {
backendStartFailed = true;
}
backendProcess = null;
clearBackendPidFile();
if (!resolved) {
resolved = true;
clearTimeout(readyTimeout);
@@ -973,14 +909,6 @@ function startBackendServer() {
});
}
function clearBackendPidFile() {
try {
fs.unlinkSync(getBackendPidFilePath());
} catch {
// already absent
}
}
function stopBackendServer() {
if (!backendProcess) return;
@@ -1003,7 +931,6 @@ function stopBackendServer() {
backendProcess.on("exit", () => {
clearTimeout(forceKillTimeout);
backendProcess = null;
clearBackendPidFile();
});
}
@@ -1518,16 +1445,6 @@ ipcMain.handle("save-server-config", (event, config) => {
// --- Remote sync (optional desktop <-> self-hosted server sync) ---
// Surfaces the pre-standalone-rework server-config.json (if a serverUrl was
// ever set in it) so the renderer can prompt upgraded installs to set up
// Remote Sync -- their hosts live on that old server and won't appear
// locally until sync is enabled. A fresh install never had this file, so
// this is naturally false for anyone who never used the old architecture.
ipcMain.handle("get-legacy-server-config", () => {
const config = getServerConfigSync();
return { serverUrl: config?.serverUrl || null };
});
ipcMain.handle("get-desktop-settings", () => {
return remoteSync.getDesktopSettings();
});
@@ -1725,33 +1642,43 @@ const C2S_WS_HIGH_WATERMARK = 1024 * 1024;
const C2S_WS_LOW_WATERMARK = 256 * 1024;
const C2S_STREAM_WRITE_LIMIT = 8 * 1024 * 1024;
// C2S (client-to-server) tunnels relay through a connected, self-hosted
// Termix server -- the same "remote server" concept Remote Sync connects
// to, not the always-local embedded backend. There's no separate C2S
// server-URL setting in the UI; it has always shared whatever remote
// server the rest of the app was pointed at. Before the standalone-first
// rework that was server-config.json; now it's remote-sync-config.json,
// since that's the only remaining notion of "a connected remote server."
function getC2SRelayUrl() {
const config = remoteSync.getRemoteSyncConfig();
const serverUrl = config?.serverUrl;
const config = getServerConfigSync();
const serverUrl =
config?.serverUrl || (!isDev ? "http://127.0.0.1:30003" : null);
if (!serverUrl) {
throw new Error(
"No remote Termix server connected -- enable Remote Sync first",
);
throw new Error("No Termix server configured");
}
const base = serverUrl.replace(/\/$/, "");
const relayHttpUrl = `${base}/ssh/tunnel/c2s/stream`;
const relayHttpUrl = base.endsWith(":30003")
? `${base}/ssh/tunnel/c2s/stream`
: `${base}/ssh/tunnel/c2s/stream`;
return relayHttpUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
}
async function getC2SRelayHeaders() {
const jwt = remoteSync.getRemoteSyncJwt();
async function getC2SRelayHeaders(relayUrl) {
const cookieUrl = relayUrl
.replace(/^ws:/, "http:")
.replace(/^wss:/, "https:");
let jwt;
if (mainWindow?.webContents?.session) {
const cookies = await mainWindow.webContents.session.cookies.get({
url: cookieUrl,
name: "jwt",
});
jwt = cookies[0]?.value;
}
if (!jwt) {
jwt = getRememberedElectronAuthCookie("jwt", cookieUrl)?.value;
}
if (!jwt) return {};
return {
Authorization: `Bearer ${jwt}`,
Cookie: `jwt=${encodeURIComponent(jwt)}`,
};
}
@@ -1851,7 +1778,7 @@ async function openC2SRelay(
) {
const tunnelName = tunnel.name || getC2STunnelName(tunnel);
const relayUrl = getC2SRelayUrl();
const headers = await getC2SRelayHeaders();
const headers = await getC2SRelayHeaders(relayUrl);
logToFile(`[c2s] opening relay for ${tunnelName}`, {
relayUrl,
targetHost,
@@ -1956,7 +1883,7 @@ async function openC2SRelay(
async function testC2SRelay(tunnel, targetHost, targetPort) {
const relayUrl = getC2SRelayUrl();
const headers = await getC2SRelayHeaders();
const headers = await getC2SRelayHeaders(relayUrl);
const ws = new WebSocket(
relayUrl,
getWebSocketOptions(relayUrl, { headers }),
@@ -2235,7 +2162,7 @@ async function startC2SRemoteTunnel(tunnel, index = 0) {
}
const relayUrl = getC2SRelayUrl();
const headers = await getC2SRelayHeaders();
const headers = await getC2SRelayHeaders(relayUrl);
const ws = new WebSocket(
relayUrl,
getWebSocketOptions(relayUrl, { headers }),
@@ -2917,33 +2844,31 @@ ipcMain.handle("close-external-editor", (_event, editId) => {
ipcMain.handle("test-server-connection", async (event, serverUrl) => {
try {
const normalizedServerUrl = serverUrl.replace(/\/$/, "");
const healthUrl = `${normalizedServerUrl}/health`;
// This is a best-effort reachability probe, not a hard gate: a reverse
// proxy doing SSO in front of the real server (Pangolin, Authelia,
// Cloudflare Access, etc.) intercepts this unauthenticated request
// before it ever reaches Termix's own /health route, and returns its
// own login page (HTML, or a redirect) instead of {"status":"ok"}.
// That's a legitimate, working setup -- the login iframe shown right
// after this check is what actually proves the server is real, by
// completing an authenticated round-trip. So any response at all here
// (any status code, any body) means "something is there, let the user
// proceed"; only a network-level failure (nothing answered at all)
// blocks continuing.
try {
const response = await httpFetch(healthUrl, {
method: "GET",
timeout: 10000,
});
const data = await response.text();
const looksLikeHtml =
data.includes("<html") ||
data.includes("<!DOCTYPE") ||
data.includes("<head>") ||
data.includes("<body>");
if (response.ok) {
const data = await response.text();
if (
data.includes("<html") ||
data.includes("<!DOCTYPE") ||
data.includes("<head>") ||
data.includes("<body>")
) {
return {
success: false,
error:
"Server returned HTML instead of JSON. This does not appear to be a Termix server.",
};
}
if (response.ok && !looksLikeHtml) {
try {
const healthData = JSON.parse(data);
if (
@@ -2963,27 +2888,64 @@ ipcMain.handle("test-server-connection", async (event, serverUrl) => {
console.log("Health endpoint did not return valid JSON");
}
}
// Reachable, but not a recognized Termix health response -- likely a
// proxy/SSO login page in front of the real server. Let the user
// proceed; the login step next will fail clearly if this really
// isn't a Termix server.
return {
success: true,
status: response.status,
testedUrl: healthUrl,
warning: looksLikeHtml
? "Could not confirm this is a Termix server (the response looked like an HTML page, which can happen behind a login-protected reverse proxy). You can continue, and the next step will fail clearly if this isn't actually a Termix server."
: "Server responded, but not with the expected health check format. Continuing anyway.",
};
} catch (urlError) {
console.error("Health check failed:", urlError);
return {
success: false,
error:
"Server is not responding. Please ensure the server is running and accessible.",
};
}
try {
const versionUrl = `${normalizedServerUrl}/version`;
const response = await httpFetch(versionUrl, {
method: "GET",
timeout: 10000,
});
if (response.ok) {
const data = await response.text();
if (
data.includes("<html") ||
data.includes("<!DOCTYPE") ||
data.includes("<head>") ||
data.includes("<body>")
) {
return {
success: false,
error:
"Server returned HTML instead of JSON. This does not appear to be a Termix server.",
};
}
try {
const versionData = JSON.parse(data);
if (
versionData &&
(versionData.status === "up_to_date" ||
versionData.status === "requires_update" ||
(versionData.localVersion &&
versionData.version &&
versionData.latest_release))
) {
return {
success: true,
status: response.status,
testedUrl: versionUrl,
warning:
"Health endpoint not available, but server appears to be running",
};
}
} catch (parseError) {
console.log("Version endpoint did not return valid JSON");
}
}
} catch (versionError) {
console.error("Version check failed:", versionError);
}
return {
success: false,
error:
"Server is not responding or does not appear to be a valid Termix server. Please ensure the server is running and accessible.",
};
} catch (error) {
return { success: false, error: error.message };
}
+3 -46
View File
@@ -69,7 +69,6 @@ function getRemoteSyncStatePath() {
function getDesktopSettings() {
return readJson(getDesktopSettingsPath(), {
defaultConnectionOrigin: "local",
migrationNoticeAcknowledged: false,
});
}
@@ -230,18 +229,8 @@ class RemoteSyncEngine {
return this.status;
}
if (!this.localJwt) {
// Local login hasn't handed us a token yet -- this is expected for the
// first tick or two right after a cold boot (renderer hasn't finished
// its own session check yet), but if it never arrives (e.g. a gap in
// whichever code path establishes the local session), sync would
// otherwise silently no-op forever with no visible error. Surface it
// as a normal, non-alarming "not synced yet" status rather than
// leaving lastSyncedAt/lastError untouched.
this.updateStatus({
connected: true,
syncing: false,
lastError: "Waiting for local session",
});
// Local login hasn't handed us a token yet (e.g. very early after
// boot) -- skip this tick rather than fail loudly.
return this.status;
}
@@ -332,39 +321,7 @@ class RemoteSyncEngine {
if (!res.ok) {
throw new Error(`Request failed (${res.status}): ${url}`);
}
const text = await res.text();
// A reverse-proxy SSO in front of the remote server (Pangolin, Authelia,
// etc.) can intercept even an authenticated, Bearer-token'd request and
// serve its own login page instead of forwarding to Termix -- that comes
// back as a normal 200 OK, so the status checks above don't catch it.
// This is NOT the same as needsReauth/a bad Termix JWT: sync runs as a
// plain server-to-server fetch() in this main process, with no browser
// cookie jar at all, so re-authenticating through the login iframe (which
// only affects the renderer's browser session) can never fix this --
// reconnecting would tell the user to do something that doesn't help.
// The proxy has to allow this traffic through some other way (an API
// bypass rule, a separate hostname/port that isn't proxy-gated, etc.),
// so this gets its own distinct, honest error rather than piggybacking
// on needsReauth or a raw JSON.parse crash.
const looksLikeHtml =
text.includes("<html") ||
text.includes("<!DOCTYPE") ||
text.includes("<head>") ||
text.includes("<body>");
if (looksLikeHtml) {
const err = new Error(
"The reverse proxy in front of this server is blocking sync traffic with its own login page. Reconnecting won't fix this -- the proxy needs to let Termix's API requests through (e.g. an SSO bypass rule for the sync API, or a non-proxied hostname/port for it).",
);
err.proxyBlocked = true;
throw err;
}
try {
return JSON.parse(text);
} catch {
throw new Error(`Server returned invalid JSON: ${url}`);
}
return res.json();
}
async pullSide(baseUrl, token, entityType, since) {
-1
View File
@@ -775,7 +775,6 @@ const migrateSchema = () => {
addColumnIfNotExists("user_preferences", "compact_host_view", "INTEGER");
addColumnIfNotExists("user_preferences", "status_color_scheme", "TEXT");
addColumnIfNotExists("user_preferences", "custom_themes", "TEXT");
addColumnIfNotExists("user_preferences", "custom_keybindings", "TEXT");
sqlite.exec(`
CREATE TABLE IF NOT EXISTS dashboard_service_links (
-1
View File
@@ -892,7 +892,6 @@ export const userPreferences = sqliteTable("user_preferences", {
compactHostView: integer("compact_host_view", { mode: "boolean" }),
statusColorScheme: text("status_color_scheme"),
customThemes: text("custom_themes"),
customKeybindings: text("custom_keybindings"),
updatedAt: text("updated_at")
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
@@ -145,7 +145,7 @@ export class CredentialRepository {
): Promise<number> {
const rows = await this.context.drizzle
.update(sshCredentials)
.set({ folder: newName, updatedAt: sql`CURRENT_TIMESTAMP` })
.set({ folder: newName })
.where(
and(
eq(sshCredentials.userId, userId),
@@ -168,7 +168,7 @@ export class CredentialRepository {
): Promise<CredentialRecord | null> {
const rows = await this.context.drizzle
.update(sshCredentials)
.set({ ...update, updatedAt: sql`CURRENT_TIMESTAMP` })
.set(update)
.where(
and(
eq(sshCredentials.id, credentialId),
@@ -195,7 +195,7 @@ export class CredentialRepository {
const rows = await this.context.drizzle
.update(sshCredentials)
.set({ ...encryptedUpdate, updatedAt: sql`CURRENT_TIMESTAMP` })
.set(encryptedUpdate)
.where(
and(
eq(sshCredentials.id, credentialId),
@@ -1,4 +1,4 @@
import { and, eq, inArray, sql } from "drizzle-orm";
import { and, eq, inArray } from "drizzle-orm";
import { randomUUID } from "crypto";
import { hostAccess, hosts } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
@@ -152,7 +152,7 @@ export class HostRepository {
): Promise<HostRecord | null> {
const rows = await this.context.drizzle
.update(hosts)
.set({ ...update, updatedAt: sql`CURRENT_TIMESTAMP` })
.set(update)
.where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)))
.returning();
@@ -175,7 +175,7 @@ export class HostRepository {
const rows = await this.context.drizzle
.update(hosts)
.set({ ...encryptedUpdate, updatedAt: sql`CURRENT_TIMESTAMP` })
.set(encryptedUpdate)
.where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)))
.returning();
@@ -215,7 +215,7 @@ export class HostRepository {
const rows = await this.context.drizzle
.update(hosts)
.set({ ...update, updatedAt: sql`CURRENT_TIMESTAMP` })
.set(update)
.where(and(inArray(hosts.id, hostIds), eq(hosts.userId, userId)))
.returning({ id: hosts.id });
@@ -1,54 +0,0 @@
import type { Request } from "express";
import type { UserRecord } from "../repositories/user-repository.js";
export function isLoopbackRequest(req: Request): boolean {
const ip = req.ip || req.socket?.remoteAddress || "";
return (
ip === "127.0.0.1" ||
ip === "::1" ||
ip === "::ffff:127.0.0.1" ||
ip.endsWith(":127.0.0.1")
);
}
export function extractBearerOrCookieToken(req: Request): string | undefined {
const cookieToken = (req as Request & { cookies?: Record<string, string> })
.cookies?.jwt;
if (cookieToken) return cookieToken;
const authHeader = req.headers["authorization"];
if (authHeader?.startsWith("Bearer ")) {
return authHeader.slice("Bearer ".length);
}
return undefined;
}
/**
* Decides who the desktop auto-session endpoint should silently log in as.
*
* The local embedded backend's trust boundary is machine access (loopback),
* not any individual account's credentials -- anyone who can reach loopback
* already has full filesystem access to the local, encrypted-at-rest
* database and its keys. A login form must never appear for the local
* backend, under any circumstance, including a local database that ended
* up with more than one user (e.g. from repeated manual registration
* during testing, or a household sharing one install) -- a user in that
* state deserves to get into the app they installed, not a confusing,
* unexplained dead end. So this always returns a single, deterministic
* user: the admin account if one exists, else the earliest-registered
* account. It never returns null.
*/
export function resolveDesktopAutoSessionUser(
allUsers: UserRecord[],
): UserRecord | null {
if (allUsers.length === 0) return null;
if (allUsers.length === 1) return allUsers[0];
const admin = allUsers.find((user) => user.isAdmin);
if (admin) return admin;
return [...allUsers].sort(
(a, b) =>
new Date(a.registeredAt).getTime() - new Date(b.registeredAt).getTime(),
)[0];
}
@@ -225,9 +225,6 @@ export function stripSensitiveFields(
result.hasKeyPassword = !!host.keyPassword;
result.hasPassword = !!host.password;
result.hasSudoPassword = !!host.sudoPassword;
result.hasRdpPassword = !!host.rdpPassword;
result.hasVncPassword = !!host.vncPassword;
result.hasTelnetPassword = !!host.telnetPassword;
for (const field of SENSITIVE_FIELDS) {
delete result[field];
}
+1 -3
View File
@@ -1498,7 +1498,7 @@ router.get(
* name: field
* schema:
* type: string
* enum: [password, sudoPassword, rdpPassword, vncPassword, telnetPassword, key, keyPassword]
* enum: [password, sudoPassword, vncPassword, key, keyPassword]
* responses:
* 200:
* description: The requested password value.
@@ -1518,9 +1518,7 @@ router.get(
![
"password",
"sudoPassword",
"rdpPassword",
"vncPassword",
"telnetPassword",
"key",
"keyPassword",
].includes(field)
@@ -1,49 +0,0 @@
const VALID_ACTION_TYPES = [
"copy",
"paste",
"sendControlCode",
"sendText",
"runSnippet",
];
export function isValidKeyCombo(combo: unknown): boolean {
return (
!!combo &&
typeof combo === "object" &&
typeof (combo as { key?: unknown }).key === "string" &&
typeof (combo as { isCode?: unknown }).isCode === "boolean" &&
typeof (combo as { ctrl?: unknown }).ctrl === "boolean" &&
typeof (combo as { alt?: unknown }).alt === "boolean" &&
typeof (combo as { shift?: unknown }).shift === "boolean" &&
typeof (combo as { meta?: unknown }).meta === "boolean"
);
}
export function isValidKeybindingAction(action: unknown): boolean {
if (!action || typeof action !== "object") return false;
const type = (action as { type?: unknown }).type;
if (typeof type !== "string" || !VALID_ACTION_TYPES.includes(type))
return false;
if (type === "sendText") {
return typeof (action as { text?: unknown }).text === "string";
}
if (type === "sendControlCode") {
const code = (action as { controlCode?: unknown }).controlCode;
return typeof code === "string" && /^[a-zA-Z]$/.test(code);
}
if (type === "runSnippet") {
return typeof (action as { snippetId?: unknown }).snippetId === "string";
}
return true;
}
export function isValidKeybinding(entry: unknown): boolean {
return (
!!entry &&
typeof entry === "object" &&
typeof (entry as { id?: unknown }).id === "string" &&
typeof (entry as { enabled?: unknown }).enabled === "boolean" &&
isValidKeyCombo((entry as { combo?: unknown }).combo) &&
isValidKeybindingAction((entry as { action?: unknown }).action)
);
}
@@ -8,7 +8,6 @@ import type {
UserPreferenceRecord,
UserPreferenceUpdate,
} from "../repositories/user-preference-repository.js";
import { isValidKeybinding } from "./keybinding-validation.js";
const router = express.Router();
const authManager = AuthManager.getInstance();
@@ -35,7 +34,6 @@ const pickPreferences = (row?: UserPreferenceRecord | null) => ({
compactHostView: row?.compactHostView ?? null,
statusColorScheme: row?.statusColorScheme ?? null,
customThemes: row?.customThemes ?? null,
customKeybindings: row?.customKeybindings ?? null,
});
/**
@@ -113,10 +111,6 @@ const pickPreferences = (row?: UserPreferenceRecord | null) => ({
* type: string
* nullable: true
* description: JSON-encoded array of the user's saved global custom terminal themes.
* customKeybindings:
* type: string
* nullable: true
* description: JSON-encoded array of the user's custom terminal keybindings.
*/
router.get("/", authenticateJWT, async (req: Request, res: Response) => {
const userId = (req as AuthenticatedRequest).userId;
@@ -189,9 +183,6 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
* customThemes:
* type: string
* description: JSON-encoded array of the user's saved global custom terminal themes.
* customKeybindings:
* type: string
* description: JSON-encoded array of the user's custom terminal keybindings.
* responses:
* 200:
* description: Preferences updated successfully.
@@ -219,7 +210,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
compactHostView,
statusColorScheme,
customThemes,
customKeybindings,
} = req.body as {
reopenTabsOnLogin?: boolean;
theme?: string | null;
@@ -241,7 +231,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
compactHostView?: boolean | null;
statusColorScheme?: string | null;
customThemes?: string | null;
customKeybindings?: string | null;
};
const updates: UserPreferenceUpdate = {
@@ -266,7 +255,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
hiddenRailTabs,
statusColorScheme,
customThemes,
customKeybindings,
})) {
if (value !== undefined && value !== null && typeof value !== "string") {
return res.status(400).json({ error: `${key} must be a string` });
@@ -301,28 +289,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
}
}
if (customKeybindings !== undefined && customKeybindings !== null) {
let parsedKeybindings: unknown;
try {
parsedKeybindings = JSON.parse(customKeybindings);
} catch {
return res
.status(400)
.json({ error: "customKeybindings must be a JSON-encoded array" });
}
if (!Array.isArray(parsedKeybindings) || parsedKeybindings.length > 200) {
return res.status(400).json({
error: "customKeybindings must be a JSON array of at most 200 bindings",
});
}
if (!parsedKeybindings.every(isValidKeybinding)) {
return res.status(400).json({
error:
"Each custom keybinding must have an id, enabled flag, valid combo, and valid action",
});
}
}
const boolFields: Record<string, boolean | null | undefined> = {
commandAutocomplete,
commandPaletteEnabled,
@@ -368,8 +334,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
if (statusColorScheme !== undefined)
updates.statusColorScheme = statusColorScheme;
if (customThemes !== undefined) updates.customThemes = customThemes;
if (customKeybindings !== undefined)
updates.customKeybindings = customKeybindings;
if (Object.keys(updates).length === 1) {
return res.status(400).json({ error: "No preferences provided" });
+33 -20
View File
@@ -18,11 +18,6 @@ import {
isOidcTokenCallback,
} from "../../utils/oidc-desktop-callback.js";
import { deleteUserAndRelatedData } from "./delete-user-data.js";
import {
isLoopbackRequest,
extractBearerOrCookieToken,
resolveDesktopAutoSessionUser,
} from "./desktop-auto-session.js";
import { shouldShowDonationModal } from "./donation-modal-utils.js";
import {
getOIDCConfigFromEnv,
@@ -1853,13 +1848,9 @@ router.post(
* description: Not authenticated.
*/
router.get("/me/token", authenticateJWT, (req: Request, res: Response) => {
// authenticateJWT accepts either the jwt cookie or an Authorization:
// Bearer header (see auth-manager.ts's createAuthMiddleware) -- this must
// check both too, or a request that only carried the header (e.g. the
// Electron renderer's own axios interceptor, which always attaches a
// stored localStorage JWT as a Bearer header) would pass authentication
// here but still get back a null token.
res.json({ token: extractBearerOrCookieToken(req) ?? null });
const token = (req as Request & { cookies: Record<string, string> }).cookies
?.jwt;
res.json({ token: token || null });
});
/**
@@ -1889,19 +1880,29 @@ router.get("/setup-required", async (req, res) => {
}
});
function isLoopbackRequest(req: Request): boolean {
const ip = req.ip || req.socket?.remoteAddress || "";
return (
ip === "127.0.0.1" ||
ip === "::1" ||
ip === "::ffff:127.0.0.1" ||
ip.endsWith(":127.0.0.1")
);
}
/**
* @openapi
* /users/internal/auto-session:
* post:
* summary: Mint a session for the sole local desktop user
* description: Used by the Electron desktop app to skip the login form entirely when running standalone against the embedded local backend. Only available over loopback. Logs in as the sole local user regardless of its credentials; if the local database has more than one user (e.g. repeated manual registration), deterministically logs in as the admin account, or the earliest-registered account if none is admin -- a login form must never appear for the local backend under any circumstance. Only declines if zero local users exist at all, which normal desktop provisioning never produces. Provisions the resolved user's data-encryption key if missing before minting the session, matching every other login path -- self-heals an account that previously ended up with a valid session but no usable encryption key.
* summary: Mint a session for the auto-provisioned local desktop user
* description: Used by the Electron desktop app to skip the login form when running standalone with a single auto-provisioned local user. Only available over loopback and only when exactly one user exists -- a real multi-user or synced install never satisfies this, so no further secret is required.
* tags:
* - Users
* responses:
* 200:
* description: Session created.
* 403:
* description: Forbidden, or no local users exist.
* description: Forbidden, or more than one user exists.
* 500:
* description: Failed to create session.
*/
@@ -1917,14 +1918,26 @@ router.post("/internal/auto-session", async (req, res) => {
const userRepository = createCurrentUserRepository();
const allUsers = await userRepository.listAll();
const userRecord = resolveDesktopAutoSessionUser(allUsers);
if (!userRecord) {
if (allUsers.length !== 1) {
return res.status(403).json({
error: "No local users exist",
error: "Auto-session is only available for a single local user",
});
}
await authManager.registerUser(userRecord.id);
const existingToken = extractBearerOrCookieToken(req);
const userRecord = allUsers[0];
// If the caller already holds a still-valid session for this same
// user (e.g. a duplicate call racing the first one, such as React
// StrictMode's double-invoke of effects in dev), reuse it instead of
// minting a fresh one. Minting unconditionally here would set a new
// `jwt` cookie on every call; since the auth middleware prefers the
// cookie over the Authorization header, a second mint silently
// invalidates whatever token the app already started using.
const existingToken =
(req as Request & { cookies?: Record<string, string> }).cookies?.jwt ||
(req.headers["authorization"]?.startsWith("Bearer ")
? req.headers["authorization"].slice("Bearer ".length)
: undefined);
if (existingToken) {
const existingPayload = await authManager.verifyJWTToken(existingToken);
if (existingPayload?.userId === userRecord.id) {
+16 -42
View File
@@ -117,24 +117,6 @@ export class SSHAuthManager {
return;
}
// JumpCloud Protect / DUO-style push MFA: a menu choice ("Choose [1] Push,
// or [2] TOTP:") followed by an empty-answerable confirm ("Press enter to
// send Push request:"). Checked before the TOTP regex because the menu
// prompt's own text ("...or [2] TOTP:") would otherwise match it and get
// misrouted into the numeric-code flow.
const pushPromptPattern =
/choose.*push.*totp|press enter.*(push|send)|push notification|authentication by phone/i;
const isPushPrompt = promptTexts.some((p) => pushPromptPattern.test(p));
if (isPushPrompt) {
sshLogger.info("Push/menu MFA prompt detected", {
operation: "ssh_keyboard_interactive_push",
hostId: this.context.hostId,
});
this.handlePasswordAuth(prompts, finish, resolvedCredentials, hostConfig);
return;
}
const totpPromptIndex = prompts.findIndex((p) =>
/verification code|verification_code|token|otp|2fa|authenticator|google.*auth/i.test(
p.prompt,
@@ -331,10 +313,6 @@ export class SSHAuthManager {
? passwordPromptIndex
: firstUnansweredIndex;
const pushPromptPattern =
/choose.*push.*totp|press enter.*(push|send)|push notification|authentication by phone/i;
const isPushPrompt = pushPromptPattern.test(prompts[promptIndex].prompt);
this.context.keyboardInteractiveFinish = (userResponses: string[]) => {
const userInput = (userResponses[0] || "").trim();
@@ -355,25 +333,22 @@ export class SSHAuthManager {
clearTimeout(this.context.totpTimeout);
}
this.context.totpTimeout = setTimeout(
() => {
if (this.context.keyboardInteractiveFinish) {
this.context.keyboardInteractiveFinish = null;
this.context.keyboardInteractiveResponded = false;
sshLogger.warn("Password prompt timeout", {
operation: "password_timeout",
hostId: this.context.hostId,
});
this.context.ws.send(
JSON.stringify({
type: "error",
message: "Password verification timeout. Please reconnect.",
}),
);
}
},
isPushPrompt ? 300000 : 180000,
);
this.context.totpTimeout = setTimeout(() => {
if (this.context.keyboardInteractiveFinish) {
this.context.keyboardInteractiveFinish = null;
this.context.keyboardInteractiveResponded = false;
sshLogger.warn("Password prompt timeout", {
operation: "password_timeout",
hostId: this.context.hostId,
});
this.context.ws.send(
JSON.stringify({
type: "error",
message: "Password verification timeout. Please reconnect.",
}),
);
}
}, 180000);
this.sendLog("auth", "info", "Password authentication required");
@@ -381,7 +356,6 @@ export class SSHAuthManager {
JSON.stringify({
type: "password_required",
prompt: prompts[promptIndex].prompt,
echo: prompts[promptIndex].echo,
}),
);
return;
-6
View File
@@ -32,12 +32,6 @@ const wss = new WebSocketServer({
port: 30009,
});
wss.on("error", (error) => {
sshLogger.error("Docker console WebSocket server error", error, {
operation: "wss_error",
});
});
async function detectShell(
session: SSHSession,
containerId: string,
-6
View File
@@ -21,12 +21,6 @@ const authManager = AuthManager.getInstance();
const wss = new WebSocketServer({ port: 30011 });
wss.on("error", (error) => {
sshLogger.error("Serial WebSocket server error", error, {
operation: "wss_error",
});
});
wss.on("connection", async (ws: WebSocket, req) => {
let userId: string | undefined;
-13
View File
@@ -112,12 +112,6 @@ const wss = new WebSocketServer({
port: 30002,
});
wss.on("error", (error) => {
sshLogger.error("WebSocket server error", error, {
operation: "wss_error",
});
});
/**
* Auth path for anonymous share-link guests (?shareToken=<linkToken>).
* Never touches DataCrypto/user credentials - guests join an already-live
@@ -263,13 +257,6 @@ wss.on("connection", async (ws: WebSocket, req) => {
let userId: string | undefined;
let sessionId: string | undefined;
ws.on("error", (error) => {
sshLogger.error("WebSocket connection error", error, {
operation: "ws_error",
sessionId,
});
});
const urlObj = new URL(req.url || "", "http://localhost");
const shareToken = urlObj.searchParams.get("shareToken");
-12
View File
@@ -42,21 +42,9 @@ const c2sRelayWss = new WebSocketServer({
path: "/ssh/tunnel/c2s/stream",
});
c2sRelayWss.on("error", (error) => {
tunnelLogger.error("C2S relay WebSocket server error", error, {
operation: "c2s_relay_wss_error",
});
});
c2sRelayWss.on("connection", (ws, req) => {
let opened = false;
ws.on("error", (error) => {
tunnelLogger.error("C2S relay WebSocket connection error", error, {
operation: "c2s_relay_ws_error",
});
});
ws.once("message", async (raw) => {
try {
const token = extractRequestToken(req);
+1 -15
View File
@@ -23,21 +23,7 @@ async function provisionLocalDesktopUserIfNeeded(): Promise<void> {
const userRepository = createCurrentUserRepository();
const existingCount = await userRepository.countAll();
if (existingCount > 0) {
const allUsers = await userRepository.listAll();
for (const user of allUsers) {
try {
await AuthManager.getInstance().registerUser(user.id);
} catch (dekError) {
systemLogger.error(
"Failed to verify/provision data-encryption key for existing user",
dekError,
{ operation: "desktop_dek_healing", userId: user.id },
);
}
}
return;
}
if (existingCount > 0) return;
const id = crypto.randomUUID();
const { isFirstUser } = await userRepository.createFirstLocalUser({
@@ -220,20 +220,11 @@ describe("HostRepository and CredentialRepository", () => {
).toBe("primary");
expect((await repo.credentials.findById(created.id))?.name).toBe("primary");
// Backdate updated_at so the update's CURRENT_TIMESTAMP bump is
// deterministically observable regardless of clock resolution --
// the sync engine's last-write-wins conflict resolution depends on
// every mutating update actually advancing this column.
repo.sqlite
.prepare("UPDATE ssh_credentials SET updated_at = ? WHERE id = ?")
.run("2000-01-01 00:00:00", created.id);
const updated = await repo.credentials.updateForUser("user-1", created.id, {
folder: "ops",
tags: "linux,admin",
});
expect(updated?.folder).toBe("ops");
expect(updated?.updatedAt).not.toBe("2000-01-01 00:00:00");
expect(
await repo.credentials.findByIdForUser("user-2", created.id),
@@ -348,20 +339,15 @@ describe("HostRepository and CredentialRepository", () => {
expect(raw.password).toBe("user-encrypted-password");
repo.sqlite
.prepare("UPDATE ssh_credentials SET updated_at = ? WHERE id = ?")
.run("2000-01-01 00:00:00", created.id);
await repo.credentials.updateEncryptedForUser("user-1", created.id, {
password: "updated-secret",
});
const updatedRaw = repo.sqlite
.prepare("SELECT password, updated_at FROM ssh_credentials WHERE id = ?")
.get(created.id) as { password: string; updated_at: string };
.prepare("SELECT password FROM ssh_credentials WHERE id = ?")
.get(created.id) as { password: string };
expect(updatedRaw.password).toBe("user-encrypted-password");
expect(updatedRaw.updated_at).not.toBe("2000-01-01 00:00:00");
expect(DataCrypto.encryptRecord).toHaveBeenCalledWith(
"ssh_credentials",
expect.objectContaining({ password: "updated-secret" }),
@@ -392,7 +378,7 @@ describe("HostRepository and CredentialRepository", () => {
const onWrite = vi.fn();
const repo = await createRepositories(onWrite);
const primary = await repo.credentials.create({
await repo.credentials.create({
userId: "user-1",
name: "primary",
authType: "password",
@@ -410,9 +396,6 @@ describe("HostRepository and CredentialRepository", () => {
authType: "password",
folder: "prod",
});
repo.sqlite
.prepare("UPDATE ssh_credentials SET updated_at = ? WHERE id = ?")
.run("2000-01-01 00:00:00", primary.id);
onWrite.mockClear();
await expect(
@@ -422,11 +405,6 @@ describe("HostRepository and CredentialRepository", () => {
expect(await repo.credentials.listFolders("user-1")).toEqual(["ops"]);
expect(await repo.credentials.listFolders("user-2")).toEqual(["prod"]);
expect(onWrite).toHaveBeenCalledTimes(1);
const renamedRow = repo.sqlite
.prepare("SELECT updated_at FROM ssh_credentials WHERE id = ?")
.get(primary.id) as { updated_at: string };
expect(renamedRow.updated_at).not.toBe("2000-01-01 00:00:00");
});
it("returns empty credential reads when user data is locked", async () => {
@@ -467,16 +445,11 @@ describe("HostRepository and CredentialRepository", () => {
(await repo.hosts.listByUserId("user-1")).map((item) => item.id),
).toEqual([host.id]);
repo.sqlite
.prepare("UPDATE ssh_data SET updated_at = ? WHERE id = ?")
.run("2000-01-01 00:00:00", host.id);
const updated = await repo.hosts.updateForUser("user-1", host.id, {
name: "web-1-renamed",
folder: "prod",
});
expect(updated?.name).toBe("web-1-renamed");
expect(updated?.updatedAt).not.toBe("2000-01-01 00:00:00");
expect(await repo.hosts.findByIdForUser("user-2", host.id)).toBeNull();
expect(await repo.hosts.deleteForUser("user-1", host.id)).toEqual({
@@ -517,20 +490,15 @@ describe("HostRepository and CredentialRepository", () => {
expect(raw.password).toBe("encrypted-host-password");
repo.sqlite
.prepare("UPDATE ssh_data SET updated_at = ? WHERE id = ?")
.run("2000-01-01 00:00:00", created.id);
await repo.hosts.updateEncryptedForUser("user-1", created.id, {
password: "updated-secret",
});
const updatedRaw = repo.sqlite
.prepare("SELECT password, updated_at FROM ssh_data WHERE id = ?")
.get(created.id) as { password: string; updated_at: string };
.prepare("SELECT password FROM ssh_data WHERE id = ?")
.get(created.id) as { password: string };
expect(updatedRaw.password).toBe("encrypted-host-password");
expect(updatedRaw.updated_at).not.toBe("2000-01-01 00:00:00");
expect(DataCrypto.encryptRecord).toHaveBeenCalledWith(
"ssh_data",
expect.objectContaining({ password: "updated-secret" }),
@@ -655,9 +623,6 @@ describe("HostRepository and CredentialRepository", () => {
username: "root",
authType: "password",
});
repo.sqlite
.prepare("UPDATE ssh_data SET updated_at = ? WHERE id IN (?, ?)")
.run("2000-01-01 00:00:00", first.id, second.id);
onWrite.mockClear();
const states = await repo.hosts.listBulkUpdateState("user-1", [
@@ -675,12 +640,6 @@ describe("HostRepository and CredentialRepository", () => {
expect((await repo.hosts.findById(first.id))?.folder).toBe("ops");
expect((await repo.hosts.findById(other.id))?.folder).toBeNull();
expect(onWrite).toHaveBeenCalledTimes(1);
expect((await repo.hosts.findById(first.id))?.updatedAt).not.toBe(
"2000-01-01 00:00:00",
);
expect((await repo.hosts.findById(second.id))?.updatedAt).not.toBe(
"2000-01-01 00:00:00",
);
});
it("records credential usage and increments usage counters", async () => {
@@ -48,7 +48,6 @@ describe("UserPreferenceRepository", () => {
compact_host_view INTEGER,
status_color_scheme TEXT,
custom_themes TEXT,
custom_keybindings TEXT,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@@ -1,146 +0,0 @@
import { describe, expect, it } from "vitest";
import type { Request } from "express";
import type { UserRecord } from "../../../database/repositories/user-repository.js";
import {
isLoopbackRequest,
extractBearerOrCookieToken,
resolveDesktopAutoSessionUser,
} from "../../../database/routes/desktop-auto-session.js";
function makeUser(overrides: Partial<UserRecord> = {}): UserRecord {
return {
id: "user-1",
username: "local",
passwordHash: "",
isOidc: false,
totpEnabled: false,
isAdmin: false,
registeredAt: "2026-01-01T00:00:00.000Z",
...overrides,
} as UserRecord;
}
describe("isLoopbackRequest", () => {
it.each(["127.0.0.1", "::1", "::ffff:127.0.0.1"])(
"accepts %s as loopback",
(ip) => {
expect(isLoopbackRequest({ ip, socket: {} } as unknown as Request)).toBe(
true,
);
},
);
it("accepts an IPv4-mapped loopback suffix", () => {
expect(
isLoopbackRequest({
ip: "::ffff:127.0.0.1",
socket: {},
} as unknown as Request),
).toBe(true);
});
it("rejects a non-loopback IP", () => {
expect(
isLoopbackRequest({
ip: "192.168.1.50",
socket: {},
} as unknown as Request),
).toBe(false);
});
it("falls back to socket.remoteAddress when req.ip is empty", () => {
expect(
isLoopbackRequest({
ip: "",
socket: { remoteAddress: "127.0.0.1" },
} as unknown as Request),
).toBe(true);
});
});
describe("extractBearerOrCookieToken", () => {
it("prefers the jwt cookie over the Authorization header", () => {
const req = {
cookies: { jwt: "cookie-token" },
headers: { authorization: "Bearer header-token" },
} as unknown as Request;
expect(extractBearerOrCookieToken(req)).toBe("cookie-token");
});
it("falls back to a Bearer Authorization header", () => {
const req = {
cookies: {},
headers: { authorization: "Bearer header-token" },
} as unknown as Request;
expect(extractBearerOrCookieToken(req)).toBe("header-token");
});
it("returns undefined when neither is present", () => {
const req = { cookies: {}, headers: {} } as unknown as Request;
expect(extractBearerOrCookieToken(req)).toBeUndefined();
});
it("ignores a non-Bearer Authorization header", () => {
const req = {
cookies: {},
headers: { authorization: "Basic abc123" },
} as unknown as Request;
expect(extractBearerOrCookieToken(req)).toBeUndefined();
});
});
describe("resolveDesktopAutoSessionUser", () => {
it("returns the sole local user regardless of having a real password", () => {
const user = makeUser({ passwordHash: "$2a$10$realbcryptvaluehere" });
expect(resolveDesktopAutoSessionUser([user])).toBe(user);
});
it("returns the sole local user even when OIDC-enabled", () => {
const user = makeUser({ isOidc: true });
expect(resolveDesktopAutoSessionUser([user])).toBe(user);
});
it("returns the sole local user even when TOTP-enabled", () => {
const user = makeUser({ totpEnabled: true });
expect(resolveDesktopAutoSessionUser([user])).toBe(user);
});
it("returns the auto-provisioned passwordless placeholder", () => {
const user = makeUser({ passwordHash: "" });
expect(resolveDesktopAutoSessionUser([user])).toBe(user);
});
it("declines when zero users exist", () => {
expect(resolveDesktopAutoSessionUser([])).toBeNull();
});
it("never declines for a multi-user local database -- prefers the admin account", () => {
const admin = makeUser({
id: "user-2",
isAdmin: true,
registeredAt: "2026-02-01T00:00:00.000Z",
});
const result = resolveDesktopAutoSessionUser([
makeUser({
id: "user-1",
isAdmin: false,
registeredAt: "2026-01-01T00:00:00.000Z",
}),
admin,
]);
expect(result).toBe(admin);
});
it("falls back to the earliest-registered account when no admin exists", () => {
const earliest = makeUser({
id: "user-1",
registeredAt: "2026-01-01T00:00:00.000Z",
});
const result = resolveDesktopAutoSessionUser([
makeUser({ id: "user-2", registeredAt: "2026-03-01T00:00:00.000Z" }),
earliest,
makeUser({ id: "user-3", registeredAt: "2026-02-01T00:00:00.000Z" }),
]);
expect(result).toBe(earliest);
});
});
@@ -160,28 +160,6 @@ describe("stripSensitiveFields", () => {
expect(result.hasPassword).toBe(false);
expect(result.hasKey).toBe(false);
});
it("strips rdp/vnc/telnet passwords and adds their presence flags", () => {
const result = stripSensitiveFields({
name: "rdp-box",
rdpPassword: "rdp-secret",
vncPassword: "vnc-secret",
telnetPassword: "telnet-secret",
});
expect(result.rdpPassword).toBeUndefined();
expect(result.vncPassword).toBeUndefined();
expect(result.telnetPassword).toBeUndefined();
expect(result.hasRdpPassword).toBe(true);
expect(result.hasVncPassword).toBe(true);
expect(result.hasTelnetPassword).toBe(true);
});
it("marks rdp/vnc/telnet presence flags false when absent", () => {
const result = stripSensitiveFields({ name: "rdp-box" });
expect(result.hasRdpPassword).toBe(false);
expect(result.hasVncPassword).toBe(false);
expect(result.hasTelnetPassword).toBe(false);
});
});
describe("transformHostResponse", () => {
@@ -1,102 +0,0 @@
import { describe, it, expect } from "vitest";
import {
isValidKeyCombo,
isValidKeybindingAction,
isValidKeybinding,
} from "../../../database/routes/keybinding-validation.js";
const validCombo = {
key: "c",
isCode: false,
ctrl: true,
alt: false,
shift: false,
meta: false,
};
describe("isValidKeyCombo", () => {
it("accepts a well-formed combo", () => {
expect(isValidKeyCombo(validCombo)).toBe(true);
});
it("rejects a combo missing a boolean field", () => {
const { ctrl: _ctrl, ...rest } = validCombo;
expect(isValidKeyCombo(rest)).toBe(false);
});
it("rejects a non-object", () => {
expect(isValidKeyCombo("ctrl+c")).toBe(false);
expect(isValidKeyCombo(null)).toBe(false);
});
});
describe("isValidKeybindingAction", () => {
it("accepts copy and paste with no extra fields", () => {
expect(isValidKeybindingAction({ type: "copy" })).toBe(true);
expect(isValidKeybindingAction({ type: "paste" })).toBe(true);
});
it("rejects an unknown action type", () => {
expect(isValidKeybindingAction({ type: "explode" })).toBe(false);
});
it("requires text for sendText", () => {
expect(isValidKeybindingAction({ type: "sendText" })).toBe(false);
expect(isValidKeybindingAction({ type: "sendText", text: "ls -la" })).toBe(
true,
);
});
it("requires a single-letter controlCode for sendControlCode", () => {
expect(
isValidKeybindingAction({ type: "sendControlCode", controlCode: "w" }),
).toBe(true);
expect(
isValidKeybindingAction({ type: "sendControlCode", controlCode: "ww" }),
).toBe(false);
expect(
isValidKeybindingAction({ type: "sendControlCode", controlCode: "1" }),
).toBe(false);
expect(isValidKeybindingAction({ type: "sendControlCode" })).toBe(false);
});
it("requires snippetId for runSnippet", () => {
expect(
isValidKeybindingAction({ type: "runSnippet", snippetId: "42" }),
).toBe(true);
expect(isValidKeybindingAction({ type: "runSnippet" })).toBe(false);
});
});
describe("isValidKeybinding", () => {
const base = {
id: "kb-1",
enabled: true,
combo: validCombo,
action: { type: "copy" },
};
it("accepts a well-formed keybinding", () => {
expect(isValidKeybinding(base)).toBe(true);
});
it("rejects a keybinding missing id", () => {
const { id: _id, ...rest } = base;
expect(isValidKeybinding(rest)).toBe(false);
});
it("rejects a keybinding missing enabled", () => {
const { enabled: _enabled, ...rest } = base;
expect(isValidKeybinding(rest)).toBe(false);
});
it("rejects a keybinding with an invalid combo", () => {
expect(isValidKeybinding({ ...base, combo: {} })).toBe(false);
});
it("rejects a keybinding with an invalid action", () => {
expect(isValidKeybinding({ ...base, action: { type: "sendText" } })).toBe(
false,
);
});
});
@@ -1,204 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../database/repositories/factory.js", () => ({
createCurrentHostResolutionRepository: () => ({
findCredentialByIdForUser: async () => null,
}),
}));
vi.mock("../../utils/logger.js", () => ({
sshLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
authLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
import { SSHAuthManager } from "../../hosts/auth-manager.js";
function createManager() {
const sent: Record<string, unknown>[] = [];
const ws = { send: (data: string) => sent.push(JSON.parse(data)) } as any;
const manager = new SSHAuthManager({
userId: "user-1",
ws,
hostId: 1,
isKeyboardInteractive: false,
keyboardInteractiveResponded: false,
keyboardInteractiveFinish: null,
totpPromptSent: false,
warpgateAuthPromptSent: false,
totpTimeout: null,
warpgateAuthTimeout: null,
totpAttempts: 0,
});
return { manager, sent };
}
describe("SSHAuthManager.handleKeyboardInteractive", () => {
beforeEach(() => {
vi.useRealTimers();
});
it("routes a TOTP verification prompt to the totp flow", () => {
const { manager, sent } = createManager();
const finish = vi.fn();
manager.handleKeyboardInteractive(
"",
"",
"",
[{ prompt: "Verification code: ", echo: true }],
finish,
{ username: "root", authType: "none" },
);
expect(sent).toEqual([
{
type: "connection_log",
data: {
stage: "auth",
level: "info",
message: "TOTP verification required",
},
},
{ type: "totp_required", prompt: "Verification code: " },
]);
expect(finish).not.toHaveBeenCalled();
});
it("forwards echo:true for a JumpCloud-style push/TOTP menu prompt", () => {
const { manager, sent } = createManager();
const finish = vi.fn();
manager.handleKeyboardInteractive(
"",
"",
"",
[{ prompt: "Choose [1] Push, or [2] TOTP: ", echo: true }],
finish,
{ username: "root", authType: "none" },
);
expect(sent).toEqual([
{
type: "connection_log",
data: {
stage: "auth",
level: "info",
message: "Password authentication required",
},
},
{
type: "password_required",
prompt: "Choose [1] Push, or [2] TOTP: ",
echo: true,
},
]);
});
it("silently auto-answers a plain password prompt when a stored password exists", () => {
const { manager, sent } = createManager();
const finish = vi.fn();
manager.handleKeyboardInteractive(
"",
"",
"",
[{ prompt: "Password: ", echo: false }],
finish,
{ username: "root", password: "hunter2", authType: "password" },
);
expect(finish).toHaveBeenCalledWith(["hunter2"]);
expect(sent).toEqual([]);
});
it("prompts the user for a push-confirm prompt and accepts an empty response", () => {
const { manager, sent } = createManager();
const finish = vi.fn();
manager.handleKeyboardInteractive(
"",
"",
"",
[{ prompt: "Press enter to send Push request: ", echo: true }],
finish,
{ username: "root", authType: "none" },
);
expect(sent).toEqual([
{
type: "connection_log",
data: {
stage: "auth",
level: "info",
message: "Password authentication required",
},
},
{
type: "password_required",
prompt: "Press enter to send Push request: ",
echo: true,
},
]);
manager.context.keyboardInteractiveFinish?.([""]);
expect(finish).toHaveBeenCalledWith([""]);
});
it("uses a longer timeout for push-style prompts than generic prompts", () => {
vi.useFakeTimers();
try {
const { manager, sent } = createManager();
const finish = vi.fn();
manager.handleKeyboardInteractive(
"",
"",
"",
[{ prompt: "Press enter to send Push request: ", echo: true }],
finish,
{ username: "root", authType: "none" },
);
vi.advanceTimersByTime(180001);
expect(sent.some((m) => m.type === "error")).toBe(false);
vi.advanceTimersByTime(120000);
expect(sent.some((m) => m.type === "error")).toBe(true);
} finally {
vi.useRealTimers();
}
});
it("routes Warpgate prompts to the warpgate flow, not the generic path", () => {
const { manager, sent } = createManager();
const finish = vi.fn();
manager.handleKeyboardInteractive(
"Warpgate Authentication",
"Visit https://warpgate.example.com/auth to continue. Security key: AB12",
"",
[{ prompt: "Press enter once done: ", echo: true }],
finish,
{ username: "root", authType: "none" },
);
expect(sent).toEqual([
{
type: "connection_log",
data: {
stage: "auth",
level: "info",
message: "Warpgate authentication required",
},
},
{
type: "warpgate_auth_required",
url: "https://warpgate.example.com/auth",
securityKey: "AB12",
instructions:
"Visit https://warpgate.example.com/auth to continue. Security key: AB12",
},
]);
});
});
@@ -1,107 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import type { LookupAddress, LookupAllOptions } from "dns";
import {
createDnsLookupHook,
isBlockedAddress,
} from "../../utils/safe-outbound-fetch.js";
describe("isBlockedAddress", () => {
it("allows public IPv4 addresses", () => {
expect(isBlockedAddress("8.8.8.8")).toBe(false);
expect(isBlockedAddress("104.21.52.150")).toBe(false);
});
it("blocks private/reserved IPv4 ranges", () => {
expect(isBlockedAddress("10.0.0.1")).toBe(true);
expect(isBlockedAddress("172.16.0.1")).toBe(true);
expect(isBlockedAddress("192.168.1.1")).toBe(true);
expect(isBlockedAddress("127.0.0.1")).toBe(true);
expect(isBlockedAddress("169.254.1.1")).toBe(true);
expect(isBlockedAddress("100.64.0.1")).toBe(true);
});
it("allows public IPv6 addresses", () => {
expect(isBlockedAddress("2606:4700:3034::ac43:c88d")).toBe(false);
expect(isBlockedAddress("2001:4860:4860::8888")).toBe(false);
});
it("blocks private/reserved IPv6 ranges", () => {
expect(isBlockedAddress("::1")).toBe(true);
expect(isBlockedAddress("fc00::1")).toBe(true);
expect(isBlockedAddress("fe80::1")).toBe(true);
});
it("blocks IPv4-mapped-IPv6 spoofing of private addresses", () => {
expect(isBlockedAddress("::ffff:127.0.0.1")).toBe(true);
expect(isBlockedAddress("::ffff:192.168.1.1")).toBe(true);
expect(isBlockedAddress("::ffff:10.0.0.1")).toBe(true);
});
it("does not block IPv4-mapped-IPv6 form of public addresses", () => {
expect(isBlockedAddress("::ffff:104.21.52.150")).toBe(false);
expect(isBlockedAddress("::ffff:8.8.8.8")).toBe(false);
});
it("blocks unparseable input", () => {
expect(isBlockedAddress("not-an-ip")).toBe(true);
});
});
// These exercise createDnsLookupHook directly against a fake resolver,
// bypassing fetch()/undici entirely. That's the actual code path the
// original bug lived in — a public IPv4 address getting misclassified as
// private — and testing it through a real Agent/fetch call would only
// add flakiness (real TCP connects, undici's own quirks) without adding
// coverage of the logic that actually broke.
function runHook(
addresses: LookupAddress[],
error: NodeJS.ErrnoException | null = null,
) {
const fakeLookup = (
_host: string,
_opts: LookupAllOptions,
cb: (err: NodeJS.ErrnoException | null, addrs: LookupAddress[]) => void,
) => cb(error, addresses);
const hook = createDnsLookupHook(fakeLookup);
const callback = vi.fn();
hook("example.invalid", { all: true }, callback);
return callback;
}
describe("createDnsLookupHook", () => {
it("allows a public IPv4 address through", () => {
const callback = runHook([{ address: "104.21.52.150", family: 4 }]);
expect(callback).toHaveBeenCalledWith(null, "104.21.52.150", 4);
});
it("rejects a private address with the private-destination error", () => {
const callback = runHook([{ address: "192.168.1.1", family: 4 }]);
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
message: "Private destinations are not allowed",
}),
"",
0,
);
});
it("rejects with a distinct error when DNS returns no addresses", () => {
const callback = runHook([]);
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
message: "DNS resolution returned no addresses",
}),
"",
0,
);
});
it("propagates a real DNS lookup error untouched", () => {
const dnsError = Object.assign(new Error("getaddrinfo ENOTFOUND"), {
code: "ENOTFOUND",
});
const callback = runHook([], dnsError);
expect(callback).toHaveBeenCalledWith(dnsError, "", 0);
});
});
+31 -73
View File
@@ -1,54 +1,28 @@
import { lookup, type LookupAddress, type LookupAllOptions } from "dns";
import { lookup } from "dns";
import { BlockList, isIP } from "net";
import { Agent } from "undici";
type DnsLookupFn = (
hostname: string,
options: LookupAllOptions,
callback: (
err: NodeJS.ErrnoException | null,
addresses: LookupAddress[],
) => void,
) => void;
type LookupHookCallback = (
error: NodeJS.ErrnoException | Error | null,
address: string,
family: number,
) => void;
const blockedAddresses = new BlockList();
// Derived, not hand-duplicated: Node's BlockList matches addresses across
// families through their IPv4-mapped-IPv6 form regardless of which `type`
// you pass to check()/addSubnet() (see the addAddress('123.123.123.123') /
// check('::ffff:123.123.123.123') example on
// https://nodejs.org/api/net.html#class-netblocklist). So every IPv4 range
// below needs an "::ffff:<net>" mirror in the IPv6 list, or a spoofed
// literal like "::ffff:127.0.0.1" slips through unblocked. Generating the
// mirror from this list instead of maintaining two lists by hand means the
// two can't drift out of sync the way they did before.
const blockedIpv4Ranges = [
for (const [network, prefix] of [
["0.0.0.0", 8],
["10.0.0.0", 8],
["100.64.0.0", 10], // CGNAT
["100.64.0.0", 10],
["127.0.0.0", 8],
["169.254.0.0", 16], // link-local
["169.254.0.0", 16],
["172.16.0.0", 12],
["192.168.0.0", 16],
["198.18.0.0", 15], // benchmarking
["224.0.0.0", 4], // multicast
["240.0.0.0", 4], // reserved
] as const;
for (const [network, prefix] of blockedIpv4Ranges) {
["198.18.0.0", 15],
["224.0.0.0", 4],
["240.0.0.0", 4],
] as const) {
blockedAddresses.addSubnet(network, prefix, "ipv4");
blockedAddresses.addSubnet(`::ffff:${network}`, prefix + 96, "ipv6");
}
for (const [network, prefix] of [
["::", 128],
["::1", 128],
["::ffff:0:0", 96],
["fc00::", 7],
["fe80::", 10],
["ff00::", 8],
@@ -56,7 +30,7 @@ for (const [network, prefix] of [
blockedAddresses.addSubnet(network, prefix, "ipv6");
}
export function isBlockedAddress(address: string): boolean {
function isBlockedAddress(address: string): boolean {
const family = isIP(address);
return (
family === 0 ||
@@ -64,42 +38,6 @@ export function isBlockedAddress(address: string): boolean {
);
}
// Extracted so the blocklist decision can be tested directly against a
// fake DNS resolver, instead of only through a real fetch()/Agent call —
// the actual bug here lived entirely in this callback, several layers
// below where undici's own "fetch failed" wrapping would otherwise hide it.
export function createDnsLookupHook(dnsLookup: DnsLookupFn = lookup) {
return function lookupHook(
host: string,
lookupOptions: LookupAllOptions,
callback: LookupHookCallback,
): void {
dnsLookup(
host,
{ ...lookupOptions, all: true, verbatim: true },
(error, addresses) => {
if (error) return callback(error, "", 0);
if (!addresses.length) {
return callback(
new Error("DNS resolution returned no addresses"),
"",
0,
);
}
if (addresses.some(({ address }) => isBlockedAddress(address))) {
return callback(
new Error("Private destinations are not allowed"),
"",
0,
);
}
const selected = addresses[0];
callback(null, selected.address, selected.family);
},
);
};
}
export async function safeOutboundFetch(
rawUrl: string,
options: RequestInit,
@@ -120,7 +58,27 @@ export async function safeOutboundFetch(
const dispatcher = new Agent({
connect: {
lookup: createDnsLookupHook(),
lookup(host, lookupOptions, callback) {
lookup(
host,
{ ...lookupOptions, all: true, verbatim: true },
(error, addresses) => {
if (error) return callback(error, "", 0);
if (
!addresses.length ||
addresses.some(({ address }) => isBlockedAddress(address))
) {
return callback(
new Error("Private destinations are not allowed"),
"",
0,
);
}
const selected = addresses[0];
callback(null, selected.address, selected.family);
},
);
},
},
});
+7 -7
View File
@@ -31,13 +31,13 @@ try {
nativeRequire("ssh2/lib/protocol/crypto/build/Release/sshcrypto.node");
ssh2BindingAvailable = true;
} catch {
// The pure-JS fallback in ssh2 for chacha20-poly1305@openssh.com is broken and
// corrupts the transport: the target sshd aborts the KEX with
// "ssh_dispatch_run_fatal: ... incomplete message [preauth]" and the client times out.
// A working OpenSSL "chacha20" cipher is NOT sufficient here — only the native
// binding (sshcrypto.node) makes chacha20-poly1305 usable. Keep it disabled otherwise
// so filterCiphers() drops it and the connection negotiates AES-GCM instead.
ssh2BindingAvailable = false;
try {
// ESM fallback: check if chacha20 works via OpenSSL createCipheriv
crypto.createCipheriv("chacha20", Buffer.alloc(32), Buffer.alloc(16));
ssh2BindingAvailable = true;
} catch {
ssh2BindingAvailable = false;
}
}
function filterCiphers(list: CipherAlgorithm[]): CipherAlgorithm[] {
+6 -41
View File
@@ -186,9 +186,6 @@ function App() {
// When session-verified, Auth must not mount during the transition — it would trigger
// silent OIDC redirect and cause an infinite refresh loop.
const fadingInFromLoginRef = useRef(false);
// Dedupes concurrent handleLogout() calls within the same tick -- see
// handleLogout for why phase state alone isn't sufficient for this.
const loggingOutRef = useRef(false);
useEffect(() => {
const savedAccent = localStorage.getItem("termix-accent");
@@ -214,18 +211,7 @@ function App() {
if (isElectron()) {
try {
const token = await getCurrentToken();
if (token) {
localStorage.setItem("jwt", token);
// Remote Sync's engine (main process) needs this local JWT to
// authenticate against the embedded backend during sync, same
// as a fresh login provides via handleLogin below -- a session
// restore (the common case on every normal launch) must hand
// it over too, or sync silently never runs after the first
// app restart.
window.electronAPI
?.invoke?.("notify-local-login", token)
.catch(() => {});
}
if (token) localStorage.setItem("jwt", token);
} catch {
// Non-fatal: WebSocket connections will fall back to cookie auth
}
@@ -251,29 +237,21 @@ function App() {
setPhase("idle-auth");
return;
}
// Transient failure: retry rather than logging out. In Electron the
// embedded local backend is bundled, always-on infrastructure that
// always eventually comes up (a slow cold boot just takes longer),
// and Auth.tsx never shows a login form for it anyway -- so there's
// no reason to ever give up and manufacture a logout here. Outside
// Electron a genuinely broken backend still needs to surface the
// login screen eventually, so that case keeps a retry cap.
if (!isElectron() && verifyRetryCount >= 5) {
// Transient failure: retry shortly rather than logging out. Cap
// retries so a genuinely broken backend still surfaces the login
// screen eventually instead of spinning forever.
if (verifyRetryCount >= 5) {
clearStoredAuth();
setPhase("idle-auth");
return;
}
const delay = isElectron()
? Math.min(1000 * 2 ** verifyRetryCount, 10000)
: 3000;
timerRef.current = setTimeout(() => {
setVerifyRetryCount((c) => c + 1);
}, delay);
}, 3000);
});
}, [phase, verifyRetryCount]);
function handleLogin(u: string) {
loggingOutRef.current = false;
setAuthUsername(u);
fadingInFromLoginRef.current = true;
setPhase("fading-in");
@@ -290,24 +268,11 @@ function App() {
}
function handleLogout() {
// A single background hiccup can trigger several independent 401s at
// once (e.g. a burst of unrelated polls all failing together in the
// same tick), each calling this. React batches the resulting setPhase
// calls, so checking `phase` here can't distinguish the first call in
// a batch from the second -- both would see the same pre-update value
// and both would proceed, each overwriting timerRef with a fresh
// 450ms timer. A steady trickle of these could keep resetting the
// countdown so the transition never actually completes, which looks
// exactly like "nothing happens." loggingOutRef is synchronous and
// isn't subject to batching, so it correctly dedupes within one tick.
if (loggingOutRef.current) return;
loggingOutRef.current = true;
clearStoredAuth();
setPhase("fading-out");
timerRef.current = setTimeout(() => {
setAuthUsername("");
setPhase("idle-auth");
loggingOutRef.current = false;
}, 450);
}
-3
View File
@@ -157,7 +157,6 @@ export type Host = {
rdpCredentialId?: string;
rdpUser?: string;
rdpPassword?: string;
hasRdpPassword?: boolean;
domain?: string;
security?: string;
ignoreCert?: boolean;
@@ -165,14 +164,12 @@ export type Host = {
vncAuthType?: "direct" | "credential";
vncCredentialId?: string;
vncPassword?: string;
hasVncPassword?: boolean;
vncUser?: string;
telnetAuthType?: "direct" | "credential";
telnetCredentialId?: string;
telnetUser?: string;
telnetPassword?: string;
hasTelnetPassword?: boolean;
guacamoleConfig?: Record<string, unknown>;
forceKeyboardInteractive?: boolean;
+6 -20
View File
@@ -132,7 +132,6 @@ import {
} from "@/main-axios";
import { DonationReminderModal } from "@/user/DonationReminderModal.tsx";
import { RemoteSyncBanner } from "@/components/RemoteSyncBanner.tsx";
import { MigrationNoticeDialog } from "@/components/MigrationNoticeDialog.tsx";
import { dbHealthMonitor } from "@/lib/db-health-monitor";
import type { SSHHostWithStatus } from "@/main-axios";
import { ServerStatusProvider } from "@/lib/ServerStatusContext";
@@ -254,9 +253,6 @@ export function AppShell({
const [sidebarOpen, setSidebarOpen] = useState(true);
const [railView, setRailView] = useState<RailView>("hosts");
const [remoteSyncInitialServerUrl, setRemoteSyncInitialServerUrl] = useState<
string | undefined
>(undefined);
const [sidebarWidth, setSidebarWidth] = useState(() => {
const saved = localStorage.getItem("termix_sidebarWidth");
return saved ? parseInt(saved, 10) : 291;
@@ -1821,7 +1817,6 @@ export function AppShell({
onPrefsChange={(updates) =>
setUserPrefs((current) => ({ ...current, ...updates }))
}
remoteSyncInitialServerUrl={remoteSyncInitialServerUrl}
/>
</div>
)}
@@ -1886,21 +1881,12 @@ export function AppShell({
style={{ height: "100dvh" }}
>
{isElectron() && (
<>
<RemoteSyncBanner
onReconnect={() => {
setRailView("user-profile");
if (!sidebarOpen) setSidebarOpen(true);
}}
/>
<MigrationNoticeDialog
onOpenRemoteSync={(url) => {
setRemoteSyncInitialServerUrl(url);
setRailView("user-profile");
if (!sidebarOpen) setSidebarOpen(true);
}}
/>
</>
<RemoteSyncBanner
onReconnect={() => {
setRailView("user-profile");
if (!sidebarOpen) setSidebarOpen(true);
}}
/>
)}
<div className="flex flex-1 min-h-0">
{/* Skinny icon rail — desktop only, hidden on mobile */}
-2
View File
@@ -102,9 +102,7 @@ export async function adminGetHostPassword(
field:
| "password"
| "sudoPassword"
| "rdpPassword"
| "vncPassword"
| "telnetPassword"
| "key"
| "keyPassword" = "password",
): Promise<string | null> {
-2
View File
@@ -99,9 +99,7 @@ export async function getHostPassword(
field:
| "password"
| "sudoPassword"
| "rdpPassword"
| "vncPassword"
| "telnetPassword"
| "key"
| "keyPassword" = "password",
): Promise<string | null> {
-14
View File
@@ -1,7 +1,6 @@
import { authApi } from "@/main-axios";
import { createTtlRequestCache } from "@/lib/ttl-request-cache";
import type { TerminalTheme } from "@/lib/terminal-themes";
import type { CustomKeybinding } from "@/types/keybindings";
// OPEN TABS API
// ============================================================================
@@ -115,7 +114,6 @@ export interface UserPreferences {
compactHostView?: boolean | null;
statusColorScheme?: string | null;
customThemes?: string | null;
customKeybindings?: string | null;
}
export function parseCustomThemes(raw?: string | null): SavedCustomTheme[] {
@@ -128,18 +126,6 @@ export function parseCustomThemes(raw?: string | null): SavedCustomTheme[] {
}
}
export function parseCustomKeybindings(
raw?: string | null,
): CustomKeybinding[] {
if (!raw) return [];
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
export async function getUserPreferences(): Promise<UserPreferences> {
const response = await authApi.get("/user-preferences");
return response.data;
+25 -1
View File
@@ -1,5 +1,10 @@
import { AxiosError } from "axios";
import { authApi, handleApiError, markUserAuthenticated } from "@/main-axios";
import {
authApi,
handleApiError,
isElectron,
markUserAuthenticated,
} from "@/main-axios";
import type { AuthResponse } from "@/main-axios";
// ALERTS
@@ -58,6 +63,25 @@ export async function verifyTOTPLogin(
rememberMe,
});
const isInIframe =
typeof window !== "undefined" && window.self !== window.top;
if (isInIframe && isElectron() && response.data.success) {
try {
window.parent.postMessage(
{
type: "AUTH_SUCCESS",
source: "totp_verify",
platform: "desktop",
timestamp: Date.now(),
},
window.location.origin,
);
} catch (e) {
console.error("[main-axios] Error posting message to parent:", e);
}
}
if (response.data.success) {
markUserAuthenticated();
}
+6 -80
View File
@@ -274,7 +274,6 @@ export function Auth({ onLogin }: AuthProps) {
const [desktopAutoSessionDone, setDesktopAutoSessionDone] = useState<
boolean | null
>(!isElectron() || isInElectronWebView() ? true : null);
const [desktopAutoSessionRetries, setDesktopAutoSessionRetries] = useState(0);
useEffect(() => {
try {
@@ -344,57 +343,26 @@ export function Auth({ onLogin }: AuthProps) {
.finally(() => setDbHealthChecking(false));
}, [desktopAutoSessionDone]);
// A cold first launch spawns the embedded backend as a separate process
// that can take anywhere from a couple seconds to much longer to finish
// booting (DB init, SSL, antivirus scanning a freshly-unpacked binary,
// slow disks, etc.) -- well after the renderer has already mounted. The
// embedded backend is bundled, always-on infrastructure, not something
// that can be "not there" -- it always eventually comes up. So a
// "retry" outcome (connection error, not a real verdict) is retried
// forever with capped backoff rather than ever giving up and falling
// through to the login form: that form is not a valid destination for a
// standalone install with no remote sync configured, since the only
// local account has no password to log in with. Only a definitive
// "declined" (backend reachable and says no -- multiple users, or the
// sole local user has a real credential) stops retrying and shows the
// real form.
useEffect(() => {
if (desktopAutoSessionDone !== null) return;
let cancelled = false;
let retryTimer: ReturnType<typeof setTimeout> | null = null;
requestDesktopAutoSession()
.then((outcome) => {
.then((res) => {
if (cancelled) return;
if (outcome.kind === "success") {
storeAuth(outcome.data.username || "");
onLogin(
outcome.data.username || "",
outcome.data.userId || undefined,
!!outcome.data.is_admin,
);
return;
}
if (outcome.kind === "retry") {
const delay = Math.min(1000 * 2 ** desktopAutoSessionRetries, 10000);
retryTimer = setTimeout(() => {
if (!cancelled) setDesktopAutoSessionRetries((c) => c + 1);
}, delay);
if (res?.success) {
storeAuth(res.username || "");
onLogin(res.username || "", res.userId || undefined, !!res.is_admin);
return;
}
setDesktopAutoSessionDone(true);
})
.catch(() => {
if (cancelled) return;
const delay = Math.min(1000 * 2 ** desktopAutoSessionRetries, 10000);
retryTimer = setTimeout(() => {
if (!cancelled) setDesktopAutoSessionRetries((c) => c + 1);
}, delay);
if (!cancelled) setDesktopAutoSessionDone(true);
});
return () => {
cancelled = true;
if (retryTimer) clearTimeout(retryTimer);
};
}, [desktopAutoSessionDone, desktopAutoSessionRetries, onLogin]);
}, [desktopAutoSessionDone, onLogin]);
useEffect(() => {
if (view === "totp" && totpInputRef.current) totpInputRef.current.focus();
@@ -535,19 +503,11 @@ export function Auth({ onLogin }: AuthProps) {
return;
}
if (isInElectronWebView()) {
// The iframe's login request never carries the X-Electron-App header
// (only the top-level Electron renderer's axios instances do), so the
// backend never includes the JWT in the login response body -- it
// only lands in an HttpOnly cookie scoped to this iframe's origin.
// Read it back via /users/me/token, same as the mobile-webview OIDC
// callback below does, so the parent window can persist it.
const token = res?.token ?? (await getCurrentToken());
window.parent.postMessage(
{
type: "AUTH_SUCCESS",
source: "auth_component",
platform: "desktop",
token: token ?? null,
timestamp: Date.now(),
},
"*",
@@ -601,35 +561,6 @@ export function Auth({ onLogin }: AuthProps) {
setView("totp");
return;
}
if (isInMobileWebView()) {
// Native-app requests get the JWT in the login response body.
const token = res?.token ?? "";
(window as ExtendedWindow).ReactNativeWebView?.postMessage(
JSON.stringify({ type: "AUTH_SUCCESS", token }),
);
setWebviewAuthSuccess(true);
return;
}
if (isInElectronWebView()) {
// Registration inside the Remote Sync iframe must hand off to the
// parent window the same way handleLogin does -- otherwise this
// component's own onLogin() below fires on the iframe's own,
// independent copy of the app, rendering the full remote AppShell
// inside the small login dialog instead of closing it.
const token = res?.token ?? (await getCurrentToken());
window.parent.postMessage(
{
type: "AUTH_SUCCESS",
source: "auth_component",
platform: "desktop",
token: token ?? null,
timestamp: Date.now(),
},
"*",
);
setWebviewAuthSuccess(true);
return;
}
const meRes = await getUserInfo();
storeAuth(meRes.username || username.trim());
toast.success(t("messages.registrationSuccess"));
@@ -673,16 +604,11 @@ export function Auth({ onLogin }: AuthProps) {
return;
}
if (isInElectronWebView()) {
// See the equivalent branch in handleLogin: the iframe never sends
// X-Electron-App, so the JWT never lands in the response body here
// either -- read it back from the HttpOnly cookie that was just set.
const token = res?.token ?? (await getCurrentToken());
window.parent.postMessage(
{
type: "AUTH_SUCCESS",
source: "totp_auth_component",
platform: "desktop",
token: token ?? null,
timestamp: Date.now(),
},
"*",
+2 -2
View File
@@ -215,7 +215,7 @@ export function ElectronLoginForm({
const isEmbeddedServer = serverUrl.includes("localhost:30001");
return (
<div className="relative w-full h-full bg-background flex flex-col">
<div className="fixed inset-0 w-screen h-screen bg-background flex flex-col">
{isAuthenticating && (
<div className="absolute inset-0 flex items-center justify-center bg-background z-50">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
@@ -223,7 +223,7 @@ export function ElectronLoginForm({
)}
{!isAuthenticating && (
<div className="flex items-center justify-between p-4 pr-12 bg-background border-b border-border">
<div className="flex items-center justify-between p-4 bg-background border-b border-border">
<button
onClick={onChangeServer}
className="flex items-center gap-2 text-foreground hover:text-primary transition-colors"
+397
View File
@@ -0,0 +1,397 @@
import React, { useState, useEffect, useRef } from "react";
import { Button } from "@/components/button.tsx";
import { Input } from "@/components/input.tsx";
import { Label } from "@/components/label.tsx";
import { Alert, AlertTitle, AlertDescription } from "@/components/alert.tsx";
import { Switch } from "@/components/switch.tsx";
import { useTranslation } from "react-i18next";
import {
getServerConfig,
saveServerConfig,
getEmbeddedServerStatus,
type ServerConfig,
} from "@/main-axios.ts";
import { Server, Monitor, Loader2, ChevronDown, X } from "lucide-react";
const SAVED_URLS_KEY = "termix_saved_server_urls";
const MAX_SAVED_URLS = 5;
function getSavedUrls(): string[] {
try {
const raw = localStorage.getItem(SAVED_URLS_KEY);
if (!raw) return [];
return JSON.parse(raw);
} catch {
return [];
}
}
function addSavedUrl(url: string) {
const urls = getSavedUrls().filter((u) => u !== url);
urls.unshift(url);
localStorage.setItem(
SAVED_URLS_KEY,
JSON.stringify(urls.slice(0, MAX_SAVED_URLS)),
);
}
function removeSavedUrl(url: string) {
const urls = getSavedUrls().filter((u) => u !== url);
localStorage.setItem(SAVED_URLS_KEY, JSON.stringify(urls));
}
interface ServerConfigProps {
onServerConfigured: (serverUrl: string) => void;
onUseEmbedded?: () => void;
onCancel?: () => void;
isFirstTime?: boolean;
}
export function ElectronServerConfig({
onServerConfigured,
onUseEmbedded,
onCancel,
isFirstTime = false,
}: ServerConfigProps) {
const { t } = useTranslation();
const [serverUrl, setServerUrl] = useState("");
const [allowInvalidCertificate, setAllowInvalidCertificate] = useState(false);
const [loading, setLoading] = useState(false);
const [embeddedLoading, setEmbeddedLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [embeddedAvailable, setEmbeddedAvailable] = useState<boolean | null>(
null,
);
const [savedUrls, setSavedUrls] = useState<string[]>([]);
const [dropdownOpen, setDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
loadServerConfig();
checkEmbeddedBackend();
setSavedUrls(getSavedUrls());
}, []);
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
) {
setDropdownOpen(false);
}
}
if (dropdownOpen) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [dropdownOpen]);
const loadServerConfig = async () => {
try {
const config = await getServerConfig();
if (config?.serverUrl) {
setServerUrl(config.serverUrl);
}
setAllowInvalidCertificate(!!config?.allowInvalidCertificate);
} catch (error) {
console.error("Server config operation failed:", error);
}
};
const checkEmbeddedBackend = async () => {
try {
const status = await getEmbeddedServerStatus();
setEmbeddedAvailable(!!status?.running);
} catch {
setEmbeddedAvailable(true);
}
};
const probeBackend = async (): Promise<boolean> => {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 3000);
try {
const res = await fetch("http://localhost:30001/health", {
signal: controller.signal,
});
clearTimeout(timer);
return res.ok;
} catch {
clearTimeout(timer);
}
const controller2 = new AbortController();
const timer2 = setTimeout(() => controller2.abort(), 3000);
try {
await fetch("http://localhost:30001/version", {
signal: controller2.signal,
});
clearTimeout(timer2);
return true;
} catch {
clearTimeout(timer2);
return false;
}
};
const handleUseEmbedded = async () => {
setEmbeddedLoading(true);
setError(null);
try {
await new Promise((r) => setTimeout(r, 1500));
const maxRetries = 15;
for (let i = 0; i < maxRetries; i++) {
if (await probeBackend()) {
if (onUseEmbedded) {
onUseEmbedded();
} else {
onServerConfigured("http://localhost:30001");
}
return;
}
if (i < maxRetries - 1) {
await new Promise((r) => setTimeout(r, 2000));
}
}
setError(t("serverConfig.embeddedNotReady"));
} catch (err) {
setError(
err instanceof Error ? err.message : t("serverConfig.embeddedNotReady"),
);
} finally {
setEmbeddedLoading(false);
}
};
const handleSaveConfig = async () => {
if (!serverUrl.trim()) {
setError(t("serverConfig.enterServerUrl"));
return;
}
setLoading(true);
setError(null);
try {
const normalizedUrl = serverUrl.trim();
if (
!normalizedUrl.startsWith("http://") &&
!normalizedUrl.startsWith("https://")
) {
setError(t("serverConfig.mustIncludeProtocol"));
setLoading(false);
return;
}
const config: ServerConfig = {
serverUrl: normalizedUrl,
lastUpdated: new Date().toISOString(),
allowInvalidCertificate:
normalizedUrl.startsWith("https://") && allowInvalidCertificate,
};
const success = await saveServerConfig(config);
if (success) {
addSavedUrl(normalizedUrl);
setSavedUrls(getSavedUrls());
onServerConfigured(normalizedUrl);
} else {
setError(t("serverConfig.saveFailed"));
}
} catch {
setError(t("serverConfig.saveError"));
} finally {
setLoading(false);
}
};
const handleUrlChange = (value: string) => {
setServerUrl(value);
setError(null);
};
return (
<div className="fixed inset-0 flex items-center justify-center bg-background p-6">
<div className="flex flex-col gap-5 p-6 border border-border bg-card max-w-md w-full">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2.5">
<Server className="size-4 text-accent-brand shrink-0" />
<p className="font-bold">{t("serverConfig.title")}</p>
</div>
<p className="text-sm text-muted-foreground">
{t("serverConfig.description")}
</p>
</div>
{embeddedAvailable !== false && (
<>
<Button
type="button"
variant="outline"
className="w-full"
onClick={handleUseEmbedded}
disabled={embeddedLoading || loading}
>
{embeddedLoading ? (
<span className="flex items-center gap-2">
<Loader2 className="size-4 animate-spin" />
{t("serverConfig.embeddedConnecting")}
</span>
) : (
<span className="flex items-center gap-2">
<Monitor className="size-4" />
{t("serverConfig.useEmbedded")}
<span className="px-1.5 py-0.5 text-[10px] font-bold bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border border-yellow-500/30">
BETA
</span>
</span>
)}
</Button>
<p className="text-xs text-muted-foreground -mt-3">
{t("serverConfig.embeddedDesc")}
</p>
<div className="flex items-center gap-3">
<div className="h-px flex-1 bg-border" />
<span className="text-xs text-muted-foreground">
{t("common.or") || "OR"}
</span>
<div className="h-px flex-1 bg-border" />
</div>
</>
)}
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-1.5">
<Label htmlFor="server-url">{t("serverConfig.serverUrl")}</Label>
<div className="relative" ref={dropdownRef}>
<Input
id="server-url"
type="text"
placeholder="https://your-server.com"
value={serverUrl}
onChange={(e) => handleUrlChange(e.target.value)}
disabled={loading || embeddedLoading}
className={savedUrls.length > 0 ? "pr-9" : ""}
onFocus={() => {
if (savedUrls.length > 0) setDropdownOpen(true);
}}
/>
{savedUrls.length > 0 && (
<button
type="button"
tabIndex={-1}
onClick={() => setDropdownOpen((o) => !o)}
disabled={loading || embeddedLoading}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
aria-label={t("serverConfig.savedServers")}
>
<ChevronDown className="size-4" />
</button>
)}
{dropdownOpen && savedUrls.length > 0 && (
<div className="absolute z-50 w-full top-full mt-1 border border-border bg-card shadow-md">
<p className="px-2.5 py-1.5 text-[10px] font-bold uppercase tracking-widest text-muted-foreground border-b border-border">
{t("serverConfig.savedServers")}
</p>
{savedUrls.map((url) => (
<div
key={url}
className="flex items-center justify-between group hover:bg-muted transition-colors"
>
<button
type="button"
className="flex-1 text-left px-2.5 py-2 text-sm font-mono truncate"
onClick={() => {
handleUrlChange(url);
setDropdownOpen(false);
}}
>
{url}
</button>
<button
type="button"
className="px-2 py-2 text-muted-foreground hover:text-destructive transition-colors shrink-0"
title={t("serverConfig.removeServer")}
onClick={(e) => {
e.stopPropagation();
removeSavedUrl(url);
const updated = getSavedUrls();
setSavedUrls(updated);
if (updated.length === 0) setDropdownOpen(false);
}}
>
<X className="size-3.5" />
</button>
</div>
))}
</div>
)}
</div>
</div>
{serverUrl.trim().startsWith("https://") && (
<div className="flex items-start justify-between gap-3 border border-border bg-muted/20 p-3">
<div className="flex flex-col gap-1">
<Label htmlFor="allow-invalid-certificate">
{t("serverConfig.allowInvalidCertificate")}
</Label>
<p className="text-xs text-muted-foreground">
{t("serverConfig.allowInvalidCertificateDesc")}
</p>
</div>
<Switch
id="allow-invalid-certificate"
checked={allowInvalidCertificate}
onCheckedChange={setAllowInvalidCertificate}
disabled={loading || embeddedLoading}
/>
</div>
)}
{error && (
<Alert variant="destructive">
<AlertTitle>{t("common.error")}</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="flex gap-2">
{onCancel && !isFirstTime && (
<Button
type="button"
variant="outline"
className="flex-1"
onClick={onCancel}
disabled={loading || embeddedLoading}
>
{t("common.cancel")}
</Button>
)}
<Button
type="button"
className={`bg-accent-brand hover:bg-accent-brand/90 text-background font-bold ${onCancel && !isFirstTime ? "flex-1" : "w-full"}`}
onClick={handleSaveConfig}
disabled={loading || embeddedLoading || !serverUrl.trim()}
>
{loading ? (
<span className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-background border-t-transparent rounded-full animate-spin" />
{t("serverConfig.saving")}
</span>
) : (
t("serverConfig.saveConfig")
)}
</Button>
</div>
<p className="text-xs text-muted-foreground text-center">
{t("serverConfig.helpText")}
</p>
</div>
</div>
</div>
);
}
File diff suppressed because it is too large Load Diff
+37
View File
@@ -0,0 +1,37 @@
import React, { useState } from "react";
import { Auth } from "@/auth/LoginPage.tsx";
interface LoginScreenProps {
authLoading: boolean;
onAuthSuccess: (authData: {
isAdmin: boolean;
username: string | null;
userId: string | null;
}) => void;
}
export function LoginScreen({
authLoading,
onAuthSuccess,
}: LoginScreenProps): React.ReactElement {
const [loggedIn, setLoggedIn] = useState(false);
const [, setIsAdmin] = useState(false);
const [, setUsername] = useState<string | null>(null);
const [, setUserId] = useState<string | null>(null);
const [, setDbError] = useState<string | null>(null);
return (
<div className="w-full h-full flex items-center justify-center">
<Auth
setLoggedIn={setLoggedIn}
setIsAdmin={setIsAdmin}
setUsername={setUsername}
setUserId={setUserId}
loggedIn={loggedIn}
authLoading={authLoading}
setDbError={setDbError}
onAuthSuccess={onAuthSuccess}
/>
</div>
);
}
-107
View File
@@ -1,107 +0,0 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Server } from "lucide-react";
import { isElectron } from "@/lib/electron";
import { Button } from "@/components/button.tsx";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/dialog.tsx";
type DesktopSettings = {
defaultConnectionOrigin: "local" | "remote";
migrationNoticeAcknowledged?: boolean;
};
// One-time notice for Electron installs that previously pointed the whole
// app at a remote Termix server (the pre-2.6.0 architecture). That install
// now runs a fully local, standalone backend by default -- the hosts,
// credentials, and snippets that lived on the old remote server won't show
// up here until the user explicitly turns on Remote Sync and reconnects to
// that same server. A fresh install never had a legacy serverUrl, so this
// never fires for anyone who didn't go through the old flow, and it only
// ever shows once per install (tracked in desktop-settings.json).
export function MigrationNoticeDialog({
onOpenRemoteSync,
}: {
onOpenRemoteSync: (serverUrl: string) => void;
}) {
const { t } = useTranslation();
const [legacyServerUrl, setLegacyServerUrl] = useState<string | null>(null);
const [open, setOpen] = useState(false);
useEffect(() => {
if (!isElectron()) return;
let cancelled = false;
Promise.all([
window.electronAPI?.invoke?.("get-legacy-server-config") as Promise<{
serverUrl: string | null;
} | null>,
window.electronAPI?.invoke?.("get-desktop-settings") as Promise<
DesktopSettings | undefined
>,
])
.then(([legacyConfig, settings]) => {
if (cancelled) return;
const serverUrl = legacyConfig?.serverUrl || null;
if (serverUrl && !settings?.migrationNoticeAcknowledged) {
setLegacyServerUrl(serverUrl);
setOpen(true);
}
})
.catch(() => {});
return () => {
cancelled = true;
};
}, []);
const acknowledge = async () => {
setOpen(false);
const settings = ((await window.electronAPI?.invoke?.(
"get-desktop-settings",
)) as DesktopSettings | undefined) ?? { defaultConnectionOrigin: "local" };
await window.electronAPI?.invoke?.("save-desktop-settings", {
...settings,
migrationNoticeAcknowledged: true,
});
};
const handleSetUpSync = async () => {
const url = legacyServerUrl || "";
await acknowledge();
onOpenRemoteSync(url);
};
if (!legacyServerUrl) return null;
return (
<Dialog open={open} onOpenChange={(next) => !next && acknowledge()}>
<DialogContent className="bg-card border border-border max-w-lg">
<DialogHeader>
<div className="flex items-center gap-2">
<Server className="size-4 text-accent-brand" />
<DialogTitle>{t("migrationNotice.title")}</DialogTitle>
</div>
<DialogDescription className="text-sm text-foreground/90 pt-2 space-y-2">
<p>{t("migrationNotice.body1")}</p>
<p>{t("migrationNotice.body2", { url: legacyServerUrl })}</p>
</DialogDescription>
</DialogHeader>
<DialogFooter className="flex items-center gap-2 sm:justify-between">
<Button variant="outline" size="sm" onClick={acknowledge}>
{t("migrationNotice.dismiss")}
</Button>
<Button size="sm" onClick={handleSetUpSync}>
{t("migrationNotice.setUpSync")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
+9 -66
View File
@@ -2,14 +2,9 @@ import React, { useEffect, useState } from "react";
import { TabProvider } from "@/shell/TabContext.tsx";
import { CommandHistoryProvider } from "@/features/terminal/command-history/CommandHistoryContext.tsx";
import { SidebarProvider } from "@/components/sidebar.tsx";
import {
getSSHHosts,
getUserInfo,
isElectron,
requestDesktopAutoSession,
} from "@/main-axios.ts";
import { getSSHHosts, getUserInfo } from "@/main-axios.ts";
import type { SSHHost } from "@/types";
import { Auth } from "@/auth/Auth.tsx";
import { LoginScreen } from "@/auth/LoginScreen.tsx";
import { Toaster } from "@/components/sonner.tsx";
import { dbHealthMonitor } from "@/lib/db-health-monitor.ts";
import { useTranslation } from "react-i18next";
@@ -43,70 +38,20 @@ export const FullScreenAppWrapper: React.FC<FullScreenAppWrapperProps> = ({
}, []);
useEffect(() => {
// Popped-out windows (terminal/tunnel/file-manager/etc.) share the main
// window's session rather than owning their own login -- there's no
// login form to fall back to here. On a cold Electron launch this
// window's renderer can start before the embedded backend has finished
// booting, so a bare failure isn't proof of "logged out"; in Electron
// it's retried forever (the embedded backend is bundled, always-on
// infrastructure that always eventually comes up) with a fallback to
// the same auto-session exchange the main window uses, since a login
// form must never appear for the local backend. Outside Electron a
// capped retry still applies, matching the web app's normal behavior.
let cancelled = false;
let retryTimer: ReturnType<typeof setTimeout> | null = null;
const maxAttempts = 10;
const checkAuth = async (attempt: number) => {
const checkAuth = async () => {
try {
const userInfo = await getUserInfo();
if (cancelled) return;
if (userInfo) {
setIsAuthenticated(true);
setAuthLoading(false);
return;
}
} catch {
// fall through to retry/auto-session below
}
if (cancelled) return;
if (isElectron()) {
const outcome = await requestDesktopAutoSession();
if (cancelled) return;
if (outcome.kind === "success") {
setIsAuthenticated(true);
setAuthLoading(false);
return;
}
if (outcome.kind === "declined") {
setIsAuthenticated(false);
setAuthLoading(false);
return;
}
// "retry": keep retrying indefinitely with capped backoff.
const delay = Math.min(1000 * 2 ** attempt, 10000);
retryTimer = setTimeout(() => {
if (!cancelled) checkAuth(attempt + 1);
}, delay);
return;
}
if (attempt >= maxAttempts) {
setIsAuthenticated(false);
} finally {
setAuthLoading(false);
return;
}
retryTimer = setTimeout(() => {
if (!cancelled) checkAuth(attempt + 1);
}, 1000);
};
checkAuth(0);
return () => {
cancelled = true;
if (retryTimer) clearTimeout(retryTimer);
};
checkAuth();
}, []);
useEffect(() => {
@@ -139,11 +84,6 @@ export const FullScreenAppWrapper: React.FC<FullScreenAppWrapperProps> = ({
window.location.reload();
};
// Electron never reaches an unauthenticated render: checkAuth above
// retries and falls back to the same auto-session exchange the main
// window uses, so this branch is web-only there -- a real "not logged
// in" case for a bookmarked/shared full-screen link.
if (authLoading) {
return (
<div
@@ -170,7 +110,10 @@ export const FullScreenAppWrapper: React.FC<FullScreenAppWrapperProps> = ({
className="w-full h-screen overflow-hidden flex items-center justify-center"
style={{ backgroundColor: "var(--bg-base)" }}
>
<Auth onLogin={handleAuthSuccess} />
<LoginScreen
authLoading={authLoading}
onAuthSuccess={handleAuthSuccess}
/>
<Toaster
position="bottom-right"
richColors={false}
+12 -121
View File
@@ -65,13 +65,6 @@ import {
getNextTerminalFontSize,
getTerminalFontZoomDirection,
} from "./terminal-font-zoom.ts";
import {
getUserPreferences,
parseCustomKeybindings,
} from "@/api/open-tabs-api";
import { findMatchingKeybinding } from "@/lib/keybinding-match";
import { dispatchKeybindingAction } from "@/lib/keybinding-dispatch";
import type { CustomKeybinding } from "@/types/keybindings";
export type { TerminalHandle, TerminalHostConfig } from "./terminal-types.ts";
type HostKeyVerificationData = Omit<
@@ -168,10 +161,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
: themeColors.background;
const fitAddonRef = useRef<FitAddon | null>(null);
const webSocketRef = useRef<WebSocket | null>(null);
const customKeybindingsRef = useRef<CustomKeybinding[]>([]);
const cachedSnippetsRef = useRef<{ id: number; content: string }[] | null>(
null,
);
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
const wasDisconnectedBySSH = useRef(false);
const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
@@ -197,10 +186,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
const [totpRequired, setTotpRequired] = useState(false);
const [totpPrompt, setTotpPrompt] = useState<string>("");
const [isPasswordPrompt, setIsPasswordPrompt] = useState(false);
const [mfaPromptMode, setMfaPromptMode] = useState<
"totp" | "password" | "menu" | "push"
>("totp");
const [mfaWaiting, setMfaWaiting] = useState(false);
const [showAuthDialog, setShowAuthDialog] = useState(false);
const [authDialogReason, setAuthDialogReason] = useState<
"no_keyboard" | "auth_failed" | "timeout"
@@ -531,25 +516,17 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
}
function handleTotpSubmit(code: string) {
const isPushMode = mfaPromptMode === "push";
if (webSocketRef.current && (code || isPushMode)) {
if (webSocketRef.current && code) {
if (totpTimeoutRef.current) {
clearTimeout(totpTimeoutRef.current);
totpTimeoutRef.current = null;
}
webSocketRef.current.send(
JSON.stringify({
type: isPasswordPrompt ? "password_response" : "totp_response",
data: { code },
}),
);
if (isPushMode) {
// Server blocks until the phone approval completes; keep the dialog
// open in a waiting state rather than closing it immediately. The
// existing timeout continues running until "connected" or "error".
setMfaWaiting(true);
return;
}
if (totpTimeoutRef.current) {
clearTimeout(totpTimeoutRef.current);
totpTimeoutRef.current = null;
}
setTotpRequired(false);
setTotpPrompt("");
setIsPasswordPrompt(false);
@@ -563,9 +540,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
}
setTotpRequired(false);
setTotpPrompt("");
setIsPasswordPrompt(false);
setMfaPromptMode("totp");
setMfaWaiting(false);
if (onClose) onClose();
}
@@ -1360,8 +1334,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
setTotpRequired(true);
setTotpPrompt(msg.prompt || t("terminal.totpCodeLabel"));
setIsPasswordPrompt(false);
setMfaPromptMode("totp");
setMfaWaiting(false);
if (connectionTimeoutRef.current) {
clearTimeout(connectionTimeoutRef.current);
connectionTimeoutRef.current = null;
@@ -1377,24 +1349,10 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
}, 180000);
} else if (msg.type === "totp_retry") {
// Existing prompt remains visible while the backend asks for another code.
setMfaWaiting(false);
} else if (msg.type === "password_required") {
const promptText: string = msg.prompt || "";
const pushPromptPattern =
/choose.*push.*totp|press enter.*(push|send)|push notification|authentication by phone/i;
const isPush = pushPromptPattern.test(promptText);
const isMenu = !isPush && msg.echo === true;
const mode: "menu" | "push" | "password" = isPush
? "push"
: isMenu
? "menu"
: "password";
setTotpRequired(true);
setTotpPrompt(promptText || t("common.password"));
setTotpPrompt(msg.prompt || t("common.password"));
setIsPasswordPrompt(true);
setMfaPromptMode(mode);
setMfaWaiting(false);
if (connectionTimeoutRef.current) {
clearTimeout(connectionTimeoutRef.current);
connectionTimeoutRef.current = null;
@@ -1402,15 +1360,12 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
if (totpTimeoutRef.current) {
clearTimeout(totpTimeoutRef.current);
}
totpTimeoutRef.current = setTimeout(
() => {
setTotpRequired(false);
if (webSocketRef.current) {
webSocketRef.current.close();
}
},
isPush ? 300000 : 180000,
);
totpTimeoutRef.current = setTimeout(() => {
setTotpRequired(false);
if (webSocketRef.current) {
webSocketRef.current.close();
}
}, 180000);
} else if (msg.type === "warpgate_auth_required") {
setWarpgateAuthRequired(true);
setWarpgateAuthUrl(msg.url || "");
@@ -2408,76 +2363,14 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
};
}, [hostConfig.id, hostConfig.instanceId]);
useEffect(() => {
let cancelled = false;
const loadKeybindings = () => {
cachedSnippetsRef.current = null;
getUserPreferences()
.then((prefs) => {
if (!cancelled) {
customKeybindingsRef.current = parseCustomKeybindings(
prefs.customKeybindings,
).filter((kb) => kb.enabled);
}
})
.catch(() => {
// keep previous/empty bindings on failure, non-fatal
});
};
loadKeybindings();
window.addEventListener("customKeybindingsChanged", loadKeybindings);
return () => {
cancelled = true;
window.removeEventListener("customKeybindingsChanged", loadKeybindings);
};
}, []);
useEffect(() => {
if (!terminal) return;
const getSnippetById = async (
id: string,
): Promise<{ content: string } | undefined> => {
try {
if (!cachedSnippetsRef.current) {
cachedSnippetsRef.current = (await getSnippets()) as unknown as {
id: number;
content: string;
}[];
}
const numericId = Number(id);
return cachedSnippetsRef.current?.find((s) => s.id === numericId);
} catch {
return undefined;
}
};
const handleCustomKey = (e: KeyboardEvent): boolean => {
if (e.type !== "keydown") {
return true;
}
// Custom user keybindings take priority over built-in defaults, but
// never override autocomplete popup navigation while it is open.
if (!showAutocompleteRef.current) {
const matched = findMatchingKeybinding(
e,
customKeybindingsRef.current,
);
if (matched) {
e.preventDefault();
e.stopPropagation();
dispatchKeybindingAction(matched.action, {
terminal,
webSocketRef,
writeTextToClipboard,
readTextFromClipboard,
getSnippetById,
});
return false;
}
}
// Forward global app shortcuts to AppShell directly — xterm swallows
// all keydown events and synthetic re-dispatch is unreliable.
// stopPropagation prevents the same event from also firing the window listener.
@@ -3012,8 +2905,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
<TOTPDialog
isOpen={totpRequired}
prompt={totpPrompt}
mode={mfaPromptMode}
waiting={mfaWaiting}
onSubmit={handleTotpSubmit}
onCancel={handleTotpCancel}
backgroundColor={backgroundColor}
-106
View File
@@ -1,106 +0,0 @@
import type { KeyCombo } from "@/types/keybindings";
export interface DefaultKeybindingInfo {
id: string;
combo: KeyCombo;
description: string;
}
export const BUILT_IN_DEFAULTS: DefaultKeybindingInfo[] = [
{
id: "default-copy-ctrlc",
combo: {
key: "c",
isCode: false,
ctrl: true,
alt: false,
shift: false,
meta: false,
},
description: "Copy selection (Ctrl+C, only when text is selected)",
},
{
id: "default-copy-ctrlshiftc",
combo: {
key: "c",
isCode: false,
ctrl: true,
alt: false,
shift: true,
meta: false,
},
description: "Copy selection (Ctrl+Shift+C)",
},
{
id: "default-copy-cmdc",
combo: {
key: "c",
isCode: false,
ctrl: false,
alt: false,
shift: false,
meta: true,
},
description: "Copy selection (Cmd+C)",
},
{
id: "default-paste-ctrlshiftv",
combo: {
key: "v",
isCode: false,
ctrl: true,
alt: false,
shift: true,
meta: false,
},
description: "Paste from clipboard (Ctrl+Shift+V)",
},
{
id: "default-ctrlaltw",
combo: {
key: "w",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
description: "Send Ctrl+W to shell (blocked from closing browser tab)",
},
{
id: "default-ctrlaltt",
combo: {
key: "t",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
description: "Send Ctrl+T to shell (blocked from opening browser tab)",
},
{
id: "default-ctrlaltn",
combo: {
key: "n",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
description: "Send Ctrl+N to shell (blocked from opening browser window)",
},
{
id: "default-ctrlaltq",
combo: {
key: "q",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
description: "Send Ctrl+Q to shell (blocked from quitting browser)",
},
];
-56
View File
@@ -1,56 +0,0 @@
import type { Terminal } from "@xterm/xterm";
import type { KeybindingAction } from "@/types/keybindings";
export interface KeybindingDispatchContext {
terminal: Terminal;
webSocketRef: React.MutableRefObject<WebSocket | null>;
writeTextToClipboard: (text: string) => Promise<boolean>;
readTextFromClipboard: () => Promise<string>;
getSnippetById: (id: string) => Promise<{ content: string } | undefined>;
}
export function dispatchKeybindingAction(
action: KeybindingAction,
ctx: KeybindingDispatchContext,
): void {
const sendRaw = (data: string) => {
if (ctx.webSocketRef.current?.readyState === 1) {
ctx.webSocketRef.current.send(JSON.stringify({ type: "input", data }));
}
};
switch (action.type) {
case "copy": {
const selection = ctx.terminal.getSelection();
if (selection) {
ctx.writeTextToClipboard(selection);
ctx.terminal.clearSelection();
}
return;
}
case "paste": {
ctx.readTextFromClipboard().then((text) => {
if (text) ctx.terminal.paste(text);
});
return;
}
case "sendControlCode": {
if (!action.controlCode) return;
const code = action.controlCode.toLowerCase().charCodeAt(0) - 96;
if (code >= 1 && code <= 26) sendRaw(String.fromCharCode(code));
return;
}
case "sendText": {
sendRaw((action.text ?? "") + (action.appendEnter ? "\r" : ""));
return;
}
case "runSnippet": {
if (!action.snippetId) return;
ctx.getSnippetById(action.snippetId).then((snippet) => {
if (snippet)
sendRaw(snippet.content + (action.appendEnter !== false ? "\r" : ""));
});
return;
}
}
}
-17
View File
@@ -1,17 +0,0 @@
import type { CustomKeybinding, KeyCombo } from "@/types/keybindings";
export function eventMatchesCombo(e: KeyboardEvent, combo: KeyCombo): boolean {
if (e.ctrlKey !== combo.ctrl) return false;
if (e.altKey !== combo.alt) return false;
if (e.shiftKey !== combo.shift) return false;
if (e.metaKey !== combo.meta) return false;
const actual = combo.isCode ? e.code : e.key.toLowerCase();
return actual === combo.key;
}
export function findMatchingKeybinding(
e: KeyboardEvent,
bindings: CustomKeybinding[],
): CustomKeybinding | undefined {
return bindings.find((kb) => eventMatchesCombo(e, kb.combo));
}
-58
View File
@@ -389,13 +389,6 @@
"noSavedServers": "No saved servers",
"removeServer": "Remove"
},
"migrationNotice": {
"title": "Termix Desktop now runs standalone",
"body1": "This app used to connect straight to your Termix server. It's been reworked to run fully on its own, storing hosts and credentials locally so it keeps working offline. Two-way sync with a self-hosted Termix server is now optional.",
"body2": "Your existing hosts and credentials are still on {{url}}. Turn on Remote Sync and reconnect to that server to bring them back and keep both in sync going forward.",
"dismiss": "Not now",
"setUpSync": "Set Up Remote Sync"
},
"remoteSync": {
"title": "Remote Sync",
"description": "Optionally connect this desktop app to a self-hosted Termix server to sync your hosts, credentials, and snippets across devices. The app always works fully offline whether or not you connect.",
@@ -413,7 +406,6 @@
"serverUrl": "Server URL",
"enterServerUrl": "Please enter a server URL",
"mustIncludeProtocol": "Server URL must start with http:// or https://",
"connectionTestFailed": "Could not reach a Termix server at that URL",
"allowInvalidCertificate": "Allow invalid certificate",
"allowInvalidCertificateDesc": "Use only for trusted self-hosted servers with self-signed or IP-address certificates.",
"savedServers": "Saved Servers",
@@ -1328,7 +1320,6 @@
"rdpPort": "RDP Port",
"username": "Username",
"password": "Password",
"passwordSaved": "Password saved, type to change",
"domain": "Domain",
"securityMode": "Security Mode",
"colorDepth": "Color Depth",
@@ -1636,11 +1627,6 @@
"totpRequired": "Two-Factor Authentication Required",
"totpCodeLabel": "Verification Code",
"totpVerify": "Verify",
"mfaPromptRequired": "Authentication Required",
"mfaPushRequired": "Push Authentication Required",
"mfaMenuPlaceholder": "Enter your response",
"mfaWaitingApproval": "Waiting for approval on your device...",
"mfaSendRequest": "Send Request",
"warpgateAuthRequired": "Warpgate Authentication Required",
"warpgateSecurityKey": "Security Key",
"warpgateAuthUrl": "Authentication URL",
@@ -3270,47 +3256,6 @@
"executionSuccess": "Success",
"executionFailed": "Failed"
},
"keybindings": {
"title": "Keyboard Shortcuts",
"description": "Customize terminal copy, paste, and control shortcuts, or bind keys to send text or run a snippet.",
"defaultsHeading": "Built-in Shortcuts",
"customHeading": "Custom Shortcuts",
"addBinding": "Add Shortcut",
"addBindingTitle": "Add Shortcut",
"editBindingTitle": "Edit Shortcut",
"loading": "Loading...",
"noCustomBindings": "No custom shortcuts yet",
"defaultBadge": "Default",
"customizedBadge": "Customized",
"customize": "Customize",
"resetToDefault": "Reset to default",
"resetAllToDefaults": "Reset all to defaults",
"close": "Close",
"cancel": "Cancel",
"saveBinding": "Save Shortcut",
"saveError": "Failed to save keyboard shortcuts",
"pressKeysToRecord": "Key combination",
"pressKeysPlaceholder": "Click and press keys",
"recording": "Press keys...",
"comboRequiredError": "Press a key combination first",
"textRequiredError": "Enter the text to send",
"controlCodeRequiredError": "Enter a single letter for the control code",
"snippetRequiredError": "Select a snippet",
"conflictWarning": "This combination is already bound to {{combo}}. Saving will make both fire.",
"actionLabel": "Action",
"actionCopy": "Copy selection",
"actionPaste": "Paste",
"actionSendControlCode": "Send Ctrl+letter signal",
"actionSendText": "Send text",
"actionRunSnippet": "Run existing snippet",
"controlCodeLabel": "Letter",
"textLabel": "Text to send",
"appendEnterLabel": "Press Enter after sending",
"snippetLabel": "Snippet",
"selectSnippetPlaceholder": "Select a snippet",
"orphanedSnippetWarning": "snippet not found",
"clipboardPermissionNote": "May prompt for clipboard permission on first use in some browsers."
},
"userProfile": {
"donateTitle": "Keep Termix alive",
"donateDescription": "Termix is free, self-hosted, and built by only a few people in their spare time. If it's saved you time or money, a crypto donation helps keep it going.",
@@ -3355,9 +3300,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Command Autocomplete",
"commandAutocompleteDesc": "Show autocomplete while typing",
"keyboardShortcuts": "Keyboard Shortcuts",
"keyboardShortcutsDescription": "Customize terminal copy, paste, and command shortcuts",
"manageShortcuts": "Manage",
"terminalLinkBehavior": "Terminal Link Click",
"terminalLinkBehaviorDesc": "Default behavior when clicking links in the terminal",
"historyTracking": "History Tracking",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Geen gestoorde bedieners nie",
"removeServer": "Verwyder"
},
"migrationNotice": {
"title": "Termix Desktop loop nou alleenstaande",
"body1": "Hierdie toepassing het voorheen direk aan jou Termix-bediener gekoppel. Dit is herontwerp om volledig op sy eie te loop, en stoor gashere en geloofsbriewe plaaslik sodat dit vanlyn kan bly werk. Tweerigting-sinchronisasie met 'n self-gehoste Termix-bediener is nou opsioneel.",
"body2": "Jou bestaande gashere en geloofsbriewe is steeds op {{url}}. Skakel Afstandsinchronisasie aan en koppel weer aan daardie bediener om hulle terug te bring en albei vorentoe gesinchroniseer te hou.",
"dismiss": "Nie nou nie",
"setUpSync": "Stel afstandsinchronisasie op"
},
"remoteSync": {
"title": "Afstandsinchronisasie",
"description": "Koppel opsioneel hierdie lessenaar-app aan 'n self-gehoste Termix-bediener om jou gashere, geloofsbriewe en brokkies oor toestelle te sinkroniseer. Die app werk altyd ten volle vanlyn, of jy nou koppel of nie.",
"notConnected": "Nie gekoppel nie",
"connected": "Verbonde",
"connectedTo": "Gekoppel aan {{url}}",
"lastSynced": "Laas gesinkroniseer {{time}}",
"neverSynced": "Nooit gesinkroniseer nie",
"syncError": "Sinkronisasiefout: {{message}}",
"needsReauth": "Aanmelding het verval",
"connectButton": "Koppel aan bediener",
"disconnectButton": "Ontkoppel",
"syncNowButton": "Sinkroniseer nou",
"syncing": "Sinkroniseer...",
"serverUrl": "Bediener-URL",
"enterServerUrl": "Voer asseblief 'n bediener-URL in",
"mustIncludeProtocol": "Bediener-URL moet met http:// of https:// begin",
"connectionTestFailed": "Kon nie 'n Termix-bediener by daardie URL bereik nie",
"allowInvalidCertificate": "Laat ongeldige sertifikaat toe",
"allowInvalidCertificateDesc": "Gebruik slegs vir vertroude selfgehoste bedieners met selfgetekende of IP-adres sertifikate.",
"savedServers": "Gestoorde bedieners",
"removeServer": "Verwyder",
"continueButton": "Gaan voort",
"cancelButton": "Kanselleer",
"signInTitle": "Meld aan by {{url}}",
"originTitle": "Verbindingsoorsprong",
"originDescription": "Kies waar SSH-verbindings standaard vandaan kom. Dit kan per gasheer oorskryf word.",
"originLocal": "Hierdie toestel (plaaslike netwerk)",
"originRemote": "Afstandbediener",
"bannerReconnect": "Herkoppel",
"bannerMessage": "Afstandsinchronisasie benodig herverifikasie"
},
"versionCheck": {
"error": "Weergawekontrolefout",
"checkFailed": "Kon nie vir opdaterings kyk nie",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Oorskryf geloofsbriewe gebruikersnaam",
"overrideCredentialUsernameDesc": "Gebruik die gebruikersnaam wat hierbo gespesifiseer is in plaas van die gebruikersnaam van die geloofsbriewe",
"oidcUsernameHint": "Gebruik $oidc.preferred_username om jou OIDC-aanmeldnaam te vervang.",
"tailscaleUsernameHint": "Dit moet 'n Unix-gebruiker wees aan wie jou Tailscale-identiteit in die tailnet se SSH ACL toegestaan word, nie noodwendig root nie.",
"jumpHostChain": "Springgasheerketting",
"portKnocking": "Port Knocking",
"addKnock": "Voeg poort by",
@@ -690,11 +650,6 @@
"delayAfterMs": "Vertraging na (ms)",
"useSocks5Proxy": "Gebruik SOCKS5 Proxy",
"useSocks5ProxyDesc": "Roeteer verbinding deur 'n proxy-bediener",
"connectionOrigin": "Verbindingsoorsprong",
"connectionOriginDesc": "Waar hierdie gasheer se SSH-verbinding vandaan kom. Oorskryf die rekenaarprogram se globale verstekwaarde.",
"connectionOriginDefault": "Gebruik verstekwaarde",
"connectionOriginLocal": "Hierdie toestel (plaaslike netwerk)",
"connectionOriginRemote": "Afstandbediener",
"proxyHost": "Proxy-gasheer",
"proxyPort": "Proxy-poort",
"proxyUsername": "Proxy-gebruikersnaam",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Helder Siaan",
"customThemeBrightWhite": "Helderwit",
"customThemeResetTooltip": "Stel terug na verstekwaardes",
"savedThemesLabel": "Gestoorde temas",
"saveAsGlobalTheme": "Stoor as globale tema",
"saveGlobalThemeNamePrompt": "Voer 'n naam vir hierdie tema in",
"saveGlobalThemeSuccess": "Tema gestoor",
"saveGlobalThemeError": "Kon nie tema stoor nie",
"applyGlobalThemeTooltip": "Pas hierdie tema toe",
"deleteGlobalThemeTooltip": "Vee hierdie tema uit",
"noSavedThemes": "Geen gestoorde temas nog nie",
"syntaxHighlightingLabel": "Sintaksis-uitlig",
"syntaxHighlightingDesc": "Kleur terminaaluitvoer in (foute, paaie, IP's, tydstempels)",
"syntaxHighlightingCategories": "Merk kategorieë uit",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Begin of koppel outomaties aan tmux-sessie",
"enableSessionLogging": "Sessie-logging",
"enableSessionLoggingDesc": "Neem terminaalsessie-uitvoer op vir latere hersiening",
"allowSessionSharing": "Laat Sessiedeling Toe",
"allowSessionSharingDesc": "Laat regstreekse sessies op hierdie gasheer via skakel of met ander gebruikers gedeel word",
"enableCommandHistory": "Opdraggeskiedenis",
"enableCommandHistoryDesc": "Rekordopdragte word in hierdie terminaal uitgevoer vir geskiedenis en outovoltooiing",
"linkClickBehaviorLabel": "Skakelklikgedrag",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} ingevoer",
"proxmoxResultUpdated": "{{count}} opgedateer",
"proxmoxResultFailed": "{{count}} het misluk",
"proxmoxResultSkippedNoIp": "{{count}} oorgeslaan (geen IP gevind nie)",
"proxmoxImportComplete": "Proxmox-invoer voltooi: {{summary}}",
"proxmoxDiscoveryFailed": "Ontdekking het misluk",
"proxmoxImportFailed": "Invoer het misluk",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Kon nie geloofsbriewe stoor nie",
"credentialNameRequired": "Voer asseblief 'n naam vir die geloofsbrief in",
"credentialAuthRequired": "Voeg 'n wagwoord, 'n SSH-sleutel of albei by",
"createCredentialFromHostBtn": "Skep geloofsbriewe",
"createCredentialFromHostTitle": "Skep geloofsbriewe vanaf gasheer",
"createCredentialFromHostDesc": "Skep 'n herbruikbare, deelbare geloofsbriewe-inskrywing wat vooraf ingevul is met hierdie gasheer se huidige gebruikersnaam, wagwoord en/of SSH-sleutel.",
"backToHosts": "Terug na gashere",
"backToCredentials": "Terug na geloofsbriewe",
"pinned": "Vasgepen",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Gebruik / om vlakke te skei en geneste vouers te skep.",
"folderColor": "Kleur",
"folderIcon": "Ikoon",
"folderCredential": "Geloofsbrief",
"folderCredentialNone": "Geen geloofsbriewe toegeken nie",
"folderCredentialHint": "Gashere in hierdie vouer wat \"Gestoorde geloofsbriewe\"-magtiging gebruik sonder dat hul eie geloofsbriewe gekies is, sal hierdie een erf.",
"folderPreview": "Voorskou",
"folderNameFallback": "Naamlose vouer",
"createFolderButton": "Skep vouer",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Etikette",
"shareHost": "Deel gasheer",
"shareHostTitle": "Deel: {{name}}",
"shareFolder": "Deel vouer",
"shareFolderTitle": "Deel vouer: {{name}}",
"folderSharedSuccessfully": "Gedeelde {{count}} gasheer(s) in vouer",
"failedToShareFolder": "Kon nie vouer deel nie",
"sharing": {
"loadError": "Kon nie deeldata laai nie. Probeer asseblief weer.",
"shareWithSection": "Deel met",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Deel ({{count}})",
"currentAccess": "Huidige toegang",
"noAccessEntries": "Hierdie gasheer is nog nie gedeel nie",
"folderShareSummary": "Gedeelde {{shared}} van {{total}} gasheer(s) in hierdie vouer",
"grantedBy": "Toegeken deur",
"expires": "Vervaldatum",
"expired": "Verstryk",
@@ -1309,8 +1242,6 @@
"authMethod": "Magtigingsmetode",
"authTypeDirect": "Direkte",
"authTypeCredential": "Geloofsbrief",
"authTypeNone": "Geen",
"authTypeNoneDesc": "Geen aanmeldingsbesonderhede word gestoor nie. Jy sal elke keer as jy koppel vir 'n gebruikersnaam en wagwoord gevra word; hulle word nie gestoor nie.",
"selectCredential": "Kies 'n geloofsbrief...",
"connectionSettings": "Verbindingsinstellings",
"displaySettings": "Skerminstellings",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-poort",
"username": "Gebruikersnaam",
"password": "Wagwoord",
"passwordSaved": "Wagwoord gestoor, tik om te verander",
"domain": "Domein",
"securityMode": "Sekuriteitsmodus",
"colorDepth": "Kleurdiepte",
@@ -1494,60 +1424,7 @@
"expiresIn": "Verval oor {{duration}}",
"search": "Soek verbindings...",
"noSearchResults": "Geen verbindings stem ooreen met jou soektog nie",
"rename": "Hernoem sessie",
"sectionSharedWithMe": "Met my gedeel",
"sharedBy": "Gedeel deur {{username}}",
"join": "Sluit aan",
"sharedSessionLabel": "{{hostName}} (gedeel)"
},
"sessionSharing": {
"guestView": {
"loading": "Verbind met gedeelde sessie...",
"linkInvalid": "Hierdie deelskakel is ongeldig, het verval of is herroep",
"rateLimited": "Te veel pogings, probeer asseblief weer binnekort",
"sessionEnded": "Hierdie sessie is verby",
"readOnlyBadge": "Slegs kyk"
},
"modalTitle": "Deel sessie",
"shareButton": "Deel",
"notReadyToShare": "Sessie is nog nie gereed om te deel nie",
"modeTab": {
"link": "Skakel",
"user": "Gebruiker"
},
"linkModeDescription": "Enigiemand met hierdie skakel kan aansluit, geen rekening nodig nie.",
"userModeDescription": "Deel met 'n spesifieke gebruiker wat reeds toegang tot hierdie gasheer het. Indien hulle nog nie toegang het nie, deel die gasheer eers met hulle of gebruik eerder 'n skakel. Sodra dit gedeel is, verskyn die sessie in hul Verbindings-oortjie.",
"permissionLevel": {
"label": "Toestemmingsvlak",
"readOnly": "Leesalleen",
"readOnlyDescription": "Kan die sessie regstreeks kyk, maar kan nie tik of interaksie hê nie.",
"readWrite": "Lees-skryf",
"readWriteDescription": "Kan tik en met die sessie interaksie hê net soos die eienaar."
},
"expiryLabel": "Skakelvervaldatum",
"createLinkButton": "Skep skakel",
"createShareButton": "Deel met gebruiker",
"searchUsersPlaceholder": "Soek gebruikers...",
"noUsersFound": "Geen gebruikers gevind nie",
"linkCreated": "Deel skakel geskep",
"linkCopied": "Skakel gekopieer na knipbord",
"copyLink": "Kopieer skakel",
"shareCreated": "Sessie gedeel. Dit sal in hul Verbindings-oortjie verskyn.",
"shareFailed": "Kon nie deel skep nie",
"userLacksHostAccess": "Daardie gebruiker het nog nie toegang tot hierdie gasheer nie. Deel die gasheer eers met hulle, of gebruik eerder 'n skakel.",
"activeShares": "Aktiewe aandele",
"noActiveShares": "Geen aktiewe delings vir hierdie sessie nie",
"revoke": "Herroep",
"revokeConfirmTitle": "Herroep hierdie deling?",
"revokeConfirmDescription": "Enigiemand wat hierdie deel gebruik, sal onmiddellik toegang verloor.",
"revoked": "Deel herroep",
"revokeFailed": "Kon nie deel herroep nie",
"joinCount": "{{count}} sluit aan",
"joinCount_other": "{{count}} sluit aan",
"expiresAt": "Vervaldatum {{date}}",
"linkShareBadge": "Skakel",
"userShareBadge": "Gebruiker: {{username}}",
"loadSharesFailed": "Kon nie aktiewe delings laai nie"
"rename": "Hernoem sessie"
},
"guacamole": {
"connecting": "Verbind met {{type}} sessie...",
@@ -1559,9 +1436,6 @@
"reconnect": "Herkoppel",
"retry": "Probeer weer",
"guacdUnavailable": "Diens vir afstandwerkskerm (guacd) is nie beskikbaar nie. Maak asseblief seker dat guacd loop, toeganklik is en behoorlik in administrateurinstellings gekonfigureer is.",
"credentialPromptTitle": "Voer RDP-aanbewyse in",
"credentialPromptDescription": "Hierdie gasheer is ingestel om vir geloofsbriewe te vra tydens verbinding. Hulle word slegs vir hierdie sessie gebruik en word nie gestoor nie.",
"connect": "Verbind",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Herkoppel Sessie",
"collapse": "Vou nutsbalk in",
"expand": "Vou die nutsbalk uit",
"dragHandle": "Sleep om te herposisioneer",
"switchToTrackpad": "Skakel oor na raakvlakmodus (sleep om wyser te beweeg, tik om te klik)",
"switchToTouch": "Skakel oor na raakmodus (tik direk waar jy wil klik)"
"dragHandle": "Sleep om te herposisioneer"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Tweefaktor-verifikasie vereis",
"totpCodeLabel": "Verifikasiekode",
"totpVerify": "Verifieer",
"mfaPromptRequired": "Verifikasie Vereis",
"mfaPushRequired": "Stoot-verifikasie vereis",
"mfaMenuPlaceholder": "Voer jou antwoord in",
"mfaWaitingApproval": "Wag vir goedkeuring op jou toestel...",
"mfaSendRequest": "Stuur versoek",
"warpgateAuthRequired": "Warpgate-verifikasie vereis",
"warpgateSecurityKey": "Sekuriteitsleutel",
"warpgateAuthUrl": "Verifikasie-URL",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Tempo beperk: Te veel verifikasiepogings. Probeer asseblief later weer.",
"resetCodeRateLimitedWithTime": "Tempo beperk: Te veel verifikasiepogings. Wag asseblief {{time}} sekondes voordat u weer probeer.",
"authTokenSaveFailed": "Kon nie verifikasietoken stoor nie",
"failedToLoadServer": "Kon nie bediener laai nie",
"remoteServerRequired": "Afstandbediener word benodig. Koppel 'n afstandbediener in Instellings om hierdie verbindingstipe te gebruik."
"failedToLoadServer": "Kon nie bediener laai nie"
},
"messages": {
"registrationDisabled": "Nuwe rekeningregistrasie is tans deur 'n administrateur gedeaktiveer. Meld asseblief aan of kontak 'n administrateur.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Kon nie SSL-instellings stoor nie",
"sslRequiresDomain": "Domein en e-posadres word vereis",
"sslInfoNote": "Nadat u 'n sertifikaat uitgereik het, aktiveer SSL in u omgewingveranderlikes (ENABLE_SSL=true) en herbegin Termix.",
"sslManualOption": "Handleiding (laai sertifikaat op)",
"sslManualCert": "Sertifikaat (PEM)",
"sslManualCertPlaceholder": "-----BEGIN SERTIFIKAAT-----",
"sslManualKey": "Privaatsleutel (PEM)",
"sslManualKeyPlaceholder": "-----BEGIN PRIVAAT SLEUTEL-----",
"sslManualDesc": "Plak jou bestaande sertifikaat en privaat sleutel, insluitend 'n volledige ketting indien vereis deur jou CA.",
"sslManualUpload": "Laai sertifikaat op en installeer dit",
"sslManualUploadLoading": "Laai tans sertifikaat op...",
"sslManualUploadSuccess": "Sertifikaat suksesvol opgelaai en geïnstalleer",
"sslManualUploadFailed": "Sertifikaatoplaai het misluk",
"sslManualRequiresFields": "Sertifikaat en privaat sleutel word vereis",
"auditLogTotal": "{{total}} totale inskrywings",
"auditLogEmpty": "Geen ouditlogboekinskrywings gevind nie",
"auditLogSuccess": "Sukses",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Opdraggeskiedenis",
"commandHistoryEnabledDesc": "Laat die opname van opdraggeskiedenis toe. Wanneer dit gedeaktiveer is, word geskiedenis nie gestoor nie, ongeag die instellings per gasheer.",
"updateCommandHistoryFailed": "Kon nie die instelling vir opdraggeskiedenis opdateer nie",
"analyticsEnabled": "Deel Anonieme Gebruiksstatistieke",
"analyticsEnabledDesc": "Stuur 'n anonieme daaglikse telling van gebruikers, gashere en funksiegebruik om Termix te help verbeter. Geen persoonlike data of verbindingsbesonderhede word ooit ingesluit nie.",
"updateAnalyticsFailed": "Kon nie analitiese instelling opdateer nie",
"sessionSharingGloballyEnabled": "Laat Sessiedeling Toe",
"sessionSharingGloballyEnabledDesc": "Laat lewendige terminaal-, RDP-, VNC- en Telnet-sessies toe om instansiewyd gedeel te word. Oorskryf elke deelskakelaar per gasheer wanneer dit gedeaktiveer is.",
"updateSessionSharingFailed": "Kon nie sessiedelinginstelling opdateer nie",
"sessionTimeout": "Sessie-tydsbeperking",
"hours": "ure",
"sessionTimeoutRange": "Min 1 uur · Maks 720 uur",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Sukses",
"executionFailed": "Misluk"
},
"keybindings": {
"title": "Sleutelbordkortpaaie",
"description": "Pasmaak terminaalkopieer-, plak- en beheerkortpaaie, of bind sleutels om teks te stuur of 'n brokkie uit te voer.",
"defaultsHeading": "Ingeboude kortpaaie",
"customHeading": "Pasgemaakte kortpaaie",
"addBinding": "Voeg kortpad by",
"addBindingTitle": "Voeg kortpad by",
"editBindingTitle": "Wysig Kortpad",
"loading": "Laai tans...",
"noCustomBindings": "Geen persoonlike kortpaaie nog nie",
"defaultBadge": "Standaard",
"customizedBadge": "Aangepas",
"customize": "Pasmaak",
"resetToDefault": "Stel terug na verstekwaarde",
"resetAllToDefaults": "Stel alles terug na verstekwaardes",
"close": "Maak toe",
"cancel": "Kanselleer",
"saveBinding": "Stoor Kortpad",
"saveError": "Kon nie sleutelbordkortpaaie stoor nie",
"pressKeysToRecord": "Sleutelkombinasie",
"pressKeysPlaceholder": "Klik en druk sleutels",
"recording": "Druk sleutels...",
"comboRequiredError": "Druk eers 'n sleutelkombinasie",
"textRequiredError": "Voer die teks in om te stuur",
"controlCodeRequiredError": "Voer 'n enkele letter vir die beheerkode in",
"snippetRequiredError": "Kies 'n uittreksel",
"conflictWarning": "Hierdie kombinasie is reeds gebind aan {{combo}}. As jy dit stoor, sal albei aktiveer.",
"actionLabel": "Aksie",
"actionCopy": "Kopieer seleksie",
"actionPaste": "Plak",
"actionSendControlCode": "Stuur Ctrl+letter sein",
"actionSendText": "Stuur SMS",
"actionRunSnippet": "Voer bestaande brokkie uit",
"controlCodeLabel": "Brief",
"textLabel": "SMS om te stuur",
"appendEnterLabel": "Druk Enter na stuur",
"snippetLabel": "Brokkie",
"selectSnippetPlaceholder": "Kies 'n uittreksel",
"orphanedSnippetWarning": "brokkie nie gevind nie",
"clipboardPermissionNote": "Kan in sommige blaaiers met die eerste gebruik vir knipbordtoestemming vra."
},
"userProfile": {
"donateTitle": "Hou Termix lewendig",
"donateDescription": "Termix is gratis, self-gehost en gebou deur slegs 'n paar mense in hul vrye tyd. As dit jou tyd of geld bespaar het, help 'n kripto-skenking om dit aan die gang te hou.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Voorkoms",
"sectionSecurity": "Sekuriteit",
"sectionApiKeys": "API-sleutels",
"sectionData": "Data",
"sectionC2sTunnels": "C2S-tonnels",
"usernameLabel": "Gebruikersnaam",
"roleLabel": "Rol",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminaal",
"commandAutocomplete": "Opdrag Outomatiese Voltooiing",
"commandAutocompleteDesc": "Wys outovoltooiing terwyl jy tik",
"keyboardShortcuts": "Sleutelbordkortpaaie",
"keyboardShortcutsDescription": "Pas terminale kopieer-, plak- en opdragkortpaaie aan",
"manageShortcuts": "Bestuur",
"terminalLinkBehavior": "Klik op die terminaalskakel",
"terminalLinkBehaviorDesc": "Standaardgedrag wanneer skakels in die terminaal geklik word",
"historyTracking": "Geskiedenisopsporing",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Sluit jou sleutel in die",
"apiKeyUsageHintHeader": "opskrif.",
"apiKeyPermissionsHint": "Sleutels erf die regte van die skeppergebruiker.",
"exportData": "Voer my data uit",
"exportDataDesc": "Laai 'n rugsteun van jou gashere, geloofsbriewe en instellings af om na 'n ander toestel oor te dra",
"export": "Uitvoer",
"exporting": "Uitvoer tans...",
"importData": "Voer my data in",
"importDataDesc": "Herstel jou gashere, geloofsbriewe en instellings vanaf 'n .sqlite-rugsteunlêer",
"importDataSelected": "Gekies: {{name}}",
"selectFile": "Kies Lêer",
"changeFile": "Verandering",
"import": "Invoer",
"importing": "Voer tans in...",
"exportSuccess": "Data suksesvol uitgevoer",
"exportFailed": "Data-uitvoer het misluk",
"importSelectFile": "Kies asseblief eers 'n lêer",
"importCompleted": "Invoer voltooi: {{total}} items ingevoer, {{skipped}} oorgeslaan",
"importFailed": "Invoer het misluk: {{error}}",
"roleUser": "Gebruiker",
"authMethodDual": "Dubbele Magtiging",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "Bladsy Op",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Plak",
"editQuickKeys": "Wysig vinnige sleutels",
"quickKeysTitle": "Vinnige sleutels",
"quickKeysDesc": "Tik × om te verwyder. Ondersteun tot 8 karakters.",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Няма запазени сървъри",
"removeServer": "Премахване"
},
"migrationNotice": {
"title": "Termix Desktop вече работи самостоятелно",
"body1": "Това приложение се свързваше директно с вашия Termix сървър. То е преработено, за да работи напълно самостоятелно, съхранявайки хостове и идентификационни данни локално, така че да продължи да работи офлайн. Двупосочната синхронизация със самостоятелно хостван Termix сървър вече е по избор.",
"body2": "Вашите съществуващи хостове и идентификационни данни все още са на {{url}}. Включете отдалечената синхронизация и се свържете отново с този сървър, за да ги върнете и да поддържате синхронизирани занапред.",
"dismiss": "Не сега",
"setUpSync": "Настройване на отдалечена синхронизация"
},
"remoteSync": {
"title": "Дистанционна синхронизация",
"description": "По желание свържете това настолно приложение към самостоятелно хостван Termix сървър, за да синхронизирате вашите хостове, идентификационни данни и фрагменти на различни устройства. Приложението винаги работи напълно офлайн, независимо дали се свързвате или не.",
"notConnected": "Няма връзка",
"connected": "Свързан",
"connectedTo": "Свързано с {{url}}",
"lastSynced": "Последна синхронизация {{time}}",
"neverSynced": "Никога не е синхронизирано",
"syncError": "Грешка при синхронизиране: {{message}}",
"needsReauth": "Входът е изтекъл",
"connectButton": "Свързване със сървъра",
"disconnectButton": "Изключване",
"syncNowButton": "Синхронизирай сега",
"syncing": "Синхронизиране...",
"serverUrl": "URL адрес на сървъра",
"enterServerUrl": "Моля, въведете URL адрес на сървъра",
"mustIncludeProtocol": "URL адресът на сървъра трябва да започва с http:// или https://",
"connectionTestFailed": "Не можа да се свърже със сървър на Termix на този URL адрес",
"allowInvalidCertificate": "Разрешаване на невалиден сертификат",
"allowInvalidCertificateDesc": "Използвайте само за надеждни самостоятелно хоствани сървъри със самоподписани или IP-адресни сертификати.",
"savedServers": "Запазени сървъри",
"removeServer": "Премахване",
"continueButton": "Продължи",
"cancelButton": "Отказ",
"signInTitle": "Влезте в {{url}}",
"originTitle": "Произход на връзката",
"originDescription": "Изберете откъде произлизат SSH връзките по подразбиране. Това може да се промени за всеки хост.",
"originLocal": "Това устройство (локална мрежа)",
"originRemote": "Отдалечен сървър",
"bannerReconnect": "Свържете се отново",
"bannerMessage": "Дистанционната синхронизация изисква повторно удостоверяване"
},
"versionCheck": {
"error": "Грешка при проверка на версията",
"checkFailed": "Проверката за актуализации не бе успешна",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Замяна на потребителско име за идентификационни данни",
"overrideCredentialUsernameDesc": "Използвайте посоченото по-горе потребителско име вместо потребителското име от идентификационните данни",
"oidcUsernameHint": "Използвайте $oidc.preferred_username, за да заместите вашето OIDC потребителско име.",
"tailscaleUsernameHint": "Това трябва да е Unix потребител, чиято самоличност на Tailscale е предоставена в SSH ACL на tailnet, не е задължително root.",
"jumpHostChain": "Верига за прескачане на хост",
"portKnocking": "Порт Нокинг",
"addKnock": "Добавяне на порт",
@@ -690,11 +650,6 @@
"delayAfterMs": "Закъснение след (ms)",
"useSocks5Proxy": "Използвайте SOCKS5 прокси",
"useSocks5ProxyDesc": "Маршрутизиране на връзката през прокси сървър",
"connectionOrigin": "Произход на връзката",
"connectionOriginDesc": "Откъде произлиза SSH връзката на този хост. Заменя глобалната настройка по подразбиране на настолното приложение.",
"connectionOriginDefault": "Използвай по подразбиране",
"connectionOriginLocal": "Това устройство (локална мрежа)",
"connectionOriginRemote": "Отдалечен сървър",
"proxyHost": "Прокси хост",
"proxyPort": "Прокси порт",
"proxyUsername": "Потребителско име на прокси",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Ярко циан",
"customThemeBrightWhite": "Ярко бяло",
"customThemeResetTooltip": "Възстановяване на фабричните настройки",
"savedThemesLabel": "Запазени теми",
"saveAsGlobalTheme": "Запазване като глобална тема",
"saveGlobalThemeNamePrompt": "Въведете име за тази тема",
"saveGlobalThemeSuccess": "Темата е запазена",
"saveGlobalThemeError": "Запазването на темата не бе успешно",
"applyGlobalThemeTooltip": "Приложете тази тема",
"deleteGlobalThemeTooltip": "Изтрий тази тема",
"noSavedThemes": "Все още няма запазени теми",
"syntaxHighlightingLabel": "Подчертаване на синтаксиса",
"syntaxHighlightingDesc": "Оцветяване на изхода на терминала (грешки, пътища, IP адреси, времеви отпечатъци)",
"syntaxHighlightingCategories": "Маркирайте категориите",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Автоматично стартиране или прикачване към tmux сесия",
"enableSessionLogging": "Записване на сесии",
"enableSessionLoggingDesc": "Записване на изхода от терминалната сесия за по-късен преглед",
"allowSessionSharing": "Разрешаване на споделяне на сесия",
"allowSessionSharingDesc": "Позволете споделянето на сесиите на живо на този хост чрез линк или с други потребители",
"enableCommandHistory": "История на командите",
"enableCommandHistoryDesc": "Запис на команди, изпълнявани в този терминал, за история и автоматично довършване",
"linkClickBehaviorLabel": "Поведение при кликване върху връзки",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} импортирано",
"proxmoxResultUpdated": "{{count}} актуализирано",
"proxmoxResultFailed": "{{count}} неуспешно",
"proxmoxResultSkippedNoIp": "{{count}} пропуснато (не е намерен IP адрес)",
"proxmoxImportComplete": "Импортирането на Proxmox е завършено: {{summary}}",
"proxmoxDiscoveryFailed": "Откриването не беше успешно",
"proxmoxImportFailed": "Импортирането не бе успешно",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Запазването на идентификационните данни не бе успешно",
"credentialNameRequired": "Моля, въведете име за идентификационните данни",
"credentialAuthRequired": "Добавете парола, SSH ключ или и двете",
"createCredentialFromHostBtn": "Създаване на идентификационни данни",
"createCredentialFromHostTitle": "Създаване на идентификационни данни от хост",
"createCredentialFromHostDesc": "Създайте запис за многократна употреба и споделяне на идентификационни данни, предварително попълнен с текущото потребителско име, парола и/или SSH ключ на този хост.",
"backToHosts": "Обратно към Домакини",
"backToCredentials": "Обратно към удостоверенията",
"pinned": "Закачено",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Използвайте / за разделяне на нивата и създаване на вложени папки.",
"folderColor": "Цвят",
"folderIcon": "Икона",
"folderCredential": "Пълномощия",
"folderCredentialNone": "Няма присвоени идентификационни данни",
"folderCredentialHint": "Хостовете в тази папка, които използват удостоверяване „Съхранени идентификационни данни“, без да са избрани собствени идентификационни данни, ще наследят тези.",
"folderPreview": "Преглед",
"folderNameFallback": "Неозаглавена папка",
"createFolderButton": "Създаване на папка",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Етикети",
"shareHost": "Споделяне на хост",
"shareHostTitle": "Споделяне: {{name}}",
"shareFolder": "Споделяне на папка",
"shareFolderTitle": "Споделяне на папка: {{name}}",
"folderSharedSuccessfully": "Споделени {{count}} хост(ове) в папка",
"failedToShareFolder": "Споделянето на папката не бе успешно",
"sharing": {
"loadError": "Зареждането на данните за споделяне не бе успешно. Моля, опитайте отново.",
"shareWithSection": "Сподели с",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Споделяне ({{count}})",
"currentAccess": "Текущ достъп",
"noAccessEntries": "Този хост все още не е споделен",
"folderShareSummary": "Споделени {{shared}} от {{total}} хост(ове) в тази папка",
"grantedBy": "Предоставено от",
"expires": "Изтича",
"expired": "Изтекъл",
@@ -1309,8 +1242,6 @@
"authMethod": "Метод за удостоверяване",
"authTypeDirect": "Директно",
"authTypeCredential": "Пълномощия",
"authTypeNone": "Няма",
"authTypeNoneDesc": "Не се съхраняват идентификационни данни. Ще бъдете подканени да въведете потребителско име и парола всеки път, когато се свържете; те не се запазват.",
"selectCredential": "Изберете идентификационен номер...",
"connectionSettings": "Настройки за връзка",
"displaySettings": "Настройки на дисплея",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP порт",
"username": "Потребителско име",
"password": "Парола",
"passwordSaved": "Паролата е запазена, въведете, за да я промените",
"domain": "Домейн",
"securityMode": "Режим на сигурност",
"colorDepth": "Дълбочина на цвета",
@@ -1494,60 +1424,7 @@
"expiresIn": "Изтича след {{duration}}",
"search": "Търсене на връзки...",
"noSearchResults": "Няма връзки, които да отговарят на търсенето ви",
"rename": "Преименуване на сесия",
"sectionSharedWithMe": "Споделено с мен",
"sharedBy": "Споделено от {{username}}",
"join": "Присъединете се",
"sharedSessionLabel": "{{hostName}} (споделено)"
},
"sessionSharing": {
"guestView": {
"loading": "Свързване със споделена сесия...",
"linkInvalid": "Този линк за споделяне е невалиден, изтекъл или е отменен",
"rateLimited": "Твърде много опити, моля опитайте отново скоро",
"sessionEnded": "Тази сесия приключи",
"readOnlyBadge": "Само преглед"
},
"modalTitle": "Споделяне на сесия",
"shareButton": "Споделяне",
"notReadyToShare": "Сесията все още не е готова за споделяне",
"modeTab": {
"link": "Връзка",
"user": "Потребител"
},
"linkModeDescription": "Всеки с този линк може да се присъедини, не е необходим акаунт.",
"userModeDescription": "Споделете с конкретен потребител, който вече има достъп до този хост. Ако все още няма достъп, първо споделете хоста с него или използвайте връзка. След като бъде споделена, сесията се показва в раздела му „Връзки“.",
"permissionLevel": {
"label": "Ниво на разрешение",
"readOnly": "Само за четене",
"readOnlyDescription": "Може да гледа сесията на живо, но не може да пише или да взаимодейства.",
"readWrite": "Четене и запис",
"readWriteDescription": "Може да пише и да взаимодейства със сесията точно като собственика."
},
"expiryLabel": "Изтичане на срока на валидност на връзката",
"createLinkButton": "Създаване на връзка",
"createShareButton": "Споделяне с потребителя",
"searchUsersPlaceholder": "Търсене на потребители...",
"noUsersFound": "Не са намерени потребители",
"linkCreated": "Линкът за споделяне е създаден",
"linkCopied": "Връзката е копирана в буферната памет",
"copyLink": "Копиране на връзката",
"shareCreated": "Сесията е споделена. Тя ще се покаже в раздела им „Връзки“.",
"shareFailed": "Създаването на споделено съдържание не бе успешно",
"userLacksHostAccess": "Тозият потребител все още няма достъп до този хост. Първо споделете хоста с него или използвайте линк.",
"activeShares": "Активни акции",
"noActiveShares": "Няма активни споделяния за тази сесия",
"revoke": "Отмяна",
"revokeConfirmTitle": "Да се отмени ли това споделяне?",
"revokeConfirmDescription": "Всеки, който използва това споделяне, ще загуби достъп незабавно.",
"revoked": "Акцията е отменена",
"revokeFailed": "Неуспешно отменяне на споделянето",
"joinCount": "{{count}} присъединяване",
"joinCount_other": "{{count}} се присъединява",
"expiresAt": "Изтича {{date}}",
"linkShareBadge": "Връзка",
"userShareBadge": "Потребител: {{username}}",
"loadSharesFailed": "Зареждането на активните споделяния не бе успешно"
"rename": "Преименуване на сесия"
},
"guacamole": {
"connecting": "Свързване към сесия {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Свържете се отново",
"retry": "Опитай отново",
"guacdUnavailable": "Услугата за отдалечен работен плот (guacd) не е налична. Моля, уверете се, че guacd работи, е достъпна и е конфигурирана правилно в администраторските настройки.",
"credentialPromptTitle": "Въведете RDP идентификационни данни",
"credentialPromptDescription": "Този хост е настроен да изисква идентификационни данни при свързване. Те се използват само за тази сесия и не се запазват.",
"connect": "Свържете се",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Сесия за повторно свързване",
"collapse": "Свиване на лентата с инструменти",
"expand": "Разгъване на лентата с инструменти",
"dragHandle": "Плъзнете, за да препозиционирате",
"switchToTrackpad": "Превключване в режим на тракпад (плъзнете, за да преместите курсора, докоснете, за да щракнете)",
"switchToTouch": "Превключване към режим на докосване (докоснете директно там, където искате да щракнете)"
"dragHandle": "Плъзнете, за да препозиционирате"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Изисква се двуфакторно удостоверяване",
"totpCodeLabel": "Код за потвърждение",
"totpVerify": "Проверка",
"mfaPromptRequired": "Изисква се удостоверяване",
"mfaPushRequired": "Изисква се удостоверяване чрез push",
"mfaMenuPlaceholder": "Въведете отговора си",
"mfaWaitingApproval": "Чака се одобрение на вашето устройство...",
"mfaSendRequest": "Изпрати заявка",
"warpgateAuthRequired": "Изисква се удостоверяване на Warpgate",
"warpgateSecurityKey": "Ключ за сигурност",
"warpgateAuthUrl": "URL адрес за удостоверяване",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Ограничена скорост: Твърде много опити за потвърждение. Моля, опитайте отново по-късно.",
"resetCodeRateLimitedWithTime": "Ограничена скорост: Твърде много опити за потвърждение. Моля, изчакайте {{time}} секунди, преди да опитате отново.",
"authTokenSaveFailed": "Запазването на токена за удостоверяване не бе успешно",
"failedToLoadServer": "Зареждането на сървъра не бе успешно",
"remoteServerRequired": "Изисква се отдалечен сървър. Свържете се с отдалечен сървър в „Настройки“, за да използвате този тип връзка."
"failedToLoadServer": "Зареждането на сървъра не бе успешно"
},
"messages": {
"registrationDisabled": "Регистрацията на нов акаунт в момента е деактивирана от администратор. Моля, влезте или се свържете с администратор.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Запазването на настройките за SSL не бе успешно",
"sslRequiresDomain": "Домейнът и имейлът са задължителни",
"sslInfoNote": "След издаване на сертификат, активирайте SSL в променливите на средата (ENABLE_SSL=true) и рестартирайте Termix.",
"sslManualOption": "Ръководство (сертификат за качване)",
"sslManualCert": "Сертификат (PEM)",
"sslManualCertPlaceholder": "-----ЗАПОЧВАНЕ НА СЕРТИФИКАТ-----",
"sslManualKey": "Частен ключ (PEM)",
"sslManualKeyPlaceholder": "-----ЗАПОЧВАНЕ НА ЛИЧЕН КЛЮЧ-----",
"sslManualDesc": "Поставете съществуващия си сертификат и частен ключ, включително пълната верига, ако се изисква от вашия CA.",
"sslManualUpload": "Сертификат за качване и инсталиране",
"sslManualUploadLoading": "Качване на сертификат...",
"sslManualUploadSuccess": "Сертификатът е качен и инсталиран успешно",
"sslManualUploadFailed": "Качването на сертификата не бе успешно",
"sslManualRequiresFields": "Изискват се сертификат и частен ключ",
"auditLogTotal": "{{total}} общо записи",
"auditLogEmpty": "Няма намерени записи в журнала за одит",
"auditLogSuccess": "Успех",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "История на командите",
"commandHistoryEnabledDesc": "Разрешаване на записване на историята на командите. Когато е деактивирано, историята не се запазва, независимо от настройките за всеки хост.",
"updateCommandHistoryFailed": "Актуализирането на настройката за историята на командите не бе успешно",
"analyticsEnabled": "Споделяне на анонимни статистически данни за употреба",
"analyticsEnabledDesc": "Изпраща анонимен дневен брой потребители, хостове и използване на функции, за да помогне за подобряването на Termix. Никога не се включват лични данни или подробности за връзката.",
"updateAnalyticsFailed": "Актуализирането на настройката за анализ не бе успешно",
"sessionSharingGloballyEnabled": "Разрешаване на споделяне на сесия",
"sessionSharingGloballyEnabledDesc": "Разрешава споделянето на сесии на живо на терминал, RDP, VNC и Telnet в целия екземпляр. Заменя всяко превключвател за споделяне на хост, когато е деактивиран.",
"updateSessionSharingFailed": "Актуализирането на настройката за споделяне на сесията не бе успешно",
"sessionTimeout": "Време за изчакване на сесията",
"hours": "часове",
"sessionTimeoutRange": "Мин. 1 ч. · Макс. 720 ч.",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Успех",
"executionFailed": "Неуспешно"
},
"keybindings": {
"title": "Клавишни комбинации",
"description": "Персонализирайте клавишните комбинации за копиране, поставяне и управление на терминала или обвържете клавишите за изпращане на текст или изпълнение на фрагмент.",
"defaultsHeading": "Вградени преки пътища",
"customHeading": "Персонализирани преки пътища",
"addBinding": "Добавяне на пряк път",
"addBindingTitle": "Добавяне на пряк път",
"editBindingTitle": "Редактиране на пряк път",
"loading": "Зареждане...",
"noCustomBindings": "Все още няма персонализирани преки пътища",
"defaultBadge": "По подразбиране",
"customizedBadge": "Персонализирано",
"customize": "Персонализиране",
"resetToDefault": "Възстановяване на фабричните настройки",
"resetAllToDefaults": "Възстановяване на всички настройки по подразбиране",
"close": "Затвори",
"cancel": "Отказ",
"saveBinding": "Запазване на пряк път",
"saveError": "Запазването на клавишните комбинации не бе успешно",
"pressKeysToRecord": "Комбинация от клавиши",
"pressKeysPlaceholder": "Щракнете и натиснете клавиши",
"recording": "Натиснете клавишите...",
"comboRequiredError": "Първо натиснете клавишна комбинация",
"textRequiredError": "Въведете текста за изпращане",
"controlCodeRequiredError": "Въведете една буква за контролния код",
"snippetRequiredError": "Изберете фрагмент",
"conflictWarning": "Тази комбинация вече е свързана с {{combo}}. Запазването ще доведе до задействане и на двете.",
"actionLabel": "Действие",
"actionCopy": "Копиране на селекцията",
"actionPaste": "Паста",
"actionSendControlCode": "Изпрати сигнал Ctrl+буква",
"actionSendText": "Изпращане на текст",
"actionRunSnippet": "Изпълнете съществуващия фрагмент",
"controlCodeLabel": "Писмо",
"textLabel": "Текст за изпращане",
"appendEnterLabel": "Натиснете Enter след изпращане",
"snippetLabel": "Фрагмент",
"selectSnippetPlaceholder": "Изберете фрагмент",
"orphanedSnippetWarning": "фрагмент не е намерен",
"clipboardPermissionNote": "Може да поиска разрешение за достъп до клипборда при първото използване в някои браузъри."
},
"userProfile": {
"donateTitle": "Пази Термикс жив",
"donateDescription": "Termix е безплатен, самостоятелно хостван и изграден само от няколко души в свободното им време. Ако ви е спестил време или пари, дарението от крипто помага да продължи да функционира.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Външен вид",
"sectionSecurity": "Сигурност",
"sectionApiKeys": "API ключове",
"sectionData": "Данни",
"sectionC2sTunnels": "Тунели C2S",
"usernameLabel": "Потребителско име",
"roleLabel": "Роля",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Терминал",
"commandAutocomplete": "Автоматично довършване на команди",
"commandAutocompleteDesc": "Показване на автоматично довършване при въвеждане",
"keyboardShortcuts": "Клавишни комбинации",
"keyboardShortcutsDescription": "Персонализиране на клавишните комбинации за копиране, поставяне и команди в терминала",
"manageShortcuts": "Управление",
"terminalLinkBehavior": "Щракване върху връзката на терминала",
"terminalLinkBehaviorDesc": "Поведение по подразбиране при кликване върху връзки в терминала",
"historyTracking": "Проследяване на историята",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Включете ключа си в",
"apiKeyUsageHintHeader": "заглавка.",
"apiKeyPermissionsHint": "Ключовете наследяват разрешенията на потребителя, който ги е създал.",
"exportData": "Експортиране на моите данни",
"exportDataDesc": "Изтеглете резервно копие на вашите хостове, идентификационни данни и настройки, за да ги прехвърлите на друго устройство",
"export": "Експорт",
"exporting": "Експортиране...",
"importData": "Импортиране на моите данни",
"importDataDesc": "Възстановете вашите хостове, идентификационни данни и настройки от .sqlite архивен файл",
"importDataSelected": "Избрано: {{name}}",
"selectFile": "Изберете файл",
"changeFile": "Промяна",
"import": "Внос",
"importing": "Импортиране...",
"exportSuccess": "Данните са експортирани успешно",
"exportFailed": "Експортирането на данни не бе успешно",
"importSelectFile": "Моля, първо изберете файл",
"importCompleted": "Импортирането е завършено: {{total}} елемента са импортирани, {{skipped}} са пропуснати",
"importFailed": "Импортирането не бе успешно: {{error}}",
"roleUser": "Потребител",
"authMethodDual": "Двойно удостоверяване",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Дел",
"paste": "Паста",
"editQuickKeys": "Редактиране на бързи клавиши",
"quickKeysTitle": "Бързи клавиши",
"quickKeysDesc": "Докоснете × за премахване. Поддържа до 8 знака.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "কোনো সংরক্ষিত সার্ভার নেই",
"removeServer": "অপসারণ করুন"
},
"migrationNotice": {
"title": "টার্মিক্স ডেস্কটপ এখন স্বতন্ত্রভাবে চলে।",
"body1": "এই অ্যাপটি আগে সরাসরি আপনার টারমিক্স সার্ভারের সাথে সংযুক্ত হতো। এটিকে এখন সম্পূর্ণ স্বাধীনভাবে চলার জন্য নতুন করে তৈরি করা হয়েছে, যা হোস্ট এবং ক্রেডেনশিয়াল স্থানীয়ভাবে সংরক্ষণ করে, ফলে এটি অফলাইনেও কাজ করতে থাকে। সেলফ-হোস্টেড টারমিক্স সার্ভারের সাথে দ্বিমুখী সিঙ্ক এখন ঐচ্ছিক।",
"body2": "আপনার বর্তমান হোস্ট এবং ক্রেডেনশিয়াল এখনও {{url}}-এ রয়েছে। সেগুলোকে ফিরিয়ে আনতে এবং ভবিষ্যতে উভয়কে সিঙ্কে রাখতে রিমোট সিঙ্ক চালু করুন এবং সেই সার্ভারে পুনরায় সংযোগ করুন।",
"dismiss": "এখন নয়",
"setUpSync": "রিমোট সিঙ্ক সেট আপ করুন"
},
"remoteSync": {
"title": "রিমোট সিঙ্ক",
"description": "ঐচ্ছিকভাবে, আপনার হোস্ট, ক্রেডেনশিয়াল এবং স্নিপেটগুলো বিভিন্ন ডিভাইসে সিঙ্ক করার জন্য এই ডেস্কটপ অ্যাপটিকে একটি সেলফ-হোস্টেড টারমিক্স সার্ভারের সাথে সংযুক্ত করুন। আপনি সংযুক্ত করুন বা না করুন, অ্যাপটি সর্বদা সম্পূর্ণ অফলাইনে কাজ করে।",
"notConnected": "সংযুক্ত নয়",
"connected": "সংযুক্ত",
"connectedTo": "{{url}} এর সাথে সংযুক্ত",
"lastSynced": "সর্বশেষ সিঙ্ক করা হয়েছে {{time}}",
"neverSynced": "কখনো সিঙ্ক করা হয়নি",
"syncError": "সিঙ্ক ত্রুটি: {{message}}",
"needsReauth": "সাইন-ইন এর মেয়াদ শেষ হয়ে গেছে",
"connectButton": "সার্ভারে সংযোগ করুন",
"disconnectButton": "সংযোগ বিচ্ছিন্ন করুন",
"syncNowButton": "এখন সিঙ্ক করুন",
"syncing": "সিঙ্ক হচ্ছে...",
"serverUrl": "সার্ভার ইউআরএল",
"enterServerUrl": "অনুগ্রহ করে একটি সার্ভার URL লিখুন",
"mustIncludeProtocol": "সার্ভার URL অবশ্যই http:// অথবা https:// দিয়ে শুরু হতে হবে।",
"connectionTestFailed": "ঐ URL-এ কোনো Termix সার্ভারে পৌঁছানো যায়নি।",
"allowInvalidCertificate": "অবৈধ সার্টিফিকেট অনুমোদন করুন",
"allowInvalidCertificateDesc": "শুধুমাত্র সেলফ-সাইন্ড বা আইপি-অ্যাড্রেস সার্টিফিকেটযুক্ত বিশ্বস্ত সেলফ-হোস্টেড সার্ভারের জন্য ব্যবহার করুন।",
"savedServers": "সংরক্ষিত সার্ভার",
"removeServer": "অপসারণ করুন",
"continueButton": "চালিয়ে যান",
"cancelButton": "বাতিল করুন",
"signInTitle": "{{url}} এ সাইন ইন করুন",
"originTitle": "সংযোগের উৎস",
"originDescription": "ডিফল্টরূপে SSH সংযোগগুলি কোথা থেকে শুরু হবে তা বেছে নিন। এটি প্রতিটি হোস্টের জন্য আলাদাভাবে পরিবর্তন করা যেতে পারে।",
"originLocal": "এই ডিভাইস (স্থানীয় নেটওয়ার্ক)",
"originRemote": "রিমোট সার্ভার",
"bannerReconnect": "পুনরায় সংযোগ করুন",
"bannerMessage": "রিমোট সিঙ্কের জন্য পুনরায় প্রমাণীকরণ প্রয়োজন।"
},
"versionCheck": {
"error": "সংস্করণ যাচাই ত্রুটি",
"checkFailed": "আপডেট পরীক্ষা করতে ব্যর্থ হয়েছে",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "ক্রেডেনশিয়াল ব্যবহারকারীর নাম ওভাররাইড করুন",
"overrideCredentialUsernameDesc": "ক্রেডেনশিয়ালের ইউজারনেমের পরিবর্তে উপরে উল্লেখিত ইউজারনেমটি ব্যবহার করুন।",
"oidcUsernameHint": "আপনার OIDC লগইন নামের পরিবর্তে $oidc.preferred_username ব্যবহার করুন।",
"tailscaleUsernameHint": "এটি অবশ্যই একজন ইউনিক্স ব্যবহারকারী হতে হবে, যার মাধ্যমে আপনার টেইলনেট-এর SSH ACL-এ আপনার টেইলস্কেল পরিচয়কে অনুমতি দেওয়া হয়; এটি রুট হওয়া আবশ্যক নয়।",
"jumpHostChain": "জাম্প হোস্ট চেইন",
"portKnocking": "পোর্ট নকিং",
"addKnock": "পোর্ট যোগ করুন",
@@ -690,11 +650,6 @@
"delayAfterMs": "বিলম্বের পর (মিলিসেকেন্ড)",
"useSocks5Proxy": "SOCKS5 প্রক্সি ব্যবহার করুন",
"useSocks5ProxyDesc": "প্রক্সি সার্ভারের মাধ্যমে সংযোগ স্থাপন করুন",
"connectionOrigin": "সংযোগের উৎস",
"connectionOriginDesc": "এই হোস্টের SSH সংযোগটি কোথা থেকে শুরু হয়। এটি ডেস্কটপ অ্যাপের গ্লোবাল ডিফল্টকে ওভাররাইড করে।",
"connectionOriginDefault": "ডিফল্ট ব্যবহার করুন",
"connectionOriginLocal": "এই ডিভাইস (স্থানীয় নেটওয়ার্ক)",
"connectionOriginRemote": "রিমোট সার্ভার",
"proxyHost": "প্রক্সি হোস্ট",
"proxyPort": "প্রক্সি পোর্ট",
"proxyUsername": "প্রক্সি ব্যবহারকারীর নাম",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "উজ্জ্বল সায়ান",
"customThemeBrightWhite": "উজ্জ্বল সাদা",
"customThemeResetTooltip": "ডিফল্টে রিসেট করুন",
"savedThemesLabel": "সংরক্ষিত থিম",
"saveAsGlobalTheme": "গ্লোবাল থিম হিসেবে সংরক্ষণ করুন",
"saveGlobalThemeNamePrompt": "এই থিমের জন্য একটি নাম লিখুন",
"saveGlobalThemeSuccess": "থিম সংরক্ষিত",
"saveGlobalThemeError": "থিম সংরক্ষণ করতে ব্যর্থ হয়েছে",
"applyGlobalThemeTooltip": "এই থিমটি প্রয়োগ করুন",
"deleteGlobalThemeTooltip": "এই থিমটি মুছে ফেলুন",
"noSavedThemes": "এখনো কোনো থিম সংরক্ষণ করা হয়নি",
"syntaxHighlightingLabel": "সিনট্যাক্স হাইলাইটিং",
"syntaxHighlightingDesc": "টার্মিনাল আউটপুট রঙিন করুন (ত্রুটি, পাথ, আইপি, টাইমস্ট্যাম্প)",
"syntaxHighlightingCategories": "হাইলাইট বিভাগ",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "স্বয়ংক্রিয়ভাবে tmux সেশন চালু করুন বা সংযুক্ত করুন",
"enableSessionLogging": "সেশন লগিং",
"enableSessionLoggingDesc": "পরবর্তী পর্যালোচনার জন্য টার্মিনাল সেশনের আউটপুট রেকর্ড করুন।",
"allowSessionSharing": "সেশন শেয়ারিং অনুমোদন করুন",
"allowSessionSharingDesc": "এই হোস্টের লাইভ সেশনগুলো লিঙ্কের মাধ্যমে বা অন্যান্য ব্যবহারকারীদের সাথে শেয়ার করা যাবে।",
"enableCommandHistory": "কমান্ডের ইতিহাস",
"enableCommandHistoryDesc": "ইতিহাস এবং স্বয়ংক্রিয় সম্পূর্ণতার জন্য এই টার্মিনালে চালানো কমান্ডগুলো রেকর্ড করুন।",
"linkClickBehaviorLabel": "লিঙ্ক ক্লিকের আচরণ",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} আমদানি করা হয়েছে",
"proxmoxResultUpdated": "{{count}} আপডেট করা হয়েছে",
"proxmoxResultFailed": "{{count}} ব্যর্থ হয়েছে",
"proxmoxResultSkippedNoIp": "{{count}} বাদ দেওয়া হয়েছে (কোন আইপি পাওয়া যায়নি)",
"proxmoxImportComplete": "প্রক্সমক্স আমদানি সম্পন্ন: {{summary}}",
"proxmoxDiscoveryFailed": "আবিষ্কার ব্যর্থ হয়েছে",
"proxmoxImportFailed": "আমদানি ব্যর্থ হয়েছে",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "পরিচয়পত্র সংরক্ষণ করতে ব্যর্থ হয়েছে",
"credentialNameRequired": "অনুগ্রহ করে পরিচয়পত্রের জন্য একটি নাম লিখুন।",
"credentialAuthRequired": "একটি পাসওয়ার্ড, একটি SSH কী, অথবা উভয়ই যোগ করুন।",
"createCredentialFromHostBtn": "পরিচয়পত্র তৈরি করুন",
"createCredentialFromHostTitle": "হোস্ট থেকে ক্রেডেনশিয়াল তৈরি করুন",
"createCredentialFromHostDesc": "এই হোস্টের বর্তমান ইউজারনেম, পাসওয়ার্ড এবং/অথবা SSH কী দিয়ে আগে থেকেই পূরণ করা একটি পুনঃব্যবহারযোগ্য ও শেয়ারযোগ্য ক্রেডেনশিয়াল এন্ট্রি তৈরি করুন।",
"backToHosts": "হোস্টদের কাছে ফিরে যান",
"backToCredentials": "পরিচয়পত্রে ফিরে যান",
"pinned": "পিন করা",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "স্তর আলাদা করতে এবং নেস্টেড ফোল্ডার তৈরি করতে / ব্যবহার করুন।",
"folderColor": "রঙ",
"folderIcon": "আইকন",
"folderCredential": "শংসাপত্র",
"folderCredentialNone": "কোনো পরিচয়পত্র বরাদ্দ করা হয়নি",
"folderCredentialHint": "এই ফোল্ডারের যে হোস্টগুলো নিজেদের ক্রেডেনশিয়াল নির্বাচন না করে 'সংরক্ষিত ক্রেডেনশিয়াল' প্রমাণীকরণ ব্যবহার করে, তারা উত্তরাধিকারসূত্রে এটিই পাবে।",
"folderPreview": "প্রিভিউ",
"folderNameFallback": "শিরোনামহীন ফোল্ডার",
"createFolderButton": "ফোল্ডার তৈরি করুন",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "ট্যাগ",
"shareHost": "হোস্ট শেয়ার করুন",
"shareHostTitle": "শেয়ার করুন: {{name}}",
"shareFolder": "শেয়ার ফোল্ডার",
"shareFolderTitle": "শেয়ার ফোল্ডার: {{name}}",
"folderSharedSuccessfully": "ফোল্ডারে শেয়ার করা {{count}} হোস্ট(গুলি)",
"failedToShareFolder": "ফোল্ডার শেয়ার করতে ব্যর্থ হয়েছে",
"sharing": {
"loadError": "শেয়ারিং ডেটা লোড করা সম্ভব হয়নি। অনুগ্রহ করে আবার চেষ্টা করুন।",
"shareWithSection": "শেয়ার করুন",
@@ -1287,7 +1221,6 @@
"shareWithCount": "শেয়ার করুন ({{count}})",
"currentAccess": "বর্তমান অ্যাক্সেস",
"noAccessEntries": "এই হোস্টটি এখনও শেয়ার করা হয়নি।",
"folderShareSummary": "এই ফোল্ডারে {{total}} টি {{shared}} টি শেয়ার করা হোস্ট।",
"grantedBy": "মঞ্জুর করেছেন",
"expires": "মেয়াদ শেষ",
"expired": "মেয়াদোত্তীর্ণ",
@@ -1309,8 +1242,6 @@
"authMethod": "প্রমাণীকরণ পদ্ধতি",
"authTypeDirect": "সরাসরি",
"authTypeCredential": "শংসাপত্র",
"authTypeNone": "কোনোটিই না",
"authTypeNoneDesc": "কোনো পরিচয়পত্র সংরক্ষিত থাকে না। প্রতিবার সংযোগ করার সময় আপনাকে একটি ইউজারনেম এবং পাসওয়ার্ড দিতে বলা হবে; সেগুলি সংরক্ষণ করা হয় না।",
"selectCredential": "একটি শংসাপত্র নির্বাচন করুন...",
"connectionSettings": "সংযোগ সেটিংস",
"displaySettings": "ডিসপ্লে সেটিংস",
@@ -1328,7 +1259,6 @@
"rdpPort": "আরডিপি পোর্ট",
"username": "ব্যবহারকারীর নাম",
"password": "পাসওয়ার্ড",
"passwordSaved": "পাসওয়ার্ড সংরক্ষিত আছে, পরিবর্তন করতে টাইপ করুন",
"domain": "ডোমেইন",
"securityMode": "নিরাপত্তা মোড",
"colorDepth": "রঙের গভীরতা",
@@ -1494,60 +1424,7 @@
"expiresIn": "{{duration}}-এ মেয়াদ শেষ হবে",
"search": "সংযোগগুলি অনুসন্ধান করুন...",
"noSearchResults": "আপনার অনুসন্ধানের সাথে কোনো সংযোগ মেলেনি।",
"rename": "পুনঃনামকরণ অধিবেশন",
"sectionSharedWithMe": "আমার সাথে শেয়ার করা হয়েছে",
"sharedBy": "{{username}} দ্বারা শেয়ার করা হয়েছে",
"join": "যোগদান করুন",
"sharedSessionLabel": "{{hostName}} (ভাগ করা)"
},
"sessionSharing": {
"guestView": {
"loading": "শেয়ার করা সেশনে সংযোগ স্থাপন করা হচ্ছে...",
"linkInvalid": "এই শেয়ার লিঙ্কটি অবৈধ, মেয়াদোত্তীর্ণ, বা বাতিল করা হয়েছে।",
"rateLimited": "অনেকবার চেষ্টা করা হয়েছে, অনুগ্রহ করে কিছুক্ষণ পরে আবার চেষ্টা করুন।",
"sessionEnded": "এই অধিবেশন শেষ হয়েছে",
"readOnlyBadge": "শুধুমাত্র দেখার জন্য"
},
"modalTitle": "সেশন শেয়ার করুন",
"shareButton": "শেয়ার",
"notReadyToShare": "সেশনটি এখনও শেয়ার করার জন্য প্রস্তুত নয়।",
"modeTab": {
"link": "লিঙ্ক",
"user": "ব্যবহারকারী"
},
"linkModeDescription": "এই লিঙ্কের মাধ্যমে যে কেউ যোগ দিতে পারবেন, কোনো অ্যাকাউন্টের প্রয়োজন নেই।",
"userModeDescription": "এমন একজন নির্দিষ্ট ব্যবহারকারীর সাথে শেয়ার করুন যার এই হোস্টে আগে থেকেই অ্যাক্সেস আছে। যদি তাদের এখনও অ্যাক্সেস না থাকে, তাহলে প্রথমে তাদের সাথে হোস্টটি শেয়ার করুন অথবা এর পরিবর্তে একটি লিঙ্ক ব্যবহার করুন। শেয়ার করা হয়ে গেলে, সেশনটি তাদের 'কানেকশনস' ট্যাবে দেখা যাবে।",
"permissionLevel": {
"label": "অনুমতি স্তর",
"readOnly": "শুধুমাত্র পঠনযোগ্য",
"readOnlyDescription": "সেশনটি সরাসরি দেখা যাবে কিন্তু টাইপ বা অংশগ্রহণ করা যাবে না।",
"readWrite": "পড়া-লেখা",
"readWriteDescription": "মালিকের মতোই সেশনে টাইপ করতে এবং ইন্টারঅ্যাক্ট করতে পারবেন।"
},
"expiryLabel": "লিঙ্কের মেয়াদ শেষ",
"createLinkButton": "লিঙ্ক তৈরি করুন",
"createShareButton": "ব্যবহারকারীর সাথে শেয়ার করুন",
"searchUsersPlaceholder": "ব্যবহারকারীদের অনুসন্ধান করুন...",
"noUsersFound": "কোন ব্যবহারকারী খুঁজে পাওয়া যায়নি",
"linkCreated": "শেয়ার লিঙ্ক তৈরি করা হয়েছে",
"linkCopied": "লিঙ্ক ক্লিপবোর্ডে কপি করা হয়েছে",
"copyLink": "লিঙ্ক কপি করুন",
"shareCreated": "সেশনটি শেয়ার করা হয়েছে। এটি তাদের কানেকশনস ট্যাবে দেখা যাবে।",
"shareFailed": "শেয়ার তৈরি করতে ব্যর্থ হয়েছে",
"userLacksHostAccess": "ওই ব্যবহারকারীর এখনও এই হোস্টে অ্যাক্সেস নেই। প্রথমে তাদের সাথে হোস্টটি শেয়ার করুন, অথবা এর পরিবর্তে একটি লিঙ্ক ব্যবহার করুন।",
"activeShares": "সক্রিয় শেয়ার",
"noActiveShares": "এই সেশনের জন্য কোনো সক্রিয় শেয়ার নেই",
"revoke": "বাতিল করুন",
"revokeConfirmTitle": "এই শেয়ারটি প্রত্যাহার করবেন?",
"revokeConfirmDescription": "যে কেউ এই শেয়ারটি ব্যবহার করলে, তিনি অবিলম্বে অ্যাক্সেস হারাবেন।",
"revoked": "শেয়ার প্রত্যাহার করা হয়েছে",
"revokeFailed": "শেয়ার প্রত্যাহার করতে ব্যর্থ হয়েছে",
"joinCount": "{{count}} যোগদান করুন",
"joinCount_other": "{{count}} যোগদান করে",
"expiresAt": "মেয়াদ শেষ হবে {{date}}",
"linkShareBadge": "লিঙ্ক",
"userShareBadge": "ব্যবহারকারী: {{username}}",
"loadSharesFailed": "সক্রিয় শেয়ারগুলি লোড করতে ব্যর্থ হয়েছে"
"rename": "পুনঃনামকরণ অধিবেশন"
},
"guacamole": {
"connecting": "{{type}} সেশনে সংযোগ স্থাপন করা হচ্ছে...",
@@ -1559,9 +1436,6 @@
"reconnect": "পুনরায় সংযোগ করুন",
"retry": "পুনরায় চেষ্টা করুন",
"guacdUnavailable": "রিমোট ডেস্কটপ সার্ভিস (guacd) উপলব্ধ নেই। অনুগ্রহ করে নিশ্চিত করুন যে guacd চালু ও অ্যাক্সেসযোগ্য আছে এবং অ্যাডমিন সেটিংসে সঠিকভাবে কনফিগার করা আছে।",
"credentialPromptTitle": "RDP পরিচয়পত্র প্রবেশ করান",
"credentialPromptDescription": "এই হোস্টটি সংযোগের সময় ক্রেডেনশিয়াল চাওয়ার জন্য সেট করা আছে। এগুলি শুধুমাত্র এই সেশনের জন্য ব্যবহৃত হয় এবং সংরক্ষিত হয় না।",
"connect": "সংযোগ করুন",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "পুনরায় সংযোগ সেশন",
"collapse": "টুলবার সংকুচিত করুন",
"expand": "টুলবার প্রসারিত করুন",
"dragHandle": "অবস্থান পরিবর্তন করতে টেনে আনুন",
"switchToTrackpad": "ট্র্যাকপ্যাড মোডে যান (কার্সার সরাতে ড্র্যাগ করুন, ক্লিক করতে ট্যাপ করুন)",
"switchToTouch": "টাচ মোডে যান (যেখানে ক্লিক করতে চান সেখানে সরাসরি ট্যাপ করুন)"
"dragHandle": "অবস্থান পরিবর্তন করতে টেনে আনুন"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "দ্বি-স্তরীয় প্রমাণীকরণ আবশ্যক",
"totpCodeLabel": "যাচাইকরণ কোড",
"totpVerify": "যাচাই করুন",
"mfaPromptRequired": "প্রমাণীকরণ আবশ্যক",
"mfaPushRequired": "পুশ প্রমাণীকরণ আবশ্যক",
"mfaMenuPlaceholder": "আপনার প্রতিক্রিয়া লিখুন",
"mfaWaitingApproval": "আপনার ডিভাইসে অনুমোদনের জন্য অপেক্ষা করা হচ্ছে...",
"mfaSendRequest": "অনুরোধ পাঠান",
"warpgateAuthRequired": "ওয়ার্পগেট প্রমাণীকরণ আবশ্যক",
"warpgateSecurityKey": "নিরাপত্তা চাবি",
"warpgateAuthUrl": "প্রমাণীকরণ ইউআরএল",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "রেট সীমিত: অনেক বেশি যাচাইকরণ প্রচেষ্টা করা হয়েছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন।",
"resetCodeRateLimitedWithTime": "রেট সীমিত: অনেক বেশি যাচাইকরণ প্রচেষ্টা করা হয়েছে। অনুগ্রহ করে আবার চেষ্টা করার আগে {{time}} সেকেন্ড অপেক্ষা করুন।",
"authTokenSaveFailed": "প্রমাণীকরণ টোকেন সংরক্ষণ করতে ব্যর্থ হয়েছে",
"failedToLoadServer": "সার্ভার লোড করতে ব্যর্থ হয়েছে",
"remoteServerRequired": "রিমোট সার্ভার প্রয়োজন। এই সংযোগের ধরণটি ব্যবহার করতে সেটিংস-এ একটি রিমোট সার্ভার সংযুক্ত করুন।"
"failedToLoadServer": "সার্ভার লোড করতে ব্যর্থ হয়েছে"
},
"messages": {
"registrationDisabled": "অ্যাডমিন কর্তৃক নতুন অ্যাকাউন্ট নিবন্ধন বর্তমানে বন্ধ রাখা হয়েছে। অনুগ্রহ করে লগ ইন করুন অথবা কোনো প্রশাসকের সাথে যোগাযোগ করুন।",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "SSL সেটিংস সংরক্ষণ করতে ব্যর্থ হয়েছে",
"sslRequiresDomain": "ডোমেইন এবং ইমেল প্রয়োজন",
"sslInfoNote": "সার্টিফিকেট ইস্যু করার পর, আপনার এনভায়রনমেন্ট ভেরিয়েবলে SSL সক্রিয় করুন (ENABLE_SSL=true) এবং Termix রিস্টার্ট করুন।",
"sslManualOption": "ম্যানুয়াল (শংসাপত্র আপলোড করুন)",
"sslManualCert": "সার্টিফিকেট (পিইএম)",
"sslManualCertPlaceholder": "-----সনদপত্র শুরু-----",
"sslManualKey": "ব্যক্তিগত কী (পিইএম)",
"sslManualKeyPlaceholder": "-----ব্যক্তিগত কী শুরু-----",
"sslManualDesc": "আপনার বিদ্যমান সার্টিফিকেট এবং প্রাইভেট কী পেস্ট করুন, আপনার CA-এর প্রয়োজন হলে সম্পূর্ণ চেইনও অন্তর্ভুক্ত করুন।",
"sslManualUpload": "সার্টিফিকেট আপলোড ও ইনস্টল করুন",
"sslManualUploadLoading": "সার্টিফিকেট আপলোড করা হচ্ছে...",
"sslManualUploadSuccess": "সার্টিফিকেট সফলভাবে আপলোড এবং ইনস্টল করা হয়েছে।",
"sslManualUploadFailed": "সার্টিফিকেট আপলোড ব্যর্থ হয়েছে",
"sslManualRequiresFields": "সার্টিফিকেট এবং প্রাইভেট কী প্রয়োজন",
"auditLogTotal": "{{total}} মোট এন্ট্রি",
"auditLogEmpty": "কোনো অডিট লগ এন্ট্রি পাওয়া যায়নি",
"auditLogSuccess": "সাফল্য",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "কমান্ডের ইতিহাস",
"commandHistoryEnabledDesc": "কমান্ড হিস্ট্রি রেকর্ডিংয়ের অনুমতি দিন। এটি নিষ্ক্রিয় করা থাকলে, হোস্ট-ভিত্তিক সেটিংস নির্বিশেষে হিস্ট্রি সংরক্ষিত হয় না।",
"updateCommandHistoryFailed": "কমান্ড হিস্ট্রি সেটিং আপডেট করতে ব্যর্থ হয়েছে",
"analyticsEnabled": "বেনামী ব্যবহারের পরিসংখ্যান শেয়ার করুন",
"analyticsEnabledDesc": "টারমিক্সকে উন্নত করতে সাহায্য করার জন্য ব্যবহারকারী, হোস্ট এবং ফিচার ব্যবহারের একটি বেনামী দৈনিক গণনা পাঠায়। এতে কোনো ব্যক্তিগত তথ্য বা সংযোগের বিবরণ অন্তর্ভুক্ত করা হয় না।",
"updateAnalyticsFailed": "অ্যানালিটিক্স সেটিং আপডেট করতে ব্যর্থ হয়েছে",
"sessionSharingGloballyEnabled": "সেশন শেয়ারিং অনুমোদন করুন",
"sessionSharingGloballyEnabledDesc": "লাইভ টার্মিনাল, RDP, VNC, এবং টেলনেট সেশনগুলোকে ইনস্ট্যান্স-ব্যাপী শেয়ার করার অনুমতি দিন। এটি নিষ্ক্রিয় করা হলে, প্রতি-হোস্ট শেয়ারিং টগলকে ওভাররাইড করে।",
"updateSessionSharingFailed": "সেশন শেয়ারিং সেটিং আপডেট করতে ব্যর্থ হয়েছে",
"sessionTimeout": "সেশন টাইমআউট",
"hours": "ঘন্টা",
"sessionTimeoutRange": "সর্বনিম্ন ১ ঘণ্টা · সর্বোচ্চ ৭২০ ঘণ্টা",
@@ -3270,47 +3119,6 @@
"executionSuccess": "সাফল্য",
"executionFailed": "ব্যর্থ"
},
"keybindings": {
"title": "কীবোর্ড শর্টকাট",
"description": "টার্মিনালের কপি, পেস্ট ও কন্ট্রোল শর্টকাটগুলো নিজের মতো করে সাজিয়ে নিন, অথবা টেক্সট পাঠাতে বা কোনো স্নিপেট চালাতে কী-গুলো বাইন্ড করুন।",
"defaultsHeading": "অন্তর্নির্মিত শর্টকাট",
"customHeading": "কাস্টম শর্টকাট",
"addBinding": "শর্টকাট যোগ করুন",
"addBindingTitle": "শর্টকাট যোগ করুন",
"editBindingTitle": "সম্পাদনা শর্টকাট",
"loading": "লোড হচ্ছে...",
"noCustomBindings": "এখনো কোনো কাস্টম শর্টকাট নেই",
"defaultBadge": "ডিফল্ট",
"customizedBadge": "কাস্টমাইজড",
"customize": "কাস্টমাইজ করুন",
"resetToDefault": "ডিফল্টে রিসেট করুন",
"resetAllToDefaults": "সবকিছু ডিফল্টে রিসেট করুন",
"close": "বন্ধ করুন",
"cancel": "বাতিল করুন",
"saveBinding": "সংরক্ষণ শর্টকাট",
"saveError": "কিবোর্ড শর্টকাট সংরক্ষণ করা যায়নি",
"pressKeysToRecord": "চাবির সংমিশ্রণ",
"pressKeysPlaceholder": "ক্লিক করুন এবং কী টিপুন",
"recording": "কীগুলো চাপুন...",
"comboRequiredError": "প্রথমে একটি কী-কম্বিনেশন চাপুন",
"textRequiredError": "পাঠানোর জন্য টেক্সটটি লিখুন।",
"controlCodeRequiredError": "কন্ট্রোল কোডের জন্য একটি অক্ষর লিখুন।",
"snippetRequiredError": "একটি স্নিপেট নির্বাচন করুন",
"conflictWarning": "এই সংমিশ্রণটি ইতিমধ্যেই {{combo}}এর সাথে আবদ্ধ। সংরক্ষণ করলে উভয়ই সক্রিয় হবে।",
"actionLabel": "পদক্ষেপ",
"actionCopy": "কপি নির্বাচন",
"actionPaste": "পেস্ট",
"actionSendControlCode": "Ctrl+অক্ষর সংকেত পাঠান",
"actionSendText": "টেক্সট পাঠান",
"actionRunSnippet": "বিদ্যমান কোড চালান",
"controlCodeLabel": "চিঠি",
"textLabel": "পাঠানোর জন্য টেক্সট",
"appendEnterLabel": "পাঠানোর পর এন্টার চাপুন",
"snippetLabel": "স্নিপেট",
"selectSnippetPlaceholder": "একটি স্নিপেট নির্বাচন করুন",
"orphanedSnippetWarning": "স্নিপেটটি খুঁজে পাওয়া যায়নি",
"clipboardPermissionNote": "কিছু ব্রাউজারে প্রথমবার ব্যবহারের সময় ক্লিপবোর্ডের অনুমতি চাইতে পারে।"
},
"userProfile": {
"donateTitle": "টারমিক্সকে বাঁচিয়ে রাখুন",
"donateDescription": "টারমিক্স বিনামূল্যে, সেলফ-হোস্টেড এবং মাত্র কয়েকজন ব্যক্তি তাদের অবসর সময়ে এটি তৈরি করেছেন। যদি এটি আপনার সময় বা অর্থ বাঁচিয়ে থাকে, তবে একটি ক্রিপ্টো অনুদান এটিকে চালিয়ে যেতে সাহায্য করে।",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "চেহারা",
"sectionSecurity": "নিরাপত্তা",
"sectionApiKeys": "এপিআই কী",
"sectionData": "ডেটা",
"sectionC2sTunnels": "সি২এস টানেল",
"usernameLabel": "ব্যবহারকারীর নাম",
"roleLabel": "ভূমিকা",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "টার্মিনাল",
"commandAutocomplete": "কমান্ড অটোকমপ্লিট",
"commandAutocompleteDesc": "টাইপ করার সময় স্বয়ংক্রিয় সম্পূর্ণতা দেখান",
"keyboardShortcuts": "কীবোর্ড শর্টকাট",
"keyboardShortcutsDescription": "টার্মিনাল কপি, পেস্ট এবং কমান্ড শর্টকাট কাস্টমাইজ করুন",
"manageShortcuts": "পরিচালনা করুন",
"terminalLinkBehavior": "টার্মিনাল লিঙ্ক ক্লিক",
"terminalLinkBehaviorDesc": "টার্মিনালে লিঙ্কে ক্লিক করার সময় ডিফল্ট আচরণ",
"historyTracking": "ইতিহাস ট্র্যাকিং",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "আপনার চাবিটি অন্তর্ভুক্ত করুন",
"apiKeyUsageHintHeader": "হেডার।",
"apiKeyPermissionsHint": "কী-গুলো সৃষ্টিকারী ব্যবহারকারীর অনুমতি উত্তরাধিকারসূত্রে লাভ করে।",
"exportData": "আমার ডেটা রপ্তানি করুন",
"exportDataDesc": "অন্য ডিভাইসে স্থানান্তর করার জন্য আপনার হোস্ট, ক্রেডেনশিয়াল এবং সেটিংসের একটি ব্যাকআপ ডাউনলোড করুন।",
"export": "রপ্তানি",
"exporting": "রপ্তানি করা হচ্ছে...",
"importData": "আমার ডেটা আমদানি করুন",
"importDataDesc": "একটি .sqlite ব্যাকআপ ফাইল থেকে আপনার হোস্ট, ক্রেডেনশিয়াল এবং সেটিংস পুনরুদ্ধার করুন।",
"importDataSelected": "নির্বাচিত: {{name}}",
"selectFile": "ফাইল নির্বাচন করুন",
"changeFile": "পরিবর্তন",
"import": "আমদানি",
"importing": "আমদানি করা হচ্ছে...",
"exportSuccess": "ডেটা সফলভাবে রপ্তানি করা হয়েছে",
"exportFailed": "ডেটা রপ্তানি ব্যর্থ হয়েছে",
"importSelectFile": "অনুগ্রহ করে প্রথমে একটি ফাইল নির্বাচন করুন।",
"importCompleted": "আমদানি সম্পন্ন হয়েছে: {{total}} টি আইটেম আমদানি করা হয়েছে, {{skipped}} টি বাদ দেওয়া হয়েছে",
"importFailed": "আমদানি ব্যর্থ হয়েছে: {{error}}",
"roleUser": "ব্যবহারকারী",
"authMethodDual": "দ্বৈত প্রমাণীকরণ",
"authMethodOidc": "ওআইডিসি",
@@ -3591,7 +3379,6 @@
"pageUp": "পিজিআপ",
"pageDown": "পিজিডিএন",
"delete": "ডেল",
"paste": "পেস্ট",
"editQuickKeys": "দ্রুত কী সম্পাদনা করুন",
"quickKeysTitle": "দ্রুত চাবি",
"quickKeysDesc": "মুছে ফেলতে × চাপুন। সর্বোচ্চ ৮টি অক্ষর সমর্থন করে।",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Cap servidor desat",
"removeServer": "Elimina"
},
"migrationNotice": {
"title": "Termix Desktop ara funciona de manera autònoma",
"body1": "Aquesta aplicació abans es connectava directament al vostre servidor Termix. S'ha reelaborat per funcionar completament per si sola, emmagatzemant els hosts i les credencials localment per tal que continuï funcionant fora de línia. La sincronització bidireccional amb un servidor Termix autoallotjat ara és opcional.",
"body2": "Els vostres amfitrions i credencials existents encara són a {{url}}. Activeu la sincronització remota i torneu a connectar-vos a aquest servidor per tornar-los a sincronitzar i mantenir-los tots dos sincronitzats en el futur.",
"dismiss": "Ara no",
"setUpSync": "Configura la sincronització remota"
},
"remoteSync": {
"title": "Sincronització remota",
"description": "Opcionalment, podeu connectar aquesta aplicació d'escriptori a un servidor Termix autoallotjat per sincronitzar els vostres hosts, credencials i fragments entre dispositius. L'aplicació sempre funciona completament fora de línia, tant si us connecteu com si no.",
"notConnected": "No connectat",
"connected": "Connectat",
"connectedTo": "Connectat a {{url}}",
"lastSynced": "Última sincronització {{time}}",
"neverSynced": "Mai sincronitzat",
"syncError": "Error de sincronització: {{message}}",
"needsReauth": "L'inici de sessió ha caducat",
"connectButton": "Connecta't al servidor",
"disconnectButton": "Desconnecta",
"syncNowButton": "Sincronitza ara",
"syncing": "Sincronitzant...",
"serverUrl": "URL del servidor",
"enterServerUrl": "Si us plau, introduïu una URL de servidor",
"mustIncludeProtocol": "L'URL del servidor ha de començar per http:// o https://",
"connectionTestFailed": "No s'ha pogut connectar amb un servidor Termix en aquesta URL.",
"allowInvalidCertificate": "Permetre certificat no vàlid",
"allowInvalidCertificateDesc": "Utilitzeu-ho només per a servidors autoallotjats de confiança amb certificats autosignats o d'adreça IP.",
"savedServers": "Servidors desats",
"removeServer": "Elimina",
"continueButton": "Continua",
"cancelButton": "Cancel·la",
"signInTitle": "Inicia la sessió a {{url}}",
"originTitle": "Origen de la connexió",
"originDescription": "Trieu l'origen de les connexions SSH per defecte. Això es pot anul·lar per cada host.",
"originLocal": "Aquest dispositiu (xarxa local)",
"originRemote": "Servidor remot",
"bannerReconnect": "Reconnecta",
"bannerMessage": "La sincronització remota necessita una reautenticació"
},
"versionCheck": {
"error": "Error de comprovació de versió",
"checkFailed": "No s'ha pogut comprovar si hi ha actualitzacions",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Substitueix les credencials i el nom d'usuari",
"overrideCredentialUsernameDesc": "Feu servir el nom d'usuari especificat anteriorment en comptes del nom d'usuari de la credencial",
"oidcUsernameHint": "Feu servir $oidc.preferred_username per substituir el vostre nom d'usuari d'inici de sessió OIDC.",
"tailscaleUsernameHint": "Aquest ha de ser un usuari d'Unix; la teva identitat Tailscale està atorgada a l'ACL SSH de la xarxa principal, no necessàriament un usuari root.",
"jumpHostChain": "Cadena d'amfitrió de salt",
"portKnocking": "Cop de port",
"addKnock": "Afegeix un port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Retard després (ms)",
"useSocks5Proxy": "Utilitza el servidor intermediari SOCKS5",
"useSocks5ProxyDesc": "Enrutar la connexió a través d'un servidor proxy",
"connectionOrigin": "Origen de la connexió",
"connectionOriginDesc": "D'on s'origina la connexió SSH d'aquest amfitrió. Substitueix el valor predeterminat global de l'aplicació d'escriptori.",
"connectionOriginDefault": "Utilitza el valor per defecte",
"connectionOriginLocal": "Aquest dispositiu (xarxa local)",
"connectionOriginRemote": "Servidor remot",
"proxyHost": "Amfitrió intermediari",
"proxyPort": "Port de proxy",
"proxyUsername": "Nom d'usuari del proxy",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Cian brillant",
"customThemeBrightWhite": "Blanc brillant",
"customThemeResetTooltip": "Restableix els valors predeterminats",
"savedThemesLabel": "Temes desats",
"saveAsGlobalTheme": "Desa com a tema global",
"saveGlobalThemeNamePrompt": "Introdueix un nom per a aquest tema",
"saveGlobalThemeSuccess": "Tema desat",
"saveGlobalThemeError": "No s'ha pogut desar el tema",
"applyGlobalThemeTooltip": "Aplica aquest tema",
"deleteGlobalThemeTooltip": "Suprimeix aquest tema",
"noSavedThemes": "Encara no hi ha temes desats",
"syntaxHighlightingLabel": "Ressaltat de sintaxi",
"syntaxHighlightingDesc": "Coloritzar la sortida del terminal (errors, camins, IP, marques de temps)",
"syntaxHighlightingCategories": "Categories destacades",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Inicia o connecta automàticament a la sessió de tmux",
"enableSessionLogging": "Registre de sessions",
"enableSessionLoggingDesc": "Enregistra la sortida de la sessió de terminal per a una revisió posterior",
"allowSessionSharing": "Permetre la compartició de sessions",
"allowSessionSharingDesc": "Permet que les sessions en directe d'aquest amfitrió es comparteixin mitjançant un enllaç o amb altres usuaris",
"enableCommandHistory": "Historial de comandaments",
"enableCommandHistoryDesc": "Enregistra les ordres executades en aquest terminal per a l'historial i l'autocompleció",
"linkClickBehaviorLabel": "Comportament dels clics d'enllaç",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importat",
"proxmoxResultUpdated": "{{count}} actualitzat",
"proxmoxResultFailed": "{{count}} ha fallat",
"proxmoxResultSkippedNoIp": "{{count}} omès (no s'ha trobat cap IP)",
"proxmoxImportComplete": "Importació de Proxmox completada: {{summary}}",
"proxmoxDiscoveryFailed": "El descobriment ha fallat",
"proxmoxImportFailed": "La importació ha fallat",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "No s'ha pogut desar la credencial",
"credentialNameRequired": "Si us plau, introduïu un nom per a la credencial",
"credentialAuthRequired": "Afegiu una contrasenya, una clau SSH o totes dues",
"createCredentialFromHostBtn": "Crea credencials",
"createCredentialFromHostTitle": "Crea credencials des de l'amfitrió",
"createCredentialFromHostDesc": "Crea una entrada de credencials reutilitzable i compartible preomplerta amb el nom d'usuari, la contrasenya i/o la clau SSH actuals d'aquest amfitrió.",
"backToHosts": "Torna a Amfitrions",
"backToCredentials": "Torna a les credencials",
"pinned": "Fixat",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Feu servir / per separar nivells i crear carpetes imbricades.",
"folderColor": "Color",
"folderIcon": "Icona",
"folderCredential": "Credencial",
"folderCredentialNone": "Cap credencial assignada",
"folderCredentialHint": "Els amfitrions d'aquesta carpeta que utilitzen l'autenticació \"Credencial emmagatzemada\" sense tenir les seves pròpies credencials seleccionades heretaran aquesta.",
"folderPreview": "Vista prèvia",
"folderNameFallback": "Carpeta sense títol",
"createFolderButton": "Crea una carpeta",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Etiquetes",
"shareHost": "Comparteix l'amfitrió",
"shareHostTitle": "Comparteix: {{name}}",
"shareFolder": "Compartir carpeta",
"shareFolderTitle": "Compartir carpeta: {{name}}",
"folderSharedSuccessfully": "Host(s) compartit(s) {{count}} a la carpeta",
"failedToShareFolder": "No s'ha pogut compartir la carpeta",
"sharing": {
"loadError": "No s'han pogut carregar les dades de compartició. Torna-ho a provar.",
"shareWithSection": "Comparteix amb",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Comparteix ({{count}})",
"currentAccess": "Accés actual",
"noAccessEntries": "Aquest amfitrió encara no s'ha compartit",
"folderShareSummary": "S'han compartit {{shared}} de {{total}} amfitrions en aquesta carpeta",
"grantedBy": "Atorgat per",
"expires": "Caduca",
"expired": "Caducat",
@@ -1309,8 +1242,6 @@
"authMethod": "Mètode d'autenticació",
"authTypeDirect": "Directe",
"authTypeCredential": "Credencial",
"authTypeNone": "Cap",
"authTypeNoneDesc": "No s'emmagatzemen les credencials. Se us demanarà un nom d'usuari i una contrasenya cada vegada que us connecteu; no es desen.",
"selectCredential": "Selecciona una credencial...",
"connectionSettings": "Configuració de connexió",
"displaySettings": "Configuració de la pantalla",
@@ -1328,7 +1259,6 @@
"rdpPort": "Port RDP",
"username": "Nom d'usuari",
"password": "Contrasenya",
"passwordSaved": "Contrasenya desada, escriu per canviar-la",
"domain": "Domini",
"securityMode": "Mode de seguretat",
"colorDepth": "Profunditat de color",
@@ -1494,60 +1424,7 @@
"expiresIn": "Caduca en {{duration}}",
"search": "Cerca connexions...",
"noSearchResults": "No hi ha connexions que coincideixin amb la teva cerca",
"rename": "Canvia el nom de la sessió",
"sectionSharedWithMe": "Compartit amb mi",
"sharedBy": "Compartit per {{username}}",
"join": "Uneix-te",
"sharedSessionLabel": "{{hostName}} (compartit)"
},
"sessionSharing": {
"guestView": {
"loading": "Connectant a la sessió compartida...",
"linkInvalid": "Aquest enllaç per compartir no és vàlid, ha caducat o s'ha revocat.",
"rateLimited": "Massa intents, si us plau, torneu-ho a intentar en breu",
"sessionEnded": "Aquesta sessió ha finalitzat",
"readOnlyBadge": "Només visualització"
},
"modalTitle": "Comparteix la sessió",
"shareButton": "Comparteix",
"notReadyToShare": "La sessió encara no està a punt per compartir",
"modeTab": {
"link": "Enllaç",
"user": "Usuari"
},
"linkModeDescription": "Qualsevol persona amb aquest enllaç s'hi pot unir, no cal tenir cap compte.",
"userModeDescription": "Comparteix amb un usuari específic que ja tingui accés a aquest amfitrió. Si encara no hi té accés, comparteix primer l'amfitrió amb ell o utilitza un enllaç. Un cop compartida, la sessió apareix a la pestanya Connexions.",
"permissionLevel": {
"label": "Nivell de permís",
"readOnly": "Només lectura",
"readOnlyDescription": "Puc veure la sessió en directe però no puc escriure ni interactuar.",
"readWrite": "Lectura i escriptura",
"readWriteDescription": "Pot escriure i interactuar amb la sessió igual que el propietari."
},
"expiryLabel": "Caducitat de l'enllaç",
"createLinkButton": "Crea un enllaç",
"createShareButton": "Comparteix amb l'usuari",
"searchUsersPlaceholder": "Cerca usuaris...",
"noUsersFound": "No s'han trobat usuaris",
"linkCreated": "Enllaç de compartició creat",
"linkCopied": "Enllaç copiat al porta-retalls",
"copyLink": "Copia l'enllaç",
"shareCreated": "Sessió compartida. Apareixerà a la pestanya Connexions.",
"shareFailed": "No s'ha pogut crear la compartició",
"userLacksHostAccess": "Aquest usuari encara no té accés a aquest amfitrió. Primer comparteix l'amfitrió amb ell o utilitza un enllaç.",
"activeShares": "Accions actives",
"noActiveShares": "No hi ha cap compartició activa per a aquesta sessió",
"revoke": "Revocar",
"revokeConfirmTitle": "Voleu revocar aquesta compartició?",
"revokeConfirmDescription": "Qualsevol persona que utilitzi aquesta opció compartida perdrà l'accés immediatament.",
"revoked": "Compartir revocat",
"revokeFailed": "No s'ha pogut revocar la compartició",
"joinCount": "{{count}} uneix-te",
"joinCount_other": "{{count}} s'uneix",
"expiresAt": "Caduca {{date}}",
"linkShareBadge": "Enllaç",
"userShareBadge": "Usuari: {{username}}",
"loadSharesFailed": "No s'han pogut carregar les comparticions actives"
"rename": "Canvia el nom de la sessió"
},
"guacamole": {
"connecting": "Connectant a la sessió {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Reconnecta",
"retry": "Torna-ho a intentar",
"guacdUnavailable": "El servei d'escriptori remot (guacd) no està disponible. Assegureu-vos que guacd estigui en execució, sigui accessible i estigui configurat correctament a la configuració d'administrador.",
"credentialPromptTitle": "Introduïu les credencials RDP",
"credentialPromptDescription": "Aquest amfitrió està configurat per demanar credencials en connectar-se. Només s'utilitzen per a aquesta sessió i no es desen.",
"connect": "Connecta",
"ctrlAltDel": "Ctrl+Alt+Supr",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Supr",
@@ -1587,9 +1461,7 @@
"reconnect": "Sessió de reconnexió",
"collapse": "Replega la barra d'eines",
"expand": "Expandeix la barra d'eines",
"dragHandle": "Arrossega per reposicionar",
"switchToTrackpad": "Canvia al mode trackpad (arrossega per moure el cursor, toca per fer clic)",
"switchToTouch": "Canvia al mode tàctil (toca directament on vols fer clic)"
"dragHandle": "Arrossega per reposicionar"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Cal autenticació de dos factors",
"totpCodeLabel": "Codi de verificació",
"totpVerify": "Verifica",
"mfaPromptRequired": "Cal autenticació",
"mfaPushRequired": "Autenticació push obligatòria",
"mfaMenuPlaceholder": "Introdueix la teva resposta",
"mfaWaitingApproval": "Esperant aprovació al dispositiu...",
"mfaSendRequest": "Enviar sol·licitud",
"warpgateAuthRequired": "Cal autenticació de Warpgate",
"warpgateSecurityKey": "Clau de seguretat",
"warpgateAuthUrl": "URL d'autenticació",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Tarifa limitada: massa intents de verificació. Torna-ho a provar més tard.",
"resetCodeRateLimitedWithTime": "Taxa limitada: massa intents de verificació. Espereu {{time}} segons abans de tornar-ho a intentar.",
"authTokenSaveFailed": "No s'ha pogut desar el testimoni d'autenticació",
"failedToLoadServer": "No s'ha pogut carregar el servidor",
"remoteServerRequired": "Cal un servidor remot. Connecteu un servidor remot a Configuració per utilitzar aquest tipus de connexió."
"failedToLoadServer": "No s'ha pogut carregar el servidor"
},
"messages": {
"registrationDisabled": "El registre de comptes nous està desactivat actualment per un administrador. Si us plau, inicia la sessió o contacta amb un administrador.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "No s'ha pogut desar la configuració SSL",
"sslRequiresDomain": "Cal el domini i el correu electrònic",
"sslInfoNote": "Després d'emetre un certificat, activeu SSL a les variables d'entorn (ENABLE_SSL=true) i reinicieu Termix.",
"sslManualOption": "Manual (pujar certificat)",
"sslManualCert": "Certificat (PEM)",
"sslManualCertPlaceholder": "-----CERTIFICAT D'INICI-----",
"sslManualKey": "Clau privada (PEM)",
"sslManualKeyPlaceholder": "-----INICI DE LA CLAU PRIVADA-----",
"sslManualDesc": "Enganxeu el certificat i la clau privada existents, incloent-hi una cadena completa si la vostra autoritat de certificació ho requereix.",
"sslManualUpload": "Pujar i instal·lar el certificat",
"sslManualUploadLoading": "S'està carregant el certificat...",
"sslManualUploadSuccess": "El certificat s'ha carregat i instal·lat correctament",
"sslManualUploadFailed": "Ha fallat la càrrega del certificat",
"sslManualRequiresFields": "Cal un certificat i una clau privada",
"auditLogTotal": "{{total}} total d'entrades",
"auditLogEmpty": "No s'han trobat entrades del registre d'auditoria",
"auditLogSuccess": "Èxit",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Historial de comandaments",
"commandHistoryEnabledDesc": "Permet l'enregistrament de l'historial d'ordres. Quan està desactivat, l'historial no es desa independentment de la configuració de cada host.",
"updateCommandHistoryFailed": "No s'ha pogut actualitzar la configuració de l'historial d'ordres",
"analyticsEnabled": "Comparteix estadístiques d'ús anònimes",
"analyticsEnabledDesc": "Envia un recompte diari anònim d'usuaris, amfitrions i ús de funcions per ajudar a millorar Termix. No s'hi inclouen mai dades personals ni detalls de connexió.",
"updateAnalyticsFailed": "No s'ha pogut actualitzar la configuració d'analítica",
"sessionSharingGloballyEnabled": "Permetre la compartició de sessions",
"sessionSharingGloballyEnabledDesc": "Permet que les sessions de terminal, RDP, VNC i Telnet en directe es comparteixin a tota la instància. Anul·la tots els activables i desactivables per compartir per host quan estan desactivats.",
"updateSessionSharingFailed": "No s'ha pogut actualitzar la configuració de compartició de sessions",
"sessionTimeout": "Temps d'espera de la sessió",
"hours": "hores",
"sessionTimeoutRange": "Mín 1 h · Màx 720 h",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Èxit",
"executionFailed": "Fallat"
},
"keybindings": {
"title": "Dreceres de teclat",
"description": "Personalitza les dreceres de copiar, enganxar i controlar del terminal, o vincula tecles per enviar text o executar un fragment.",
"defaultsHeading": "Dreceres integrades",
"customHeading": "Dreceres personalitzades",
"addBinding": "Afegeix una drecera",
"addBindingTitle": "Afegeix una drecera",
"editBindingTitle": "Edita la drecera",
"loading": "S'està carregant...",
"noCustomBindings": "Encara no hi ha dreceres personalitzades",
"defaultBadge": "Per defecte",
"customizedBadge": "Personalitzat",
"customize": "Personalitza",
"resetToDefault": "Restableix els valors predeterminats",
"resetAllToDefaults": "Restableix-ho tot als valors predeterminats",
"close": "Tanca",
"cancel": "Cancel·la",
"saveBinding": "Desa la drecera",
"saveError": "No s'han pogut desar les dreceres de teclat",
"pressKeysToRecord": "Combinació de tecles",
"pressKeysPlaceholder": "Feu clic i premeu les tecles",
"recording": "Premeu les tecles...",
"comboRequiredError": "Premeu primer una combinació de tecles",
"textRequiredError": "Introduïu el text per enviar",
"controlCodeRequiredError": "Introduïu una sola lletra per al codi de control",
"snippetRequiredError": "Selecciona un fragment",
"conflictWarning": "Aquesta combinació ja està vinculada a {{combo}}. Si deseu, totes dues es dispararan.",
"actionLabel": "Acció",
"actionCopy": "Copia la selecció",
"actionPaste": "Enganxa",
"actionSendControlCode": "Envia el senyal Ctrl+lletra",
"actionSendText": "Enviar text",
"actionRunSnippet": "Executa el fragment existent",
"controlCodeLabel": "Carta",
"textLabel": "Text per enviar",
"appendEnterLabel": "Premeu Intro després d'enviar",
"snippetLabel": "Fragment",
"selectSnippetPlaceholder": "Selecciona un fragment",
"orphanedSnippetWarning": "fragment no trobat",
"clipboardPermissionNote": "Pot demanar permisos al porta-retalls en el primer ús en alguns navegadors."
},
"userProfile": {
"donateTitle": "Mantingueu viva la Termix",
"donateDescription": "Termix és gratuït, s'allotja per si mateix i només l'han creat unes poques persones en el seu temps lliure. Si t'ha estalviat temps o diners, una donació de criptomonedes t'ajudarà a mantenir-lo en marxa.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Aspecte",
"sectionSecurity": "Seguretat",
"sectionApiKeys": "Claus API",
"sectionData": "Dades",
"sectionC2sTunnels": "Túnels C2S",
"usernameLabel": "Nom d'usuari",
"roleLabel": "Rol",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Autocompletar ordres",
"commandAutocompleteDesc": "Mostra l'autocompleció mentre s'escriu",
"keyboardShortcuts": "Dreceres de teclat",
"keyboardShortcutsDescription": "Personalitza les dreceres de comandament i copiar, enganxar i copiar al terminal",
"manageShortcuts": "Gestiona",
"terminalLinkBehavior": "Clic d'enllaç del terminal",
"terminalLinkBehaviorDesc": "Comportament predeterminat en fer clic a enllaços al terminal",
"historyTracking": "Seguiment de l'historial",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Inclou la teva clau a la",
"apiKeyUsageHintHeader": "capçalera.",
"apiKeyPermissionsHint": "Les claus hereten els permisos de l'usuari que les crea.",
"exportData": "Exporta les meves dades",
"exportDataDesc": "Baixeu una còpia de seguretat dels vostres amfitrions, credencials i configuració per transferir-la a un altre dispositiu",
"export": "Exporta",
"exporting": "Exportant...",
"importData": "Importa les meves dades",
"importDataDesc": "Restaura els teus hosts, credencials i configuració des d'un fitxer de còpia de seguretat .sqlite",
"importDataSelected": "Seleccionat: {{name}}",
"selectFile": "Selecciona un fitxer",
"changeFile": "Canvi",
"import": "Importa",
"importing": "Important...",
"exportSuccess": "Dades exportades correctament",
"exportFailed": "L'exportació de dades ha fallat",
"importSelectFile": "Si us plau, seleccioneu primer un fitxer",
"importCompleted": "Importació completada: {{total}} elements importats, {{skipped}} omesos",
"importFailed": "La importació ha fallat: {{error}}",
"roleUser": "Usuari",
"authMethodDual": "Doble autenticació",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "Re Pàg",
"pageDown": "Av Pàg",
"delete": "Eliminar",
"paste": "Enganxa",
"editQuickKeys": "Edita les tecles ràpides",
"quickKeysTitle": "Tecles ràpides",
"quickKeysDesc": "Toca × per eliminar. Admet fins a 8 caràcters.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Žádné uložené servery",
"removeServer": "Odstranit"
},
"migrationNotice": {
"title": "Termix Desktop nyní běží samostatně",
"body1": "Tato aplikace se dříve připojovala přímo k vašemu serveru Termix. Byla přepracována tak, aby běžela plně samostatně a lokálně ukládala hostitele a přihlašovací údaje, takže fungovala i offline. Obousměrná synchronizace se samostatně hostovaným serverem Termix je nyní volitelná.",
"body2": "Vaši stávající hostitelé a přihlašovací údaje jsou stále na {{url}}. Zapněte vzdálenou synchronizaci a znovu se připojte k danému serveru, abyste je obnovili a udrželi obojí synchronizované i v budoucnu.",
"dismiss": "Teď ne",
"setUpSync": "Nastavení vzdálené synchronizace"
},
"remoteSync": {
"title": "Vzdálená synchronizace",
"description": "Volitelně můžete tuto desktopovou aplikaci připojit k serveru Termix hostovanému na vlastním serveru a synchronizovat hostitele, přihlašovací údaje a úryvky kódu napříč zařízeními. Aplikace vždy funguje plně offline, ať už se připojíte, nebo ne.",
"notConnected": "Nepřipojeno",
"connected": "Připojeno",
"connectedTo": "Připojeno k {{url}}",
"lastSynced": "Naposledy synchronizováno {{time}}",
"neverSynced": "Nikdy nesynchronizováno",
"syncError": "Chyba synchronizace: {{message}}",
"needsReauth": "Platnost přihlášení vypršela",
"connectButton": "Připojení k serveru",
"disconnectButton": "Odpojit",
"syncNowButton": "Synchronizovat nyní",
"syncing": "Synchronizace...",
"serverUrl": "URL serveru",
"enterServerUrl": "Zadejte prosím URL adresu serveru",
"mustIncludeProtocol": "URL serveru musí začínat http:// nebo https://",
"connectionTestFailed": "Na dané URL adrese se nepodařilo spojit se se serverem Termix.",
"allowInvalidCertificate": "Povolit neplatný certifikát",
"allowInvalidCertificateDesc": "Používejte pouze pro důvěryhodné servery s vlastním hostováním a certifikáty s vlastním podpisem nebo certifikáty IP adresy.",
"savedServers": "Uložené servery",
"removeServer": "Odstranit",
"continueButton": "Pokračovat",
"cancelButton": "Zrušit",
"signInTitle": "Přihlaste se do {{url}}",
"originTitle": "Původ připojení",
"originDescription": "Ve výchozím nastavení vyberte, odkud SSH připojení pocházejí. Toto nastavení lze u jednotlivých hostitelů přepsat.",
"originLocal": "Toto zařízení (lokální síť)",
"originRemote": "Vzdálený server",
"bannerReconnect": "Znovu se připojte",
"bannerMessage": "Vzdálená synchronizace vyžaduje opětovné ověření"
},
"versionCheck": {
"error": "Chyba kontroly verze",
"checkFailed": "Nepodařilo se zkontrolovat aktualizace",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Přepsat uživatelské jméno pro přihlašovací údaje",
"overrideCredentialUsernameDesc": "Použijte výše uvedené uživatelské jméno místo uživatelského jména z přihlašovacích údajů.",
"oidcUsernameHint": "Použijte $oidc.preferred_username jako náhradu za vaše přihlašovací jméno OIDC.",
"tailscaleUsernameHint": "Musí se jednat o uživatele Unixu, kterému je vaše identita Tailscale udělena v SSH ACL tailnetu, nikoli nutně o uživatele root.",
"jumpHostChain": "Přejít k hostitelskému řetězci",
"portKnocking": "Klepnutí na přístav",
"addKnock": "Přidat port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Zpoždění po (ms)",
"useSocks5Proxy": "Použít proxy SOCKS5",
"useSocks5ProxyDesc": "Směrování připojení přes proxy server",
"connectionOrigin": "Původ připojení",
"connectionOriginDesc": "Odkud pochází SSH připojení tohoto hostitele. Přepíše globální výchozí nastavení desktopové aplikace.",
"connectionOriginDefault": "Použít výchozí nastavení",
"connectionOriginLocal": "Toto zařízení (lokální síť)",
"connectionOriginRemote": "Vzdálený server",
"proxyHost": "Proxy hostitel",
"proxyPort": "Proxy port",
"proxyUsername": "Uživatelské jméno proxy serveru",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Jasně azurová",
"customThemeBrightWhite": "Jasně bílá",
"customThemeResetTooltip": "Obnovit na výchozí nastavení",
"savedThemesLabel": "Uložená témata",
"saveAsGlobalTheme": "Uložit jako globální motiv",
"saveGlobalThemeNamePrompt": "Zadejte název tohoto motivu",
"saveGlobalThemeSuccess": "Téma uloženo",
"saveGlobalThemeError": "Uložení motivu se nezdařilo",
"applyGlobalThemeTooltip": "Použít toto téma",
"deleteGlobalThemeTooltip": "Smazat toto téma",
"noSavedThemes": "Zatím žádné uložené motivy",
"syntaxHighlightingLabel": "Zvýrazňování syntaxe",
"syntaxHighlightingDesc": "Obarvení výstupu terminálu (chyby, cesty, IP adresy, časová razítka)",
"syntaxHighlightingCategories": "Zvýraznit kategorie",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Automatické spuštění nebo připojení k relaci tmux",
"enableSessionLogging": "Protokolování relací",
"enableSessionLoggingDesc": "Zaznamenejte výstup terminálové relace pro pozdější kontrolu",
"allowSessionSharing": "Povolit sdílení relace",
"allowSessionSharingDesc": "Umožnit sdílení živých relací na tomto hostiteli prostřednictvím odkazu nebo s ostatními uživateli",
"enableCommandHistory": "Historie příkazů",
"enableCommandHistoryDesc": "Zaznamenávejte příkazy spouštěné v tomto terminálu pro historii a automatické dokončování",
"linkClickBehaviorLabel": "Chování při kliknutí na odkaz",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importováno",
"proxmoxResultUpdated": "{{count}} aktualizováno",
"proxmoxResultFailed": "{{count}} selhalo",
"proxmoxResultSkippedNoIp": "{{count}} přeskočeno (nenalezena žádná IP adresa)",
"proxmoxImportComplete": "Import Proxmoxu dokončen: {{summary}}",
"proxmoxDiscoveryFailed": "Objev selhal",
"proxmoxImportFailed": "Import se nezdařil",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Uložení přihlašovacích údajů se nezdařilo",
"credentialNameRequired": "Zadejte prosím název pro přihlašovací údaje",
"credentialAuthRequired": "Přidejte heslo, SSH klíč nebo obojí",
"createCredentialFromHostBtn": "Vytvořit pověření",
"createCredentialFromHostTitle": "Vytvořit přihlašovací údaje od hostitele",
"createCredentialFromHostDesc": "Vytvořte opakovaně použitelný a sdílený záznam o přihlašovacích údajích, který bude předvyplněn aktuálním uživatelským jménem, heslem a/nebo SSH klíčem tohoto hostitele.",
"backToHosts": "Zpět k hostitelům",
"backToCredentials": "Zpět k pověřovacím dokumentům",
"pinned": "Připnuto",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Pomocí kláves / oddělte úrovně a vytvořte vnořené složky.",
"folderColor": "Barva",
"folderIcon": "Ikona",
"folderCredential": "Pověření",
"folderCredentialNone": "Nebyly přiřazeny žádné pověření",
"folderCredentialHint": "Hostitelé v této složce, kteří používají ověřování „Uložené přihlašovací údaje“, aniž by měli vybrány vlastní přihlašovací údaje, zdědí tento.",
"folderPreview": "Náhled",
"folderNameFallback": "Nepojmenovaná složka",
"createFolderButton": "Vytvořit složku",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Štítky",
"shareHost": "Sdílení hostitele",
"shareHostTitle": "Sdílet: {{name}}",
"shareFolder": "Sdílet složku",
"shareFolderTitle": "Sdílená složka: {{name}}",
"folderSharedSuccessfully": "Sdílených {{count}} hostitelů ve složce",
"failedToShareFolder": "Sdílení složky se nezdařilo",
"sharing": {
"loadError": "Nepodařilo se načíst data sdílení. Zkuste to prosím znovu.",
"shareWithSection": "Sdílet s",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Sdílet ({{count}})",
"currentAccess": "Aktuální přístup",
"noAccessEntries": "Tento hostitel ještě nebyl sdílen",
"folderShareSummary": "Sdílené {{shared}} z {{total}} hostitelů v této složce",
"grantedBy": "Uděleno",
"expires": "Platnost vyprší",
"expired": "Platnost vypršela",
@@ -1309,8 +1242,6 @@
"authMethod": "Metoda ověřování",
"authTypeDirect": "Řídit",
"authTypeCredential": "Pověření",
"authTypeNone": "Žádný",
"authTypeNoneDesc": "Nejsou uloženy žádné přihlašovací údaje. Při každém připojení budete vyzváni k zadání uživatelského jména a hesla; tyto údaje se neukládají.",
"selectCredential": "Vyberte pověření...",
"connectionSettings": "Nastavení připojení",
"displaySettings": "Nastavení displeje",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP port",
"username": "Uživatelské jméno",
"password": "Heslo",
"passwordSaved": "Heslo uloženo, zadejte heslo pro změnu",
"domain": "Doména",
"securityMode": "Bezpečnostní režim",
"colorDepth": "Barevná hloubka",
@@ -1494,60 +1424,7 @@
"expiresIn": "Platnost vyprší za {{duration}}",
"search": "Hledat spojení...",
"noSearchResults": "Vašemu vyhledávání neodpovídají žádná spojení",
"rename": "Přejmenovat relaci",
"sectionSharedWithMe": "Sdíleno se mnou",
"sharedBy": "Sdíleno uživatelem {{username}}",
"join": "Připojit se",
"sharedSessionLabel": "{{hostName}} (sdílené)"
},
"sessionSharing": {
"guestView": {
"loading": "Připojování ke sdílené relaci...",
"linkInvalid": "Tento odkaz pro sdílení je neplatný, jeho platnost vypršela nebo byl zrušen.",
"rateLimited": "Příliš mnoho pokusů, zkuste to prosím znovu brzy",
"sessionEnded": "Tato relace skončila",
"readOnlyBadge": "Pouze zobrazení"
},
"modalTitle": "Sdílení relace",
"shareButton": "Podíl",
"notReadyToShare": "Relace ještě není připravena ke sdílení",
"modeTab": {
"link": "Odkaz",
"user": "Uživatel"
},
"linkModeDescription": "Připojit se může kdokoli s tímto odkazem, účet není potřeba.",
"userModeDescription": "Sdílejte s konkrétním uživatelem, který již má k tomuto hostiteli přístup. Pokud ještě nemá přístup, nejprve s ním hostitele sdílejte nebo použijte odkaz. Po sdílení se relace zobrazí na jeho kartě Připojení.",
"permissionLevel": {
"label": "Úroveň oprávnění",
"readOnly": "Pouze pro čtení",
"readOnlyDescription": "Může sledovat relaci živě, ale nemůže psát ani interagovat.",
"readWrite": "Čtení a zápis",
"readWriteDescription": "Může psát a interagovat s relací stejně jako její vlastník."
},
"expiryLabel": "Platnost vypršení platnosti odkazu",
"createLinkButton": "Vytvořit odkaz",
"createShareButton": "Sdílet s uživatelem",
"searchUsersPlaceholder": "Hledat uživatele...",
"noUsersFound": "Nenaleženi žádní uživatelé",
"linkCreated": "Odkaz pro sdílení vytvořen",
"linkCopied": "Odkaz zkopírován do schránky",
"copyLink": "Kopírovat odkaz",
"shareCreated": "Relace sdílena. Zobrazí se na jejich kartě Připojení.",
"shareFailed": "Vytvoření sdílené položky se nezdařilo",
"userLacksHostAccess": "Tento uživatel zatím nemá přístup k tomuto hostiteli. Nejprve s ním hostitele sdílejte nebo použijte odkaz.",
"activeShares": "Aktivní akcie",
"noActiveShares": "Pro tuto relaci nejsou žádné aktivní sdílení",
"revoke": "Zrušit",
"revokeConfirmTitle": "Zrušit toto sdílení?",
"revokeConfirmDescription": "Každý, kdo tuto sdílenou složku používá, okamžitě ztratí přístup.",
"revoked": "Podíl zrušen",
"revokeFailed": "Zrušení sdílení se nezdařilo",
"joinCount": "{{count}} připojit se",
"joinCount_other": "{{count}} se připojí",
"expiresAt": "Platnost vyprší {{date}}",
"linkShareBadge": "Odkaz",
"userShareBadge": "Uživatel: {{username}}",
"loadSharesFailed": "Nepodařilo se načíst aktivní sdílení"
"rename": "Přejmenovat relaci"
},
"guacamole": {
"connecting": "Připojování k relaci {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Znovu se připojte",
"retry": "Zkusit znovu",
"guacdUnavailable": "Služba vzdálené plochy (guacd) není k dispozici. Ujistěte se prosím, že je guacd spuštěná, přístupná a správně nakonfigurovaná v nastavení administrátora.",
"credentialPromptTitle": "Zadejte přihlašovací údaje RDP",
"credentialPromptDescription": "Tento hostitel je nastaven tak, aby při připojení vyžadoval přihlašovací údaje. Tyto přihlašovací údaje se používají pouze pro tuto relaci a neukládají se.",
"connect": "Připojit",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Znovu připojit relaci",
"collapse": "Sbalit panel nástrojů",
"expand": "Rozbalit panel nástrojů",
"dragHandle": "Přetažením změníte pozici",
"switchToTrackpad": "Přepnout do režimu trackpadu (tažením přesunete kurzor, klepnutím kliknete)",
"switchToTouch": "Přepnout do dotykového režimu (klepněte přímo na místo, kam chcete kliknout)"
"dragHandle": "Přetažením změníte pozici"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Vyžadováno dvoufaktorové ověřování",
"totpCodeLabel": "Ověřovací kód",
"totpVerify": "Ověřit",
"mfaPromptRequired": "Vyžadováno ověření",
"mfaPushRequired": "Vyžadováno ověřování push transakcí",
"mfaMenuPlaceholder": "Zadejte svou odpověď",
"mfaWaitingApproval": "Čekání na schválení na vašem zařízení...",
"mfaSendRequest": "Odeslat požadavek",
"warpgateAuthRequired": "Vyžadováno ověřování Warpgate",
"warpgateSecurityKey": "Bezpečnostní klíč",
"warpgateAuthUrl": "URL ověřování",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Omezená rychlost: Příliš mnoho pokusů o ověření. Zkuste to prosím znovu později.",
"resetCodeRateLimitedWithTime": "Omezená rychlost: Příliš mnoho pokusů o ověření. Před dalším pokusem prosím počkejte {{time}} sekund.",
"authTokenSaveFailed": "Uložení ověřovacího tokenu se nezdařilo",
"failedToLoadServer": "Nepodařilo se načíst server",
"remoteServerRequired": "Je vyžadován vzdálený server. Pro použití tohoto typu připojení se připojte ke vzdálenému serveru v Nastavení."
"failedToLoadServer": "Nepodařilo se načíst server"
},
"messages": {
"registrationDisabled": "Registrace nového účtu je momentálně zakázána administrátorem. Přihlaste se prosím nebo kontaktujte administrátora.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Nepodařilo se uložit nastavení SSL",
"sslRequiresDomain": "Doména a e-mail jsou povinné",
"sslInfoNote": "Po vystavení certifikátu povolte SSL v proměnných prostředí (ENABLE_SSL=true) a restartujte Termix.",
"sslManualOption": "Manuál (nahrání certifikátu)",
"sslManualCert": "Certifikát (PEM)",
"sslManualCertPlaceholder": "-----ZAČÁTEK CERTIFIKÁTU-----",
"sslManualKey": "Soukromý klíč (PEM)",
"sslManualKeyPlaceholder": "-----ZAČÁTEK SOUKROMÉHO KLÍČE-----",
"sslManualDesc": "Vložte svůj stávající certifikát a soukromý klíč, včetně celého řetězce, pokud to vaše certifikační autorita vyžaduje.",
"sslManualUpload": "Nahrát a nainstalovat certifikát",
"sslManualUploadLoading": "Nahrávání certifikátu...",
"sslManualUploadSuccess": "Certifikát byl úspěšně nahrán a nainstalován.",
"sslManualUploadFailed": "Nahrávání certifikátu se nezdařilo",
"sslManualRequiresFields": "Je vyžadován certifikát a soukromý klíč",
"auditLogTotal": "{{total}} celkem záznamů",
"auditLogEmpty": "Nenalezeny žádné položky protokolu auditu",
"auditLogSuccess": "Úspěch",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Historie příkazů",
"commandHistoryEnabledDesc": "Povolit záznam historie příkazů. Pokud je tato možnost zakázána, historie se neukládá bez ohledu na nastavení pro jednotlivé hostitele.",
"updateCommandHistoryFailed": "Nepodařilo se aktualizovat nastavení historie příkazů.",
"analyticsEnabled": "Sdílení anonymních statistik používání",
"analyticsEnabledDesc": "Odesílá anonymní denní počet uživatelů, hostitelů a využití funkcí, aby pomohl vylepšit Termix. Nikdy neobsahuje žádné osobní údaje ani podrobnosti o připojení.",
"updateAnalyticsFailed": "Nepodařilo se aktualizovat nastavení analytických nástrojů",
"sessionSharingGloballyEnabled": "Povolit sdílení relace",
"sessionSharingGloballyEnabledDesc": "Povolit sdílení relací živého terminálu, RDP, VNC a Telnet v rámci celé instance. Pokud je tato možnost zakázána, přepíše všechny přepínače sdílení pro jednotlivé hostitele.",
"updateSessionSharingFailed": "Nepodařilo se aktualizovat nastavení sdílení relace",
"sessionTimeout": "Časový limit relace",
"hours": "hodiny",
"sessionTimeoutRange": "Min. 1h · Max. 720h",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Úspěch",
"executionFailed": "Selhalo"
},
"keybindings": {
"title": "Klávesové zkratky",
"description": "Přizpůsobte si klávesové zkratky pro kopírování, vkládání a ovládání v terminálu nebo propojte klávesy pro odeslání textu nebo spuštění úryvku kódu.",
"defaultsHeading": "Vestavěné zkratky",
"customHeading": "Vlastní zkratky",
"addBinding": "Přidat zkratku",
"addBindingTitle": "Přidat zkratku",
"editBindingTitle": "Upravit zkratku",
"loading": "Načítání...",
"noCustomBindings": "Zatím žádné vlastní zkratky",
"defaultBadge": "Výchozí",
"customizedBadge": "Přizpůsobeno",
"customize": "Přizpůsobit",
"resetToDefault": "Obnovit výchozí nastavení",
"resetAllToDefaults": "Obnovit vše na výchozí nastavení",
"close": "Blízko",
"cancel": "Zrušit",
"saveBinding": "Uložit zástupce",
"saveError": "Uložení klávesových zkratek se nezdařilo",
"pressKeysToRecord": "Kombinace kláves",
"pressKeysPlaceholder": "Klikněte a stiskněte klávesy",
"recording": "Stiskněte klávesy...",
"comboRequiredError": "Nejprve stiskněte kombinaci kláves",
"textRequiredError": "Zadejte text k odeslání",
"controlCodeRequiredError": "Zadejte jedno písmeno pro kontrolní kód",
"snippetRequiredError": "Vyberte úryvek",
"conflictWarning": "Tato kombinace je již vázána na {{combo}}. Uložením se obě spustí.",
"actionLabel": "Akce",
"actionCopy": "Kopírovat výběr",
"actionPaste": "Pasta",
"actionSendControlCode": "Odeslat signál Ctrl+písmeno",
"actionSendText": "Odeslat text",
"actionRunSnippet": "Spustit existující úryvek kódu",
"controlCodeLabel": "Dopis",
"textLabel": "Text k odeslání",
"appendEnterLabel": "Po odeslání stiskněte Enter",
"snippetLabel": "Úryvek",
"selectSnippetPlaceholder": "Vyberte úryvek",
"orphanedSnippetWarning": "úryvek nenalezen",
"clipboardPermissionNote": "V některých prohlížečích se může při prvním použití zobrazit výzva k zadání povolení ke schránce."
},
"userProfile": {
"donateTitle": "Udrž Termixe naživu",
"donateDescription": "Termix je zdarma, hostovaný na vlastních platformách a vyvíjí ho jen několik lidí ve svém volném čase. Pokud vám to ušetřilo čas nebo peníze, dar kryptoměn vám pomůže udržet ho v chodu.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Vzhled",
"sectionSecurity": "Zabezpečení",
"sectionApiKeys": "Klíče API",
"sectionData": "Data",
"sectionC2sTunnels": "Tunely C2S",
"usernameLabel": "Uživatelské jméno",
"roleLabel": "Role",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminál",
"commandAutocomplete": "Automatické dokončování příkazů",
"commandAutocompleteDesc": "Zobrazit automatické doplňování při psaní",
"keyboardShortcuts": "Klávesové zkratky",
"keyboardShortcutsDescription": "Přizpůsobení zkratek pro kopírování, vkládání a příkazy v terminálu",
"manageShortcuts": "Spravovat",
"terminalLinkBehavior": "Kliknutí na odkaz terminálu",
"terminalLinkBehaviorDesc": "Výchozí chování při kliknutí na odkazy v terminálu",
"historyTracking": "Sledování historie",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Vložte svůj klíč do",
"apiKeyUsageHintHeader": "záhlaví.",
"apiKeyPermissionsHint": "Klíče dědí oprávnění uživatele, který je vytvořil.",
"exportData": "Exportovat má data",
"exportDataDesc": "Stáhněte si zálohu svých hostitelů, přihlašovacích údajů a nastavení pro přenos do jiného zařízení",
"export": "Vývozní",
"exporting": "Export...",
"importData": "Importovat má data",
"importDataDesc": "Obnovte své hostitele, přihlašovací údaje a nastavení ze záložního souboru .sqlite",
"importDataSelected": "Vybráno: {{name}}",
"selectFile": "Vybrat soubor",
"changeFile": "Přeměna",
"import": "Importovat",
"importing": "Import...",
"exportSuccess": "Data byla úspěšně exportována",
"exportFailed": "Export dat se nezdařil",
"importSelectFile": "Nejprve prosím vyberte soubor",
"importCompleted": "Import dokončen: {{total}} položek importováno, {{skipped}} přeskočeno",
"importFailed": "Import se nezdařil: {{error}}",
"roleUser": "Uživatel",
"authMethodDual": "Duální ověřování",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Pasta",
"editQuickKeys": "Upravit rychlé klávesy",
"quickKeysTitle": "Rychlé klávesy",
"quickKeysDesc": "Klepnutím na × odstraníte. Podporuje až 8 znaků.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Ingen gemte servere",
"removeServer": "Fjerne"
},
"migrationNotice": {
"title": "Termix Desktop kører nu selvstændigt",
"body1": "Denne app plejede at oprette forbindelse direkte til din Termix-server. Den er blevet omarbejdet til at køre fuldt ud alene og gemmer værter og legitimationsoplysninger lokalt, så den fortsætter med at fungere offline. Tovejssynkronisering med en selvhostet Termix-server er nu valgfri.",
"body2": "Dine eksisterende værter og loginoplysninger er stadig på {{url}}. Aktivér fjernsynkronisering, og opret forbindelse til serveren igen for at få dem tilbage og holde begge synkroniseret fremadrettet.",
"dismiss": "Ikke nu",
"setUpSync": "Opsæt fjernsynkronisering"
},
"remoteSync": {
"title": "Fjernsynkronisering",
"description": "Du kan eventuelt tilslutte denne desktop-app til en selvhostet Termix-server for at synkronisere dine værter, legitimationsoplysninger og kodestykker på tværs af enheder. Appen fungerer altid fuldt offline, uanset om du opretter forbindelse eller ej.",
"notConnected": "Ikke forbundet",
"connected": "Forbundet",
"connectedTo": "Forbundet til {{url}}",
"lastSynced": "Sidst synkroniseret {{time}}",
"neverSynced": "Aldrig synkroniseret",
"syncError": "Synkroniseringsfejl: {{message}}",
"needsReauth": "Login udløbet",
"connectButton": "Opret forbindelse til serveren",
"disconnectButton": "Afbryde",
"syncNowButton": "Synkroniser nu",
"syncing": "Synkroniserer...",
"serverUrl": "Server-URL",
"enterServerUrl": "Indtast venligst en server-URL",
"mustIncludeProtocol": "Server-URL skal starte med http:// eller https://",
"connectionTestFailed": "Kunne ikke nå en Termix-server på den URL",
"allowInvalidCertificate": "Tillad ugyldigt certifikat",
"allowInvalidCertificateDesc": "Brug kun til betroede, selvhostede servere med selvsignerede certifikater eller IP-adressecertifikater.",
"savedServers": "Gemte servere",
"removeServer": "Fjerne",
"continueButton": "Fortsætte",
"cancelButton": "Ophæve",
"signInTitle": "Log ind på {{url}}",
"originTitle": "Forbindelsens oprindelse",
"originDescription": "Vælg som standard, hvor SSH-forbindelser stammer fra. Dette kan tilsidesættes pr. vært.",
"originLocal": "Denne enhed (lokalt netværk)",
"originRemote": "Fjernserver",
"bannerReconnect": "Genopret forbindelse",
"bannerMessage": "Fjernsynkronisering skal godkendes igen"
},
"versionCheck": {
"error": "Versionskontrolfejl",
"checkFailed": "Kunne ikke søge efter opdateringer",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Tilsidesæt brugernavn til legitimationsoplysninger",
"overrideCredentialUsernameDesc": "Brug det ovenfor angivne brugernavn i stedet for legitimationsoplysningernes brugernavn",
"oidcUsernameHint": "Brug $oidc.preferred_username til at erstatte dit OIDC-loginnavn.",
"tailscaleUsernameHint": "Dette skal være en Unix-bruger, som din Tailscale-identitet er tildelt i tailnets SSH ACL, ikke nødvendigvis root.",
"jumpHostChain": "Jump Host Chain",
"portKnocking": "Port Knocking",
"addKnock": "Tilføj port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Forsinkelse efter (ms)",
"useSocks5Proxy": "Brug SOCKS5 Proxy",
"useSocks5ProxyDesc": "Rut forbindelsen via en proxyserver",
"connectionOrigin": "Forbindelsens oprindelse",
"connectionOriginDesc": "Hvor denne værts SSH-forbindelse stammer fra. Tilsidesætter desktop-appens globale standard.",
"connectionOriginDefault": "Brug standard",
"connectionOriginLocal": "Denne enhed (lokalt netværk)",
"connectionOriginRemote": "Fjernserver",
"proxyHost": "Proxy-vært",
"proxyPort": "Proxyport",
"proxyUsername": "Proxy-brugernavn",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Lys cyan",
"customThemeBrightWhite": "Klar hvid",
"customThemeResetTooltip": "Nulstil til standardindstillinger",
"savedThemesLabel": "Gemte temaer",
"saveAsGlobalTheme": "Gem som globalt tema",
"saveGlobalThemeNamePrompt": "Indtast et navn til dette tema",
"saveGlobalThemeSuccess": "Tema gemt",
"saveGlobalThemeError": "Temaet kunne ikke gemmes",
"applyGlobalThemeTooltip": "Anvend dette tema",
"deleteGlobalThemeTooltip": "Slet dette tema",
"noSavedThemes": "Ingen gemte temaer endnu",
"syntaxHighlightingLabel": "Syntaksfremhævning",
"syntaxHighlightingDesc": "Farvlæg terminaloutput (fejl, stier, IP'er, tidsstempler)",
"syntaxHighlightingCategories": "Fremhæv kategorier",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Start eller tilslut automatisk til tmux-session",
"enableSessionLogging": "Sessionslogning",
"enableSessionLoggingDesc": "Optag output fra terminalsessionen til senere gennemgang",
"allowSessionSharing": "Tillad deling af sessioner",
"allowSessionSharingDesc": "Tillad live-sessioner på denne vært at blive delt via link eller med andre brugere",
"enableCommandHistory": "Kommandohistorik",
"enableCommandHistoryDesc": "Optagelseskommandoer køres i denne terminal for historik og autofuldførelse",
"linkClickBehaviorLabel": "Linkkliksadfærd",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importeret",
"proxmoxResultUpdated": "{{count}} opdateret",
"proxmoxResultFailed": "{{count}} mislykkedes",
"proxmoxResultSkippedNoIp": "{{count}} sprunget over (ingen IP fundet)",
"proxmoxImportComplete": "Proxmox-import fuldført: {{summary}}",
"proxmoxDiscoveryFailed": "Opdagelsen mislykkedes",
"proxmoxImportFailed": "Importen mislykkedes",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Kunne ikke gemme legitimationsoplysninger",
"credentialNameRequired": "Indtast venligst et navn til legitimationsoplysningerne",
"credentialAuthRequired": "Tilføj en adgangskode, en SSH-nøgle eller begge dele",
"createCredentialFromHostBtn": "Opret legitimationsoplysninger",
"createCredentialFromHostTitle": "Opret legitimationsoplysninger fra vært",
"createCredentialFromHostDesc": "Opret en genanvendelig, delbar legitimationsoplysningspost, der er forudfyldt med denne værts nuværende brugernavn, adgangskode og/eller SSH-nøgle.",
"backToHosts": "Tilbage til værter",
"backToCredentials": "Tilbage til legitimationsoplysninger",
"pinned": "Fastgjort",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Brug / til at adskille niveauer og oprette indbyggede mapper.",
"folderColor": "Farve",
"folderIcon": "Ikon",
"folderCredential": "Legitimationsoplysninger",
"folderCredentialNone": "Ingen legitimationsoplysninger tildelt",
"folderCredentialHint": "Værter i denne mappe, der bruger godkendelsen \"Gemte legitimationsoplysninger\" uden at have valgt deres egne legitimationsoplysninger, arver denne.",
"folderPreview": "Forhåndsvisning",
"folderNameFallback": "Unavngivet mappe",
"createFolderButton": "Opret mappe",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Tags",
"shareHost": "Del vært",
"shareHostTitle": "Del: {{name}}",
"shareFolder": "Del mappe",
"shareFolderTitle": "Del mappe: {{name}}",
"folderSharedSuccessfully": "Delte {{count}} vært(er) i mappe",
"failedToShareFolder": "Mappen kunne ikke deles",
"sharing": {
"loadError": "Delingsdataene kunne ikke indlæses. Prøv igen.",
"shareWithSection": "Del med",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Del ({{count}})",
"currentAccess": "Nuværende adgang",
"noAccessEntries": "Denne vært er ikke blevet delt endnu",
"folderShareSummary": "Delte {{shared}} af {{total}} vært(er) i denne mappe",
"grantedBy": "Tildelt af",
"expires": "Udløber",
"expired": "Udløbet",
@@ -1309,8 +1242,6 @@
"authMethod": "Godkendelsesmetode",
"authTypeDirect": "Direkte",
"authTypeCredential": "Legitimationsoplysninger",
"authTypeNone": "Ingen",
"authTypeNoneDesc": "Ingen loginoplysninger gemmes. Du vil blive bedt om et brugernavn og en adgangskode, hver gang du opretter forbindelse; de gemmes ikke.",
"selectCredential": "Vælg en legitimationsoplysninger...",
"connectionSettings": "Forbindelsesindstillinger",
"displaySettings": "Skærmindstillinger",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-port",
"username": "Brugernavn",
"password": "Adgangskode",
"passwordSaved": "Adgangskode gemt, skriv for at ændre",
"domain": "Domæne",
"securityMode": "Sikkerhedstilstand",
"colorDepth": "Farvedybde",
@@ -1494,60 +1424,7 @@
"expiresIn": "Udløber om {{duration}}",
"search": "Søg efter forbindelser...",
"noSearchResults": "Ingen forbindelser matcher din søgning",
"rename": "Omdøb session",
"sectionSharedWithMe": "Delt med mig",
"sharedBy": "Delt af {{username}}",
"join": "Deltag",
"sharedSessionLabel": "{{hostName}} (delt)"
},
"sessionSharing": {
"guestView": {
"loading": "Forbinder til delt session...",
"linkInvalid": "Dette delingslink er ugyldigt, udløbet eller er blevet tilbagekaldt",
"rateLimited": "For mange forsøg, prøv igen om lidt",
"sessionEnded": "Denne session er afsluttet",
"readOnlyBadge": "Kun visning"
},
"modalTitle": "Del session",
"shareButton": "Dele",
"notReadyToShare": "Sessionen er ikke klar til deling endnu",
"modeTab": {
"link": "Forbindelse",
"user": "Bruger"
},
"linkModeDescription": "Alle med dette link kan tilmelde sig, ingen konto kræves.",
"userModeDescription": "Del med en bestemt bruger, der allerede har adgang til denne vært. Hvis de ikke har adgang endnu, skal du først dele værten med dem eller bruge et link i stedet. Når sessionen er delt, vises den under deres fane Forbindelser.",
"permissionLevel": {
"label": "Tilladelsesniveau",
"readOnly": "Skrivebeskyttet",
"readOnlyDescription": "Kan se sessionen live, men kan ikke skrive eller interagere.",
"readWrite": "Læs-skriv",
"readWriteDescription": "Kan skrive og interagere med sessionen ligesom ejeren."
},
"expiryLabel": "Linkudløb",
"createLinkButton": "Opret link",
"createShareButton": "Del med brugeren",
"searchUsersPlaceholder": "Søg efter brugere...",
"noUsersFound": "Ingen brugere fundet",
"linkCreated": "Delingslink oprettet",
"linkCopied": "Link kopieret til udklipsholder",
"copyLink": "Kopiér link",
"shareCreated": "Session delt. Den vises i fanen Forbindelser.",
"shareFailed": "Kunne ikke oprette deling",
"userLacksHostAccess": "Den bruger har endnu ikke adgang til denne vært. Del værten med dem først, eller brug et link i stedet.",
"activeShares": "Aktive aktier",
"noActiveShares": "Ingen aktive delinger for denne session",
"revoke": "Tilbagekald",
"revokeConfirmTitle": "Tilbagekald denne deling?",
"revokeConfirmDescription": "Enhver, der bruger denne deling, mister adgangen med det samme.",
"revoked": "Deling tilbagekaldt",
"revokeFailed": "Delingen kunne ikke tilbagekaldes",
"joinCount": "{{count}} tilmeld dig",
"joinCount_other": "{{count}} deltager",
"expiresAt": "Udløber {{date}}",
"linkShareBadge": "Forbindelse",
"userShareBadge": "Bruger: {{username}}",
"loadSharesFailed": "Kunne ikke indlæse aktive delinger"
"rename": "Omdøb session"
},
"guacamole": {
"connecting": "Forbinder til {{type}} session...",
@@ -1559,9 +1436,6 @@
"reconnect": "Genopret forbindelse",
"retry": "Prøv igen",
"guacdUnavailable": "Fjernskrivebordstjenesten (guacd) er ikke tilgængelig. Sørg for, at guacd kører, er tilgængelig og konfigureret korrekt i administratorindstillingerne.",
"credentialPromptTitle": "Indtast RDP-legitimationsoplysninger",
"credentialPromptDescription": "Denne vært er indstillet til at bede om legitimationsoplysninger ved forbindelse. De bruges kun til denne session og gemmes ikke.",
"connect": "Forbinde",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Genopret forbindelse til sessionen",
"collapse": "Skjul værktøjslinjen",
"expand": "Udvid værktøjslinjen",
"dragHandle": "Træk for at flytte",
"switchToTrackpad": "Skift til trackpad-tilstand (træk for at flytte markøren, tryk for at klikke)",
"switchToTouch": "Skift til berøringstilstand (tryk direkte der, hvor du vil klikke)"
"dragHandle": "Træk for at flytte"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Tofaktorgodkendelse påkrævet",
"totpCodeLabel": "Bekræftelseskode",
"totpVerify": "Verificere",
"mfaPromptRequired": "Godkendelse påkrævet",
"mfaPushRequired": "Push-godkendelse påkrævet",
"mfaMenuPlaceholder": "Indtast dit svar",
"mfaWaitingApproval": "Venter på godkendelse på din enhed...",
"mfaSendRequest": "Send anmodning",
"warpgateAuthRequired": "Warpgate-godkendelse påkrævet",
"warpgateSecurityKey": "Sikkerhedsnøgle",
"warpgateAuthUrl": "Godkendelses-URL",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Begrænset hastighed: For mange bekræftelsesforsøg. Prøv igen senere.",
"resetCodeRateLimitedWithTime": "Begrænset hastighed: For mange bekræftelsesforsøg. Vent {{time}} sekunder, før du prøver igen.",
"authTokenSaveFailed": "Kunne ikke gemme godkendelsestoken",
"failedToLoadServer": "Kunne ikke indlæse serveren",
"remoteServerRequired": "Fjernserver kræves. Opret forbindelse til en fjernserver i Indstillinger for at bruge denne forbindelsestype."
"failedToLoadServer": "Kunne ikke indlæse serveren"
},
"messages": {
"registrationDisabled": "Registrering af nye kontoer er i øjeblikket deaktiveret af en administrator. Log venligst ind eller kontakt en administrator.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Kunne ikke gemme SSL-indstillinger",
"sslRequiresDomain": "Domæne og e-mail er påkrævet",
"sslInfoNote": "Efter udstedelse af et certifikat, skal du aktivere SSL i dine miljøvariabler (ENABLE_SSL=true) og genstarte Termix.",
"sslManualOption": "Manual (upload certifikat)",
"sslManualCert": "Certifikat (PEM)",
"sslManualCertPlaceholder": "-----STARTCERTIFIKAT-----",
"sslManualKey": "Privat nøgle (PEM)",
"sslManualKeyPlaceholder": "-----START PRIVAT NØGLE-----",
"sslManualDesc": "Indsæt dit eksisterende certifikat og din private nøgle, inklusive en fuld kæde, hvis det kræves af din CA.",
"sslManualUpload": "Upload og installer certifikat",
"sslManualUploadLoading": "Uploader certifikat...",
"sslManualUploadSuccess": "Certifikatet er uploadet og installeret",
"sslManualUploadFailed": "Upload af certifikat mislykkedes",
"sslManualRequiresFields": "Certifikat og privat nøgle er påkrævet",
"auditLogTotal": "{{total}} samlede poster",
"auditLogEmpty": "Ingen poster i revisionsloggen fundet",
"auditLogSuccess": "Succes",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Kommandohistorik",
"commandHistoryEnabledDesc": "Tillad optagelse af kommandohistorik. Når den er deaktiveret, gemmes historikken ikke uanset indstillingerne for den enkelte vært.",
"updateCommandHistoryFailed": "Kunne ikke opdatere indstillingen for kommandohistorik",
"analyticsEnabled": "Del anonym brugsstatistik",
"analyticsEnabledDesc": "Sender en anonym daglig optælling af brugere, værter og funktionsbrug for at forbedre Termix. Ingen personlige data eller forbindelsesoplysninger inkluderes nogensinde.",
"updateAnalyticsFailed": "Analyseindstillingen kunne ikke opdateres",
"sessionSharingGloballyEnabled": "Tillad deling af sessioner",
"sessionSharingGloballyEnabledDesc": "Tillad deling af live terminal-, RDP-, VNC- og Telnet-sessioner på tværs af instanser. Tilsidesætter alle delingsindstillinger pr. vært, når de er deaktiveret.",
"updateSessionSharingFailed": "Indstillingen for deling af session kunne ikke opdateres",
"sessionTimeout": "Sessionstimeout",
"hours": "timer",
"sessionTimeoutRange": "Min. 1 time · Maks. 720 timer",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Succes",
"executionFailed": "Mislykkedes"
},
"keybindings": {
"title": "Tastaturgenveje",
"description": "Tilpas genveje til kopiering, indsættelse og styring af terminaler, eller bind taster for at sende tekst eller køre et kodestykke.",
"defaultsHeading": "Indbyggede genveje",
"customHeading": "Brugerdefinerede genveje",
"addBinding": "Tilføj genvej",
"addBindingTitle": "Tilføj genvej",
"editBindingTitle": "Rediger genvej",
"loading": "Indlæser...",
"noCustomBindings": "Ingen brugerdefinerede genveje endnu",
"defaultBadge": "Misligholdelse",
"customizedBadge": "Tilpasset",
"customize": "Tilpas",
"resetToDefault": "Nulstil til standard",
"resetAllToDefaults": "Nulstil alle til standardindstillingerne",
"close": "Tæt",
"cancel": "Ophæve",
"saveBinding": "Gem genvej",
"saveError": "Kunne ikke gemme tastaturgenveje",
"pressKeysToRecord": "Tastekombination",
"pressKeysPlaceholder": "Klik og tryk på tasterne",
"recording": "Tryk på tasterne...",
"comboRequiredError": "Tryk først på en tastekombination",
"textRequiredError": "Indtast den tekst, der skal sendes",
"controlCodeRequiredError": "Indtast et enkelt bogstav for kontrolkoden",
"snippetRequiredError": "Vælg et uddrag",
"conflictWarning": "Denne kombination er allerede bundet til {{combo}}. Hvis du gemmer dem, aktiveres begge.",
"actionLabel": "Handling",
"actionCopy": "Kopiér valg",
"actionPaste": "Indsæt",
"actionSendControlCode": "Send Ctrl+bogstavsignal",
"actionSendText": "Send sms",
"actionRunSnippet": "Kør eksisterende kodestykke",
"controlCodeLabel": "Bogstav",
"textLabel": "Tekst der skal sendes",
"appendEnterLabel": "Tryk Enter efter afsendelse",
"snippetLabel": "Uddrag",
"selectSnippetPlaceholder": "Vælg et uddrag",
"orphanedSnippetWarning": "uddrag ikke fundet",
"clipboardPermissionNote": "Kan bede om tilladelse til udklipsholder ved første brug i nogle browsere."
},
"userProfile": {
"donateTitle": "Hold Termix i live",
"donateDescription": "Termix er gratis, selvhostet og bygget af kun få personer i deres fritid. Hvis det har sparet dig tid eller penge, kan en kryptodonation hjælpe med at holde det kørende.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Udseende",
"sectionSecurity": "Sikkerhed",
"sectionApiKeys": "API-nøgler",
"sectionData": "Data",
"sectionC2sTunnels": "C2S-tunneler",
"usernameLabel": "Brugernavn",
"roleLabel": "Rolle",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Kommando Autofuldførelse",
"commandAutocompleteDesc": "Vis autofuldførelse under indtastning",
"keyboardShortcuts": "Tastaturgenveje",
"keyboardShortcutsDescription": "Tilpas genveje til terminalkopiering, -indsætning og -kommandoer",
"manageShortcuts": "Styre",
"terminalLinkBehavior": "Klik på terminallink",
"terminalLinkBehaviorDesc": "Standardadfærd ved klik på links i terminalen",
"historyTracking": "Historiksporing",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Medtag din nøgle i",
"apiKeyUsageHintHeader": "overskrift.",
"apiKeyPermissionsHint": "Nøgler arver tilladelserne fra den oprettende bruger.",
"exportData": "Eksporter mine data",
"exportDataDesc": "Download en sikkerhedskopi af dine værter, loginoplysninger og indstillinger for at overføre dem til en anden enhed.",
"export": "Eksportere",
"exporting": "Eksporterer...",
"importData": "Importer mine data",
"importDataDesc": "Gendan dine værter, legitimationsoplysninger og indstillinger fra en .sqlite-backupfil",
"importDataSelected": "Valgt: {{name}}",
"selectFile": "Vælg fil",
"changeFile": "Forandring",
"import": "Importere",
"importing": "Importerer...",
"exportSuccess": "Data eksporteret",
"exportFailed": "Dataeksport mislykkedes",
"importSelectFile": "Vælg venligst en fil først",
"importCompleted": "Import fuldført: {{total}} elementer importeret, {{skipped}} sprunget over",
"importFailed": "Import mislykkedes: {{error}}",
"roleUser": "Bruger",
"authMethodDual": "Dobbeltgodkendelse",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "Side op",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Indsæt",
"editQuickKeys": "Rediger genvejstaster",
"quickKeysTitle": "Hurtigtaster",
"quickKeysDesc": "Tryk på × for at fjerne. Understøtter op til 8 tegn.",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Δεν υπάρχουν αποθηκευμένοι διακομιστές",
"removeServer": "Αφαιρώ"
},
"migrationNotice": {
"title": "Το Termix Desktop λειτουργεί πλέον αυτόνομα.",
"body1": "Αυτή η εφαρμογή συνδεόταν απευθείας με τον διακομιστή Termix. Έχει αναδιαμορφωθεί ώστε να λειτουργεί πλήρως αυτόνομα, αποθηκεύοντας τοπικά τους κεντρικούς υπολογιστές και τα διαπιστευτήρια, ώστε να συνεχίζει να λειτουργεί εκτός σύνδεσης. Ο αμφίδρομος συγχρονισμός με έναν αυτο-φιλοξενούμενο διακομιστή Termix είναι πλέον προαιρετικός.",
"body2": "Οι υπάρχοντες κεντρικοί υπολογιστές και τα διαπιστευτήριά σας εξακολουθούν να βρίσκονται στη θέση {{url}}. Ενεργοποιήστε τον Απομακρυσμένο συγχρονισμό και συνδεθείτε ξανά σε αυτόν τον διακομιστή για να τα επαναφέρετε και να διατηρήσετε και τα δύο συγχρονισμένα στο μέλλον.",
"dismiss": "Όχι τώρα",
"setUpSync": "Ρύθμιση απομακρυσμένου συγχρονισμού"
},
"remoteSync": {
"title": "Απομακρυσμένος συγχρονισμός",
"description": "Προαιρετικά, συνδέστε αυτήν την εφαρμογή για υπολογιστές σε έναν αυτο-φιλοξενούμενο διακομιστή Termix για να συγχρονίσετε τους κεντρικούς υπολογιστές, τα διαπιστευτήρια και τα αποσπάσματα σε όλες τις συσκευές. Η εφαρμογή λειτουργεί πάντα πλήρως εκτός σύνδεσης, ανεξάρτητα από το αν συνδεθείτε ή όχι.",
"notConnected": "Δεν είναι συνδεδεμένο",
"connected": "Συνδεδεμένος",
"connectedTo": "Συνδεδεμένο με {{url}}",
"lastSynced": "Τελευταίος συγχρονισμός {{time}}",
"neverSynced": "Δεν συγχρονίστηκε ποτέ",
"syncError": "Σφάλμα συγχρονισμού: {{message}}",
"needsReauth": "Η σύνδεση έληξε",
"connectButton": "Σύνδεση με διακομιστή",
"disconnectButton": "Αποσυνδέω",
"syncNowButton": "Συγχρονισμός τώρα",
"syncing": "Συγχρονισμός...",
"serverUrl": "URL διακομιστή",
"enterServerUrl": "Παρακαλώ εισάγετε μια διεύθυνση URL διακομιστή",
"mustIncludeProtocol": "Η διεύθυνση URL του διακομιστή πρέπει να ξεκινά με http:// ή https://",
"connectionTestFailed": "Δεν ήταν δυνατή η επικοινωνία με έναν διακομιστή Termix σε αυτήν τη διεύθυνση URL",
"allowInvalidCertificate": "Να επιτρέπεται το μη έγκυρο πιστοποιητικό",
"allowInvalidCertificateDesc": "Χρησιμοποιήστε μόνο για αξιόπιστους αυτο-φιλοξενούμενους διακομιστές με αυτο-υπογεγραμμένα πιστοποιητικά ή πιστοποιητικά διεύθυνσης IP.",
"savedServers": "Αποθηκευμένοι διακομιστές",
"removeServer": "Αφαιρώ",
"continueButton": "Συνεχίζω",
"cancelButton": "Ματαίωση",
"signInTitle": "Συνδεθείτε στο {{url}}",
"originTitle": "Προέλευση σύνδεσης",
"originDescription": "Επιλέξτε από προεπιλογή την προέλευση των συνδέσεων SSH. Αυτό μπορεί να παρακαμφθεί ανά κεντρικό υπολογιστή.",
"originLocal": "Αυτή η συσκευή (τοπικό δίκτυο)",
"originRemote": "Απομακρυσμένος διακομιστής",
"bannerReconnect": "Επανασύνδεση",
"bannerMessage": "Ο απομακρυσμένος συγχρονισμός απαιτεί εκ νέου έλεγχο ταυτότητας"
},
"versionCheck": {
"error": "Σφάλμα ελέγχου έκδοσης",
"checkFailed": "Αποτυχία ελέγχου για ενημερώσεις",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Παράκαμψη ονόματος χρήστη διαπιστευτηρίων",
"overrideCredentialUsernameDesc": "Χρησιμοποιήστε το όνομα χρήστη που καθορίστηκε παραπάνω αντί για το όνομα χρήστη του πιστοποιητικού",
"oidcUsernameHint": "Χρησιμοποιήστε το $oidc.preferred_username για να αντικαταστήσετε το όνομα σύνδεσης OIDC.",
"tailscaleUsernameHint": "Αυτός πρέπει να είναι χρήστης Unix. Η ταυτότητα Tailscale σας έχει εκχωρηθεί στο SSH ACL του tailnet, όχι απαραίτητα root.",
"jumpHostChain": "Αλυσίδα υποδοχής άλματος",
"portKnocking": "Χτύπημα στο λιμάνι",
"addKnock": "Προσθήκη θύρας",
@@ -690,11 +650,6 @@
"delayAfterMs": "Καθυστέρηση μετά (ms)",
"useSocks5Proxy": "Χρήση διακομιστή μεσολάβησης SOCKS5",
"useSocks5ProxyDesc": "Δρομολόγηση σύνδεσης μέσω διακομιστή μεσολάβησης",
"connectionOrigin": "Προέλευση σύνδεσης",
"connectionOriginDesc": "Από πού προέρχεται η σύνδεση SSH αυτού του κεντρικού υπολογιστή. Παρακάμπτει την καθολική προεπιλογή της εφαρμογής για υπολογιστές.",
"connectionOriginDefault": "Χρήση προεπιλογής",
"connectionOriginLocal": "Αυτή η συσκευή (τοπικό δίκτυο)",
"connectionOriginRemote": "Απομακρυσμένος διακομιστής",
"proxyHost": "Κεντρικός υπολογιστής μεσολάβησης",
"proxyPort": "Θύρα διακομιστή μεσολάβησης",
"proxyUsername": "Όνομα χρήστη διακομιστή μεσολάβησης",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Φωτεινό κυανό",
"customThemeBrightWhite": "Φωτεινό λευκό",
"customThemeResetTooltip": "Επαναφορά στις προεπιλογές",
"savedThemesLabel": "Αποθηκευμένα Θέματα",
"saveAsGlobalTheme": "Αποθήκευση ως καθολικό θέμα",
"saveGlobalThemeNamePrompt": "Εισαγάγετε ένα όνομα για αυτό το θέμα",
"saveGlobalThemeSuccess": "Το θέμα αποθηκεύτηκε",
"saveGlobalThemeError": "Η αποθήκευση του θέματος απέτυχε",
"applyGlobalThemeTooltip": "Εφαρμογή αυτού του θέματος",
"deleteGlobalThemeTooltip": "Διαγραφή αυτού του θέματος",
"noSavedThemes": "Δεν υπάρχουν ακόμη αποθηκευμένα θέματα",
"syntaxHighlightingLabel": "Επισήμανση σύνταξης",
"syntaxHighlightingDesc": "Χρωματισμός εξόδου τερματικού (σφάλματα, διαδρομές, IP, χρονικές σημάνσεις)",
"syntaxHighlightingCategories": "Επισημάνετε κατηγορίες",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Αυτόματη εκκίνηση ή σύνδεση σε συνεδρία tmux",
"enableSessionLogging": "Καταγραφή συνεδρίας",
"enableSessionLoggingDesc": "Καταγραφή εξόδου συνεδρίας τερματικού για μελλοντική αναθεώρηση",
"allowSessionSharing": "Επιτρέψτε την κοινή χρήση περιόδου σύνδεσης",
"allowSessionSharingDesc": "Να επιτρέπεται η κοινοποίηση ζωντανών συνεδριών σε αυτόν τον πάροχο μέσω συνδέσμου ή με άλλους χρήστες",
"enableCommandHistory": "Ιστορικό εντολών",
"enableCommandHistoryDesc": "Οι εντολές εγγραφής εκτελούνται σε αυτό το τερματικό για ιστορικό και αυτόματη συμπλήρωση",
"linkClickBehaviorLabel": "Συμπεριφορά κλικ σε σύνδεσμο",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} εισήχθη",
"proxmoxResultUpdated": "{{count}} ενημερώθηκε",
"proxmoxResultFailed": "{{count}} απέτυχε",
"proxmoxResultSkippedNoIp": "{{count}} παραλείφθηκε (δεν βρέθηκε IP)",
"proxmoxImportComplete": "Η εισαγωγή Proxmox ολοκληρώθηκε: {{summary}}",
"proxmoxDiscoveryFailed": "Η ανακάλυψη απέτυχε",
"proxmoxImportFailed": "Η εισαγωγή απέτυχε.",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Αποτυχία αποθήκευσης διαπιστευτηρίων",
"credentialNameRequired": "Παρακαλώ εισάγετε ένα όνομα για τα διαπιστευτήρια",
"credentialAuthRequired": "Προσθήκη κωδικού πρόσβασης, κλειδιού SSH ή και των δύο",
"createCredentialFromHostBtn": "Δημιουργία διαπιστευτηρίων",
"createCredentialFromHostTitle": "Δημιουργία διαπιστευτηρίων από τον κεντρικό υπολογιστή",
"createCredentialFromHostDesc": "Δημιουργήστε μια επαναχρησιμοποιήσιμη και κοινόχρηστη καταχώριση διαπιστευτηρίων, προσυμπληρωμένη με το τρέχον όνομα χρήστη, τον κωδικό πρόσβασης ή/και το κλειδί SSH αυτού του κεντρικού υπολογιστή.",
"backToHosts": "Πίσω στους οικοδεσπότες",
"backToCredentials": "Επιστροφή στα Διαπιστευτήρια",
"pinned": "Καρφιτσωμένο",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Χρησιμοποιήστε τα / για να διαχωρίσετε τα επίπεδα και να δημιουργήσετε ένθετους φακέλους.",
"folderColor": "Χρώμα",
"folderIcon": "Εικόνισμα",
"folderCredential": "Πιστοποιητικό",
"folderCredentialNone": "Δεν έχουν εκχωρηθεί πιστοποιητικά",
"folderCredentialHint": "Οι κεντρικοί υπολογιστές σε αυτόν τον φάκελο που χρησιμοποιούν έλεγχο ταυτότητας \"Αποθηκευμένα διαπιστευτήρια\" χωρίς να έχουν επιλέξει τα δικά τους διαπιστευτήρια θα κληρονομήσουν αυτό το ένα.",
"folderPreview": "Πρεμιέρα",
"folderNameFallback": "Φάκελος χωρίς τίτλο",
"createFolderButton": "Δημιουργία φακέλου",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Ετικέτες",
"shareHost": "Κοινή χρήση κεντρικού υπολογιστή",
"shareHostTitle": "Κοινή χρήση: {{name}}",
"shareFolder": "Κοινή χρήση φακέλου",
"shareFolderTitle": "Κοινόχρηστος φάκελος: {{name}}",
"folderSharedSuccessfully": "Κοινόχρηστοι {{count}} κεντρικοί υπολογιστές στον φάκελο",
"failedToShareFolder": "Αποτυχία κοινής χρήσης φακέλου",
"sharing": {
"loadError": "Αποτυχία φόρτωσης δεδομένων κοινής χρήσης. Δοκιμάστε ξανά.",
"shareWithSection": "Κοινοποίηση με",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Κοινή χρήση ({{count}})",
"currentAccess": "Τρέχουσα πρόσβαση",
"noAccessEntries": "Αυτός ο κεντρικός υπολογιστής δεν έχει κοινοποιηθεί ακόμη",
"folderShareSummary": "Κοινόχρηστο {{shared}} από {{total}} κεντρικούς υπολογιστές σε αυτόν τον φάκελο",
"grantedBy": "Χορηγήθηκε από",
"expires": "Λήγει",
"expired": "Λήξη",
@@ -1309,8 +1242,6 @@
"authMethod": "Μέθοδος Εξουσιοδότησης",
"authTypeDirect": "Απευθείας",
"authTypeCredential": "Πιστοποιητικό",
"authTypeNone": "Κανένας",
"authTypeNoneDesc": "Δεν αποθηκεύονται διαπιστευτήρια. Θα σας ζητείται όνομα χρήστη και κωδικός πρόσβασης κάθε φορά που συνδέεστε. Δεν αποθηκεύονται.",
"selectCredential": "Επιλέξτε ένα πιστοποιητικό...",
"connectionSettings": "Ρυθμίσεις σύνδεσης",
"displaySettings": "Ρυθμίσεις οθόνης",
@@ -1328,7 +1259,6 @@
"rdpPort": "Θύρα RDP",
"username": "Όνομα χρήστη",
"password": "Σύνθημα",
"passwordSaved": "Ο κωδικός πρόσβασης αποθηκεύτηκε, πληκτρολογήστε για να τον αλλάξετε",
"domain": "Πεδίο ορισμού",
"securityMode": "Λειτουργία ασφαλείας",
"colorDepth": "Βάθος χρώματος",
@@ -1494,60 +1424,7 @@
"expiresIn": "Λήγει σε {{duration}}",
"search": "Αναζήτηση συνδέσεων...",
"noSearchResults": "Δεν υπάρχουν συνδέσεις που να αντιστοιχούν στην αναζήτησή σας",
"rename": "Μετονομασία συνεδρίας",
"sectionSharedWithMe": "Κοινοποιήθηκε σε εμένα",
"sharedBy": "Κοινοποίηση από {{username}}",
"join": "Ενώνω",
"sharedSessionLabel": "{{hostName}} (κοινόχρηστο)"
},
"sessionSharing": {
"guestView": {
"loading": "Σύνδεση σε κοινόχρηστη συνεδρία...",
"linkInvalid": "Αυτός ο σύνδεσμος κοινής χρήσης δεν είναι έγκυρος, έχει λήξει ή έχει ανακληθεί",
"rateLimited": "Πάρα πολλές προσπάθειες, δοκιμάστε ξανά σύντομα.",
"sessionEnded": "Αυτή η συνεδρία έληξε",
"readOnlyBadge": "Μόνο προβολή"
},
"modalTitle": "Κοινή χρήση περιόδου σύνδεσης",
"shareButton": "Μερίδιο",
"notReadyToShare": "Η συνεδρία δεν είναι ακόμη έτοιμη για κοινή χρήση",
"modeTab": {
"link": "Σύνδεσμος",
"user": "Μεταχειριζόμενος"
},
"linkModeDescription": "Οποιοσδήποτε έχει αυτόν τον σύνδεσμο μπορεί να συμμετάσχει, δεν απαιτείται λογαριασμός.",
"userModeDescription": "Κοινοποίηση με έναν συγκεκριμένο χρήστη που έχει ήδη πρόσβαση σε αυτόν τον κεντρικό υπολογιστή. Εάν δεν έχει ακόμη πρόσβαση, μοιραστείτε πρώτα τον κεντρικό υπολογιστή μαζί του ή χρησιμοποιήστε έναν σύνδεσμο. Μόλις κοινοποιηθεί, η συνεδρία εμφανίζεται στην καρτέλα Συνδέσεις.",
"permissionLevel": {
"label": "Επίπεδο δικαιωμάτων",
"readOnly": "Μόνο για ανάγνωση",
"readOnlyDescription": "Μπορώ να παρακολουθήσω την συνεδρία ζωντανά, αλλά δεν μπορώ να πληκτρολογήσω ή να αλληλεπιδράσω.",
"readWrite": "Ανάγνωση-εγγραφή",
"readWriteDescription": "Μπορεί να πληκτρολογήσει και να αλληλεπιδράσει με την περίοδο λειτουργίας όπως ο κάτοχος."
},
"expiryLabel": "Λήξη σύνδεσης",
"createLinkButton": "Δημιουργία συνδέσμου",
"createShareButton": "Κοινοποίηση με χρήστη",
"searchUsersPlaceholder": "Αναζήτηση χρηστών...",
"noUsersFound": "Δεν βρέθηκαν χρήστες",
"linkCreated": "Ο σύνδεσμος κοινής χρήσης δημιουργήθηκε",
"linkCopied": "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο",
"copyLink": "Αντιγραφή συνδέσμου",
"shareCreated": "Η συνεδρία κοινοποιήθηκε. Θα εμφανιστεί στην καρτέλα \"Συνδέσεις\".",
"shareFailed": "Η δημιουργία κοινής χρήσης απέτυχε",
"userLacksHostAccess": "Αυτός ο χρήστης δεν έχει ακόμη πρόσβαση σε αυτόν τον κεντρικό υπολογιστή. Κοινοποιήστε πρώτα τον κεντρικό υπολογιστή μαζί του ή χρησιμοποιήστε έναν σύνδεσμο.",
"activeShares": "Ενεργές κοινοποιήσεις",
"noActiveShares": "Δεν υπάρχουν ενεργές κοινοποιήσεις για αυτήν την περίοδο σύνδεσης",
"revoke": "Ανακαλώ",
"revokeConfirmTitle": "Ανάκληση αυτής της κοινοποίησης;",
"revokeConfirmDescription": "Όποιος χρησιμοποιεί αυτήν την κοινοποίηση θα χάσει αμέσως την πρόσβαση.",
"revoked": "Η κοινοποίηση ανακλήθηκε",
"revokeFailed": "Η ανάκληση της κοινής χρήσης απέτυχε",
"joinCount": "{{count}} συμμετοχή",
"joinCount_other": "{{count}} ενώσεις",
"expiresAt": "Λήγει {{date}}",
"linkShareBadge": "Σύνδεσμος",
"userShareBadge": "Χρήστης: {{username}}",
"loadSharesFailed": "Αποτυχία φόρτωσης ενεργών κοινόχρηστων στοιχείων"
"rename": "Μετονομασία συνεδρίας"
},
"guacamole": {
"connecting": "Σύνδεση σε συνεδρία {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Επανασύνδεση",
"retry": "Δοκιμάζω πάλι",
"guacdUnavailable": "Η υπηρεσία απομακρυσμένης επιφάνειας εργασίας (guacd) δεν είναι διαθέσιμη. Βεβαιωθείτε ότι το guacd εκτελείται και είναι προσβάσιμο, καθώς και ότι έχει ρυθμιστεί σωστά στις ρυθμίσεις διαχειριστή.",
"credentialPromptTitle": "Εισαγάγετε τα διαπιστευτήρια RDP",
"credentialPromptDescription": "Αυτός ο κεντρικός υπολογιστής έχει ρυθμιστεί να ζητά διαπιστευτήρια κατά τη σύνδεση. Χρησιμοποιούνται μόνο για αυτήν την περίοδο σύνδεσης και δεν αποθηκεύονται.",
"connect": "Συνδέω",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Επανασύνδεση περιόδου σύνδεσης",
"collapse": "Σύμπτυξη γραμμής εργαλείων",
"expand": "Ανάπτυξη γραμμής εργαλείων",
"dragHandle": "Σύρετε για αλλαγή θέσης",
"switchToTrackpad": "Μετάβαση σε λειτουργία trackpad (σύρσιμο για μετακίνηση του κέρσορα, άγγιγμα για κλικ)",
"switchToTouch": "Μετάβαση σε λειτουργία αφής (πατήστε απευθείας εκεί που θέλετε να κάνετε κλικ)"
"dragHandle": "Σύρετε για αλλαγή θέσης"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Απαιτείται έλεγχος ταυτότητας δύο παραγόντων",
"totpCodeLabel": "Κωδικός επαλήθευσης",
"totpVerify": "Επαληθεύω",
"mfaPromptRequired": "Απαιτείται έλεγχος ταυτότητας",
"mfaPushRequired": "Απαιτείται έλεγχος ταυτότητας push",
"mfaMenuPlaceholder": "Εισαγάγετε την απάντησή σας",
"mfaWaitingApproval": "Αναμονή για έγκριση στη συσκευή σας...",
"mfaSendRequest": "Αποστολή αιτήματος",
"warpgateAuthRequired": "Απαιτείται έλεγχος ταυτότητας Warpgate",
"warpgateSecurityKey": "Κλειδί ασφαλείας",
"warpgateAuthUrl": "URL ελέγχου ταυτότητας",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Περιορισμένο ποσοστό: Πάρα πολλές προσπάθειες επαλήθευσης. Δοκιμάστε ξανά αργότερα.",
"resetCodeRateLimitedWithTime": "Περιορισμένος ρυθμός: Πάρα πολλές προσπάθειες επαλήθευσης. Παρακαλώ περιμένετε {{time}} δευτερόλεπτα πριν προσπαθήσετε ξανά.",
"authTokenSaveFailed": "Αποτυχία αποθήκευσης διακριτικού ελέγχου ταυτότητας",
"failedToLoadServer": "Αποτυχία φόρτωσης διακομιστή",
"remoteServerRequired": "Απαιτείται απομακρυσμένος διακομιστής. Συνδέστε έναν απομακρυσμένο διακομιστή στις Ρυθμίσεις για να χρησιμοποιήσετε αυτόν τον τύπο σύνδεσης."
"failedToLoadServer": "Αποτυχία φόρτωσης διακομιστή"
},
"messages": {
"registrationDisabled": "Η εγγραφή νέου λογαριασμού έχει απενεργοποιηθεί αυτήν τη στιγμή από έναν διαχειριστή. Συνδεθείτε ή επικοινωνήστε με έναν διαχειριστή.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Αποτυχία αποθήκευσης ρυθμίσεων SSL",
"sslRequiresDomain": "Απαιτούνται ο τομέας και το email",
"sslInfoNote": "Αφού εκδώσετε ένα πιστοποιητικό, ενεργοποιήστε το SSL στις μεταβλητές περιβάλλοντος (ENABLE_SSL=true) και επανεκκινήστε το Termix.",
"sslManualOption": "Εγχειρίδιο (μεταφόρτωση πιστοποιητικού)",
"sslManualCert": "Πιστοποιητικό (PEM)",
"sslManualCertPlaceholder": "-----ΕΝΑΡΞΗ ΠΙΣΤΟΠΟΙΗΤΙΚΟΥ-----",
"sslManualKey": "Ιδιωτικό Κλειδί (PEM)",
"sslManualKeyPlaceholder": "-----ΕΝΑΡΞΗ ΙΔΙΩΤΙΚΟΥ ΚΛΕΙΔΙΟΥ-----",
"sslManualDesc": "Επικολλήστε το υπάρχον πιστοποιητικό και το ιδιωτικό κλειδί σας, συμπεριλαμβανομένης μιας πλήρους αλυσίδας, εάν απαιτείται από την CA σας.",
"sslManualUpload": "Μεταφόρτωση και εγκατάσταση πιστοποιητικού",
"sslManualUploadLoading": "Μεταφόρτωση πιστοποιητικού...",
"sslManualUploadSuccess": "Το πιστοποιητικό μεταφορτώθηκε και εγκαταστάθηκε με επιτυχία.",
"sslManualUploadFailed": "Η μεταφόρτωση του πιστοποιητικού απέτυχε.",
"sslManualRequiresFields": "Απαιτείται πιστοποιητικό και ιδιωτικό κλειδί",
"auditLogTotal": "{{total}} συνολικές καταχωρήσεις",
"auditLogEmpty": "Δεν βρέθηκαν καταχωρήσεις αρχείου καταγραφής ελέγχου",
"auditLogSuccess": "Επιτυχία",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Ιστορικό εντολών",
"commandHistoryEnabledDesc": "Να επιτρέπεται η καταγραφή ιστορικού εντολών. Όταν είναι απενεργοποιημένη, το ιστορικό δεν αποθηκεύεται ανεξάρτητα από τις ρυθμίσεις ανά κεντρικό υπολογιστή.",
"updateCommandHistoryFailed": "Αποτυχία ενημέρωσης της ρύθμισης ιστορικού εντολών",
"analyticsEnabled": "Κοινοποίηση ανώνυμων στατιστικών χρήσης",
"analyticsEnabledDesc": "Αποστέλλει έναν ανώνυμο ημερήσιο αριθμό χρηστών, κεντρικών υπολογιστών και χρήσης λειτουργιών για να βοηθήσει στη βελτίωση του Termix. Δεν περιλαμβάνονται ποτέ προσωπικά δεδομένα ή λεπτομέρειες σύνδεσης.",
"updateAnalyticsFailed": "Η ενημέρωση της ρύθμισης αναλυτικών στοιχείων απέτυχε",
"sessionSharingGloballyEnabled": "Επιτρέψτε την κοινή χρήση περιόδου σύνδεσης",
"sessionSharingGloballyEnabledDesc": "Επιτρέπεται η κοινή χρήση των ζωντανών συνεδριών τερματικού, RDP, VNC και Telnet σε ολόκληρη την παρουσία. Παρακάμπτει κάθε εναλλαγή κοινής χρήσης ανά κεντρικό υπολογιστή όταν είναι απενεργοποιημένη.",
"updateSessionSharingFailed": "Αποτυχία ενημέρωσης ρύθμισης κοινής χρήσης περιόδου σύνδεσης",
"sessionTimeout": "Χρονικό όριο συνεδρίας",
"hours": "ώρες",
"sessionTimeoutRange": "Ελάχ. 1 ώρα · Μέγ. 720 ώρες",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Επιτυχία",
"executionFailed": "Αποτυχημένος"
},
"keybindings": {
"title": "Συντομεύσεις πληκτρολογίου",
"description": "Προσαρμόστε τις συντομεύσεις αντιγραφής, επικόλλησης και ελέγχου τερματικού ή συνδέστε τα πλήκτρα για αποστολή κειμένου ή εκτέλεση ενός αποσπάσματος.",
"defaultsHeading": "Ενσωματωμένες συντομεύσεις",
"customHeading": "Προσαρμοσμένες συντομεύσεις",
"addBinding": "Προσθήκη συντόμευσης",
"addBindingTitle": "Προσθήκη συντόμευσης",
"editBindingTitle": "Επεξεργασία συντόμευσης",
"loading": "Φόρτωση...",
"noCustomBindings": "Δεν υπάρχουν ακόμη προσαρμοσμένες συντομεύσεις",
"defaultBadge": "Αθέτηση",
"customizedBadge": "Προσαρμοσμένη",
"customize": "Προσαρμογή",
"resetToDefault": "Επαναφορά στις προεπιλογές",
"resetAllToDefaults": "Επαναφορά όλων στις προεπιλογές",
"close": "Κοντά",
"cancel": "Ματαίωση",
"saveBinding": "Αποθήκευση συντόμευσης",
"saveError": "Η αποθήκευση των συντομεύσεων πληκτρολογίου απέτυχε",
"pressKeysToRecord": "Συνδυασμός πλήκτρων",
"pressKeysPlaceholder": "Κάντε κλικ και πατήστε τα πλήκτρα",
"recording": "Πατήστε τα πλήκτρα...",
"comboRequiredError": "Πατήστε πρώτα έναν συνδυασμό πλήκτρων",
"textRequiredError": "Εισαγάγετε το κείμενο που θέλετε να στείλετε",
"controlCodeRequiredError": "Εισαγάγετε ένα μόνο γράμμα για τον κωδικό ελέγχου",
"snippetRequiredError": "Επιλέξτε ένα απόσπασμα",
"conflictWarning": "Αυτός ο συνδυασμός είναι ήδη συνδεδεμένος με {{combo}}. Η αποθήκευση θα ενεργοποιήσει και τα δύο.",
"actionLabel": "Δράση",
"actionCopy": "Αντιγραφή επιλογής",
"actionPaste": "Πάστα",
"actionSendControlCode": "Αποστολή σήματος Ctrl+γράμμα",
"actionSendText": "Αποστολή κειμένου",
"actionRunSnippet": "Εκτέλεση υπάρχοντος αποσπάσματος",
"controlCodeLabel": "Επιστολή",
"textLabel": "Μήνυμα προς αποστολή",
"appendEnterLabel": "Πατήστε Enter μετά την αποστολή",
"snippetLabel": "Απόσπασμα",
"selectSnippetPlaceholder": "Επιλέξτε ένα απόσπασμα",
"orphanedSnippetWarning": "το απόσπασμα δεν βρέθηκε",
"clipboardPermissionNote": "Ενδέχεται να ζητηθεί άδεια για το πρόχειρο κατά την πρώτη χρήση σε ορισμένα προγράμματα περιήγησης."
},
"userProfile": {
"donateTitle": "Κρατήστε το Termix ζωντανό",
"donateDescription": "Το Termix είναι δωρεάν, αυτο-φιλοξενείται και δημιουργείται από λίγα μόνο άτομα στον ελεύθερο χρόνο τους. Αν σας έχει εξοικονομήσει χρόνο ή χρήματα, μια δωρεά κρυπτονομισμάτων βοηθάει στη συνέχιση της λειτουργίας του.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Εμφάνιση",
"sectionSecurity": "Ασφάλεια",
"sectionApiKeys": "Κλειδιά API",
"sectionData": "Δεδομένα",
"sectionC2sTunnels": "Σήραγγες C2S",
"usernameLabel": "Όνομα χρήστη",
"roleLabel": "Ρόλος",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Τερματικό",
"commandAutocomplete": "Αυτόματη συμπλήρωση εντολών",
"commandAutocompleteDesc": "Εμφάνιση αυτόματης συμπλήρωσης κατά την πληκτρολόγηση",
"keyboardShortcuts": "Συντομεύσεις πληκτρολογίου",
"keyboardShortcutsDescription": "Προσαρμόστε τις συντομεύσεις αντιγραφής, επικόλλησης και εντολών στο τερματικό",
"manageShortcuts": "Διαχειρίζομαι",
"terminalLinkBehavior": "Κλικ στον σύνδεσμο τερματικού",
"terminalLinkBehaviorDesc": "Προεπιλεγμένη συμπεριφορά κατά το κλικ σε συνδέσμους στο τερματικό",
"historyTracking": "Παρακολούθηση ιστορικού",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Συμπεριλάβετε το κλειδί σας στο",
"apiKeyUsageHintHeader": "επί κεφαλής.",
"apiKeyPermissionsHint": "Τα κλειδιά κληρονομούν τα δικαιώματα του χρήστη που τα δημιούργησε.",
"exportData": "Εξαγωγή των δεδομένων μου",
"exportDataDesc": "Λήψη αντιγράφου ασφαλείας των κεντρικών υπολογιστών, των διαπιστευτηρίων και των ρυθμίσεών σας για μεταφορά σε άλλη συσκευή",
"export": "Εξαγωγή",
"exporting": "Εξαγωγή...",
"importData": "Εισαγωγή των δεδομένων μου",
"importDataDesc": "Επαναφέρετε τους κεντρικούς υπολογιστές, τα διαπιστευτήρια και τις ρυθμίσεις σας από ένα αρχείο αντιγράφου ασφαλείας .sqlite",
"importDataSelected": "Επιλεγμένα: {{name}}",
"selectFile": "Επιλογή Αρχείου",
"changeFile": "Αλλαγή",
"import": "Εισαγωγή",
"importing": "Εισαγωγή...",
"exportSuccess": "Τα δεδομένα εξήχθησαν με επιτυχία.",
"exportFailed": "Η εξαγωγή δεδομένων απέτυχε.",
"importSelectFile": "Επιλέξτε πρώτα ένα αρχείο",
"importCompleted": "Η εισαγωγή ολοκληρώθηκε: {{total}} στοιχεία εισήχθησαν, {{skipped}} παραλείφθηκαν",
"importFailed": "Η εισαγωγή απέτυχε: {{error}}",
"roleUser": "Μεταχειριζόμενος",
"authMethodDual": "Διπλή Εξουσιοδότηση",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Διαγραφή",
"paste": "Πάστα",
"editQuickKeys": "Επεξεργασία γρήγορων πλήκτρων",
"quickKeysTitle": "Γρήγορα πλήκτρα",
"quickKeysDesc": "Πατήστε × για κατάργηση. Υποστηρίζει έως και 8 χαρακτήρες.",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Ei tallennettuja palvelimia",
"removeServer": "Poistaa"
},
"migrationNotice": {
"title": "Termix Desktop toimii nyt itsenäisesti",
"body1": "Tämä sovellus muodosti aiemmin yhteyden suoraan Termix-palvelimeesi. Se on uudistettu toimimaan täysin itsenäisesti tallentaen isännät ja tunnistetiedot paikallisesti, joten se toimii edelleen offline-tilassa. Kaksisuuntainen synkronointi itse isännöidyn Termix-palvelimen kanssa on nyt valinnainen.",
"body2": "Nykyiset isäntäsi ja tunnistetietosi ovat edelleen {{url}}. Ota etäsynkronointi käyttöön ja muodosta yhteys kyseiseen palvelimeen uudelleen palauttaaksesi ne ja pitääksesi molemmat synkronoituina jatkossa.",
"dismiss": "Ei nyt",
"setUpSync": "Etäsynkronoinnin määrittäminen"
},
"remoteSync": {
"title": "Etäsynkronointi",
"description": "Voit halutessasi yhdistää tämän työpöytäsovelluksen itse isännöityyn Termix-palvelimeen synkronoidaksesi isännöitsijäsi, tunnistetietosi ja koodinpätkäsi eri laitteiden välillä. Sovellus toimii aina täysin offline-tilassa riippumatta siitä, oletko yhteydessä palvelimeen vai et.",
"notConnected": "Ei yhdistetty",
"connected": "Yhdistetty",
"connectedTo": "Yhdistetty kohteeseen {{url}}",
"lastSynced": "Viimeksi synkronoitu {{time}}",
"neverSynced": "Ei koskaan synkronoitu",
"syncError": "Synkronointivirhe: {{message}}",
"needsReauth": "Kirjautuminen vanhentunut",
"connectButton": "Yhdistä palvelimeen",
"disconnectButton": "Katkaise yhteys",
"syncNowButton": "Synkronoi nyt",
"syncing": "Synkronoidaan...",
"serverUrl": "Palvelimen URL-osoite",
"enterServerUrl": "Anna palvelimen URL-osoite",
"mustIncludeProtocol": "Palvelimen URL-osoitteen on alettava http:// tai https://",
"connectionTestFailed": "Termix-palvelimeen ei saatu yhteyttä kyseisessä URL-osoitteessa",
"allowInvalidCertificate": "Salli virheellinen varmenne",
"allowInvalidCertificateDesc": "Käytä vain luotettaville itse isännöidyille palvelimille, joissa on itse allekirjoitetut tai IP-osoitevarmenteet.",
"savedServers": "Tallennetut palvelimet",
"removeServer": "Poistaa",
"continueButton": "Jatkaa",
"cancelButton": "Peruuttaa",
"signInTitle": "Kirjaudu sisään palveluun {{url}}",
"originTitle": "Yhteyden alkuperä",
"originDescription": "Valitse oletusarvoisesti SSH-yhteyksien lähtöpaikka. Tämä voidaan ohittaa isäntäkohtaisesti.",
"originLocal": "Tämä laite (lähiverkko)",
"originRemote": "Etäpalvelin",
"bannerReconnect": "Yhdistä uudelleen",
"bannerMessage": "Etäsynkronointi vaatii uudelleentunnistuksen"
},
"versionCheck": {
"error": "Versiotarkistusvirhe",
"checkFailed": "Päivitysten tarkistaminen epäonnistui",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Ohita tunnistetiedot Käyttäjätunnus",
"overrideCredentialUsernameDesc": "Käytä yllä määritettyä käyttäjätunnusta tunnistetiedon käyttäjätunnuksen sijaan.",
"oidcUsernameHint": "Käytä OIDC-kirjautumisnimesi sijaan muotoa $oidc.preferred_username.",
"tailscaleUsernameHint": "Tämän on oltava Unix-käyttäjä, jolle Tailscale-identiteettisi on myönnetty tailnetin SSH ACL:ssä, ei välttämättä pääkäyttäjä.",
"jumpHostChain": "Hyppyisäntäketju",
"portKnocking": "Portin koputus",
"addKnock": "Lisää portti",
@@ -690,11 +650,6 @@
"delayAfterMs": "Viive jälkeen (ms)",
"useSocks5Proxy": "Käytä SOCKS5-välityspalvelinta",
"useSocks5ProxyDesc": "Reititä yhteys välityspalvelimen kautta",
"connectionOrigin": "Yhteyden alkuperä",
"connectionOriginDesc": "Mistä tämän isännän SSH-yhteys on peräisin. Ohittaa työpöytäsovelluksen yleisen oletusasetuksen.",
"connectionOriginDefault": "Käytä oletusarvoa",
"connectionOriginLocal": "Tämä laite (lähiverkko)",
"connectionOriginRemote": "Etäpalvelin",
"proxyHost": "Välityspalvelin",
"proxyPort": "Välityspalvelimen portti",
"proxyUsername": "Välityspalvelimen käyttäjätunnus",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Kirkkaan syaani",
"customThemeBrightWhite": "Kirkkaan valkoinen",
"customThemeResetTooltip": "Palauta oletusasetukset",
"savedThemesLabel": "Tallennetut teemat",
"saveAsGlobalTheme": "Tallenna globaalina teemana",
"saveGlobalThemeNamePrompt": "Anna teemalle nimi",
"saveGlobalThemeSuccess": "Teema tallennettu",
"saveGlobalThemeError": "Teeman tallentaminen epäonnistui",
"applyGlobalThemeTooltip": "Käytä tätä teemaa",
"deleteGlobalThemeTooltip": "Poista tämä teema",
"noSavedThemes": "Ei vielä tallennettuja teemoja",
"syntaxHighlightingLabel": "Syntaksin korostus",
"syntaxHighlightingDesc": "Väritä päätelaitteen tuloste (virheet, polut, IP-osoitteet, aikaleimat)",
"syntaxHighlightingCategories": "Korosta kategoriat",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Käynnistä automaattisesti tai liitä tmux-istuntoon",
"enableSessionLogging": "Istuntojen lokitiedot",
"enableSessionLoggingDesc": "Tallenna pääteistunnon tuloste myöhempää tarkastelua varten",
"allowSessionSharing": "Salli istunnon jakaminen",
"allowSessionSharingDesc": "Salli live-istuntojen jakaminen tällä isännällä linkin kautta tai muiden käyttäjien kanssa",
"enableCommandHistory": "Komentohistoria",
"enableCommandHistoryDesc": "Tallenna tässä terminaalissa suoritettavat komennot historiaa ja automaattista täydennystä varten",
"linkClickBehaviorLabel": "Linkin klikkauskäyttäytyminen",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} tuotu",
"proxmoxResultUpdated": "{{count}} päivitetty",
"proxmoxResultFailed": "{{count}} epäonnistui",
"proxmoxResultSkippedNoIp": "{{count}} ohitettu (IP-osoitetta ei löytynyt)",
"proxmoxImportComplete": "Proxmox-tuonti valmis: {{summary}}",
"proxmoxDiscoveryFailed": "Löytö epäonnistui",
"proxmoxImportFailed": "Tuonti epäonnistui",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Tunnusten tallentaminen epäonnistui",
"credentialNameRequired": "Anna tunnisteelle nimi",
"credentialAuthRequired": "Lisää salasana, SSH-avain tai molemmat",
"createCredentialFromHostBtn": "Luo tunnistetiedot",
"createCredentialFromHostTitle": "Luo tunnistetiedot isännältä",
"createCredentialFromHostDesc": "Luo uudelleenkäytettävä, jaettava tunnistetietomerkintä, joka on esitäytetty tämän isännän nykyisellä käyttäjätunnuksella, salasanalla ja/tai SSH-avaimella.",
"backToHosts": "Takaisin isäntiin",
"backToCredentials": "Takaisin tunnistetietoihin",
"pinned": "Kiinnitetty",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Käytä /-merkkiä tasojen erottamiseen ja sisäkkäisten kansioiden luomiseen.",
"folderColor": "Väri",
"folderIcon": "Kuvake",
"folderCredential": "Valtakirja",
"folderCredentialNone": "Ei tunnistetietoja",
"folderCredentialHint": "Tämän kansion isännät, jotka käyttävät \"Tallennetut tunnistetiedot\" -todennusta ilman omia tunnistetietojaan, perivät tämän.",
"folderPreview": "Esikatselu",
"folderNameFallback": "Nimetön kansio",
"createFolderButton": "Luo kansio",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Tunnisteet",
"shareHost": "Jaa isäntä",
"shareHostTitle": "Jaa: {{name}}",
"shareFolder": "Jaa kansio",
"shareFolderTitle": "Jaa kansio: {{name}}",
"folderSharedSuccessfully": "Jaetut {{count}} isäntä(t) kansiossa",
"failedToShareFolder": "Kansion jakaminen epäonnistui",
"sharing": {
"loadError": "Jakamistietojen lataaminen epäonnistui. Yritä uudelleen.",
"shareWithSection": "Jaa kanssa",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Jaa ({{count}})",
"currentAccess": "Nykyinen käyttöoikeus",
"noAccessEntries": "Tätä isäntää ei ole vielä jaettu",
"folderShareSummary": "Jaettu {{shared}} / {{total}} isäntäkoneesta tässä kansiossa",
"grantedBy": "Myöntänyt",
"expires": "Vanhenee",
"expired": "Vanhentunut",
@@ -1309,8 +1242,6 @@
"authMethod": "Todennustapa",
"authTypeDirect": "Suoraan",
"authTypeCredential": "Valtakirja",
"authTypeNone": "Ei mitään",
"authTypeNoneDesc": "Tunnuksia ei tallenneta. Sinulta kysytään käyttäjätunnusta ja salasanaa aina, kun muodostat yhteyden; niitä ei tallenneta.",
"selectCredential": "Valitse tunnistetiedot...",
"connectionSettings": "Yhteysasetukset",
"displaySettings": "Näyttöasetukset",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-portti",
"username": "Käyttäjätunnus",
"password": "Salasana",
"passwordSaved": "Salasana tallennettu, kirjoita muuttaaksesi",
"domain": "Verkkotunnus",
"securityMode": "Suojaustila",
"colorDepth": "Värisyvyys",
@@ -1494,60 +1424,7 @@
"expiresIn": "Vanhenee {{duration}} kuluttua",
"search": "Hae yhteyksiä...",
"noSearchResults": "Ei hakuasi vastaavia yhteyksiä",
"rename": "Nimeä istunto uudelleen",
"sectionSharedWithMe": "Jaettu kanssani",
"sharedBy": "Jakanut {{username}}",
"join": "Liity",
"sharedSessionLabel": "{{hostName}} (jaettu)"
},
"sessionSharing": {
"guestView": {
"loading": "Yhdistetään jaettuun istuntoon...",
"linkInvalid": "Tämä jakolinkki on virheellinen, vanhentunut tai se on peruutettu.",
"rateLimited": "Liikaa yrityksiä, yritä uudelleen pian",
"sessionEnded": "Tämä istunto on päättynyt",
"readOnlyBadge": "Vain katselu"
},
"modalTitle": "Jaa istunto",
"shareButton": "Jakaa",
"notReadyToShare": "Istunto ei ole vielä valmis jaettavaksi",
"modeTab": {
"link": "Linkki",
"user": "Käyttäjä"
},
"linkModeDescription": "Kuka tahansa, jolla on tämä linkki, voi liittyä, tiliä ei tarvita.",
"userModeDescription": "Jaa tietyn käyttäjän kanssa, jolla on jo pääsy tähän isäntään. Jos heillä ei vielä ole pääsyä, jaa isäntä ensin heidän kanssaan tai käytä linkkiä. Jakamisen jälkeen istunto näkyy heidän Yhteydet-välilehdellään.",
"permissionLevel": {
"label": "Käyttöoikeustaso",
"readOnly": "Vain lukuoikeus",
"readOnlyDescription": "Voit katsoa istuntoa suorana, mutta et voi kirjoittaa tai olla vuorovaikutuksessa.",
"readWrite": "Lue ja kirjoita",
"readWriteDescription": "Voi kirjoittaa ja olla vuorovaikutuksessa istunnon kanssa aivan kuten omistaja."
},
"expiryLabel": "Linkin vanheneminen",
"createLinkButton": "Luo linkki",
"createShareButton": "Jaa käyttäjän kanssa",
"searchUsersPlaceholder": "Hae käyttäjiä...",
"noUsersFound": "Ei käyttäjiä löytynyt",
"linkCreated": "Jaa linkki luotu",
"linkCopied": "Linkki kopioitu leikepöydälle",
"copyLink": "Kopioi linkki",
"shareCreated": "Istunto jaettu. Se näkyy heidän Yhteydet-välilehdellä.",
"shareFailed": "Jaon luominen epäonnistui",
"userLacksHostAccess": "Käyttäjällä ei ole vielä pääsyä tähän isäntään. Jaa isäntä ensin heidän kanssaan tai käytä linkkiä.",
"activeShares": "Aktiiviset jaot",
"noActiveShares": "Ei aktiivisia jakoja tällä istunnolla",
"revoke": "Peruuttaa",
"revokeConfirmTitle": "Perutaanko tämä jako?",
"revokeConfirmDescription": "Kuka tahansa tätä jakoa käyttävä menettää käyttöoikeuden välittömästi.",
"revoked": "Jako peruttu",
"revokeFailed": "Jakamisen peruuttaminen epäonnistui",
"joinCount": "{{count}} liity",
"joinCount_other": "{{count}} liitokset",
"expiresAt": "Vanhenee {{date}}",
"linkShareBadge": "Linkki",
"userShareBadge": "Käyttäjä: {{username}}",
"loadSharesFailed": "Aktiivisten jakojen lataaminen epäonnistui"
"rename": "Nimeä istunto uudelleen"
},
"guacamole": {
"connecting": "Yhdistetään {{type}} -istuntoon...",
@@ -1559,9 +1436,6 @@
"reconnect": "Yhdistä uudelleen",
"retry": "Yritä uudelleen",
"guacdUnavailable": "Etätyöpöytäpalvelu (guacd) ei ole käytettävissä. Varmista, että guacd on käynnissä, käytettävissä ja määritetty oikein järjestelmänvalvojan asetuksissa.",
"credentialPromptTitle": "Syötä RDP-tunnistetiedot",
"credentialPromptDescription": "Tämä isäntä on asetettu kysymään tunnistetietoja yhteyden muodostamisen yhteydessä. Niitä käytetään vain tässä istunnossa, eikä niitä tallenneta.",
"connect": "Yhdistä",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Yhdistä istunto uudelleen",
"collapse": "Pienennä työkalupalkki",
"expand": "Laajenna työkalupalkkia",
"dragHandle": "Vedä siirtääksesi",
"switchToTrackpad": "Vaihda ohjauslevytilaan (vedä siirtääksesi kohdistinta, napauta napsauttamalla)",
"switchToTouch": "Vaihda kosketustilaan (napauta suoraan haluamaasi kohtaan)"
"dragHandle": "Vedä siirtääksesi"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Kaksivaiheinen todennus vaaditaan",
"totpCodeLabel": "Vahvistuskoodi",
"totpVerify": "Vahvista",
"mfaPromptRequired": "Todennus vaaditaan",
"mfaPushRequired": "Push-todennus vaaditaan",
"mfaMenuPlaceholder": "Kirjoita vastauksesi",
"mfaWaitingApproval": "Odotetaan hyväksyntää laitteellasi...",
"mfaSendRequest": "Lähetä pyyntö",
"warpgateAuthRequired": "Warpgate-todennus vaaditaan",
"warpgateSecurityKey": "Suojausavain",
"warpgateAuthUrl": "Todennus-URL-osoite",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Nopeus rajoitettu: Liian monta vahvistusyritystä. Yritä myöhemmin uudelleen.",
"resetCodeRateLimitedWithTime": "Nopeus rajoitettu: Liian monta vahvistusyritystä. Odota {{time}} sekuntia ennen kuin yrität uudelleen.",
"authTokenSaveFailed": "Todennustunnuksen tallennus epäonnistui",
"failedToLoadServer": "Palvelimen lataus epäonnistui",
"remoteServerRequired": "Etäpalvelin vaaditaan. Yhdistä etäpalvelin asetuksissa käyttääksesi tätä yhteystyyppiä."
"failedToLoadServer": "Palvelimen lataus epäonnistui"
},
"messages": {
"registrationDisabled": "Ylläpitäjä on tällä hetkellä poistanut käytöstä uuden tilin rekisteröinnin. Kirjaudu sisään tai ota yhteyttä ylläpitäjään.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "SSL-asetusten tallentaminen epäonnistui",
"sslRequiresDomain": "Verkkotunnus ja sähköpostiosoite vaaditaan",
"sslInfoNote": "Kun olet myöntänyt varmenteen, ota SSL käyttöön ympäristömuuttujissasi (ENABLE_SSL=true) ja käynnistä Termix uudelleen.",
"sslManualOption": "Manuaalinen (lataa varmenne)",
"sslManualCert": "Todistus (PEM)",
"sslManualCertPlaceholder": "-----ALKUTODISTUS-----",
"sslManualKey": "Yksityinen avain (PEM)",
"sslManualKeyPlaceholder": "-----ALKU YKSITYINEN AVAIN-----",
"sslManualDesc": "Liitä olemassa oleva varmenne ja yksityinen avaimesi, mukaan lukien koko ketju, jos varmentaja sitä vaatii.",
"sslManualUpload": "Lataa ja asenna varmenne",
"sslManualUploadLoading": "Varmennetta ladataan...",
"sslManualUploadSuccess": "Varmenne ladattiin ja asennettiin onnistuneesti",
"sslManualUploadFailed": "Varmenteen lataus epäonnistui",
"sslManualRequiresFields": "Varmenne ja yksityinen avain vaaditaan",
"auditLogTotal": "{{total}} merkintöjä yhteensä",
"auditLogEmpty": "Ei tarkastuslokin merkintöjä",
"auditLogSuccess": "Menestys",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Komentohistoria",
"commandHistoryEnabledDesc": "Salli komentohistorian tallennus. Kun tämä on poistettu käytöstä, historiaa ei tallenneta isäntäkohtaisista asetuksista riippumatta.",
"updateCommandHistoryFailed": "Komentohistoria-asetuksen päivittäminen epäonnistui",
"analyticsEnabled": "Jaa anonyymit käyttötilastot",
"analyticsEnabledDesc": "Lähettää nimettömän päivittäisen määrän käyttäjistä, isännöistä ja ominaisuuksien käytöstä Termixin parantamiseksi. Henkilötietoja tai yhteystietoja ei koskaan sisällytetä mukaan.",
"updateAnalyticsFailed": "Analytiikka-asetuksen päivittäminen epäonnistui",
"sessionSharingGloballyEnabled": "Salli istunnon jakaminen",
"sessionSharingGloballyEnabledDesc": "Salli reaaliaikaisten pääte-, RDP-, VNC- ja Telnet-istuntojen jakaminen koko instanssin kesken. Poistamalla tämän asetuksen käytöstä ohitetaan kaikki isäntäkohtaisesti jakamiseen liittyvät asetukset.",
"updateSessionSharingFailed": "Istunnon jakamisasetuksen päivittäminen epäonnistui",
"sessionTimeout": "Istunnon aikakatkaisu",
"hours": "tuntia",
"sessionTimeoutRange": "Vähintään 1 h · Enintään 720 h",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Menestys",
"executionFailed": "Epäonnistui"
},
"keybindings": {
"title": "Pikanäppäimet",
"description": "Mukauta päätelaitteen kopiointi-, liittämis- ja ohjauspikakuvakkeita tai sido näppäimiä tekstin lähettämiseen tai koodinpätkän suorittamiseen.",
"defaultsHeading": "Sisäänrakennetut pikavalinnat",
"customHeading": "Mukautetut pikavalinnat",
"addBinding": "Lisää pikakuvake",
"addBindingTitle": "Lisää pikakuvake",
"editBindingTitle": "Muokkaa pikakuvaketta",
"loading": "Ladataan...",
"noCustomBindings": "Ei vielä mukautettuja pikanäppäimiä",
"defaultBadge": "Oletus",
"customizedBadge": "Räätälöity",
"customize": "Mukauta",
"resetToDefault": "Palauta oletusarvot",
"resetAllToDefaults": "Palauta kaikki oletusasetuksiin",
"close": "Lähellä",
"cancel": "Peruuttaa",
"saveBinding": "Tallenna pikakuvake",
"saveError": "Pikanäppäinten tallentaminen epäonnistui",
"pressKeysToRecord": "Näppäinyhdistelmä",
"pressKeysPlaceholder": "Napsauta ja paina näppäimiä",
"recording": "Paina näppäimiä...",
"comboRequiredError": "Paina ensin näppäinyhdistelmää",
"textRequiredError": "Kirjoita lähetettävä teksti",
"controlCodeRequiredError": "Syötä yksi kirjain ohjauskoodille",
"snippetRequiredError": "Valitse katkelma",
"conflictWarning": "Tämä yhdistelmä on jo sidottu kohtaan {{combo}}. Tallentaminen käynnistää molemmat.",
"actionLabel": "Toiminta",
"actionCopy": "Kopioi valinta",
"actionPaste": "Liitä",
"actionSendControlCode": "Lähetä Ctrl+kirjain-signaali",
"actionSendText": "Lähetä tekstiviesti",
"actionRunSnippet": "Suorita olemassa oleva koodinpätkä",
"controlCodeLabel": "Kirje",
"textLabel": "Lähetettävä tekstiviesti",
"appendEnterLabel": "Paina Enter lähetyksen jälkeen",
"snippetLabel": "Katkelma",
"selectSnippetPlaceholder": "Valitse katkelma",
"orphanedSnippetWarning": "katkelmaa ei löytynyt",
"clipboardPermissionNote": "Saattaa pyytää leikepöydän käyttöoikeutta ensimmäisellä käyttökerralla joissakin selaimissa."
},
"userProfile": {
"donateTitle": "Pidä Termix hengissä",
"donateDescription": "Termix on ilmainen, itse isännöity ja vain muutaman ihmisen vapaa-ajallaan rakentama palvelu. Jos se on säästänyt aikaasi tai rahaasi, kryptovaluutan lahjoitus auttaa pitämään sen toiminnassa.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Ulkonäkö",
"sectionSecurity": "Turvallisuus",
"sectionApiKeys": "API-avaimet",
"sectionData": "Data",
"sectionC2sTunnels": "C2S-tunnelit",
"usernameLabel": "Käyttäjätunnus",
"roleLabel": "Rooli",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminaali",
"commandAutocomplete": "Komennon automaattinen täydennys",
"commandAutocompleteDesc": "Näytä automaattinen täydennys kirjoitettaessa",
"keyboardShortcuts": "Pikanäppäimet",
"keyboardShortcutsDescription": "Mukauta päätelaitteen kopiointi-, liittämis- ja komento-pikakuvakkeita",
"manageShortcuts": "Hallitse",
"terminalLinkBehavior": "Päätelinkin napsautus",
"terminalLinkBehaviorDesc": "Oletuskäyttäytyminen linkkejä napsautettaessa terminaalissa",
"historyTracking": "Historian seuranta",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Sisällytä avaimesi mukaan",
"apiKeyUsageHintHeader": "otsikko.",
"apiKeyPermissionsHint": "Avaimet perivät luovan käyttäjän oikeudet.",
"exportData": "Vie tietoni",
"exportDataDesc": "Lataa varmuuskopio isännistäsi, tunnistetiedoistasi ja asetuksistasi siirtääksesi ne toiseen laitteeseen",
"export": "Viedä",
"exporting": "Vienti...",
"importData": "Tuo omat tiedot",
"importDataDesc": "Palauta isäntäpalvelimet, tunnistetiedot ja asetukset .sqlite-varmuuskopiotiedostosta",
"importDataSelected": "Valittu: {{name}}",
"selectFile": "Valitse tiedosto",
"changeFile": "Muuttaa",
"import": "Tuoda",
"importing": "Tuodaan...",
"exportSuccess": "Tiedot vietiin onnistuneesti",
"exportFailed": "Tietojen vienti epäonnistui",
"importSelectFile": "Valitse ensin tiedosto",
"importCompleted": "Tuonti valmis: {{total}} kohdetta tuotu, {{skipped}} ohitettu",
"importFailed": "Tuonti epäonnistui: {{error}}",
"roleUser": "Käyttäjä",
"authMethodDual": "Kaksoistutkimus",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "Sivu ylös",
"pageDown": "PgDn",
"delete": "Poista",
"paste": "Liitä",
"editQuickKeys": "Muokkaa pikanäppäimiä",
"quickKeysTitle": "Pikanäppäimet",
"quickKeysDesc": "Poista napauttamalla ×. Tukee enintään 8 merkkiä.",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "אין שרתים שמורים",
"removeServer": "לְהַסִיר"
},
"migrationNotice": {
"title": "שולחן העבודה של Termix פועל כעת באופן עצמאי",
"body1": "אפליקציה זו הייתה מתחברת ישירות לשרת Termix שלך. היא עובדה מחדש כדי לפעול באופן עצמאי לחלוטין, תוך אחסון מארחים ופרטי גישה מקומיים כך שהיא תמשיך לעבוד במצב לא מקוון. סנכרון דו-כיווני עם שרת Termix המתארח באופן עצמאי הוא כעת אופציונלי.",
"body2": "המארחים והאישורים הקיימים שלך עדיין נמצאים ב- {{url}}. הפעל את הסנכרון מרחוק והתחבר מחדש לשרת זה כדי להחזיר אותם ולשמור על שניהם מסונכרנים מעתה והלאה.",
"dismiss": "לא עכשיו",
"setUpSync": "הגדר סנכרון מרחוק"
},
"remoteSync": {
"title": "סנכרון מרחוק",
"description": "ניתן לחבר אפליקציית שולחן עבודה זו לשרת Termix המתארח באופן עצמאי כדי לסנכרן את המארחים, האישורים והקטעים שלך בין מכשירים. האפליקציה תמיד פועלת באופן מלא במצב לא מקוון, בין אם אתה מתחבר ובין אם לא.",
"notConnected": "לא מחובר",
"connected": "מְחוּבָּר",
"connectedTo": "מחובר אל {{url}}",
"lastSynced": "סנכרון אחרון {{time}}",
"neverSynced": "מעולם לא סנכרן",
"syncError": "שגיאת סינכרון: {{message}}",
"needsReauth": "פג תוקף הכניסה",
"connectButton": "התחברות לשרת",
"disconnectButton": "לְנַתֵק",
"syncNowButton": "סנכרן עכשיו",
"syncing": "מסנכרן...",
"serverUrl": "כתובת URL של השרת",
"enterServerUrl": "אנא הזן כתובת URL של שרת",
"mustIncludeProtocol": "כתובת השרת חייבת להתחיל ב-http:// או https://",
"connectionTestFailed": "לא ניתן היה להגיע לשרת Termix בכתובת URL זו",
"allowInvalidCertificate": "אפשר אישור לא חוקי",
"allowInvalidCertificateDesc": "השתמש רק עבור שרתים מהימנים המתארחים בעצמם עם אישורי חתימה עצמית או אישורי כתובת IP.",
"savedServers": "שרתים שמורים",
"removeServer": "לְהַסִיר",
"continueButton": "לְהַמשִׁיך",
"cancelButton": "לְבַטֵל",
"signInTitle": "היכנס אל {{url}}",
"originTitle": "מקור החיבור",
"originDescription": "בחר כברירת מחדל מהיכן חיבורי SSH מגיעים. ניתן לעקוף זאת לפי מארח.",
"originLocal": "מכשיר זה (רשת מקומית)",
"originRemote": "שרת מרוחק",
"bannerReconnect": "התחבר מחדש",
"bannerMessage": "סנכרון מרחוק דורש אימות מחדש"
},
"versionCheck": {
"error": "שגיאת בדיקת גרסה",
"checkFailed": "נכשל בבדיקת עדכונים",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "עקיפת שם משתמש של פרטי כניסה",
"overrideCredentialUsernameDesc": "השתמש בשם המשתמש שצוין למעלה במקום שם המשתמש של פרטי האישור",
"oidcUsernameHint": "השתמש ב- $oidc.preferred_username כדי להחליף את שם המשתמש שלך ב-OIDC.",
"tailscaleUsernameHint": "זה חייב להיות משתמש יוניקס שזהות Tailscale שלך מוענקת לו ב-SSH ACL של tailnet, לאו דווקא root.",
"jumpHostChain": "שרשרת מארח קפיצה",
"portKnocking": "פורט נוקינג",
"addKnock": "הוסף יציאה",
@@ -690,11 +650,6 @@
"delayAfterMs": "עיכוב לאחר (מילישניות)",
"useSocks5Proxy": "השתמש בפרוקסי SOCKS5",
"useSocks5ProxyDesc": "ניתוב חיבור דרך שרת פרוקסי",
"connectionOrigin": "מקור החיבור",
"connectionOriginDesc": "מאיפה מגיע חיבור ה-SSH של מארח זה. עוקף את הגדרת ברירת המחדל הגלובלית של אפליקציית שולחן העבודה.",
"connectionOriginDefault": "השתמש בברירת מחדל",
"connectionOriginLocal": "מכשיר זה (רשת מקומית)",
"connectionOriginRemote": "שרת מרוחק",
"proxyHost": "מארח פרוקסי",
"proxyPort": "יציאת פרוקסי",
"proxyUsername": "שם משתמש פרוקסי",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "ציאן בהיר",
"customThemeBrightWhite": "לבן בהיר",
"customThemeResetTooltip": "איפוס להגדרות ברירת מחדל",
"savedThemesLabel": "ערכות נושא שמורות",
"saveAsGlobalTheme": "שמור כערכת נושא גלובלית",
"saveGlobalThemeNamePrompt": "הזן שם עבור ערכת נושא זו",
"saveGlobalThemeSuccess": "ערכת הנושא נשמרה",
"saveGlobalThemeError": "שמירת ערכת הנושא נכשלה",
"applyGlobalThemeTooltip": "החל ערכת נושא זו",
"deleteGlobalThemeTooltip": "מחק את ערכת הנושא הזו",
"noSavedThemes": "עדיין אין ערכות נושא שנשמרו",
"syntaxHighlightingLabel": "הדגשת תחביר",
"syntaxHighlightingDesc": "צבע את פלט הטרמינל (שגיאות, נתיבים, כתובות IP, חותמות זמן)",
"syntaxHighlightingCategories": "הדגש קטגוריות",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "הפעל או צורף אוטומטית לסשן tmux",
"enableSessionLogging": "רישום מושבים",
"enableSessionLoggingDesc": "הקלט את פלט הפעלת הטרמינל לצורך סקירה מאוחרת יותר",
"allowSessionSharing": "אפשר שיתוף סשנים",
"allowSessionSharingDesc": "אפשר שיתוף של מפגשים חיים במארח זה באמצעות קישור או עם משתמשים אחרים",
"enableCommandHistory": "היסטוריית פקודות",
"enableCommandHistoryDesc": "פקודות הקלטה פועלות בטרמינל זה לצורך היסטוריה והשלמה אוטומטית",
"linkClickBehaviorLabel": "התנהגות לחיצה על קישור",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} מיובא",
"proxmoxResultUpdated": "{{count}} updated",
"proxmoxResultFailed": "{{count}} נכשל",
"proxmoxResultSkippedNoIp": "{{count}} דילג (לא נמצאה כתובת IP)",
"proxmoxImportComplete": "ייבוא Proxmox הושלם: {{summary}}",
"proxmoxDiscoveryFailed": "הגילוי נכשל",
"proxmoxImportFailed": "הייבוא נכשל",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "שמירת האישורים נכשלה",
"credentialNameRequired": "אנא הזן שם עבור האישור",
"credentialAuthRequired": "הוסף סיסמה, מפתח SSH או שניהם",
"createCredentialFromHostBtn": "צור אישור",
"createCredentialFromHostTitle": "צור אישור מהמארח",
"createCredentialFromHostDesc": "צור ערך אישורים רב פעמי וניתן לשיתוף, הממולא מראש בשם המשתמש, הסיסמה ו/או מפתח ה-SSH הנוכחיים של מארח זה.",
"backToHosts": "חזרה למארחים",
"backToCredentials": "חזרה לאישורים",
"pinned": "מוצמד",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "השתמשו ב-/ כדי להפריד רמות וליצור תיקיות מקוננות.",
"folderColor": "צֶבַע",
"folderIcon": "אייקון",
"folderCredential": "תְעוּדָה",
"folderCredentialNone": "לא הוקצו אישורים",
"folderCredentialHint": "מארחים בתיקייה זו המשתמשים באימות \"אישורים מאוחסנים\" מבלי שבחרו אישור משלהם, יירשו את האישור הזה.",
"folderPreview": "תצוגה מקדימה",
"folderNameFallback": "תיקייה ללא שם",
"createFolderButton": "צור תיקייה",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "תגיות",
"shareHost": "שתף מארח",
"shareHostTitle": "שתף: {{name}}",
"shareFolder": "שיתוף תיקייה",
"shareFolderTitle": "שיתוף תיקייה: {{name}}",
"folderSharedSuccessfully": "מארחים משותפים {{count}} בתיקייה",
"failedToShareFolder": "שיתוף התיקייה נכשל",
"sharing": {
"loadError": "טעינת נתוני השיתוף נכשלה. אנא נסה שוב.",
"shareWithSection": "שתף עם",
@@ -1287,7 +1221,6 @@
"shareWithCount": "שתף ({{count}})",
"currentAccess": "גישה נוכחית",
"noAccessEntries": "מארח זה עדיין לא שותף",
"folderShareSummary": "מארחים משותפים {{shared}} של {{total}} בתיקייה זו",
"grantedBy": "הוענק על ידי",
"expires": "פג תוקף",
"expired": "פג תוקף",
@@ -1309,8 +1242,6 @@
"authMethod": "שיטת אימות",
"authTypeDirect": "יָשִׁיר",
"authTypeCredential": "תְעוּדָה",
"authTypeNone": "אַף לֹא אֶחָד",
"authTypeNoneDesc": "לא נשמרים פרטי גישה. תתבקש להזין שם משתמש וסיסמה בכל פעם שתתחבר; הם לא נשמרים.",
"selectCredential": "בחר אישור...",
"connectionSettings": "הגדרות חיבור",
"displaySettings": "הגדרות תצוגה",
@@ -1328,7 +1259,6 @@
"rdpPort": "יציאת RDP",
"username": "שם משתמש",
"password": "סִיסמָה",
"passwordSaved": "הסיסמה נשמרה, הקלד כדי לשנות",
"domain": "תְחוּם",
"securityMode": "מצב אבטחה",
"colorDepth": "עומק צבע",
@@ -1494,60 +1424,7 @@
"expiresIn": "פג תוקף בעוד {{duration}}",
"search": "חיפוש חיבורים...",
"noSearchResults": "אין קשרים התואמים את החיפוש שלך",
"rename": "שינוי שם של סשן",
"sectionSharedWithMe": "שיתף איתי",
"sharedBy": "שותף על ידי {{username}}",
"join": "לְהִצְטַרֵף",
"sharedSessionLabel": "{{hostName}} (שותף)"
},
"sessionSharing": {
"guestView": {
"loading": "מתחבר לסשן משותף...",
"linkInvalid": "קישור השיתוף הזה אינו תקין, פג תוקף או בוטל",
"rateLimited": "יותר מדי ניסיונות, אנא נסה שוב בקרוב",
"sessionEnded": "מושב זה הסתיים",
"readOnlyBadge": "צפייה בלבד"
},
"modalTitle": "שיתוף סשן",
"shareButton": "לַחֲלוֹק",
"notReadyToShare": "הסשן עדיין לא מוכן לשיתוף",
"modeTab": {
"link": "לְקַשֵׁר",
"user": "מִשׁתַמֵשׁ"
},
"linkModeDescription": "כל מי שיש לו קישור זה יכול להצטרף, אין צורך בחשבון.",
"userModeDescription": "שתף עם משתמש ספציפי שכבר יש לו גישה למארח זה. אם עדיין אין לו גישה, שתף איתו תחילה את המארח או השתמש בקישור במקום זאת. לאחר השיתוף, הסשן מופיע בכרטיסייה \"חיבורים\" שלו.",
"permissionLevel": {
"label": "רמת הרשאה",
"readOnly": "לקריאה בלבד",
"readOnlyDescription": "יכול לצפות בפגישה בשידור חי אך לא להקליד או לתקשר.",
"readWrite": "קריאה-כתיבה",
"readWriteDescription": "יכול להקליד ולקיים אינטראקציה עם הסשן בדיוק כמו הבעלים."
},
"expiryLabel": "תוקף הקישור",
"createLinkButton": "צור קישור",
"createShareButton": "שתף עם המשתמש",
"searchUsersPlaceholder": "חיפוש משתמשים...",
"noUsersFound": "לא נמצאו משתמשים",
"linkCreated": "קישור השיתוף נוצר",
"linkCopied": "קישור שהועתק ללוח",
"copyLink": "העתקת קישור",
"shareCreated": "הסשן שותף. זה יופיע בלשונית החיבורים שלהם.",
"shareFailed": "יצירת השיתוף נכשלה",
"userLacksHostAccess": "למשתמש הזה עדיין אין גישה למארח הזה. שתף את המארח איתו תחילה, או השתמש בקישור במקום זאת.",
"activeShares": "מניות פעילות",
"noActiveShares": "אין שיתופים פעילים עבור סשן זה",
"revoke": "לְבַטֵל",
"revokeConfirmTitle": "לבטל את השיתוף הזה?",
"revokeConfirmDescription": "כל מי שישתמש בשיתוף הזה יאבד גישה באופן מיידי.",
"revoked": "השיתוף בוטל",
"revokeFailed": "ביטול השיתוף נכשל",
"joinCount": "{{count}} join",
"joinCount_other": "{{count}} מצטרף",
"expiresAt": "פג תוקף {{date}}",
"linkShareBadge": "לְקַשֵׁר",
"userShareBadge": "משתמש: {{username}}",
"loadSharesFailed": "נכשלה טעינת השיתופים הפעילים"
"rename": "שינוי שם של סשן"
},
"guacamole": {
"connecting": "מתחבר לסשן {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "התחבר מחדש",
"retry": "נסה שוב",
"guacdUnavailable": "שירות שולחן עבודה מרוחק (guacd) אינו זמין. אנא ודא ש-guacd פועל, נגיש ומוגדר כראוי בהגדרות המנהל.",
"credentialPromptTitle": "הזן אישורי RDP",
"credentialPromptDescription": "מארח זה מוגדר לבקש אישורים בעת התחברות. הם משמשים עבור הפעלה זו בלבד ואינם נשמרים.",
"connect": "לְחַבֵּר",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "חיבור מחדש של הפעלת המושב",
"collapse": "כיווץ סרגל הכלים",
"expand": "הרחב את סרגל הכלים",
"dragHandle": "גרור כדי למקם מחדש",
"switchToTrackpad": "מעבר למצב משטח עקיבה (גרור כדי להזיז את הסמן, הקש כדי ללחוץ)",
"switchToTouch": "מעבר למצב מגע (הקש ישירות במקום שברצונך ללחוץ)"
"dragHandle": "גרור כדי למקם מחדש"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "נדרש אימות דו-שלבי",
"totpCodeLabel": "קוד אימות",
"totpVerify": "לְאַמֵת",
"mfaPromptRequired": "נדרש אימות",
"mfaPushRequired": "נדרש אימות דחיפה",
"mfaMenuPlaceholder": "הזן את תגובתך",
"mfaWaitingApproval": "ממתין לאישור במכשיר שלך...",
"mfaSendRequest": "שלח בקשה",
"warpgateAuthRequired": "נדרש אימות Warpgate",
"warpgateSecurityKey": "מפתח אבטחה",
"warpgateAuthUrl": "כתובת URL לאימות",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "קצב מוגבל: יותר מדי ניסיונות אימות. אנא נסה שוב מאוחר יותר.",
"resetCodeRateLimitedWithTime": "קצב מוגבל: יותר מדי ניסיונות אימות. אנא המתן {{time}} שניות לפני שתנסה שוב.",
"authTokenSaveFailed": "נכשלה שמירת אסימון האימות",
"failedToLoadServer": "טעינת השרת נכשלה",
"remoteServerRequired": "נדרש שרת מרוחק. יש להתחבר לשרת מרוחק בהגדרות כדי להשתמש בסוג חיבור זה."
"failedToLoadServer": "טעינת השרת נכשלה"
},
"messages": {
"registrationDisabled": "רישום חשבון חדש מושבת כעת על ידי מנהל. אנא התחבר או צור קשר עם מנהל.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "שמירת הגדרות SSL נכשלה",
"sslRequiresDomain": "נדרשים דומיין וכתובת אימייל",
"sslInfoNote": "לאחר הנפקת האישור, הפעל את SSL במשתני הסביבה שלך (ENABLE_SSL=true) והפעל מחדש את Termix.",
"sslManualOption": "מדריך (העלאת תעודה)",
"sslManualCert": "תעודה (PEM)",
"sslManualCertPlaceholder": "-----התחלת תעודה-----",
"sslManualKey": "מפתח פרטי (PEM)",
"sslManualKeyPlaceholder": "-----התחל מפתח פרטי-----",
"sslManualDesc": "הדבק את האישור והמפתח הפרטי הקיימים שלך, כולל שרשרת מלאה אם נדרשת על ידי רשות האישורים שלך.",
"sslManualUpload": "העלאה והתקנה של אישור",
"sslManualUploadLoading": "מעלה תעודה...",
"sslManualUploadSuccess": "התעודה הועלתה והותקנה בהצלחה",
"sslManualUploadFailed": "העלאת האישור נכשלה",
"sslManualRequiresFields": "נדרשים תעודה ומפתח פרטי",
"auditLogTotal": "{{total}} סך כל הרשומות",
"auditLogEmpty": "לא נמצאו ערכי יומן ביקורת",
"auditLogSuccess": "הַצלָחָה",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "היסטוריית פקודות",
"commandHistoryEnabledDesc": "אפשר רישום היסטוריית פקודות. כאשר האפשרות מושבתת, ההיסטוריה לא נשמרת ללא קשר להגדרות של כל מחשב מארח.",
"updateCommandHistoryFailed": "נכשל עדכון הגדרת היסטוריית הפקודות",
"analyticsEnabled": "שתף סטטיסטיקות שימוש אנונימיות",
"analyticsEnabledDesc": "שולח ספירה יומית אנונימית של משתמשים, מארחים ושימוש בתכונות כדי לסייע בשיפור Termix. לא כלולים נתונים אישיים או פרטי חיבור.",
"updateAnalyticsFailed": "נכשל עדכון הגדרת הניתוח",
"sessionSharingGloballyEnabled": "אפשר שיתוף סשנים",
"sessionSharingGloballyEnabledDesc": "מאפשר שיתוף של הפעלות מסוף חי, RDP, VNC ו-Telnet בכל המופעים. מבטל כל אפשרות שיתוף לפי מארח כאשר היא מושבתת.",
"updateSessionSharingFailed": "נכשל עדכון הגדרת שיתוף הסשן",
"sessionTimeout": "פסק זמן של סשן",
"hours": "שעות",
"sessionTimeoutRange": "מינימום שעה · מקסימום 720 שעות",
@@ -3270,47 +3119,6 @@
"executionSuccess": "הַצלָחָה",
"executionFailed": "נִכשָׁל"
},
"keybindings": {
"title": "קיצורי מקלדת",
"description": "התאם אישית קיצורי דרך להעתקה, הדבקה ובקרה של הטרמינל, או חבר מקשים לשליחת טקסט או להפעלת קטע קוד.",
"defaultsHeading": "קיצורי דרך מובנים",
"customHeading": "קיצורי דרך מותאמים אישית",
"addBinding": "הוסף קיצור דרך",
"addBindingTitle": "הוסף קיצור דרך",
"editBindingTitle": "עריכת קיצור דרך",
"loading": "טְעִינָה...",
"noCustomBindings": "עדיין אין קיצורי דרך מותאמים אישית",
"defaultBadge": "בְּרִירַת מֶחדָל",
"customizedBadge": "מותאם אישית",
"customize": "התאמה אישית",
"resetToDefault": "איפוס לברירת מחדל",
"resetAllToDefaults": "איפוס הכל להגדרות ברירת המחדל",
"close": "לִסְגוֹר",
"cancel": "לְבַטֵל",
"saveBinding": "שמירת קיצור דרך",
"saveError": "שמירת קיצורי מקלדת נכשלה",
"pressKeysToRecord": "צירוף מקשים",
"pressKeysPlaceholder": "לחיצה ולחיצה על המקשים",
"recording": "לחץ על מקשים...",
"comboRequiredError": "לחץ תחילה על צירוף מקשים",
"textRequiredError": "הזן את הטקסט לשליחה",
"controlCodeRequiredError": "הזן אות אחת עבור קוד הבקרה",
"snippetRequiredError": "בחר קטע",
"conflictWarning": "צירוף זה כבר קשור ל- {{combo}}. שמירה תגרום לשניהם לפעול.",
"actionLabel": "פְּעוּלָה",
"actionCopy": "העתקת הבחירה",
"actionPaste": "לְהַדבִּיק",
"actionSendControlCode": "שלח אות Ctrl+אות",
"actionSendText": "שלח טקסט",
"actionRunSnippet": "הפעלת קטע קוד קיים",
"controlCodeLabel": "מִכְתָב",
"textLabel": "טקסט לשליחה",
"appendEnterLabel": "לחץ על Enter לאחר השליחה",
"snippetLabel": "קטע",
"selectSnippetPlaceholder": "בחר קטע",
"orphanedSnippetWarning": "קטע לא נמצא",
"clipboardPermissionNote": "ייתכן שתבקש הרשאה ללוח בעת השימוש הראשון בדפדפנים מסוימים."
},
"userProfile": {
"donateTitle": "לשמור על טרמיקס בחיים",
"donateDescription": "Termix הוא שירות חינמי, מתארח בעצמו, ונבנה על ידי מספר אנשים בלבד בזמנם הפנוי. אם זה חסך לכם זמן או כסף, תרומת קריפטו תעזור לשמור עליו.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "הוֹפָעָה",
"sectionSecurity": "בִּטָחוֹן",
"sectionApiKeys": "מפתחות API",
"sectionData": "נְתוּנִים",
"sectionC2sTunnels": "מנהרות C2S",
"usernameLabel": "שם משתמש",
"roleLabel": "תַפְקִיד",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "מָסוֹף",
"commandAutocomplete": "השלמה אוטומטית של הפקודה",
"commandAutocompleteDesc": "הצג השלמה אוטומטית בעת הקלדה",
"keyboardShortcuts": "קיצורי מקלדת",
"keyboardShortcutsDescription": "התאמה אישית של קיצורי דרך להעתקה, הדבקה ופקודות במסוף",
"manageShortcuts": "לְנַהֵל",
"terminalLinkBehavior": "לחיצה על קישור הטרמינל",
"terminalLinkBehaviorDesc": "אופן הפעולה המוגדר כברירת מחדל בעת לחיצה על קישורים בטרמינל",
"historyTracking": "מעקב אחר היסטוריה",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "כלול את המפתח שלך ב-",
"apiKeyUsageHintHeader": "כּוֹתֶרֶת.",
"apiKeyPermissionsHint": "מפתחות יורשים את ההרשאות של המשתמש היוצר.",
"exportData": "ייצוא הנתונים שלי",
"exportDataDesc": "הורד גיבוי של המארחים, האישורים וההגדרות שלך כדי להעביר למכשיר אחר",
"export": "יְצוּא",
"exporting": "ייצוא...",
"importData": "ייבא את הנתונים שלי",
"importDataDesc": "שחזר את המארחים, האישורים וההגדרות שלך מקובץ גיבוי .sqlite",
"importDataSelected": "נבחר: {{name}}",
"selectFile": "בחר קובץ",
"changeFile": "לְשַׁנוֹת",
"import": "יְבוּא",
"importing": "מייבא...",
"exportSuccess": "הנתונים יוצאו בהצלחה",
"exportFailed": "ייצוא הנתונים נכשל",
"importSelectFile": "אנא בחר קובץ תחילה",
"importCompleted": "ייבוא הושלם: {{total}} פריטים יובאו, {{skipped}} דילג",
"importFailed": "ייבוא נכשל: {{error}}",
"roleUser": "מִשׁתַמֵשׁ",
"authMethodDual": "אימות כפול",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "עמוד למעלה",
"pageDown": "PgDn",
"delete": "דל",
"paste": "לְהַדבִּיק",
"editQuickKeys": "עריכת מקשי מהירים",
"quickKeysTitle": "מקשי מהירים",
"quickKeysDesc": "הקש על × כדי להסיר. תומך בעד 8 תווים.",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Nincsenek mentett szerverek",
"removeServer": "Eltávolítás"
},
"migrationNotice": {
"title": "A Termix Desktop mostantól önállóan is fut",
"body1": "Ez az alkalmazás korábban közvetlenül a Termix szerveredhez csatlakozott. Átdolgoztuk, hogy teljesen önállóan fusson, helyben tárolja a hosztokat és a hitelesítő adatokat, így offline is működik. A kétirányú szinkronizálás egy saját üzemeltetésű Termix szerverrel mostantól opcionális.",
"body2": "A meglévő hosztok és hitelesítő adatok továbbra is a {{url}}címen találhatók. Kapcsold be a távoli szinkronizálást, és csatlakozz újra ehhez a szerverhez, hogy visszahozd őket, és a jövőben is szinkronban maradjanak.",
"dismiss": "Most nem",
"setUpSync": "Távoli szinkronizálás beállítása"
},
"remoteSync": {
"title": "Távoli szinkronizálás",
"description": "Opcionálisan csatlakoztathatod ezt az asztali alkalmazást egy saját üzemeltetésű Termix szerverhez, hogy szinkronizálhasd a hosztjaidat, hitelesítő adataidat és kódrészleteidet az eszközök között. Az alkalmazás mindig teljesen offline működik, függetlenül attól, hogy csatlakozol-e vagy sem.",
"notConnected": "Nincs csatlakoztatva",
"connected": "Csatlakoztatva",
"connectedTo": "Csatlakoztatva ehhez: {{url}}",
"lastSynced": "Utolsó szinkronizálás {{time}}",
"neverSynced": "Soha nem szinkronizálva",
"syncError": "Szinkronizálási hiba: {{message}}",
"needsReauth": "Bejelentkezés lejárt",
"connectButton": "Kapcsolódás a szerverhez",
"disconnectButton": "Leválasztás",
"syncNowButton": "Szinkronizálás most",
"syncing": "Szinkronizálás...",
"serverUrl": "Szerver URL-címe",
"enterServerUrl": "Kérjük, adjon meg egy szerver URL-címét",
"mustIncludeProtocol": "A szerver URL-címének http:// vagy https:// előtaggal kell kezdődnie.",
"connectionTestFailed": "Nem sikerült elérni a Termix szervert ezen az URL-címen.",
"allowInvalidCertificate": "Érvénytelen tanúsítvány engedélyezése",
"allowInvalidCertificateDesc": "Csak megbízható, saját üzemeltetésű szerverekhez használja, amelyek önaláírt vagy IP-cím alapú tanúsítvánnyal rendelkeznek.",
"savedServers": "Mentett szerverek",
"removeServer": "Eltávolítás",
"continueButton": "Folytatás",
"cancelButton": "Mégsem",
"signInTitle": "Jelentkezzen be ide: {{url}}",
"originTitle": "Kapcsolat eredete",
"originDescription": "Alapértelmezés szerint válassza ki az SSH-kapcsolatok forrását. Ez gazdagépenként felülbírálható.",
"originLocal": "Ez az eszköz (helyi hálózat)",
"originRemote": "Távoli szerver",
"bannerReconnect": "Újracsatlakozás",
"bannerMessage": "A távoli szinkronizáláshoz újra kell hitelesíteni"
},
"versionCheck": {
"error": "Verzióellenőrzési hiba",
"checkFailed": "Nem sikerült ellenőrizni a frissítéseket",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Hitelesítő adat felülírása Felhasználónév",
"overrideCredentialUsernameDesc": "Használja a fent megadott felhasználónevet a hitelesítő adat felhasználóneve helyett.",
"oidcUsernameHint": "A $oidc.preferred_username paraméterrel helyettesítheti az OIDC bejelentkezési nevét.",
"tailscaleUsernameHint": "Ennek Unix felhasználónak kell lennie, akinek a Tailscale identitását a tailnet SSH ACL-jében adták meg, nem feltétlenül root felhasználónak.",
"jumpHostChain": "Jump Host Chain",
"portKnocking": "Port Knocking",
"addKnock": "Port hozzáadása",
@@ -690,11 +650,6 @@
"delayAfterMs": "Késleltetés után (ms)",
"useSocks5Proxy": "Használjon SOCKS5 proxyt",
"useSocks5ProxyDesc": "Kapcsolat átirányítása proxy szerveren keresztül",
"connectionOrigin": "Kapcsolat eredete",
"connectionOriginDesc": "Honnan származik ennek a gazdagépnek az SSH-kapcsolata. Felülírja az asztali alkalmazás globális alapértelmezett beállítását.",
"connectionOriginDefault": "Alapértelmezett használata",
"connectionOriginLocal": "Ez az eszköz (helyi hálózat)",
"connectionOriginRemote": "Távoli szerver",
"proxyHost": "Proxy állomás",
"proxyPort": "Proxy port",
"proxyUsername": "Proxy felhasználónév",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Élénk ciánkék",
"customThemeBrightWhite": "Élénk fehér",
"customThemeResetTooltip": "Alapértelmezett beállítások visszaállítása",
"savedThemesLabel": "Mentett témák",
"saveAsGlobalTheme": "Mentés globális témaként",
"saveGlobalThemeNamePrompt": "Adjon meg egy nevet ennek a témának",
"saveGlobalThemeSuccess": "Téma mentve",
"saveGlobalThemeError": "Nem sikerült menteni a témát",
"applyGlobalThemeTooltip": "Alkalmazd ezt a témát",
"deleteGlobalThemeTooltip": "Téma törlése",
"noSavedThemes": "Még nincsenek mentett témák",
"syntaxHighlightingLabel": "Szintaxis kiemelése",
"syntaxHighlightingDesc": "Terminál kimenetének színezése (hibák, elérési utak, IP-címek, időbélyegek)",
"syntaxHighlightingCategories": "Kiemelt kategóriák",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Automatikus indítás vagy csatlakozás a tmux munkamenethez",
"enableSessionLogging": "Munkamenet-naplózás",
"enableSessionLoggingDesc": "Terminál munkamenet kimenetének rögzítése későbbi áttekintés céljából",
"allowSessionSharing": "Munkamenet-megosztás engedélyezése",
"allowSessionSharingDesc": "Engedélyezi az élő munkamenetek megosztását ezen a gazdagépen linken vagy más felhasználókkal",
"enableCommandHistory": "Parancsnoki előzmények",
"enableCommandHistoryDesc": "A terminálon futó parancsok rögzítése előzmények és automatikus kiegészítés céljából",
"linkClickBehaviorLabel": "Linkkattintási viselkedés",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importálva",
"proxmoxResultUpdated": "{{count}} frissítve",
"proxmoxResultFailed": "{{count}} sikertelen",
"proxmoxResultSkippedNoIp": "{{count}} kihagyva (nem található IP cím)",
"proxmoxImportComplete": "Proxmox importálás kész: {{summary}}",
"proxmoxDiscoveryFailed": "A felfedezés sikertelen",
"proxmoxImportFailed": "Importálás sikertelen",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Nem sikerült menteni a hitelesítő adatokat",
"credentialNameRequired": "Kérjük, adjon meg egy nevet a hitelesítő adatnak",
"credentialAuthRequired": "Jelszó, SSH-kulcs vagy mindkettő hozzáadása",
"createCredentialFromHostBtn": "Hitelesítő adat létrehozása",
"createCredentialFromHostTitle": "Hitelesítő adat létrehozása a gazdagépről",
"createCredentialFromHostDesc": "Hozzon létre egy újrafelhasználható, megosztható hitelesítőadat-bejegyzést, amely előre ki van töltve a gazdagép jelenlegi felhasználónevével, jelszavával és/vagy SSH-kulcsával.",
"backToHosts": "Vissza a házigazdákhoz",
"backToCredentials": "Vissza a hitelesítő adatokhoz",
"pinned": "Rögzítve",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "A / jellel választhatja el a szinteket, és hozhat létre beágyazott mappákat.",
"folderColor": "Szín",
"folderIcon": "Ikon",
"folderCredential": "Hitelesítő adat",
"folderCredentialNone": "Nincs hozzárendelve hitelesítő adat",
"folderCredentialHint": "Az ebben a mappában található, kiválasztott hitelesítő adatok nélküli „Tárolt hitelesítő adatok” hitelesítést használó hosztok öröklik ezt a hitelesítő adatot.",
"folderPreview": "Előnézet",
"folderNameFallback": "Névtelen mappa",
"createFolderButton": "Mappa létrehozása",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Címkék",
"shareHost": "Megosztási házigazda",
"shareHostTitle": "Megosztás: {{name}}",
"shareFolder": "Mappa megosztása",
"shareFolderTitle": "Megosztott mappa: {{name}}",
"folderSharedSuccessfully": "Megosztott {{count}} gazdagép(ek) a mappában",
"failedToShareFolder": "Nem sikerült megosztani a mappát",
"sharing": {
"loadError": "Nem sikerült betölteni a megosztási adatokat. Próbáld újra.",
"shareWithSection": "Megosztás",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Megosztás ({{count}})",
"currentAccess": "Jelenlegi hozzáférés",
"noAccessEntries": "Ez a hoszt még nincs megosztva",
"folderShareSummary": "Megosztott {{shared}} / {{total}} gazdagép ebben a mappában",
"grantedBy": "Megadta",
"expires": "Lejár",
"expired": "Lejárt",
@@ -1309,8 +1242,6 @@
"authMethod": "Aut. módszer",
"authTypeDirect": "Közvetlen",
"authTypeCredential": "Hitelesítő adat",
"authTypeNone": "Egyik sem",
"authTypeNoneDesc": "Nincsenek tárolt hitelesítő adatok. Minden csatlakozáskor meg kell adnia a felhasználónevet és a jelszót; ezek nem kerülnek mentésre.",
"selectCredential": "Válasszon hitelesítő adatot...",
"connectionSettings": "Kapcsolati beállítások",
"displaySettings": "Kijelzőbeállítások",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-port",
"username": "Felhasználónév",
"password": "Jelszó",
"passwordSaved": "Jelszó mentve, gépelje be a módosításhoz",
"domain": "Domain",
"securityMode": "Biztonsági mód",
"colorDepth": "Színmélység",
@@ -1494,60 +1424,7 @@
"expiresIn": "Lejár {{duration}} múlva",
"search": "Kapcsolatok keresése...",
"noSearchResults": "Nincsenek a keresésnek megfelelő kapcsolatok",
"rename": "Munkamenet átnevezése",
"sectionSharedWithMe": "Megosztva velem",
"sharedBy": "Megosztotta: {{username}}",
"join": "Csatlakozik",
"sharedSessionLabel": "{{hostName}} (megosztva)"
},
"sessionSharing": {
"guestView": {
"loading": "Kapcsolódás a megosztott munkamenethez...",
"linkInvalid": "Ez a megosztási link érvénytelen, lejárt vagy visszavonták.",
"rateLimited": "Túl sok próbálkozás, kérjük, próbáld újra később",
"sessionEnded": "Ez a munkamenet véget ért",
"readOnlyBadge": "Csak megtekintés"
},
"modalTitle": "Megosztási munkamenet",
"shareButton": "Részesedés",
"notReadyToShare": "A munkamenet még nem megosztható",
"modeTab": {
"link": "Link",
"user": "Felhasználó"
},
"linkModeDescription": "Bárki csatlakozhat a link birtokában, nincs szükség regisztrációra.",
"userModeDescription": "Megosztás egy adott felhasználóval, aki már rendelkezik hozzáféréssel ehhez a gazdagéphez. Ha még nincs hozzáférésük, először oszd meg vele a gazdagépet, vagy használj hivatkozást. A megosztás után a munkamenet megjelenik a Kapcsolatok lapon.",
"permissionLevel": {
"label": "Jogosultsági szint",
"readOnly": "Csak olvasható",
"readOnlyDescription": "Élőben nézhetem az előadást, de nem gépelhetek és nem léphetek interakcióba.",
"readWrite": "Olvasható-írható",
"readWriteDescription": "Pont úgy tud gépelni és kommunikálni a munkamenettel, mint a tulajdonos."
},
"expiryLabel": "Link lejárata",
"createLinkButton": "Link létrehozása",
"createShareButton": "Megosztás a felhasználóval",
"searchUsersPlaceholder": "Felhasználók keresése...",
"noUsersFound": "Nem található felhasználó",
"linkCreated": "Megosztási link létrehozva",
"linkCopied": "Link a vágólapra másolva",
"copyLink": "Link másolása",
"shareCreated": "Munkamenet megosztva. Megjelenik a Kapcsolatok fülön.",
"shareFailed": "Nem sikerült létrehozni a megosztást",
"userLacksHostAccess": "A felhasználónak még nincs hozzáférése ehhez a gazdagéphez. Először oszd meg vele a gazdagépet, vagy használj linket.",
"activeShares": "Aktív megosztások",
"noActiveShares": "Nincsenek aktív megosztások ebben a munkamenetben",
"revoke": "Visszavonás",
"revokeConfirmTitle": "Visszavonja ezt a megosztást?",
"revokeConfirmDescription": "Bárki, aki ezt a megosztást használja, azonnal elveszíti a hozzáférését.",
"revoked": "Megosztás visszavonva",
"revokeFailed": "Nem sikerült visszavonni a megosztást",
"joinCount": "{{count}} csatlakozás",
"joinCount_other": "{{count}} csatlakozások",
"expiresAt": "Lejár {{date}}",
"linkShareBadge": "Link",
"userShareBadge": "Felhasználó: {{username}}",
"loadSharesFailed": "Nem sikerült betölteni az aktív megosztásokat"
"rename": "Munkamenet átnevezése"
},
"guacamole": {
"connecting": "Kapcsolódás ehhez: {{type}} munkamenet...",
@@ -1559,9 +1436,6 @@
"reconnect": "Újracsatlakozás",
"retry": "Újrapróbálkozás",
"guacdUnavailable": "A távoli asztali szolgáltatás (guacd) nem érhető el. Kérjük, győződjön meg róla, hogy a guacd fut, elérhető és megfelelően van konfigurálva az adminisztrátori beállításokban.",
"credentialPromptTitle": "RDP hitelesítő adatok megadása",
"credentialPromptDescription": "Ez a gazdagép úgy van beállítva, hogy csatlakozáskor hitelesítő adatokat kérjen. Ezeket csak ehhez a munkamenethez használjuk, és nem kerülnek mentésre.",
"connect": "Csatlakozás",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Munkamenet újracsatlakoztatása",
"collapse": "Eszköztár összecsukása",
"expand": "Eszköztár kibontása",
"dragHandle": "Húzással áthelyezhető",
"switchToTrackpad": "Váltás trackpad módra (húzással mozgathatja a kurzort, koppintással kattinthat)",
"switchToTouch": "Váltás érintőképernyős módra (koppintson közvetlenül oda, ahová kattintani szeretne)"
"dragHandle": "Húzással áthelyezhető"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Kétfaktoros hitelesítés szükséges",
"totpCodeLabel": "Ellenőrző kód",
"totpVerify": "Ellenőrzés",
"mfaPromptRequired": "Hitelesítés szükséges",
"mfaPushRequired": "Push hitelesítés szükséges",
"mfaMenuPlaceholder": "Írd be a válaszod",
"mfaWaitingApproval": "Várakozás a jóváhagyásra az eszközödön...",
"mfaSendRequest": "Kérés küldése",
"warpgateAuthRequired": "Warpgate hitelesítés szükséges",
"warpgateSecurityKey": "Biztonsági kulcs",
"warpgateAuthUrl": "Hitelesítési URL",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Korlátozott sebesség: Túl sok ellenőrzési kísérlet. Kérjük, próbálja újra később.",
"resetCodeRateLimitedWithTime": "Korlátozott sebesség: Túl sok ellenőrzési kísérlet. Kérjük, várjon {{time}} másodpercet, mielőtt újra próbálkozik.",
"authTokenSaveFailed": "Nem sikerült menteni a hitelesítési tokent",
"failedToLoadServer": "Nem sikerült betölteni a szervert",
"remoteServerRequired": "Távoli szerver szükséges. Csatlakoztasson egy távoli szervert a Beállításokban a kapcsolattípus használatához."
"failedToLoadServer": "Nem sikerült betölteni a szervert"
},
"messages": {
"registrationDisabled": "Az új fiók regisztrációját jelenleg egy adminisztrátor letiltotta. Kérjük, jelentkezzen be, vagy vegye fel a kapcsolatot egy adminisztrátorral.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Nem sikerült menteni az SSL-beállításokat",
"sslRequiresDomain": "Domain és email cím megadása kötelező",
"sslInfoNote": "Tanúsítvány kiállítása után engedélyezze az SSL-t a környezeti változókban (ENABLE_SSL=true), és indítsa újra a Termixet.",
"sslManualOption": "Kézikönyv (tanúsítvány feltöltése)",
"sslManualCert": "Tanúsítvány (PEM)",
"sslManualCertPlaceholder": "-----TANÚSÍTVÁNY KEZDETE-----",
"sslManualKey": "Privát kulcs (PEM)",
"sslManualKeyPlaceholder": "-----PRIVÁT KULCS KEZDETE-----",
"sslManualDesc": "Illeszd be a meglévő tanúsítványodat és privát kulcsodat, beleértve a teljes láncot is, ha a hitelesítésszolgáltatód megköveteli.",
"sslManualUpload": "Tanúsítvány feltöltése és telepítése",
"sslManualUploadLoading": "Tanúsítvány feltöltése...",
"sslManualUploadSuccess": "A tanúsítvány feltöltése és telepítése sikeresen megtörtént",
"sslManualUploadFailed": "A tanúsítvány feltöltése sikertelen",
"sslManualRequiresFields": "Tanúsítvány és privát kulcs szükséges",
"auditLogTotal": "{{total}} összes bejegyzés",
"auditLogEmpty": "Nem találhatók naplóbejegyzések",
"auditLogSuccess": "Siker",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Parancsnoki előzmények",
"commandHistoryEnabledDesc": "Parancselőzmények rögzítésének engedélyezése. Ha le van tiltva, az előzmények nem kerülnek mentésre, függetlenül a gazdagép beállításaitól.",
"updateCommandHistoryFailed": "Nem sikerült frissíteni a parancselőzmények beállítását",
"analyticsEnabled": "Névtelen használati statisztikák megosztása",
"analyticsEnabledDesc": "Naponta névtelenül küldi a felhasználók, a hosztok és a funkciók használatának számát a Termix fejlesztése érdekében. Személyes adatokat vagy kapcsolati adatokat nem tartalmaz.",
"updateAnalyticsFailed": "Nem sikerült frissíteni az analitikai beállításokat",
"sessionSharingGloballyEnabled": "Munkamenet-megosztás engedélyezése",
"sessionSharingGloballyEnabledDesc": "Engedélyezi az élő terminál-, RDP-, VNC- és Telnet-munkamenetek példányonkénti megosztását. Letiltás esetén felülírja az összes gazdagépenkénti megosztási kapcsolót.",
"updateSessionSharingFailed": "Nem sikerült frissíteni a munkamenet-megosztási beállítást",
"sessionTimeout": "Munkamenet időkorlátja",
"hours": "órák",
"sessionTimeoutRange": "Minimum 1 óra · Max. 720 óra",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Siker",
"executionFailed": "Sikertelen"
},
"keybindings": {
"title": "Billentyűparancsok",
"description": "Testreszabhatja a terminál másolási, beillesztési és vezérlési billentyűparancsait, vagy billentyűket köthet szöveg küldéséhez vagy kódrészlet futtatásához.",
"defaultsHeading": "Beépített gyorsbillentyűk",
"customHeading": "Egyéni billentyűparancsok",
"addBinding": "Parancsikon hozzáadása",
"addBindingTitle": "Parancsikon hozzáadása",
"editBindingTitle": "Parancsikon szerkesztése",
"loading": "Terhelés...",
"noCustomBindings": "Még nincsenek egyéni billentyűparancsok",
"defaultBadge": "Alapértelmezett",
"customizedBadge": "Testreszabott",
"customize": "Testreszabás",
"resetToDefault": "Visszaállítás az alapértelmezettre",
"resetAllToDefaults": "Minden visszaállítása alapértelmezettre",
"close": "Közeli",
"cancel": "Mégsem",
"saveBinding": "Parancsikon mentése",
"saveError": "Nem sikerült menteni a billentyűparancsokat",
"pressKeysToRecord": "Billentyűkombináció",
"pressKeysPlaceholder": "Kattintson és nyomja meg a billentyűket",
"recording": "Nyomja meg a billentyűket...",
"comboRequiredError": "Először nyomjon meg egy billentyűkombinációt",
"textRequiredError": "Írd be a küldendő szöveget",
"controlCodeRequiredError": "Írjon be egyetlen betűt az ellenőrző kódhoz",
"snippetRequiredError": "Válasszon ki egy részletet",
"conflictWarning": "Ez a kombináció már a {{combo}}-hoz van kötve. Mentés esetén mindkettő aktiválódik.",
"actionLabel": "Akció",
"actionCopy": "Kijelölés másolása",
"actionPaste": "Paszta",
"actionSendControlCode": "Ctrl+betűjel küldése",
"actionSendText": "SMS küldése",
"actionRunSnippet": "Meglévő kódrészlet futtatása",
"controlCodeLabel": "Levél",
"textLabel": "Küldendő szöveg",
"appendEnterLabel": "Küldés után nyomja meg az Enter billentyűt",
"snippetLabel": "Töredék",
"selectSnippetPlaceholder": "Válasszon ki egy részletet",
"orphanedSnippetWarning": "részlet nem található",
"clipboardPermissionNote": "Egyes böngészőkben első használatkor vágólap-engedélyt kérhet."
},
"userProfile": {
"donateTitle": "Tartsd életben a Termixet",
"donateDescription": "A Termix ingyenes, saját tárhelyen üzemel, és csak néhány ember fejlesztette a szabadidejében. Ha időt vagy pénzt takarított meg vele, egy kriptovaluta-adomány segíthet a működésben.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Megjelenés",
"sectionSecurity": "Biztonság",
"sectionApiKeys": "API-kulcsok",
"sectionData": "Adat",
"sectionC2sTunnels": "C2S alagutak",
"usernameLabel": "Felhasználónév",
"roleLabel": "Szerep",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminál",
"commandAutocomplete": "Parancs automatikus kiegészítése",
"commandAutocompleteDesc": "Automatikus kiegészítés megjelenítése gépelés közben",
"keyboardShortcuts": "Billentyűparancsok",
"keyboardShortcutsDescription": "A terminál másolási, beillesztési és parancsikonjainak testreszabása",
"manageShortcuts": "Kezelés",
"terminalLinkBehavior": "Terminálkapcsolat kattintása",
"terminalLinkBehaviorDesc": "Alapértelmezett viselkedés hivatkozásokra kattintva a terminálban",
"historyTracking": "Előzménykövetés",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Tedd bele a kulcsodat",
"apiKeyUsageHintHeader": "fejléc.",
"apiKeyPermissionsHint": "A kulcsok öröklik a létrehozó felhasználó jogosultságait.",
"exportData": "Adatok exportálása",
"exportDataDesc": "Töltsd le a gazdagépeid, hitelesítő adataid és beállításaid biztonsági másolatát egy másik eszközre való átvitelhez.",
"export": "Export",
"exporting": "Exportálás...",
"importData": "Adatok importálása",
"importDataDesc": "Állítsa vissza a gazdagépeket, hitelesítő adatokat és beállításokat egy .sqlite biztonsági mentésfájlból",
"importDataSelected": "Kiválasztva: {{name}}",
"selectFile": "Fájl kiválasztása",
"changeFile": "Változás",
"import": "Importálás",
"importing": "Importálás...",
"exportSuccess": "Az adatok exportálása sikeresen megtörtént",
"exportFailed": "Adatexportálás sikertelen",
"importSelectFile": "Kérjük, először válasszon ki egy fájlt",
"importCompleted": "Importálás befejezve: {{total}} elem importálva, {{skipped}} kihagyva",
"importFailed": "Importálás sikertelen: {{error}}",
"roleUser": "Felhasználó",
"authMethodDual": "Kettős hitelesítés",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Paszta",
"editQuickKeys": "Gyorsbillentyűk szerkesztése",
"quickKeysTitle": "Gyorsbillentyűk",
"quickKeysDesc": "Koppintson a × jelre az eltávolításhoz. Legfeljebb 8 karaktert támogat.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Tidak ada server yang tersimpan.",
"removeServer": "Menghapus"
},
"migrationNotice": {
"title": "Termix Desktop sekarang berjalan secara mandiri.",
"body1": "Aplikasi ini sebelumnya terhubung langsung ke server Termix Anda. Aplikasi ini telah dirancang ulang agar dapat berjalan sepenuhnya secara mandiri, menyimpan host dan kredensial secara lokal sehingga tetap berfungsi saat offline. Sinkronisasi dua arah dengan server Termix yang dihosting sendiri sekarang bersifat opsional.",
"body2": "Host dan kredensial Anda yang ada masih berada di {{url}}. Aktifkan Sinkronisasi Jarak Jauh dan sambungkan kembali ke server tersebut untuk mengembalikannya dan menjaga agar keduanya tetap sinkron ke depannya.",
"dismiss": "Tidak sekarang",
"setUpSync": "Siapkan Sinkronisasi Jarak Jauh"
},
"remoteSync": {
"title": "Sinkronisasi Jarak Jauh",
"description": "Secara opsional, hubungkan aplikasi desktop ini ke server Termix yang dihosting sendiri untuk menyinkronkan host, kredensial, dan cuplikan Anda di berbagai perangkat. Aplikasi ini selalu berfungsi sepenuhnya secara offline, baik Anda terhubung atau tidak.",
"notConnected": "Tidak terhubung",
"connected": "Terhubung",
"connectedTo": "Terhubung ke {{url}}",
"lastSynced": "Sinkronisasi terakhir {{time}}",
"neverSynced": "Tidak pernah disinkronkan",
"syncError": "Kesalahan sinkronisasi: {{message}}",
"needsReauth": "Login telah kedaluwarsa.",
"connectButton": "Terhubung ke Server",
"disconnectButton": "Memutuskan",
"syncNowButton": "Sinkronkan Sekarang",
"syncing": "Sinkronisasi...",
"serverUrl": "URL Server",
"enterServerUrl": "Silakan masukkan URL server",
"mustIncludeProtocol": "URL server harus diawali dengan http:// atau https://",
"connectionTestFailed": "Tidak dapat terhubung ke server Termix di URL tersebut.",
"allowInvalidCertificate": "Izinkan sertifikat tidak valid",
"allowInvalidCertificateDesc": "Gunakan hanya untuk server mandiri tepercaya dengan sertifikat yang ditandatangani sendiri atau sertifikat alamat IP.",
"savedServers": "Server Tersimpan",
"removeServer": "Menghapus",
"continueButton": "Melanjutkan",
"cancelButton": "Membatalkan",
"signInTitle": "Masuk ke {{url}}",
"originTitle": "Asal Koneksi",
"originDescription": "Pilih dari mana koneksi SSH berasal secara default. Pengaturan ini dapat diubah per host.",
"originLocal": "Perangkat ini (jaringan lokal)",
"originRemote": "Server jarak jauh",
"bannerReconnect": "Terhubung kembali",
"bannerMessage": "Sinkronisasi jarak jauh memerlukan autentikasi ulang."
},
"versionCheck": {
"error": "Kesalahan Pemeriksaan Versi",
"checkFailed": "Gagal memeriksa pembaruan.",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Ganti Nama Pengguna Kredensial",
"overrideCredentialUsernameDesc": "Gunakan nama pengguna yang telah ditentukan di atas, bukan nama pengguna kredensial.",
"oidcUsernameHint": "Gunakan $oidc.preferred_username untuk mengganti nama login OIDC Anda.",
"tailscaleUsernameHint": "Ini harus berupa pengguna Unix yang identitas Tailscale Anda diberikan dalam ACL SSH tailnet, tidak harus root.",
"jumpHostChain": "Rantai Host Lompatan",
"portKnocking": "Ketukan Pelabuhan",
"addKnock": "Tambahkan Port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Penundaan Setelah (ms)",
"useSocks5Proxy": "Gunakan Proxy SOCKS5",
"useSocks5ProxyDesc": "Rute koneksi melalui server proxy.",
"connectionOrigin": "Asal Koneksi",
"connectionOriginDesc": "Lokasi asal koneksi SSH host ini. Menggantikan pengaturan default global aplikasi desktop.",
"connectionOriginDefault": "Gunakan pengaturan default",
"connectionOriginLocal": "Perangkat ini (jaringan lokal)",
"connectionOriginRemote": "Server jarak jauh",
"proxyHost": "Host Proksi",
"proxyPort": "Port Proksi",
"proxyUsername": "Nama Pengguna Proksi",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Biru Muda Terang",
"customThemeBrightWhite": "Putih Terang",
"customThemeResetTooltip": "Kembalikan ke pengaturan default",
"savedThemesLabel": "Tema Tersimpan",
"saveAsGlobalTheme": "Simpan sebagai Tema Global",
"saveGlobalThemeNamePrompt": "Masukkan nama untuk tema ini",
"saveGlobalThemeSuccess": "Tema tersimpan",
"saveGlobalThemeError": "Gagal menyimpan tema",
"applyGlobalThemeTooltip": "Terapkan tema ini",
"deleteGlobalThemeTooltip": "Hapus tema ini",
"noSavedThemes": "Belum ada tema yang tersimpan.",
"syntaxHighlightingLabel": "Penyorotan Sintaksis",
"syntaxHighlightingDesc": "Berikan warna pada output terminal (kesalahan, jalur, IP, stempel waktu)",
"syntaxHighlightingCategories": "Kategori Unggulan",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Secara otomatis meluncurkan atau terhubung ke sesi tmux.",
"enableSessionLogging": "Pencatatan Sesi",
"enableSessionLoggingDesc": "Rekam output sesi terminal untuk ditinjau nanti.",
"allowSessionSharing": "Izinkan Berbagi Sesi",
"allowSessionSharingDesc": "Izinkan sesi langsung di host ini dibagikan melalui tautan atau dengan pengguna lain.",
"enableCommandHistory": "Riwayat Perintah",
"enableCommandHistoryDesc": "Rekam perintah yang dijalankan di terminal ini untuk riwayat dan pelengkapan otomatis.",
"linkClickBehaviorLabel": "Perilaku Klik Tautan",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} diimpor",
"proxmoxResultUpdated": "{{count}} diperbarui",
"proxmoxResultFailed": "{{count}} gagal",
"proxmoxResultSkippedNoIp": "{{count}} dilewati (tidak ditemukan IP)",
"proxmoxImportComplete": "Impor Proxmox selesai: {{summary}}",
"proxmoxDiscoveryFailed": "Penemuan gagal",
"proxmoxImportFailed": "Impor gagal",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Gagal menyimpan kredensial",
"credentialNameRequired": "Silakan masukkan nama untuk kredensial tersebut.",
"credentialAuthRequired": "Tambahkan kata sandi, kunci SSH, atau keduanya.",
"createCredentialFromHostBtn": "Buat Kredensial",
"createCredentialFromHostTitle": "Buat Kredensial dari Host",
"createCredentialFromHostDesc": "Buat entri kredensial yang dapat digunakan kembali dan dibagikan, yang telah diisi sebelumnya dengan nama pengguna, kata sandi, dan/atau kunci SSH host ini saat ini.",
"backToHosts": "Kembali ke Host",
"backToCredentials": "Kembali ke Kredensial",
"pinned": "Disematkan",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Gunakan / untuk memisahkan level dan membuat folder bertingkat.",
"folderColor": "Warna",
"folderIcon": "Ikon",
"folderCredential": "Mandat",
"folderCredentialNone": "Tidak ada kredensial yang diberikan.",
"folderCredentialHint": "Host dalam folder ini yang menggunakan otentikasi \"Kredensial tersimpan\" tanpa kredensial mereka sendiri yang dipilih akan mewarisi kredensial ini.",
"folderPreview": "Pratinjau",
"folderNameFallback": "Folder tanpa judul",
"createFolderButton": "Buat folder",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Tag",
"shareHost": "Bagikan Host",
"shareHostTitle": "Bagikan: {{name}}",
"shareFolder": "Folder Bersama",
"shareFolderTitle": "Folder berbagi: {{name}}",
"folderSharedSuccessfully": "Host bersama {{count}} di dalam folder",
"failedToShareFolder": "Gagal berbagi folder",
"sharing": {
"loadError": "Gagal memuat data berbagi. Silakan coba lagi.",
"shareWithSection": "Bagikan dengan",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Bagikan ({{count}})",
"currentAccess": "Akses saat ini",
"noAccessEntries": "Host ini belum dibagikan",
"folderShareSummary": "Berbagi {{shared}} dari {{total}} host di folder ini",
"grantedBy": "Diberikan oleh",
"expires": "Berakhir",
"expired": "Kedaluwarsa",
@@ -1309,8 +1242,6 @@
"authMethod": "Metode Otentikasi",
"authTypeDirect": "Langsung",
"authTypeCredential": "Mandat",
"authTypeNone": "Tidak ada",
"authTypeNoneDesc": "Tidak ada kredensial yang disimpan. Anda akan diminta memasukkan nama pengguna dan kata sandi setiap kali Anda terhubung; informasi tersebut tidak disimpan.",
"selectCredential": "Pilih kredensial...",
"connectionSettings": "Pengaturan Koneksi",
"displaySettings": "Pengaturan Tampilan",
@@ -1328,7 +1259,6 @@
"rdpPort": "Port RDP",
"username": "Nama belakang",
"password": "Kata sandi",
"passwordSaved": "Kata sandi tersimpan, ketik untuk mengubahnya",
"domain": "Domain",
"securityMode": "Mode Keamanan",
"colorDepth": "Kedalaman Warna",
@@ -1494,60 +1424,7 @@
"expiresIn": "Berakhir dalam {{duration}}",
"search": "Cari koneksi...",
"noSearchResults": "Tidak ada koneksi yang sesuai dengan pencarian Anda.",
"rename": "Ganti nama sesi",
"sectionSharedWithMe": "Dibagikan kepada saya",
"sharedBy": "Dibagikan oleh {{username}}",
"join": "Bergabung",
"sharedSessionLabel": "{{hostName}} (dibagikan)"
},
"sessionSharing": {
"guestView": {
"loading": "Menghubungkan ke sesi bersama...",
"linkInvalid": "Tautan berbagi ini tidak valid, telah kedaluwarsa, atau telah dicabut.",
"rateLimited": "Terlalu banyak percobaan, silakan coba lagi sebentar lagi.",
"sessionEnded": "Sesi ini telah berakhir.",
"readOnlyBadge": "Hanya tampilan"
},
"modalTitle": "Sesi berbagi",
"shareButton": "Membagikan",
"notReadyToShare": "Sesi belum siap untuk dibagikan.",
"modeTab": {
"link": "Link",
"user": "Pengguna"
},
"linkModeDescription": "Siapa pun yang memiliki tautan ini dapat bergabung, tidak perlu membuat akun.",
"userModeDescription": "Bagikan dengan pengguna tertentu yang sudah memiliki akses ke host ini. Jika mereka belum memiliki akses, bagikan host tersebut kepada mereka terlebih dahulu atau gunakan tautan. Setelah dibagikan, sesi akan muncul di tab Koneksi mereka.",
"permissionLevel": {
"label": "Tingkat izin",
"readOnly": "Hanya baca",
"readOnlyDescription": "Bisa menonton sesi secara langsung tetapi tidak bisa mengetik atau berinteraksi.",
"readWrite": "Baca-tulis",
"readWriteDescription": "Dapat mengetik dan berinteraksi dengan sesi seperti halnya pemilik."
},
"expiryLabel": "Kedaluwarsa tautan",
"createLinkButton": "Buat tautan",
"createShareButton": "Bagikan dengan pengguna",
"searchUsersPlaceholder": "Cari pengguna...",
"noUsersFound": "Tidak ada pengguna yang ditemukan",
"linkCreated": "Tautan berbagi telah dibuat",
"linkCopied": "Tautan disalin ke papan klip",
"copyLink": "Salin tautan",
"shareCreated": "Sesi dibagikan. Sesi tersebut akan muncul di tab Koneksi mereka.",
"shareFailed": "Gagal membuat saham",
"userLacksHostAccess": "Pengguna tersebut belum memiliki akses ke host ini. Bagikan host tersebut kepada mereka terlebih dahulu, atau gunakan tautan sebagai gantinya.",
"activeShares": "Saham aktif",
"noActiveShares": "Tidak ada saham aktif untuk sesi ini.",
"revoke": "Menarik kembali",
"revokeConfirmTitle": "Batalkan pembagian ini?",
"revokeConfirmDescription": "Siapa pun yang menggunakan share ini akan langsung kehilangan akses.",
"revoked": "Saham dicabut",
"revokeFailed": "Gagal mencabut saham",
"joinCount": "{{count}} bergabung",
"joinCount_other": "{{count}} bergabung",
"expiresAt": "Berakhir {{date}}",
"linkShareBadge": "Link",
"userShareBadge": "Pengguna: {{username}}",
"loadSharesFailed": "Gagal memuat saham aktif."
"rename": "Ganti nama sesi"
},
"guacamole": {
"connecting": "Menghubungkan ke sesi {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Terhubung kembali",
"retry": "Mencoba kembali",
"guacdUnavailable": "Layanan desktop jarak jauh (guacd) tidak tersedia. Pastikan guacd berjalan, dapat diakses, dan dikonfigurasi dengan benar di pengaturan admin.",
"credentialPromptTitle": "Masukkan Kredensial RDP",
"credentialPromptDescription": "Host ini diatur untuk meminta kredensial saat terhubung. Kredensial tersebut hanya digunakan untuk sesi ini dan tidak disimpan.",
"connect": "Menghubungkan",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Sesi Terhubung Kembali",
"collapse": "Ciutkan bilah alat",
"expand": "Perluas bilah alat",
"dragHandle": "Seret untuk mengubah posisi",
"switchToTrackpad": "Beralih ke mode trackpad (seret untuk memindahkan kursor, ketuk untuk mengklik)",
"switchToTouch": "Beralih ke mode sentuh (ketuk langsung di tempat yang ingin Anda klik)"
"dragHandle": "Seret untuk mengubah posisi"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Diperlukan Otentikasi Dua Faktor",
"totpCodeLabel": "Kode Verifikasi",
"totpVerify": "Memeriksa",
"mfaPromptRequired": "Autentikasi Diperlukan",
"mfaPushRequired": "Otentikasi Push Diperlukan",
"mfaMenuPlaceholder": "Masukkan jawaban Anda",
"mfaWaitingApproval": "Menunggu persetujuan di perangkat Anda...",
"mfaSendRequest": "Kirim Permintaan",
"warpgateAuthRequired": "Autentikasi Warpgate Diperlukan",
"warpgateSecurityKey": "Kunci Keamanan",
"warpgateAuthUrl": "URL Otentikasi",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Pembatasan laju: Terlalu banyak upaya verifikasi. Silakan coba lagi nanti.",
"resetCodeRateLimitedWithTime": "Pembatasan laju: Terlalu banyak upaya verifikasi. Harap tunggu {{time}} detik sebelum mencoba lagi.",
"authTokenSaveFailed": "Gagal menyimpan token otentikasi.",
"failedToLoadServer": "Server gagal dimuat.",
"remoteServerRequired": "Diperlukan server jarak jauh. Hubungkan server jarak jauh di Pengaturan untuk menggunakan jenis koneksi ini."
"failedToLoadServer": "Server gagal dimuat."
},
"messages": {
"registrationDisabled": "Pendaftaran akun baru saat ini dinonaktifkan oleh administrator. Silakan masuk atau hubungi administrator.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Gagal menyimpan pengaturan SSL",
"sslRequiresDomain": "Nama domain dan email wajib diisi.",
"sslInfoNote": "Setelah menerbitkan sertifikat, aktifkan SSL di variabel lingkungan Anda (ENABLE_SSL=true) dan mulai ulang Termix.",
"sslManualOption": "Manual (unggah sertifikat)",
"sslManualCert": "Sertifikat (PEM)",
"sslManualCertPlaceholder": "-----MULAI SERTIFIKAT-----",
"sslManualKey": "Kunci Pribadi (PEM)",
"sslManualKeyPlaceholder": "-----MULAI KUNCI PRIVAT-----",
"sslManualDesc": "Tempelkan sertifikat dan kunci pribadi Anda yang sudah ada, termasuk rantai lengkap jika dipersyaratkan oleh CA Anda.",
"sslManualUpload": "Unggah & Instal Sertifikat",
"sslManualUploadLoading": "Mengunggah sertifikat...",
"sslManualUploadSuccess": "Sertifikat berhasil diunggah dan diinstal.",
"sslManualUploadFailed": "Unggahan sertifikat gagal.",
"sslManualRequiresFields": "Sertifikat dan kunci pribadi diperlukan.",
"auditLogTotal": "{{total}} total entri",
"auditLogEmpty": "Tidak ditemukan entri log audit.",
"auditLogSuccess": "Kesuksesan",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Riwayat Perintah",
"commandHistoryEnabledDesc": "Izinkan perekaman riwayat perintah. Jika dinonaktifkan, riwayat tidak akan disimpan terlepas dari pengaturan per host.",
"updateCommandHistoryFailed": "Gagal memperbarui pengaturan riwayat perintah.",
"analyticsEnabled": "Bagikan Statistik Penggunaan Anonim",
"analyticsEnabledDesc": "Mengirimkan data harian anonim mengenai jumlah pengguna, host, dan penggunaan fitur untuk membantu meningkatkan Termix. Tidak ada data pribadi atau detail koneksi yang disertakan.",
"updateAnalyticsFailed": "Gagal memperbarui pengaturan analitik.",
"sessionSharingGloballyEnabled": "Izinkan Berbagi Sesi",
"sessionSharingGloballyEnabledDesc": "Izinkan sesi terminal langsung, RDP, VNC, dan Telnet untuk dibagikan di seluruh instance. Menggantikan setiap pengaturan berbagi per host saat dinonaktifkan.",
"updateSessionSharingFailed": "Gagal memperbarui pengaturan berbagi sesi.",
"sessionTimeout": "Waktu sesi habis",
"hours": "jam",
"sessionTimeoutRange": "Minimal 1 jam · Maksimal 720 jam",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Kesuksesan",
"executionFailed": "Gagal"
},
"keybindings": {
"title": "Pintasan Keyboard",
"description": "Sesuaikan pintasan salin, tempel, dan kontrol terminal, atau tetapkan tombol untuk mengirim teks atau menjalankan cuplikan kode.",
"defaultsHeading": "Pintasan bawaan",
"customHeading": "Pintasan Kustom",
"addBinding": "Tambahkan Pintasan",
"addBindingTitle": "Tambahkan Pintasan",
"editBindingTitle": "Edit Pintasan",
"loading": "Memuat...",
"noCustomBindings": "Belum ada pintasan khusus.",
"defaultBadge": "Bawaan",
"customizedBadge": "Disesuaikan",
"customize": "Sesuaikan",
"resetToDefault": "Atur ulang ke pengaturan default",
"resetAllToDefaults": "Atur ulang semuanya ke pengaturan default.",
"close": "Menutup",
"cancel": "Membatalkan",
"saveBinding": "Simpan Pintasan",
"saveError": "Gagal menyimpan pintasan keyboard",
"pressKeysToRecord": "Kombinasi tombol",
"pressKeysPlaceholder": "Klik dan tekan tombol",
"recording": "Tekan tombol...",
"comboRequiredError": "Tekan kombinasi tombol terlebih dahulu.",
"textRequiredError": "Masukkan teks yang ingin dikirim",
"controlCodeRequiredError": "Masukkan satu huruf untuk kode kontrol.",
"snippetRequiredError": "Pilih cuplikan",
"conflictWarning": "Kombinasi ini sudah terikat ke {{combo}}. Menyimpan akan membuat keduanya menyala.",
"actionLabel": "Tindakan",
"actionCopy": "Salin pilihan",
"actionPaste": "Pasta",
"actionSendControlCode": "Kirim sinyal Ctrl+huruf",
"actionSendText": "Kirim pesan teks",
"actionRunSnippet": "Jalankan cuplikan kode yang sudah ada.",
"controlCodeLabel": "Surat",
"textLabel": "Teks untuk dikirim",
"appendEnterLabel": "Tekan Enter setelah mengirim",
"snippetLabel": "Potongan",
"selectSnippetPlaceholder": "Pilih cuplikan",
"orphanedSnippetWarning": "cuplikan tidak ditemukan",
"clipboardPermissionNote": "Pada beberapa browser, mungkin akan meminta izin akses clipboard saat pertama kali digunakan."
},
"userProfile": {
"donateTitle": "Jaga agar Termix tetap hidup.",
"donateDescription": "Termix gratis, dihosting sendiri, dan dibangun hanya oleh beberapa orang di waktu luang mereka. Jika Termix telah menghemat waktu atau uang Anda, donasi kripto akan membantu kelangsungannya.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Penampilan",
"sectionSecurity": "Keamanan",
"sectionApiKeys": "Kunci API",
"sectionData": "Data",
"sectionC2sTunnels": "Terowongan C2S",
"usernameLabel": "Nama belakang",
"roleLabel": "Peran",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Pelengkapan Otomatis Perintah",
"commandAutocompleteDesc": "Tampilkan fitur pelengkapan otomatis saat mengetik.",
"keyboardShortcuts": "Pintasan Keyboard",
"keyboardShortcutsDescription": "Sesuaikan pintasan salin, tempel, dan perintah terminal.",
"manageShortcuts": "Mengelola",
"terminalLinkBehavior": "Klik Tautan Terminal",
"terminalLinkBehaviorDesc": "Perilaku default saat mengklik tautan di terminal.",
"historyTracking": "Pelacakan Riwayat",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Sertakan kunci Anda di dalam",
"apiKeyUsageHintHeader": "Judul.",
"apiKeyPermissionsHint": "Kunci mewarisi izin dari pengguna yang membuatnya.",
"exportData": "Ekspor Data Saya",
"exportDataDesc": "Unduh cadangan data host, kredensial, dan pengaturan Anda untuk dipindahkan ke perangkat lain.",
"export": "Ekspor",
"exporting": "Mengekspor...",
"importData": "Impor Data Saya",
"importDataDesc": "Pulihkan host, kredensial, dan pengaturan Anda dari file cadangan .sqlite.",
"importDataSelected": "Dipilih: {{name}}",
"selectFile": "Pilih File",
"changeFile": "Mengubah",
"import": "Impor",
"importing": "Pengimporan...",
"exportSuccess": "Data berhasil diekspor.",
"exportFailed": "Ekspor data gagal",
"importSelectFile": "Silakan pilih file terlebih dahulu.",
"importCompleted": "Impor selesai: {{total}} item diimpor, {{skipped}} dilewati",
"importFailed": "Impor gagal: {{error}}",
"roleUser": "Pengguna",
"authMethodDual": "Otorisasi Ganda",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Pasta",
"editQuickKeys": "Edit tombol pintasan",
"quickKeysTitle": "Tombol Pintas",
"quickKeysDesc": "Ketuk × untuk menghapus. Mendukung hingga 8 karakter.",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Geen opgeslagen servers",
"removeServer": "Verwijderen"
},
"migrationNotice": {
"title": "Termix Desktop draait nu zelfstandig.",
"body1": "Deze app maakte voorheen rechtstreeks verbinding met uw Termix-server. Hij is herwerkt om volledig zelfstandig te werken, waarbij hosts en inloggegevens lokaal worden opgeslagen, zodat hij ook offline blijft functioneren. Tweewegsynchronisatie met een zelfgehoste Termix-server is nu optioneel.",
"body2": "Uw bestaande hosts en inloggegevens bevinden zich nog steeds op {{url}}. Schakel externe synchronisatie in en maak opnieuw verbinding met die server om ze te herstellen en beide in de toekomst gesynchroniseerd te houden.",
"dismiss": "Nu niet",
"setUpSync": "Externe synchronisatie instellen"
},
"remoteSync": {
"title": "Synchronisatie op afstand",
"description": "U kunt deze desktop-app optioneel verbinden met een zelfgehoste Termix-server om uw hosts, inloggegevens en codefragmenten op verschillende apparaten te synchroniseren. De app werkt altijd volledig offline, ongeacht of u verbinding maakt of niet.",
"notConnected": "Niet verbonden",
"connected": "Aangesloten",
"connectedTo": "Verbonden met {{url}}",
"lastSynced": "Laatst gesynchroniseerd {{time}}",
"neverSynced": "Nooit gesynchroniseerd",
"syncError": "Synchronisatiefout: {{message}}",
"needsReauth": "Aanmelden verlopen",
"connectButton": "Verbinding maken met de server",
"disconnectButton": "Verbinding verbreken",
"syncNowButton": "Nu synchroniseren",
"syncing": "Synchroniseren...",
"serverUrl": "Server-URL",
"enterServerUrl": "Voer een server-URL in.",
"mustIncludeProtocol": "De server-URL moet beginnen met http:// of https://",
"connectionTestFailed": "Er kon geen Termix-server worden bereikt op die URL.",
"allowInvalidCertificate": "Ongeldig certificaat toestaan",
"allowInvalidCertificateDesc": "Uitsluitend te gebruiken voor vertrouwde, zelfgehoste servers met zelfondertekende certificaten of IP-adrescertificaten.",
"savedServers": "Opgeslagen servers",
"removeServer": "Verwijderen",
"continueButton": "Doorgaan",
"cancelButton": "Annuleren",
"signInTitle": "Meld je aan bij {{url}}",
"originTitle": "Verbindingsoorsprong",
"originDescription": "Kies de standaardlocatie voor SSH-verbindingen. Deze instelling kan per host worden gewijzigd.",
"originLocal": "Dit apparaat (lokaal netwerk)",
"originRemote": "Externe server",
"bannerReconnect": "Opnieuw verbinding maken",
"bannerMessage": "Externe synchronisatie vereist herverificatie."
},
"versionCheck": {
"error": "Versiecontrolefout",
"checkFailed": "Controleren op updates is mislukt.",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Gebruikersnaam voor het overschrijven van inloggegevens",
"overrideCredentialUsernameDesc": "Gebruik de hierboven opgegeven gebruikersnaam in plaats van de gebruikersnaam van de inloggegevens.",
"oidcUsernameHint": "Gebruik $oidc.preferred_username om uw OIDC-inlognaam te vervangen.",
"tailscaleUsernameHint": "Dit moet een Unix-gebruiker zijn die aan uw Tailscale-identiteit is toegekend in de SSH-toegangsregel van het tailnet, niet per se root.",
"jumpHostChain": "Spring naar de hostketen",
"portKnocking": "Havenkloppen",
"addKnock": "Poort toevoegen",
@@ -690,11 +650,6 @@
"delayAfterMs": "Vertraging na (ms)",
"useSocks5Proxy": "Gebruik een SOCKS5-proxy",
"useSocks5ProxyDesc": "De verbinding wordt via een proxyserver geleid.",
"connectionOrigin": "Verbindingsoorsprong",
"connectionOriginDesc": "Waar de SSH-verbinding van deze host vandaan komt. Overschrijft de algemene standaardinstelling van de desktop-app.",
"connectionOriginDefault": "Gebruik de standaardinstelling",
"connectionOriginLocal": "Dit apparaat (lokaal netwerk)",
"connectionOriginRemote": "Externe server",
"proxyHost": "Proxy-host",
"proxyPort": "Proxy-poort",
"proxyUsername": "Proxy-gebruikersnaam",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Helder cyaan",
"customThemeBrightWhite": "Helder wit",
"customThemeResetTooltip": "Terugzetten naar standaardinstellingen",
"savedThemesLabel": "Opgeslagen thema's",
"saveAsGlobalTheme": "Opslaan als globaal thema",
"saveGlobalThemeNamePrompt": "Geef dit thema een naam.",
"saveGlobalThemeSuccess": "Thema opgeslagen",
"saveGlobalThemeError": "Het opslaan van het thema is mislukt.",
"applyGlobalThemeTooltip": "Pas dit thema toe",
"deleteGlobalThemeTooltip": "Verwijder dit thema",
"noSavedThemes": "Nog geen opgeslagen thema's",
"syntaxHighlightingLabel": "Syntaxmarkering",
"syntaxHighlightingDesc": "Kleur de terminaluitvoer (fouten, paden, IP-adressen, tijdstempels) in.",
"syntaxHighlightingCategories": "Markeer categorieën",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Een tmux-sessie automatisch starten of eraan koppelen",
"enableSessionLogging": "Sessieregistratie",
"enableSessionLoggingDesc": "Sla de uitvoer van de terminalsessie op voor latere beoordeling.",
"allowSessionSharing": "Sessiedeling toestaan",
"allowSessionSharingDesc": "Sta toe dat live sessies op deze host via een link of met andere gebruikers worden gedeeld.",
"enableCommandHistory": "Commandogeschiedenis",
"enableCommandHistoryDesc": "Sla de commando's die in deze terminal worden uitgevoerd op voor de geschiedenis en automatisch aanvullen.",
"linkClickBehaviorLabel": "Klikgedrag op links",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} geïmporteerd",
"proxmoxResultUpdated": "{{count}} bijgewerkt",
"proxmoxResultFailed": "{{count}} mislukt",
"proxmoxResultSkippedNoIp": "{{count}} overgeslagen (geen IP-adres gevonden)",
"proxmoxImportComplete": "Proxmox-import voltooid: {{summary}}",
"proxmoxDiscoveryFailed": "Ontdekking mislukt",
"proxmoxImportFailed": "Importeren mislukt",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Het opslaan van de inloggegevens is mislukt.",
"credentialNameRequired": "Voer een naam in voor de legitimatie.",
"credentialAuthRequired": "Voeg een wachtwoord, een SSH-sleutel of beide toe.",
"createCredentialFromHostBtn": "Een inloggegeven aanmaken",
"createCredentialFromHostTitle": "Maak een inloggegeven aan vanuit de host.",
"createCredentialFromHostDesc": "Maak een herbruikbare, deelbare inloggegevensvermelding aan die vooraf is ingevuld met de huidige gebruikersnaam, het wachtwoord en/of de SSH-sleutel van deze host.",
"backToHosts": "Terug naar hosts",
"backToCredentials": "Terug naar inloggegevens",
"pinned": "Vastgepind",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Gebruik / om niveaus te scheiden en geneste mappen te maken.",
"folderColor": "Kleur",
"folderIcon": "Icon",
"folderCredential": "Referentie",
"folderCredentialNone": "Geen inloggegevens toegewezen",
"folderCredentialHint": "Hosts in deze map die gebruikmaken van authenticatie met \"Opgeslagen referenties\" zonder hun eigen referenties te hebben geselecteerd, zullen deze referenties overnemen.",
"folderPreview": "Voorbeeld",
"folderNameFallback": "Naamloze map",
"createFolderButton": "Map aanmaken",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Tags",
"shareHost": "Deel host",
"shareHostTitle": "Delen: {{name}}",
"shareFolder": "Map delen",
"shareFolderTitle": "Map delen: {{name}}",
"folderSharedSuccessfully": "Gedeelde {{count}} host(s) in map",
"failedToShareFolder": "Map delen mislukt",
"sharing": {
"loadError": "Het laden van de deelgegevens is mislukt. Probeer het opnieuw.",
"shareWithSection": "Delen met",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Delen ({{count}})",
"currentAccess": "Huidige toegang",
"noAccessEntries": "Deze host is nog niet gedeeld.",
"folderShareSummary": "Gedeelde {{shared}} van {{total}} host(s) in deze map",
"grantedBy": "Toegekend door",
"expires": "Verloopt",
"expired": "Verlopen",
@@ -1309,8 +1242,6 @@
"authMethod": "Authenticatiemethode",
"authTypeDirect": "Direct",
"authTypeCredential": "Referentie",
"authTypeNone": "Geen",
"authTypeNoneDesc": "Er worden geen inloggegevens opgeslagen. U wordt bij elke verbinding gevraagd om een gebruikersnaam en wachtwoord; deze worden niet bewaard.",
"selectCredential": "Selecteer een certificaat...",
"connectionSettings": "Verbindingsinstellingen",
"displaySettings": "Weergave-instellingen",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-poort",
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"passwordSaved": "Wachtwoord opgeslagen, typ om te wijzigen",
"domain": "Domein",
"securityMode": "Beveiligingsmodus",
"colorDepth": "Kleurdiepte",
@@ -1494,60 +1424,7 @@
"expiresIn": "Verloopt over {{duration}}",
"search": "Zoek naar verbanden...",
"noSearchResults": "Er zijn geen resultaten gevonden die overeenkomen met uw zoekopdracht.",
"rename": "Sessie hernoemen",
"sectionSharedWithMe": "Met mij gedeeld",
"sharedBy": "Gedeeld door {{username}}",
"join": "Meedoen",
"sharedSessionLabel": "{{hostName}} (gedeeld)"
},
"sessionSharing": {
"guestView": {
"loading": "Verbinding maken met de gedeelde sessie...",
"linkInvalid": "Deze deelbare link is ongeldig, verlopen of ingetrokken.",
"rateLimited": "Te veel pogingen, probeer het over een tijdje opnieuw.",
"sessionEnded": "Deze sessie is afgelopen.",
"readOnlyBadge": "Alleen bekijken"
},
"modalTitle": "Deel sessie",
"shareButton": "Deel",
"notReadyToShare": "De sessie is nog niet klaar om te delen.",
"modeTab": {
"link": "Link",
"user": "Gebruiker"
},
"linkModeDescription": "Iedereen met deze link kan deelnemen, een account is niet nodig.",
"userModeDescription": "Deel de host met een specifieke gebruiker die er al toegang toe heeft. Als die gebruiker nog geen toegang heeft, deel de host dan eerst met hem of haar, of gebruik een link. Zodra de sessie is gedeeld, verschijnt deze in het tabblad 'Verbindingen' van de gebruiker.",
"permissionLevel": {
"label": "Toegangsniveau",
"readOnly": "Alleen-lezen",
"readOnlyDescription": "Je kunt de sessie live volgen, maar je kunt niet typen of interageren.",
"readWrite": "Lezen en schrijven",
"readWriteDescription": "Kan typen en interactie hebben met de sessie, net als de eigenaar."
},
"expiryLabel": "Link verloopt",
"createLinkButton": "Link maken",
"createShareButton": "Delen met gebruiker",
"searchUsersPlaceholder": "Gebruikers doorzoeken...",
"noUsersFound": "Geen gebruikers gevonden",
"linkCreated": "Deellink aangemaakt",
"linkCopied": "Link gekopieerd naar het klembord.",
"copyLink": "Kopieer link",
"shareCreated": "De sessie is gedeeld. Deze verschijnt in hun tabblad 'Verbindingen'.",
"shareFailed": "Aanmaken van de share is mislukt",
"userLacksHostAccess": "Die gebruiker heeft nog geen toegang tot deze host. Deel de host eerst met hem of haar, of gebruik een link.",
"activeShares": "Actieve aandelen",
"noActiveShares": "Geen actieve aandelen voor deze sessie.",
"revoke": "Herroepen",
"revokeConfirmTitle": "Wilt u dit aandeel intrekken?",
"revokeConfirmDescription": "Iedereen die deze gedeelde map gebruikt, verliest onmiddellijk de toegang.",
"revoked": "Aandeel ingetrokken",
"revokeFailed": "Het intrekken van het aandeel is mislukt.",
"joinCount": "{{count}} join",
"joinCount_other": "{{count}} sluit zich aan",
"expiresAt": "Verloopt {{date}}",
"linkShareBadge": "Link",
"userShareBadge": "Gebruiker: {{username}}",
"loadSharesFailed": "Het laden van actieve aandelen is mislukt."
"rename": "Sessie hernoemen"
},
"guacamole": {
"connecting": "Verbinding maken met de {{type}} sessie...",
@@ -1559,9 +1436,6 @@
"reconnect": "Opnieuw verbinding maken",
"retry": "Opnieuw proberen",
"guacdUnavailable": "De service voor extern bureaublad (guacd) is niet beschikbaar. Zorg ervoor dat guacd actief en toegankelijk is en correct is geconfigureerd in de beheerdersinstellingen.",
"credentialPromptTitle": "Voer de RDP-inloggegevens in.",
"credentialPromptDescription": "Deze host is zo ingesteld dat er bij het verbinden om inloggegevens wordt gevraagd. Deze worden alleen voor deze sessie gebruikt en niet opgeslagen.",
"connect": "Verbinden",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Herverbindingssessie",
"collapse": "Werkbalk inklappen",
"expand": "Werkbalk uitklappen",
"dragHandle": "Sleep om te verplaatsen",
"switchToTrackpad": "Schakel over naar de trackpadmodus (sleep om de cursor te verplaatsen, tik om te klikken).",
"switchToTouch": "Schakel over naar de aanraakmodus (tik direct op de plek waar u wilt klikken)."
"dragHandle": "Sleep om te verplaatsen"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Tweefactorauthenticatie vereist",
"totpCodeLabel": "Verificatiecode",
"totpVerify": "Verifiëren",
"mfaPromptRequired": "Authenticatie vereist",
"mfaPushRequired": "Push-authenticatie vereist",
"mfaMenuPlaceholder": "Voer uw antwoord in",
"mfaWaitingApproval": "Wachten op goedkeuring op uw apparaat...",
"mfaSendRequest": "Verzoek verzenden",
"warpgateAuthRequired": "Authenticatie via Warpgate vereist",
"warpgateSecurityKey": "Beveiligingssleutel",
"warpgateAuthUrl": "Authenticatie-URL",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Snelheid beperkt: Te veel verificatiepogingen. Probeer het later opnieuw.",
"resetCodeRateLimitedWithTime": "Snelheid beperkt: Te veel verificatiepogingen. Wacht {{time}} seconden voordat u het opnieuw probeert.",
"authTokenSaveFailed": "Het opslaan van het authenticatietoken is mislukt.",
"failedToLoadServer": "Server laden mislukt",
"remoteServerRequired": "Externe server vereist. Verbind een externe server in de instellingen om dit verbindingstype te gebruiken."
"failedToLoadServer": "Server laden mislukt"
},
"messages": {
"registrationDisabled": "Het registreren van nieuwe accounts is momenteel uitgeschakeld door een beheerder. Log in of neem contact op met een beheerder.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Het opslaan van de SSL-instellingen is mislukt.",
"sslRequiresDomain": "Domein en e-mailadres zijn verplicht.",
"sslInfoNote": "Nadat u een certificaat hebt uitgegeven, schakelt u SSL in uw omgevingsvariabelen in (ENABLE_SSL=true) en herstart u Termix.",
"sslManualOption": "Handmatig (certificaat uploaden)",
"sslManualCert": "Certificaat (PEM)",
"sslManualCertPlaceholder": "-----BEGIN CERTIFICAAT-----",
"sslManualKey": "Privésleutel (PEM)",
"sslManualKeyPlaceholder": "-----BEGIN PRIVÉSLEUTEL-----",
"sslManualDesc": "Plak hier uw bestaande certificaat en privésleutel, inclusief de volledige certificaatketen indien vereist door uw certificeringsinstantie.",
"sslManualUpload": "Certificaat uploaden en installeren",
"sslManualUploadLoading": "Certificaat uploaden...",
"sslManualUploadSuccess": "Certificaat succesvol geüpload en geïnstalleerd",
"sslManualUploadFailed": "Het uploaden van het certificaat is mislukt.",
"sslManualRequiresFields": "Certificaat en privésleutel vereist.",
"auditLogTotal": "{{total}} totaal aantal vermeldingen",
"auditLogEmpty": "Geen auditlogboekvermeldingen gevonden",
"auditLogSuccess": "Succes",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Commandogeschiedenis",
"commandHistoryEnabledDesc": "Sta het vastleggen van de opdrachtgeschiedenis toe. Indien uitgeschakeld, wordt de geschiedenis niet opgeslagen, ongeacht de instellingen per host.",
"updateCommandHistoryFailed": "Het bijwerken van de instelling voor de opdrachtgeschiedenis is mislukt.",
"analyticsEnabled": "Deel anonieme gebruiksstatistieken",
"analyticsEnabledDesc": "Verstuurt dagelijks anoniem een telling van gebruikers, hosts en functiegebruik om Termix te verbeteren. Er worden nooit persoonlijke gegevens of verbindingsdetails meegestuurd.",
"updateAnalyticsFailed": "Het bijwerken van de analyse-instellingen is mislukt.",
"sessionSharingGloballyEnabled": "Sessiedeling toestaan",
"sessionSharingGloballyEnabledDesc": "Sta toe dat live terminal-, RDP-, VNC- en Telnet-sessies instantiebreed worden gedeeld. Deze functie overschrijft elke instelling voor delen per host wanneer deze is uitgeschakeld.",
"updateSessionSharingFailed": "Het bijwerken van de instelling voor het delen van sessies is mislukt.",
"sessionTimeout": "Sessietime-out",
"hours": "uren",
"sessionTimeoutRange": "Minimaal 1 uur · Maximaal 720 uur",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Succes",
"executionFailed": "Mislukt"
},
"keybindings": {
"title": "Toetsenbord sneltoetsen",
"description": "Pas de sneltoetsen voor kopiëren, plakken en besturing in de terminal aan, of koppel toetsen aan het verzenden van tekst of het uitvoeren van een codefragment.",
"defaultsHeading": "Ingebouwde sneltoetsen",
"customHeading": "Aangepaste sneltoetsen",
"addBinding": "Snelkoppeling toevoegen",
"addBindingTitle": "Snelkoppeling toevoegen",
"editBindingTitle": "Snelkoppeling bewerken",
"loading": "Laden...",
"noCustomBindings": "Er zijn nog geen aangepaste sneltoetsen beschikbaar.",
"defaultBadge": "Standaard",
"customizedBadge": "Op maat gemaakt",
"customize": "Aanpassen",
"resetToDefault": "Terugzetten naar standaardinstellingen",
"resetAllToDefaults": "Alles terugzetten naar de standaardinstellingen",
"close": "Dichtbij",
"cancel": "Annuleren",
"saveBinding": "Snelkoppeling opslaan",
"saveError": "Het opslaan van de sneltoetsen is mislukt.",
"pressKeysToRecord": "Toetsencombinatie",
"pressKeysPlaceholder": "Klik en druk op de toetsen",
"recording": "Druk op de toetsen...",
"comboRequiredError": "Druk eerst op een toetsencombinatie.",
"textRequiredError": "Voer de tekst in die u wilt verzenden",
"controlCodeRequiredError": "Voer één letter in als besturingscode.",
"snippetRequiredError": "Selecteer een fragment",
"conflictWarning": "Deze combinatie is al gekoppeld aan {{combo}}. Door op te slaan zullen beide vuren.",
"actionLabel": "Actie",
"actionCopy": "Selectie kopiëren",
"actionPaste": "Plakken",
"actionSendControlCode": "Verzend het Ctrl+letter-signaal",
"actionSendText": "Sms verzenden",
"actionRunSnippet": "Voer het bestaande codefragment uit.",
"controlCodeLabel": "Brief",
"textLabel": "Tekst om te verzenden",
"appendEnterLabel": "Druk op Enter na het verzenden.",
"snippetLabel": "Fragment",
"selectSnippetPlaceholder": "Selecteer een fragment",
"orphanedSnippetWarning": "fragment niet gevonden",
"clipboardPermissionNote": "In sommige browsers kan bij het eerste gebruik om toestemming voor het gebruik van het klembord worden gevraagd."
},
"userProfile": {
"donateTitle": "Houd Termix in leven",
"donateDescription": "Termix is gratis, zelf te hosten en is ontwikkeld door slechts een paar mensen in hun vrije tijd. Als het je tijd of geld heeft bespaard, helpt een cryptodonatie om het project draaiende te houden.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Verschijning",
"sectionSecurity": "Beveiliging",
"sectionApiKeys": "API-sleutels",
"sectionData": "Gegevens",
"sectionC2sTunnels": "C2S-tunnels",
"usernameLabel": "Gebruikersnaam",
"roleLabel": "Rol",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Opdracht automatisch aanvullen",
"commandAutocompleteDesc": "Toon automatische aanvulling tijdens het typen",
"keyboardShortcuts": "Toetsenbord sneltoetsen",
"keyboardShortcutsDescription": "Pas de sneltoetsen voor kopiëren, plakken en opdrachten in de terminal aan.",
"manageShortcuts": "Beheren",
"terminalLinkBehavior": "Terminallink Klik",
"terminalLinkBehaviorDesc": "Standaardgedrag bij het klikken op links in de terminal",
"historyTracking": "Geschiedenis bijhouden",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Voeg uw sleutel toe aan de",
"apiKeyUsageHintHeader": "koptekst.",
"apiKeyPermissionsHint": "Sleutels erven de machtigingen van de gebruiker die ze heeft aangemaakt.",
"exportData": "Mijn gegevens exporteren",
"exportDataDesc": "Download een back-up van uw hosts, inloggegevens en instellingen om deze over te zetten naar een ander apparaat.",
"export": "Exporteren",
"exporting": "Exporteren...",
"importData": "Mijn gegevens importeren",
"importDataDesc": "Herstel uw hosts, inloggegevens en instellingen vanuit een .sqlite-back-upbestand.",
"importDataSelected": "Geselecteerd: {{name}}",
"selectFile": "Selecteer bestand",
"changeFile": "Wijziging",
"import": "Importeren",
"importing": "Importeren...",
"exportSuccess": "Gegevens succesvol geëxporteerd",
"exportFailed": "Gegevensexport mislukt",
"importSelectFile": "Selecteer eerst een bestand.",
"importCompleted": "Import voltooid: {{total}} items geïmporteerd, {{skipped}} overgeslagen",
"importFailed": "Import mislukt: {{error}}",
"roleUser": "Gebruiker",
"authMethodDual": "Dubbele authenticatie",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Plakken",
"editQuickKeys": "Sneltoetsen bewerken",
"quickKeysTitle": "Sneltoetsen",
"quickKeysDesc": "Tik op × om te verwijderen. Ondersteunt maximaal 8 tekens.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Ingen lagrede servere",
"removeServer": "Fjerne"
},
"migrationNotice": {
"title": "Termix Desktop kjører nå frittstående",
"body1": "Denne appen pleide å koble seg direkte til Termix-serveren din. Den har blitt omarbeidet til å kjøre helt alene, og lagrer verter og påloggingsinformasjon lokalt, slik at den fortsetter å fungere offline. Toveis synkronisering med en selvhostet Termix-server er nå valgfritt.",
"body2": "Dine eksisterende verter og påloggingsinformasjon er fortsatt på {{url}}. Slå på ekstern synkronisering og koble til den serveren på nytt for å få dem tilbake og holde begge synkroniserte fremover.",
"dismiss": "Ikke nå",
"setUpSync": "Konfigurer ekstern synkronisering"
},
"remoteSync": {
"title": "Fjernsynkronisering",
"description": "Du kan eventuelt koble denne skrivebordsappen til en selvhostet Termix-server for å synkronisere verter, påloggingsinformasjon og kodebiter på tvers av enheter. Appen fungerer alltid helt offline, enten du kobler til eller ikke.",
"notConnected": "Ikke tilkoblet",
"connected": "Tilkoblet",
"connectedTo": "Koblet til {{url}}",
"lastSynced": "Sist synkronisert {{time}}",
"neverSynced": "Aldri synkronisert",
"syncError": "Synkroniseringsfeil: {{message}}",
"needsReauth": "Påloggingen er utløpt",
"connectButton": "Koble til server",
"disconnectButton": "Frakople",
"syncNowButton": "Synkroniser nå",
"syncing": "Synkroniserer...",
"serverUrl": "Server-URL",
"enterServerUrl": "Vennligst skriv inn en server-URL",
"mustIncludeProtocol": "Server-URL-en må starte med http:// eller https://",
"connectionTestFailed": "Kunne ikke nå en Termix-server på den URL-adressen",
"allowInvalidCertificate": "Tillat ugyldig sertifikat",
"allowInvalidCertificateDesc": "Bruk kun for klarerte, selvhostede servere med selvsignerte sertifikater eller IP-adressesertifikater.",
"savedServers": "Lagrede servere",
"removeServer": "Fjerne",
"continueButton": "Fortsette",
"cancelButton": "Kansellere",
"signInTitle": "Logg inn på {{url}}",
"originTitle": "Tilkoblingens opprinnelse",
"originDescription": "Velg hvor SSH-tilkoblinger kommer fra som standard. Dette kan overstyres per vert.",
"originLocal": "Denne enheten (lokalt nettverk)",
"originRemote": "Ekstern server",
"bannerReconnect": "Koble til igjen",
"bannerMessage": "Ekstern synkronisering trenger ny autentisering"
},
"versionCheck": {
"error": "Versjonssjekkfeil",
"checkFailed": "Kunne ikke sjekke etter oppdateringer",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Overstyr brukernavn for legitimasjon",
"overrideCredentialUsernameDesc": "Bruk brukernavnet som er spesifisert ovenfor i stedet for brukernavnet til legitimasjonen",
"oidcUsernameHint": "Bruk $oidc.preferred_username for å erstatte OIDC-påloggingsnavnet ditt.",
"tailscaleUsernameHint": "Dette må være en Unix-bruker som Tailscale-identiteten din er gitt i tailnets SSH ACL, ikke nødvendigvis root.",
"jumpHostChain": "Hoppvertkjede",
"portKnocking": "Port Knocking",
"addKnock": "Legg til port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Forsinkelse etter (ms)",
"useSocks5Proxy": "Bruk SOCKS5-proxyen",
"useSocks5ProxyDesc": "Rute tilkoblingen via en proxy-server",
"connectionOrigin": "Tilkoblingens opprinnelse",
"connectionOriginDesc": "Hvor denne vertens SSH-tilkobling kommer fra. Overstyrer skrivebordsappens globale standard.",
"connectionOriginDefault": "Bruk standard",
"connectionOriginLocal": "Denne enheten (lokalt nettverk)",
"connectionOriginRemote": "Ekstern server",
"proxyHost": "Proxy-vert",
"proxyPort": "Proxy-port",
"proxyUsername": "Proxy-brukernavn",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Lys cyan",
"customThemeBrightWhite": "Klar hvit",
"customThemeResetTooltip": "Tilbakestill til standardinnstillinger",
"savedThemesLabel": "Lagrede temaer",
"saveAsGlobalTheme": "Lagre som globalt tema",
"saveGlobalThemeNamePrompt": "Skriv inn et navn for dette temaet",
"saveGlobalThemeSuccess": "Tema lagret",
"saveGlobalThemeError": "Kunne ikke lagre temaet",
"applyGlobalThemeTooltip": "Bruk dette temaet",
"deleteGlobalThemeTooltip": "Slett dette temaet",
"noSavedThemes": "Ingen lagrede temaer ennå",
"syntaxHighlightingLabel": "Syntaksutheving",
"syntaxHighlightingDesc": "Fargelegg terminalutdata (feil, stier, IP-adresser, tidsstempler)",
"syntaxHighlightingCategories": "Fremhev kategorier",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Start eller legg til tmux-økten automatisk",
"enableSessionLogging": "Øktlogging",
"enableSessionLoggingDesc": "Ta opp utdata fra terminaløkten for senere gjennomgang",
"allowSessionSharing": "Tillat øktdeling",
"allowSessionSharingDesc": "La direktesendte økter på denne verten deles via lenke eller med andre brukere",
"enableCommandHistory": "Kommandohistorikk",
"enableCommandHistoryDesc": "Record-kommandoer kjøres i denne terminalen for historikk og autofullføring",
"linkClickBehaviorLabel": "Klikkatferd på lenker",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importert",
"proxmoxResultUpdated": "{{count}} oppdatert",
"proxmoxResultFailed": "{{count}} mislyktes",
"proxmoxResultSkippedNoIp": "{{count}} hoppet over (ingen IP-adresse funnet)",
"proxmoxImportComplete": "Proxmox-import fullført: {{summary}}",
"proxmoxDiscoveryFailed": "Oppdagelsen mislyktes",
"proxmoxImportFailed": "Importen mislyktes",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Kunne ikke lagre legitimasjonen",
"credentialNameRequired": "Vennligst skriv inn et navn for legitimasjonen",
"credentialAuthRequired": "Legg til et passord, en SSH-nøkkel eller begge deler",
"createCredentialFromHostBtn": "Opprett legitimasjon",
"createCredentialFromHostTitle": "Opprett legitimasjon fra vert",
"createCredentialFromHostDesc": "Opprett en gjenbrukbar, delbar legitimasjonsoppføring forhåndsutfylt med denne vertens nåværende brukernavn, passord og/eller SSH-nøkkel.",
"backToHosts": "Tilbake til verter",
"backToCredentials": "Tilbake til legitimasjon",
"pinned": "Festet",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Bruk / til å skille nivåer og opprette nestede mapper.",
"folderColor": "Farge",
"folderIcon": "Ikon",
"folderCredential": "Legitimasjon",
"folderCredentialNone": "Ingen legitimasjon tildelt",
"folderCredentialHint": "Verter i denne mappen som bruker autentiseringen «Lagret legitimasjon» uten å ha valgt sin egen legitimasjon, arver denne.",
"folderPreview": "Forhåndsvisning",
"folderNameFallback": "Mappe uten navn",
"createFolderButton": "Opprett mappe",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Tagger",
"shareHost": "Del vert",
"shareHostTitle": "Del: {{name}}",
"shareFolder": "Del mappe",
"shareFolderTitle": "Del mappe: {{name}}",
"folderSharedSuccessfully": "Delte {{count}} verter i mappen",
"failedToShareFolder": "Kunne ikke dele mappen",
"sharing": {
"loadError": "Kunne ikke laste inn delingsdataene. Prøv på nytt.",
"shareWithSection": "Del med",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Del ({{count}})",
"currentAccess": "Nåværende tilgang",
"noAccessEntries": "Denne verten har ikke blitt delt ennå",
"folderShareSummary": "Delte {{shared}} av {{total}} verter i denne mappen",
"grantedBy": "Tildelt av",
"expires": "Utløper",
"expired": "Utløpt",
@@ -1309,8 +1242,6 @@
"authMethod": "Autentiseringsmetode",
"authTypeDirect": "Direkte",
"authTypeCredential": "Legitimasjon",
"authTypeNone": "Ingen",
"authTypeNoneDesc": "Ingen påloggingsinformasjon lagres. Du blir bedt om å oppgi brukernavn og passord hver gang du kobler til; de lagres ikke.",
"selectCredential": "Velg en legitimasjon...",
"connectionSettings": "Tilkoblingsinnstillinger",
"displaySettings": "Skjerminnstillinger",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-port",
"username": "Brukernavn",
"password": "Passord",
"passwordSaved": "Passord lagret, skriv inn for å endre",
"domain": "Domene",
"securityMode": "Sikkerhetsmodus",
"colorDepth": "Fargedybde",
@@ -1494,60 +1424,7 @@
"expiresIn": "Utløper om {{duration}}",
"search": "Søk etter tilkoblinger...",
"noSearchResults": "Ingen forbindelser samsvarer med søket ditt",
"rename": "Gi nytt navn til økten",
"sectionSharedWithMe": "Delt med meg",
"sharedBy": "Delt av {{username}}",
"join": "Bli med",
"sharedSessionLabel": "{{hostName}} (delt)"
},
"sessionSharing": {
"guestView": {
"loading": "Kobler til delt økt...",
"linkInvalid": "Denne delingslenken er ugyldig, utløpt eller har blitt tilbakekalt",
"rateLimited": "For mange forsøk, prøv igjen snart",
"sessionEnded": "Denne økten er avsluttet",
"readOnlyBadge": "Kun visning"
},
"modalTitle": "Del økt",
"shareButton": "Dele",
"notReadyToShare": "Økten er ikke klar til deling ennå",
"modeTab": {
"link": "Lenke",
"user": "Bruker"
},
"linkModeDescription": "Alle med denne lenken kan bli med, ingen konto kreves.",
"userModeDescription": "Del med en bestemt bruker som allerede har tilgang til denne verten. Hvis de ikke har tilgang ennå, del verten med dem først eller bruk en lenke i stedet. Når økten er delt, vises den i deres Tilkoblinger-fane.",
"permissionLevel": {
"label": "Tillatelsesnivå",
"readOnly": "Skrivebeskyttet",
"readOnlyDescription": "Kan se økten direkte, men kan ikke skrive eller samhandle.",
"readWrite": "Lese-skrive",
"readWriteDescription": "Kan skrive og samhandle med økten akkurat som eieren."
},
"expiryLabel": "Utløpsdato for lenke",
"createLinkButton": "Opprett lenke",
"createShareButton": "Del med brukeren",
"searchUsersPlaceholder": "Søk etter brukere...",
"noUsersFound": "Ingen brukere funnet",
"linkCreated": "Delingslenke opprettet",
"linkCopied": "Lenke kopiert til utklippstavlen",
"copyLink": "Kopier lenke",
"shareCreated": "Økten er delt. Den vil vises i Tilkoblinger-fanen deres.",
"shareFailed": "Kunne ikke opprette delingen",
"userLacksHostAccess": "Den brukeren har ikke tilgang til denne verten ennå. Del verten med dem først, eller bruk en lenke i stedet.",
"activeShares": "Aktive aksjer",
"noActiveShares": "Ingen aktive delinger for denne økten",
"revoke": "Tilbakekalling",
"revokeConfirmTitle": "Tilbakekalle denne delingen?",
"revokeConfirmDescription": "Alle som bruker denne delingen vil miste tilgangen umiddelbart.",
"revoked": "Deling tilbakekalt",
"revokeFailed": "Kunne ikke tilbakekalle delingen",
"joinCount": "{{count}} bli med",
"joinCount_other": "{{count}} blir med",
"expiresAt": "Utløper {{date}}",
"linkShareBadge": "Lenke",
"userShareBadge": "Bruker: {{username}}",
"loadSharesFailed": "Kunne ikke laste inn aktive delinger"
"rename": "Gi nytt navn til økten"
},
"guacamole": {
"connecting": "Kobler til {{type}} økt...",
@@ -1559,9 +1436,6 @@
"reconnect": "Koble til igjen",
"retry": "Prøv på nytt",
"guacdUnavailable": "Ekstern skrivebordstjeneste (guacd) er ikke tilgjengelig. Sørg for at guacd kjører, er tilgjengelig og riktig konfigurert i administratorinnstillingene.",
"credentialPromptTitle": "Skriv inn RDP-legitimasjon",
"credentialPromptDescription": "Denne verten er satt til å be om påloggingsinformasjon ved tilkobling. Disse brukes kun for denne økten og lagres ikke.",
"connect": "Koble til",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Koble til økten på nytt",
"collapse": "Skjul verktøylinjen",
"expand": "Utvid verktøylinjen",
"dragHandle": "Dra for å flytte",
"switchToTrackpad": "Bytt til styreflatemodus (dra for å flytte markøren, trykk for å klikke)",
"switchToTouch": "Bytt til berøringsmodus (trykk direkte der du vil klikke)"
"dragHandle": "Dra for å flytte"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Tofaktorautentisering kreves",
"totpCodeLabel": "Verifiseringskode",
"totpVerify": "Verifisere",
"mfaPromptRequired": "Autentisering kreves",
"mfaPushRequired": "Push-autentisering kreves",
"mfaMenuPlaceholder": "Skriv inn svaret ditt",
"mfaWaitingApproval": "Venter på godkjenning på enheten din ...",
"mfaSendRequest": "Send forespørsel",
"warpgateAuthRequired": "Warpgate-autentisering kreves",
"warpgateSecurityKey": "Sikkerhetsnøkkel",
"warpgateAuthUrl": "Autentiserings-URL",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Frekvens begrenset: For mange bekreftelsesforsøk. Prøv igjen senere.",
"resetCodeRateLimitedWithTime": "Frekvens begrenset: For mange bekreftelsesforsøk. Vent {{time}} sekunder før du prøver på nytt.",
"authTokenSaveFailed": "Kunne ikke lagre autentiseringstokenet",
"failedToLoadServer": "Kunne ikke laste inn serveren",
"remoteServerRequired": "Ekstern server kreves. Koble til en ekstern server i Innstillinger for å bruke denne tilkoblingstypen."
"failedToLoadServer": "Kunne ikke laste inn serveren"
},
"messages": {
"registrationDisabled": "Registrering av ny konto er for øyeblikket deaktivert av en administrator. Vennligst logg inn eller kontakt en administrator.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Kunne ikke lagre SSL-innstillingene",
"sslRequiresDomain": "Domene og e-post er obligatorisk",
"sslInfoNote": "Etter at du har utstedt et sertifikat, aktiver SSL i miljøvariablene dine (ENABLE_SSL=true) og start Termix på nytt.",
"sslManualOption": "Manual (last opp sertifikat)",
"sslManualCert": "Sertifikat (PEM)",
"sslManualCertPlaceholder": "-----STARTSERTIFIKAT-----",
"sslManualKey": "Privat nøkkel (PEM)",
"sslManualKeyPlaceholder": "-----START PRIVAT NØKKEL-----",
"sslManualDesc": "Lim inn ditt eksisterende sertifikat og din private nøkkel, inkludert en fullstendig kjede hvis det kreves av CA-en din.",
"sslManualUpload": "Last opp og installer sertifikat",
"sslManualUploadLoading": "Laster opp sertifikat...",
"sslManualUploadSuccess": "Sertifikatet er lastet opp og installert",
"sslManualUploadFailed": "Opplasting av sertifikat mislyktes",
"sslManualRequiresFields": "Sertifikat og privat nøkkel kreves",
"auditLogTotal": "{{total}} totalt antall oppføringer",
"auditLogEmpty": "Ingen oppføringer i revisjonsloggen funnet",
"auditLogSuccess": "Suksess",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Kommandohistorikk",
"commandHistoryEnabledDesc": "Tillat opptak av kommandohistorikk. Når den er deaktivert, lagres ikke historikken uavhengig av innstillinger per vert.",
"updateCommandHistoryFailed": "Kunne ikke oppdatere innstillingen for kommandohistorikk",
"analyticsEnabled": "Del anonym bruksstatistikk",
"analyticsEnabledDesc": "Sender en anonym daglig telling av brukere, verter og funksjonsbruk for å forbedre Termix. Ingen personopplysninger eller tilkoblingsdetaljer inkluderes.",
"updateAnalyticsFailed": "Kunne ikke oppdatere analyseinnstillingen",
"sessionSharingGloballyEnabled": "Tillat øktdeling",
"sessionSharingGloballyEnabledDesc": "Tillat deling av Live Terminal-, RDP-, VNC- og Telnet-økter på tvers av hele instansen. Overstyrer alle delingsalternativer per vert når de er deaktivert.",
"updateSessionSharingFailed": "Kunne ikke oppdatere innstillingen for deling av økt",
"sessionTimeout": "Tidsavbrudd for økt",
"hours": "timer",
"sessionTimeoutRange": "Min. 1 t · Maks. 720 t",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Suksess",
"executionFailed": "Mislyktes"
},
"keybindings": {
"title": "Tastatursnarveier",
"description": "Tilpass snarveier for terminalkopiering, liming og kontroll, eller bind taster for å sende tekst eller kjøre et tekstutdrag.",
"defaultsHeading": "Innebygde snarveier",
"customHeading": "Tilpassede snarveier",
"addBinding": "Legg til snarvei",
"addBindingTitle": "Legg til snarvei",
"editBindingTitle": "Rediger snarvei",
"loading": "Laster inn...",
"noCustomBindings": "Ingen egendefinerte snarveier ennå",
"defaultBadge": "Misligholde",
"customizedBadge": "Tilpasset",
"customize": "Tilpass",
"resetToDefault": "Tilbakestill til standard",
"resetAllToDefaults": "Tilbakestill alle til standardinnstillinger",
"close": "Lukke",
"cancel": "Kansellere",
"saveBinding": "Lagre snarvei",
"saveError": "Kunne ikke lagre hurtigtaster",
"pressKeysToRecord": "Tastekombinasjon",
"pressKeysPlaceholder": "Klikk og trykk på tastene",
"recording": "Trykk på tastene...",
"comboRequiredError": "Trykk først på en tastekombinasjon",
"textRequiredError": "Skriv inn teksten som skal sendes",
"controlCodeRequiredError": "Skriv inn én bokstav for kontrollkoden",
"snippetRequiredError": "Velg et utdrag",
"conflictWarning": "Denne kombinasjonen er allerede bundet til {{combo}}. Lagring vil få begge til å aktiveres.",
"actionLabel": "Handling",
"actionCopy": "Kopier valg",
"actionPaste": "Lim inn",
"actionSendControlCode": "Send Ctrl+bokstavsignal",
"actionSendText": "Send tekstmelding",
"actionRunSnippet": "Kjør eksisterende kodebit",
"controlCodeLabel": "Brev",
"textLabel": "Tekst som skal sendes",
"appendEnterLabel": "Trykk Enter etter sending",
"snippetLabel": "Utdrag",
"selectSnippetPlaceholder": "Velg et utdrag",
"orphanedSnippetWarning": "utdraget ikke funnet",
"clipboardPermissionNote": "Kan be om tillatelse til utklippstavlen ved første gangs bruk i noen nettlesere."
},
"userProfile": {
"donateTitle": "Hold Termix i live",
"donateDescription": "Termix er gratis, selvhostet og bygget av bare noen få personer på fritiden. Hvis det har spart deg tid eller penger, bidrar en kryptodonasjon til å holde det gående.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Utseende",
"sectionSecurity": "Sikkerhet",
"sectionApiKeys": "API-nøkler",
"sectionData": "Data",
"sectionC2sTunnels": "C2S-tunneler",
"usernameLabel": "Brukernavn",
"roleLabel": "Rolle",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Kommando Autofullfør",
"commandAutocompleteDesc": "Vis autofullføring mens du skriver",
"keyboardShortcuts": "Tastatursnarveier",
"keyboardShortcutsDescription": "Tilpass snarveier for terminalkopiering, -liming og -kommandoer",
"manageShortcuts": "Administrer",
"terminalLinkBehavior": "Klikk på terminallenken",
"terminalLinkBehaviorDesc": "Standardoppførsel når du klikker på lenker i terminalen",
"historyTracking": "Historikksporing",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Ta med nøkkelen din i",
"apiKeyUsageHintHeader": "overskrift.",
"apiKeyPermissionsHint": "Nøkler arver tillatelsene til den opprettende brukeren.",
"exportData": "Eksporter dataene mine",
"exportDataDesc": "Last ned en sikkerhetskopi av vertene, påloggingsinformasjonen og innstillingene dine for å overføre dem til en annen enhet.",
"export": "Eksport",
"exporting": "Eksporterer...",
"importData": "Importer dataene mine",
"importDataDesc": "Gjenopprett verter, påloggingsinformasjon og innstillinger fra en .sqlite-sikkerhetskopifil",
"importDataSelected": "Valgt: {{name}}",
"selectFile": "Velg fil",
"changeFile": "Endre",
"import": "Import",
"importing": "Importerer...",
"exportSuccess": "Dataene ble eksportert",
"exportFailed": "Dataeksport mislyktes",
"importSelectFile": "Vennligst velg en fil først",
"importCompleted": "Import fullført: {{total}} elementer importert, {{skipped}} hoppet over",
"importFailed": "Importen mislyktes: {{error}}",
"roleUser": "Bruker",
"authMethodDual": "Dobbel autentisering",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Lim inn",
"editQuickKeys": "Rediger hurtigtaster",
"quickKeysTitle": "Hurtigtaster",
"quickKeysDesc": "Trykk på × for å fjerne. Støtter opptil 8 tegn.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Brak zapisanych serwerów",
"removeServer": "Usunąć"
},
"migrationNotice": {
"title": "Termix Desktop działa teraz autonomicznie",
"body1": "Ta aplikacja łączyła się bezpośrednio z serwerem Termix. Została przeprojektowana tak, aby działać w pełni samodzielnie, przechowując hosty i dane uwierzytelniające lokalnie, dzięki czemu działa w trybie offline. Dwukierunkowa synchronizacja z samodzielnie hostowanym serwerem Termix jest teraz opcjonalna.",
"body2": "Twoje obecne hosty i dane uwierzytelniające są nadal na {{url}}. Włącz synchronizację zdalną i ponownie połącz się z tym serwerem, aby je przywrócić i zapewnić synchronizację w przyszłości.",
"dismiss": "Nie teraz",
"setUpSync": "Skonfiguruj synchronizację zdalną"
},
"remoteSync": {
"title": "Zdalna synchronizacja",
"description": "Opcjonalnie połącz tę aplikację desktopową z samodzielnie hostowanym serwerem Termix, aby synchronizować hosty, dane uwierzytelniające i fragmenty kodu na różnych urządzeniach. Aplikacja zawsze działa w pełni offline, niezależnie od tego, czy masz połączenie z siecią, czy nie.",
"notConnected": "Nie połączony",
"connected": "Połączony",
"connectedTo": "Połączono z {{url}}",
"lastSynced": "Ostatnia synchronizacja {{time}}",
"neverSynced": "Nigdy nie zsynchronizowane",
"syncError": "Błąd synchronizacji: {{message}}",
"needsReauth": "Logowanie wygasło",
"connectButton": "Połącz się z serwerem",
"disconnectButton": "Odłączyć",
"syncNowButton": "Synchronizuj teraz",
"syncing": "Synchronizacja...",
"serverUrl": "Adres URL serwera",
"enterServerUrl": "Proszę podać adres URL serwera",
"mustIncludeProtocol": "Adres URL serwera musi zaczynać się od http:// lub https://",
"connectionTestFailed": "Nie można nawiązać połączenia z serwerem Termix pod tym adresem URL",
"allowInvalidCertificate": "Zezwól na nieprawidłowy certyfikat",
"allowInvalidCertificateDesc": "Używaj tylko w przypadku zaufanych serwerów hostowanych samodzielnie, z certyfikatami podpisanymi samodzielnie lub opartymi na adresie IP.",
"savedServers": "Zapisane serwery",
"removeServer": "Usunąć",
"continueButton": "Kontynuować",
"cancelButton": "Anulować",
"signInTitle": "Zaloguj się do {{url}}",
"originTitle": "Pochodzenie połączenia",
"originDescription": "Wybierz domyślne źródło połączeń SSH. Można to zmienić dla każdego hosta.",
"originLocal": "To urządzenie (sieć lokalna)",
"originRemote": "Serwer zdalny",
"bannerReconnect": "Na nowo połączyć",
"bannerMessage": "Zdalna synchronizacja wymaga ponownego uwierzytelnienia"
},
"versionCheck": {
"error": "Błąd sprawdzania wersji",
"checkFailed": "Nie udało się sprawdzić dostępności aktualizacji",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Zastąp nazwę użytkownika poświadczeń",
"overrideCredentialUsernameDesc": "Użyj nazwy użytkownika określonej powyżej zamiast nazwy użytkownika podanej w poświadczeniu",
"oidcUsernameHint": "Użyj $oidc.preferred_username, aby zastąpić swoją nazwę logowania OIDC.",
"tailscaleUsernameHint": "Musi to być użytkownik systemu Unix, któremu Twoja tożsamość Tailscale została przypisana na liście kontroli dostępu SSH sieci tailnet, niekoniecznie musi to być użytkownik root.",
"jumpHostChain": "Łańcuch hosta skoku",
"portKnocking": "Port Knocking",
"addKnock": "Dodaj port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Opóźnienie po (ms)",
"useSocks5Proxy": "Użyj serwera proxy SOCKS5",
"useSocks5ProxyDesc": "Przekieruj połączenie przez serwer proxy",
"connectionOrigin": "Pochodzenie połączenia",
"connectionOriginDesc": "Skąd pochodzi połączenie SSH tego hosta. Zastępuje domyślne globalne ustawienie aplikacji na komputerze.",
"connectionOriginDefault": "Użyj domyślnego",
"connectionOriginLocal": "To urządzenie (sieć lokalna)",
"connectionOriginRemote": "Serwer zdalny",
"proxyHost": "Host proxy",
"proxyPort": "Port proxy",
"proxyUsername": "Nazwa użytkownika proxy",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Jasny cyjan",
"customThemeBrightWhite": "Jasna biel",
"customThemeResetTooltip": "Przywróć ustawienia domyślne",
"savedThemesLabel": "Zapisane motywy",
"saveAsGlobalTheme": "Zapisz jako motyw globalny",
"saveGlobalThemeNamePrompt": "Wprowadź nazwę tego motywu",
"saveGlobalThemeSuccess": "Motyw zapisany",
"saveGlobalThemeError": "Nie udało się zapisać motywu",
"applyGlobalThemeTooltip": "Zastosuj ten motyw",
"deleteGlobalThemeTooltip": "Usuń ten motyw",
"noSavedThemes": "Brak zapisanych motywów",
"syntaxHighlightingLabel": "Podświetlanie składni",
"syntaxHighlightingDesc": "Kolorowanie danych wyjściowych terminala (błędy, ścieżki, adresy IP, znaczniki czasu)",
"syntaxHighlightingCategories": "Podświetl kategorie",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Automatyczne uruchamianie lub dołączanie do sesji tmux",
"enableSessionLogging": "Rejestrowanie sesji",
"enableSessionLoggingDesc": "Rejestruj dane wyjściowe sesji terminalowej w celu późniejszego przejrzenia",
"allowSessionSharing": "Zezwól na udostępnianie sesji",
"allowSessionSharingDesc": "Zezwól na udostępnianie sesji na żywo na tym hoście za pośrednictwem łącza lub innym użytkownikom",
"enableCommandHistory": "Historia dowodzenia",
"enableCommandHistoryDesc": "Rejestruj polecenia uruchamiane w tym terminalu w celu historii i automatycznego uzupełniania",
"linkClickBehaviorLabel": "Zachowanie kliknięcia linku",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} zaimportowano",
"proxmoxResultUpdated": "{{count}} zaktualizowano",
"proxmoxResultFailed": "{{count}} nie powiodło się",
"proxmoxResultSkippedNoIp": "{{count}} pominięto (nie znaleziono adresu IP)",
"proxmoxImportComplete": "Zakończono importowanie Proxmox: {{summary}}",
"proxmoxDiscoveryFailed": "Odkrycie nie powiodło się",
"proxmoxImportFailed": "Import nie powiódł się",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Nie udało się zapisać danych uwierzytelniających",
"credentialNameRequired": "Proszę podać nazwę poświadczenia",
"credentialAuthRequired": "Dodaj hasło, klucz SSH lub oba",
"createCredentialFromHostBtn": "Utwórz poświadczenia",
"createCredentialFromHostTitle": "Utwórz poświadczenia z hosta",
"createCredentialFromHostDesc": "Utwórz wpis danych uwierzytelniających, który można wielokrotnie wykorzystywać i udostępniać, wstępnie wypełniony bieżącą nazwą użytkownika, hasłem i/lub kluczem SSH tego hosta.",
"backToHosts": "Powrót do Gospodarzy",
"backToCredentials": "Powrót do danych uwierzytelniających",
"pinned": "Przypięte",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Użyj / do oddzielenia poziomów i utworzenia zagnieżdżonych folderów.",
"folderColor": "Kolor",
"folderIcon": "Ikona",
"folderCredential": "Mandat",
"folderCredentialNone": "Brak przypisanych danych uwierzytelniających",
"folderCredentialHint": "Hosty w tym folderze korzystające z uwierzytelniania „Zapisane dane uwierzytelniające” bez wybrania własnych danych uwierzytelniających odziedziczą to dane.",
"folderPreview": "Zapowiedź",
"folderNameFallback": "Folder bez tytułu",
"createFolderButton": "Utwórz folder",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Tagi",
"shareHost": "Udostępnij hosta",
"shareHostTitle": "Udostępnij: {{name}}",
"shareFolder": "Udostępnij folder",
"shareFolderTitle": "Udostępnij folder: {{name}}",
"folderSharedSuccessfully": "Udostępniono {{count}} hostów w folderze",
"failedToShareFolder": "Nie udało się udostępnić folderu",
"sharing": {
"loadError": "Nie udało się załadować udostępnianych danych. Spróbuj ponownie.",
"shareWithSection": "Podziel się z",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Udostępnij ({{count}})",
"currentAccess": "Aktualny dostęp",
"noAccessEntries": "Ten host nie został jeszcze udostępniony",
"folderShareSummary": "Udostępniono {{shared}} z {{total}} hostów w tym folderze",
"grantedBy": "Przyznane przez",
"expires": "Wygasa",
"expired": "Wygasły",
@@ -1309,8 +1242,6 @@
"authMethod": "Metoda uwierzytelniania",
"authTypeDirect": "Bezpośredni",
"authTypeCredential": "Mandat",
"authTypeNone": "Nic",
"authTypeNoneDesc": "Żadne dane uwierzytelniające nie są przechowywane. Przy każdym połączeniu zostaniesz poproszony o podanie nazwy użytkownika i hasła; dane te nie zostaną zapisane.",
"selectCredential": "Wybierz dane uwierzytelniające...",
"connectionSettings": "Ustawienia połączenia",
"displaySettings": "Ustawienia wyświetlania",
@@ -1328,7 +1259,6 @@
"rdpPort": "Port RDP",
"username": "Nazwa użytkownika",
"password": "Hasło",
"passwordSaved": "Hasło zapisane, wpisz, aby zmienić",
"domain": "Domena",
"securityMode": "Tryb bezpieczeństwa",
"colorDepth": "Głębia koloru",
@@ -1494,60 +1424,7 @@
"expiresIn": "Wygasa za {{duration}}",
"search": "Wyszukaj połączenia...",
"noSearchResults": "Brak połączeń odpowiadających Twojemu wyszukiwaniu",
"rename": "Zmień nazwę sesji",
"sectionSharedWithMe": "Udostępniono mi",
"sharedBy": "Udostępnione przez {{username}}",
"join": "Dołączyć",
"sharedSessionLabel": "{{hostName}} (udostępnione)"
},
"sessionSharing": {
"guestView": {
"loading": "Łączenie z sesją współdzieloną...",
"linkInvalid": "Ten link do udostępniania jest nieprawidłowy, wygasł lub został odwołany",
"rateLimited": "Zbyt wiele prób, spróbuj ponownie za chwilę",
"sessionEnded": "Ta sesja dobiegła końca",
"readOnlyBadge": "Tylko do podglądu"
},
"modalTitle": "Udostępnij sesję",
"shareButton": "Udział",
"notReadyToShare": "Sesja nie jest jeszcze gotowa do udostępnienia",
"modeTab": {
"link": "Połączyć",
"user": "Użytkownik"
},
"linkModeDescription": "Każdy mający ten link może dołączyć, nie jest wymagane założenie konta.",
"userModeDescription": "Udostępnij dane konkretnemu użytkownikowi, który ma już dostęp do tego hosta. Jeśli jeszcze nie ma dostępu, najpierw udostępnij mu hosta lub skorzystaj z linku. Po udostępnieniu sesja pojawi się na jego karcie Połączenia.",
"permissionLevel": {
"label": "Poziom uprawnień",
"readOnly": "Tylko do odczytu",
"readOnlyDescription": "Można oglądać sesję na żywo, ale nie można pisać ani brać udziału w interakcji.",
"readWrite": "Odczyt-zapis",
"readWriteDescription": "Może pisać i uczestniczyć w sesji tak samo jak właściciel."
},
"expiryLabel": "Wygaśnięcie łącza",
"createLinkButton": "Utwórz link",
"createShareButton": "Udostępnij użytkownikowi",
"searchUsersPlaceholder": "Wyszukaj użytkowników...",
"noUsersFound": "Nie znaleziono użytkowników",
"linkCreated": "Udostępnij link utworzony",
"linkCopied": "Link skopiowany do schowka",
"copyLink": "Kopiuj link",
"shareCreated": "Sesja udostępniona. Pojawi się na karcie Połączenia.",
"shareFailed": "Nie udało się utworzyć udziału",
"userLacksHostAccess": "Ten użytkownik nie ma jeszcze dostępu do tego hosta. Udostępnij mu najpierw hosta lub skorzystaj z linku.",
"activeShares": "Aktywne akcje",
"noActiveShares": "Brak aktywnych akcji w tej sesji",
"revoke": "Unieważnić",
"revokeConfirmTitle": "Cofnąć to udostępnienie?",
"revokeConfirmDescription": "Każda osoba korzystająca z tego udziału natychmiast utraci do niego dostęp.",
"revoked": "Udostępnij odwołano",
"revokeFailed": "Nie udało się odwołać udostępniania",
"joinCount": "{{count}} dołącz",
"joinCount_other": "{{count}} łączy",
"expiresAt": "Wygasa {{date}}",
"linkShareBadge": "Połączyć",
"userShareBadge": "Użytkownik: {{username}}",
"loadSharesFailed": "Nie udało się załadować aktywnych udziałów"
"rename": "Zmień nazwę sesji"
},
"guacamole": {
"connecting": "Łączenie z sesją {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Na nowo połączyć",
"retry": "Spróbować ponownie",
"guacdUnavailable": "Usługa pulpitu zdalnego (guacd) jest niedostępna. Upewnij się, że usługa guacd jest uruchomiona, dostępna i poprawnie skonfigurowana w ustawieniach administratora.",
"credentialPromptTitle": "Wprowadź dane uwierzytelniające RDP",
"credentialPromptDescription": "Ten host jest skonfigurowany tak, aby prosić o dane uwierzytelniające podczas łączenia. Są one używane tylko w tej sesji i nie są zapisywane.",
"connect": "Łączyć",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Ponowne połączenie sesji",
"collapse": "Zwiń pasek narzędzi",
"expand": "Rozwiń pasek narzędzi",
"dragHandle": "Przeciągnij, aby zmienić położenie",
"switchToTrackpad": "Przełącz na tryb gładzika (przeciągnij, aby przesunąć kursor, dotknij, aby kliknąć)",
"switchToTouch": "Przełącz na tryb dotykowy (dotknij bezpośrednio miejsca, w którym chcesz kliknąć)"
"dragHandle": "Przeciągnij, aby zmienić położenie"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Wymagane uwierzytelnianie dwuskładnikowe",
"totpCodeLabel": "Kod weryfikacyjny",
"totpVerify": "Zweryfikować",
"mfaPromptRequired": "Wymagane uwierzytelnienie",
"mfaPushRequired": "Wymagane uwierzytelnianie push",
"mfaMenuPlaceholder": "Wprowadź swoją odpowiedź",
"mfaWaitingApproval": "Oczekiwanie na zatwierdzenie na Twoim urządzeniu...",
"mfaSendRequest": "Wyślij prośbę",
"warpgateAuthRequired": "Wymagane uwierzytelnienie Warpgate",
"warpgateSecurityKey": "Klucz bezpieczeństwa",
"warpgateAuthUrl": "Adres URL uwierzytelniania",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Ograniczenie szybkości: Zbyt wiele prób weryfikacji. Spróbuj ponownie później.",
"resetCodeRateLimitedWithTime": "Ograniczenie szybkości: Zbyt wiele prób weryfikacji. Odczekaj {{time}} sekund przed ponowną próbą.",
"authTokenSaveFailed": "Nie udało się zapisać tokenu uwierzytelniającego",
"failedToLoadServer": "Nie udało się załadować serwera",
"remoteServerRequired": "Wymagany jest serwer zdalny. Połącz się z serwerem zdalnym w Ustawieniach, aby korzystać z tego typu połączenia."
"failedToLoadServer": "Nie udało się załadować serwera"
},
"messages": {
"registrationDisabled": "Rejestracja nowych kont jest obecnie wyłączona przez administratora. Zaloguj się lub skontaktuj się z administratorem.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Nie udało się zapisać ustawień SSL",
"sslRequiresDomain": "Wymagane są domena i adres e-mail",
"sslInfoNote": "Po wystawieniu certyfikatu włącz SSL w zmiennych środowiskowych (ENABLE_SSL=true) i uruchom ponownie Termix.",
"sslManualOption": "Instrukcja (przesyłanie certyfikatu)",
"sslManualCert": "Certyfikat (PEM)",
"sslManualCertPlaceholder": "-----ROZPOCZĄTEK CERTYFIKATU-----",
"sslManualKey": "Klucz prywatny (PEM)",
"sslManualKeyPlaceholder": "-----POCZĄTEK KLUCZA PRYWATNEGO-----",
"sslManualDesc": "Wklej istniejący certyfikat i klucz prywatny, łącznie z pełnym łańcuchem, jeśli jest wymagany przez urząd certyfikacji.",
"sslManualUpload": "Prześlij i zainstaluj certyfikat",
"sslManualUploadLoading": "Przesyłanie certyfikatu...",
"sslManualUploadSuccess": "Certyfikat został pomyślnie przesłany i zainstalowany",
"sslManualUploadFailed": "Nie udało się przesłać certyfikatu",
"sslManualRequiresFields": "Wymagany jest certyfikat i klucz prywatny",
"auditLogTotal": "{{total}} całkowita liczba wpisów",
"auditLogEmpty": "Nie znaleziono wpisów w dzienniku audytu",
"auditLogSuccess": "Sukces",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Historia dowodzenia",
"commandHistoryEnabledDesc": "Zezwól na rejestrowanie historii poleceń. Po wyłączeniu historia nie jest zapisywana, niezależnie od ustawień hosta.",
"updateCommandHistoryFailed": "Nie udało się zaktualizować ustawień historii poleceń",
"analyticsEnabled": "Udostępnij anonimowe statystyki użytkowania",
"analyticsEnabledDesc": "Wysyła anonimowe, codzienne zestawienie użytkowników, hostów i wykorzystania funkcji, aby pomóc w ulepszaniu Termix. Nie zawiera ono żadnych danych osobowych ani szczegółów połączenia.",
"updateAnalyticsFailed": "Nie udało się zaktualizować ustawień analityki",
"sessionSharingGloballyEnabled": "Zezwól na udostępnianie sesji",
"sessionSharingGloballyEnabledDesc": "Zezwalaj na udostępnianie sesji terminala na żywo, RDP, VNC i Telnet w obrębie instancji. Po wyłączeniu zastępuje wszystkie przełączniki udostępniania dla poszczególnych hostów.",
"updateSessionSharingFailed": "Nie udało się zaktualizować ustawień udostępniania sesji",
"sessionTimeout": "Przekroczenie limitu czasu sesji",
"hours": "godziny",
"sessionTimeoutRange": "Min. 1 godz. · Maks. 720 godz.",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Sukces",
"executionFailed": "Przegrany"
},
"keybindings": {
"title": "Skróty klawiaturowe",
"description": "Dostosuj skróty klawiaturowe do kopiowania, wklejania i sterowania w terminalu lub powiąż klawisze, aby wysłać tekst lub uruchomić fragment kodu.",
"defaultsHeading": "Wbudowane skróty",
"customHeading": "Niestandardowe skróty",
"addBinding": "Dodaj skrót",
"addBindingTitle": "Dodaj skrót",
"editBindingTitle": "Edytuj skrót",
"loading": "Załadunek...",
"noCustomBindings": "Brak niestandardowych skrótów",
"defaultBadge": "Domyślny",
"customizedBadge": "Dostosowane",
"customize": "Dostosuj",
"resetToDefault": "Przywróć ustawienia domyślne",
"resetAllToDefaults": "Przywróć ustawienia domyślne",
"close": "Zamknąć",
"cancel": "Anulować",
"saveBinding": "Zapisz skrót",
"saveError": "Nie udało się zapisać skrótów klawiaturowych",
"pressKeysToRecord": "Kombinacja klawiszy",
"pressKeysPlaceholder": "Kliknij i naciśnij klawisze",
"recording": "Naciśnij klawisze...",
"comboRequiredError": "Najpierw naciśnij kombinację klawiszy",
"textRequiredError": "Wprowadź tekst do wysłania",
"controlCodeRequiredError": "Wprowadź pojedynczą literę kodu kontrolnego",
"snippetRequiredError": "Wybierz fragment",
"conflictWarning": "Ta kombinacja jest już przypisana do {{combo}}. Zapis spowoduje, że oba zapłoną.",
"actionLabel": "Działanie",
"actionCopy": "Kopiuj wybór",
"actionPaste": "Pasta",
"actionSendControlCode": "Wyślij sygnał Ctrl+litera",
"actionSendText": "Wyślij tekst",
"actionRunSnippet": "Uruchom istniejący fragment kodu",
"controlCodeLabel": "List",
"textLabel": "Tekst do wysłania",
"appendEnterLabel": "Naciśnij Enter po wysłaniu",
"snippetLabel": "Skrawek",
"selectSnippetPlaceholder": "Wybierz fragment",
"orphanedSnippetWarning": "fragment nie znaleziony",
"clipboardPermissionNote": "Przy pierwszym użyciu w niektórych przeglądarkach może zostać wyświetlony monit o zezwolenie na dostęp do schowka."
},
"userProfile": {
"donateTitle": "Utrzymajmy Termix przy życiu",
"donateDescription": "Termix jest darmowy, hostowany samodzielnie i tworzony przez zaledwie kilka osób w wolnym czasie. Jeśli zaoszczędziłeś czas lub pieniądze, darowizna w kryptowalutach pomoże Ci go utrzymać.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Wygląd",
"sectionSecurity": "Bezpieczeństwo",
"sectionApiKeys": "Klucze API",
"sectionData": "Dane",
"sectionC2sTunnels": "Tunele C2S",
"usernameLabel": "Nazwa użytkownika",
"roleLabel": "Rola",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Automatyczne uzupełnianie poleceń",
"commandAutocompleteDesc": "Pokaż autouzupełnianie podczas pisania",
"keyboardShortcuts": "Skróty klawiaturowe",
"keyboardShortcutsDescription": "Dostosuj skróty poleceń i kopiowania w terminalu",
"manageShortcuts": "Zarządzać",
"terminalLinkBehavior": "Kliknij łącze terminala",
"terminalLinkBehaviorDesc": "Domyślne zachowanie po kliknięciu linków w terminalu",
"historyTracking": "Śledzenie historii",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Dołącz swój klucz do",
"apiKeyUsageHintHeader": "chodnikowiec.",
"apiKeyPermissionsHint": "Klucze dziedziczą uprawnienia użytkownika, który je stworzył.",
"exportData": "Eksportuj moje dane",
"exportDataDesc": "Pobierz kopię zapasową hostów, danych uwierzytelniających i ustawień, aby przenieść ją na inne urządzenie",
"export": "Eksport",
"exporting": "Eksportowanie...",
"importData": "Importuj moje dane",
"importDataDesc": "Przywróć hosty, poświadczenia i ustawienia z pliku kopii zapasowej .sqlite",
"importDataSelected": "Wybrano: {{name}}",
"selectFile": "Wybierz plik",
"changeFile": "Zmiana",
"import": "Import",
"importing": "Importowanie...",
"exportSuccess": "Dane zostały pomyślnie wyeksportowane",
"exportFailed": "Eksport danych nie powiódł się",
"importSelectFile": "Proszę najpierw wybrać plik",
"importCompleted": "Import zakończony: zaimportowano {{total}} elementów, pominięto {{skipped}}",
"importFailed": "Nieudany import: {{error}}",
"roleUser": "Użytkownik",
"authMethodDual": "Podwójna autoryzacja",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Pasta",
"editQuickKeys": "Edytuj szybkie klawisze",
"quickKeysTitle": "Szybkie klawisze",
"quickKeysDesc": "Kliknij ×, aby usunąć. Obsługuje maksymalnie 8 znaków.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Nenhum servidor salvo",
"removeServer": "Remover"
},
"migrationNotice": {
"title": "O Termix Desktop agora funciona de forma independente.",
"body1": "Este aplicativo costumava se conectar diretamente ao seu servidor Termix. Ele foi reformulado para funcionar de forma totalmente independente, armazenando hosts e credenciais localmente para que continue funcionando offline. A sincronização bidirecional com um servidor Termix auto-hospedado agora é opcional.",
"body2": "Seus hosts e credenciais atuais ainda estão em {{url}}. Ative a Sincronização Remota e reconecte-se a esse servidor para recuperá-los e mantê-los sincronizados daqui para frente.",
"dismiss": "Agora não",
"setUpSync": "Configurar sincronização remota"
},
"remoteSync": {
"title": "Sincronização remota",
"description": "Opcionalmente, conecte este aplicativo de desktop a um servidor Termix auto-hospedado para sincronizar seus hosts, credenciais e trechos de código entre dispositivos. O aplicativo funciona sempre totalmente offline, independentemente de estar conectado ou não.",
"notConnected": "Não conectado",
"connected": "Conectado",
"connectedTo": "Conectado a {{url}}",
"lastSynced": "Última sincronização {{time}}",
"neverSynced": "Nunca sincronizado",
"syncError": "Erro de sincronização: {{message}}",
"needsReauth": "Login expirou",
"connectButton": "Conectar ao servidor",
"disconnectButton": "Desconectar",
"syncNowButton": "Sincronizar agora",
"syncing": "Sincronizando...",
"serverUrl": "URL do servidor",
"enterServerUrl": "Por favor, insira um URL de servidor.",
"mustIncludeProtocol": "O URL do servidor deve começar com http:// ou https://",
"connectionTestFailed": "Não foi possível acessar um servidor Termix nesse URL.",
"allowInvalidCertificate": "Permitir certificado inválido",
"allowInvalidCertificateDesc": "Utilize somente em servidores autohospedados confiáveis com certificados autoassinados ou de endereço IP.",
"savedServers": "Servidores salvos",
"removeServer": "Remover",
"continueButton": "Continuar",
"cancelButton": "Cancelar",
"signInTitle": "Faça login em {{url}}",
"originTitle": "Origem da conexão",
"originDescription": "Por padrão, escolha de onde as conexões SSH se originam. Essa configuração pode ser alterada para cada host individualmente.",
"originLocal": "Este dispositivo (rede local)",
"originRemote": "Servidor remoto",
"bannerReconnect": "Reconectar",
"bannerMessage": "A sincronização remota requer nova autenticação."
},
"versionCheck": {
"error": "Erro na verificação de versão",
"checkFailed": "Falha ao verificar se há atualizações",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Substituir nome de usuário da credencial",
"overrideCredentialUsernameDesc": "Use o nome de usuário especificado acima em vez do nome de usuário da credencial.",
"oidcUsernameHint": "Use $oidc.preferred_username para substituir seu nome de login OIDC.",
"tailscaleUsernameHint": "Este deve ser um usuário Unix cuja identidade Tailscale é concedida na ACL SSH da tailnet, não necessariamente o usuário root.",
"jumpHostChain": "Cadeia de anfitriões de salto",
"portKnocking": "Batendo ao porto",
"addKnock": "Adicionar porta",
@@ -690,11 +650,6 @@
"delayAfterMs": "Atraso após (ms)",
"useSocks5Proxy": "Usar proxy SOCKS5",
"useSocks5ProxyDesc": "Encaminhar a conexão através de um servidor proxy",
"connectionOrigin": "Origem da conexão",
"connectionOriginDesc": "De onde se origina a conexão SSH deste host. Substitui o padrão global do aplicativo de desktop.",
"connectionOriginDefault": "Usar padrão",
"connectionOriginLocal": "Este dispositivo (rede local)",
"connectionOriginRemote": "Servidor remoto",
"proxyHost": "Host proxy",
"proxyPort": "Porta proxy",
"proxyUsername": "Nome de usuário do proxy",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Ciano brilhante",
"customThemeBrightWhite": "Branco brilhante",
"customThemeResetTooltip": "Restaurar padrões de fábrica",
"savedThemesLabel": "Temas salvos",
"saveAsGlobalTheme": "Salvar como tema global",
"saveGlobalThemeNamePrompt": "Insira um nome para este tema.",
"saveGlobalThemeSuccess": "Tema salvo",
"saveGlobalThemeError": "Falha ao salvar o tema",
"applyGlobalThemeTooltip": "Aplique este tema",
"deleteGlobalThemeTooltip": "Excluir este tema",
"noSavedThemes": "Ainda não há temas salvos.",
"syntaxHighlightingLabel": "Realce de sintaxe",
"syntaxHighlightingDesc": "Colorir a saída do terminal (erros, caminhos, IPs, registros de data e hora)",
"syntaxHighlightingCategories": "Categorias em destaque",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Iniciar ou conectar-se automaticamente a uma sessão do tmux",
"enableSessionLogging": "Registro de sessão",
"enableSessionLoggingDesc": "Registre a saída da sessão do terminal para revisão posterior.",
"allowSessionSharing": "Permitir compartilhamento de sessão",
"allowSessionSharingDesc": "Permitir que as sessões ao vivo neste servidor sejam compartilhadas por meio de link ou com outros usuários.",
"enableCommandHistory": "Histórico de comandos",
"enableCommandHistoryDesc": "Registre os comandos executados neste terminal para histórico e preenchimento automático.",
"linkClickBehaviorLabel": "Comportamento de clique em links",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importado",
"proxmoxResultUpdated": "{{count}} atualizado",
"proxmoxResultFailed": "{{count}} falhou",
"proxmoxResultSkippedNoIp": "{{count}} ignorado (nenhum IP encontrado)",
"proxmoxImportComplete": "Importação do Proxmox concluída: {{summary}}",
"proxmoxDiscoveryFailed": "A descoberta falhou",
"proxmoxImportFailed": "A importação falhou",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Falha ao salvar as credenciais",
"credentialNameRequired": "Por favor, insira um nome para a credencial.",
"credentialAuthRequired": "Adicione uma senha, uma chave SSH ou ambas.",
"createCredentialFromHostBtn": "Criar credencial",
"createCredentialFromHostTitle": "Criar credencial a partir do host",
"createCredentialFromHostDesc": "Crie uma entrada de credencial reutilizável e compartilhável, preenchida previamente com o nome de usuário, senha e/ou chave SSH atuais deste host.",
"backToHosts": "Voltar para Anfitriões",
"backToCredentials": "Voltar às credenciais",
"pinned": "Fixado",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Use / para separar níveis e criar pastas aninhadas.",
"folderColor": "Cor",
"folderIcon": "Ícone",
"folderCredential": "Credencial",
"folderCredentialNone": "Nenhuma credencial atribuída",
"folderCredentialHint": "Os hosts nesta pasta que usam autenticação com \"Credenciais armazenadas\" sem que suas próprias credenciais estejam selecionadas herdarão esta.",
"folderPreview": "Pré-visualização",
"folderNameFallback": "Pasta sem título",
"createFolderButton": "Criar pasta",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Etiquetas",
"shareHost": "Compartilhar Host",
"shareHostTitle": "Compartilhar: {{name}}",
"shareFolder": "Pasta compartilhada",
"shareFolderTitle": "Pasta compartilhada: {{name}}",
"folderSharedSuccessfully": "Host(ns) compartilhado(s) {{count}} na pasta",
"failedToShareFolder": "Falha ao compartilhar a pasta",
"sharing": {
"loadError": "Não foi possível carregar os dados de compartilhamento. Tente novamente.",
"shareWithSection": "Compartilhe com",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Compartilhar ({{count}})",
"currentAccess": "Acesso atual",
"noAccessEntries": "Este host ainda não foi compartilhado.",
"folderShareSummary": "Compartilhado {{shared}} de {{total}} hosts nesta pasta",
"grantedBy": "Concedido por",
"expires": "Expira",
"expired": "Expirado",
@@ -1309,8 +1242,6 @@
"authMethod": "Método de autenticação",
"authTypeDirect": "Direto",
"authTypeCredential": "Credencial",
"authTypeNone": "Nenhum",
"authTypeNoneDesc": "Nenhuma credencial é armazenada. Você será solicitado a inserir um nome de usuário e uma senha sempre que se conectar; eles não são salvos.",
"selectCredential": "Selecione uma credencial...",
"connectionSettings": "Configurações de conexão",
"displaySettings": "Configurações de exibição",
@@ -1328,7 +1259,6 @@
"rdpPort": "Porta RDP",
"username": "Nome de usuário",
"password": "Senha",
"passwordSaved": "Senha salva, digite para alterar",
"domain": "Domínio",
"securityMode": "Modo de segurança",
"colorDepth": "Profundidade de cor",
@@ -1494,60 +1424,7 @@
"expiresIn": "Expira em {{duration}}",
"search": "Buscar conexões...",
"noSearchResults": "Nenhuma conexão corresponde à sua pesquisa.",
"rename": "Renomear sessão",
"sectionSharedWithMe": "Compartilhou comigo",
"sharedBy": "Compartilhado por {{username}}",
"join": "Juntar",
"sharedSessionLabel": "{{hostName}} (compartilhado)"
},
"sessionSharing": {
"guestView": {
"loading": "Conectando à sessão compartilhada...",
"linkInvalid": "Este link de compartilhamento é inválido, expirou ou foi revogado.",
"rateLimited": "Muitas tentativas foram feitas, tente novamente em breve.",
"sessionEnded": "Esta sessão terminou.",
"readOnlyBadge": "Somente visualização"
},
"modalTitle": "Sessão de compartilhamento",
"shareButton": "Compartilhar",
"notReadyToShare": "A sessão ainda não está pronta para ser compartilhada.",
"modeTab": {
"link": "Link",
"user": "Usuário"
},
"linkModeDescription": "Qualquer pessoa com este link pode participar, sem necessidade de criar uma conta.",
"userModeDescription": "Compartilhe com um usuário específico que já tenha acesso a este host. Caso ele ainda não tenha acesso, compartilhe o host com ele primeiro ou use um link. Após o compartilhamento, a sessão aparecerá na aba Conexões do usuário.",
"permissionLevel": {
"label": "Nível de permissão",
"readOnly": "Somente leitura",
"readOnlyDescription": "É possível assistir à sessão ao vivo, mas não é possível digitar ou interagir.",
"readWrite": "Ler e escrever",
"readWriteDescription": "Pode digitar e interagir com a sessão da mesma forma que o proprietário."
},
"expiryLabel": "Expiração do link",
"createLinkButton": "Criar link",
"createShareButton": "Compartilhar com o usuário",
"searchUsersPlaceholder": "Pesquisar usuários...",
"noUsersFound": "Nenhum usuário encontrado",
"linkCreated": "Compartilhar link criado",
"linkCopied": "Link copiado para a área de transferência",
"copyLink": "Copiar link",
"shareCreated": "Sessão compartilhada. Ela aparecerá na aba Conexões deles.",
"shareFailed": "Falha ao criar compartilhamento",
"userLacksHostAccess": "Esse usuário ainda não tem acesso a este host. Compartilhe o host com ele primeiro ou use um link.",
"activeShares": "Ações ativas",
"noActiveShares": "Nenhuma ação ativa nesta sessão.",
"revoke": "Revogar",
"revokeConfirmTitle": "Revogar essa participação?",
"revokeConfirmDescription": "Qualquer pessoa que usar este compartilhamento perderá o acesso imediatamente.",
"revoked": "Ações revogadas",
"revokeFailed": "Falha ao revogar a participação",
"joinCount": "{{count}} junte-se",
"joinCount_other": "{{count}} entra",
"expiresAt": "Expira em {{date}}",
"linkShareBadge": "Link",
"userShareBadge": "Usuário: {{username}}",
"loadSharesFailed": "Falha ao carregar compartilhamentos ativos"
"rename": "Renomear sessão"
},
"guacamole": {
"connecting": "Conectando à sessão {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Reconectar",
"retry": "Tentar novamente",
"guacdUnavailable": "O serviço de área de trabalho remota (guacd) não está disponível. Certifique-se de que o guacd esteja em execução, acessível e configurado corretamente nas configurações de administrador.",
"credentialPromptTitle": "Insira as credenciais RDP",
"credentialPromptDescription": "Este host está configurado para solicitar credenciais ao conectar. Elas são usadas apenas para esta sessão e não são salvas.",
"connect": "Conectar",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Sessão de reconexão",
"collapse": "Recolher barra de ferramentas",
"expand": "Expandir barra de ferramentas",
"dragHandle": "Arraste para reposicionar",
"switchToTrackpad": "Alternar para o modo trackpad (arraste para mover o cursor, toque para clicar)",
"switchToTouch": "Mude para o modo de toque (toque diretamente onde deseja clicar)."
"dragHandle": "Arraste para reposicionar"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Autenticação de dois fatores obrigatória",
"totpCodeLabel": "Código de verificação",
"totpVerify": "Verificar",
"mfaPromptRequired": "Autenticação necessária",
"mfaPushRequired": "Autenticação Push necessária",
"mfaMenuPlaceholder": "Insira sua resposta",
"mfaWaitingApproval": "Aguardando aprovação no seu dispositivo...",
"mfaSendRequest": "Enviar solicitação",
"warpgateAuthRequired": "Autenticação Warpgate necessária",
"warpgateSecurityKey": "Chave de segurança",
"warpgateAuthUrl": "URL de autenticação",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Taxa de tentativas limitada: Muitas tentativas de verificação. Tente novamente mais tarde.",
"resetCodeRateLimitedWithTime": "Taxa de tentativas limitada: Muitas tentativas de verificação. Aguarde {{time}} segundos antes de tentar novamente.",
"authTokenSaveFailed": "Falha ao salvar o token de autenticação",
"failedToLoadServer": "Falha ao carregar o servidor",
"remoteServerRequired": "É necessário um servidor remoto. Conecte um servidor remoto nas Configurações para usar este tipo de conexão."
"failedToLoadServer": "Falha ao carregar o servidor"
},
"messages": {
"registrationDisabled": "O cadastro de novas contas está temporariamente desativado por um administrador. Faça login ou entre em contato com um administrador.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Falha ao salvar as configurações SSL",
"sslRequiresDomain": "Domínio e e-mail são obrigatórios.",
"sslInfoNote": "Após emitir o certificado, habilite o SSL nas suas variáveis de ambiente (ENABLE_SSL=true) e reinicie o Termix.",
"sslManualOption": "Manual (envio do certificado)",
"sslManualCert": "Certificado (PEM)",
"sslManualCertPlaceholder": "-----INÍCIO DO CERTIFICADO-----",
"sslManualKey": "Chave privada (PEM)",
"sslManualKeyPlaceholder": "-----INÍCIO DA CHAVE PRIVADA-----",
"sslManualDesc": "Cole aqui seu certificado e chave privada existentes, incluindo a cadeia completa, se exigido pela sua Autoridade Certificadora.",
"sslManualUpload": "Carregar e instalar certificado",
"sslManualUploadLoading": "Enviando certificado...",
"sslManualUploadSuccess": "Certificado carregado e instalado com sucesso.",
"sslManualUploadFailed": "O envio do certificado falhou",
"sslManualRequiresFields": "É necessário certificado e chave privada.",
"auditLogTotal": "{{total}} total de entradas",
"auditLogEmpty": "Nenhuma entrada de log de auditoria encontrada.",
"auditLogSuccess": "Sucesso",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Histórico de comandos",
"commandHistoryEnabledDesc": "Permitir o registro do histórico de comandos. Quando desativado, o histórico não é salvo, independentemente das configurações de cada host.",
"updateCommandHistoryFailed": "Falha ao atualizar a configuração do histórico de comandos",
"analyticsEnabled": "Compartilhar estatísticas de uso anônimas",
"analyticsEnabledDesc": "Envia uma contagem diária anônima de usuários, hosts e uso de recursos para ajudar a melhorar o Termix. Nenhum dado pessoal ou detalhe de conexão é incluído.",
"updateAnalyticsFailed": "Falha ao atualizar as configurações de análise",
"sessionSharingGloballyEnabled": "Permitir compartilhamento de sessão",
"sessionSharingGloballyEnabledDesc": "Permite que sessões de terminal ao vivo, RDP, VNC e Telnet sejam compartilhadas em toda a instância. Substitui todas as configurações de compartilhamento por host quando desativada.",
"updateSessionSharingFailed": "Falha ao atualizar as configurações de compartilhamento de sessão.",
"sessionTimeout": "Tempo limite da sessão",
"hours": "horas",
"sessionTimeoutRange": "Mínimo 1h · Máximo 720h",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Sucesso",
"executionFailed": "Fracassado"
},
"keybindings": {
"title": "Atalhos de teclado",
"description": "Personalize os atalhos de copiar, colar e controlar o terminal, ou associe teclas para enviar texto ou executar um trecho de código.",
"defaultsHeading": "Atalhos integrados",
"customHeading": "Atalhos personalizados",
"addBinding": "Adicionar atalho",
"addBindingTitle": "Adicionar atalho",
"editBindingTitle": "Atalho para edição",
"loading": "Carregando...",
"noCustomBindings": "Ainda não há atalhos personalizados.",
"defaultBadge": "Padrão",
"customizedBadge": "Personalizado",
"customize": "Personalizar",
"resetToDefault": "Restaurar para as configurações padrão",
"resetAllToDefaults": "Redefinir tudo para os padrões de fábrica",
"close": "Fechar",
"cancel": "Cancelar",
"saveBinding": "Atalho para salvar",
"saveError": "Falha ao salvar os atalhos de teclado.",
"pressKeysToRecord": "Combinação de teclas",
"pressKeysPlaceholder": "Clique e pressione as teclas.",
"recording": "Pressione as teclas...",
"comboRequiredError": "Pressione primeiro uma combinação de teclas",
"textRequiredError": "Digite o texto que deseja enviar.",
"controlCodeRequiredError": "Digite uma única letra para o código de controle.",
"snippetRequiredError": "Selecione um trecho",
"conflictWarning": "Esta combinação já está vinculada a {{combo}}. Salvar fará com que ambas disparem.",
"actionLabel": "Ação",
"actionCopy": "Copiar seleção",
"actionPaste": "Colar",
"actionSendControlCode": "Enviar sinal Ctrl+letra",
"actionSendText": "Enviar mensagem de texto",
"actionRunSnippet": "Executar trecho de código existente",
"controlCodeLabel": "Carta",
"textLabel": "Texto a enviar",
"appendEnterLabel": "Pressione Enter após enviar",
"snippetLabel": "Trecho",
"selectSnippetPlaceholder": "Selecione um trecho",
"orphanedSnippetWarning": "trecho não encontrado",
"clipboardPermissionNote": "Pode solicitar permissão para acessar a área de transferência no primeiro uso em alguns navegadores."
},
"userProfile": {
"donateTitle": "Mantenha o Termix vivo",
"donateDescription": "O Termix é gratuito, auto-hospedado e desenvolvido por apenas algumas pessoas em seu tempo livre. Se ele lhe economizou tempo ou dinheiro, uma doação em criptomoedas ajuda a mantê-lo funcionando.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Aparência",
"sectionSecurity": "Segurança",
"sectionApiKeys": "Chaves de API",
"sectionData": "Dados",
"sectionC2sTunnels": "Túneis C2S",
"usernameLabel": "Nome de usuário",
"roleLabel": "Papel",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "terminal",
"commandAutocomplete": "Preenchimento automático de comandos",
"commandAutocompleteDesc": "Mostrar o recurso de autocompletar enquanto digita",
"keyboardShortcuts": "Atalhos de teclado",
"keyboardShortcutsDescription": "Personalize os atalhos de copiar, colar e comandos do terminal.",
"manageShortcuts": "Gerenciar",
"terminalLinkBehavior": "Clique no link do terminal",
"terminalLinkBehaviorDesc": "Comportamento padrão ao clicar em links no terminal.",
"historyTracking": "Rastreamento de histórico",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Inclua sua chave no",
"apiKeyUsageHintHeader": "cabeçalho.",
"apiKeyPermissionsHint": "As chaves herdam as permissões do usuário que as criou.",
"exportData": "Exportar meus dados",
"exportDataDesc": "Faça o download de um backup dos seus hosts, credenciais e configurações para transferir para outro dispositivo.",
"export": "Exportar",
"exporting": "Exportador...",
"importData": "Importar meus dados",
"importDataDesc": "Restaure seus hosts, credenciais e configurações a partir de um arquivo de backup .sqlite.",
"importDataSelected": "Selecionado: {{name}}",
"selectFile": "Selecionar arquivo",
"changeFile": "Mudar",
"import": "Importar",
"importing": "Importando...",
"exportSuccess": "Dados exportados com sucesso",
"exportFailed": "A exportação de dados falhou",
"importSelectFile": "Por favor, selecione um arquivo primeiro.",
"importCompleted": "Importação concluída: {{total}} itens importados, {{skipped}} ignorados",
"importFailed": "A importação falhou: {{error}}",
"roleUser": "Usuário",
"authMethodDual": "Autenticação dupla",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Colar",
"editQuickKeys": "Editar teclas de atalho",
"quickKeysTitle": "Teclas de atalho",
"quickKeysDesc": "Toque em × para remover. Suporta até 8 caracteres.",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Niciun server salvat",
"removeServer": "Elimina"
},
"migrationNotice": {
"title": "Termix Desktop rulează acum independent",
"body1": "Această aplicație se conecta direct la serverul Termix. A fost reelaborată pentru a rula complet independent, stocând gazdele și acreditările local, astfel încât să funcționeze în continuare offline. Sincronizarea bidirecțională cu un server Termix auto-găzduit este acum opțională.",
"body2": "Gazdele și acreditările existente sunt încă pe {{url}}. Activați sincronizarea la distanță și reconectați-vă la serverul respectiv pentru a le reactiva și a le menține sincronizate de acum înainte.",
"dismiss": "Nu acum",
"setUpSync": "Configurați sincronizarea la distanță"
},
"remoteSync": {
"title": "Sincronizare la distanță",
"description": "Opțional, conectați această aplicație desktop la un server Termix auto-găzduit pentru a sincroniza gazdele, acreditările și fragmentele de date pe toate dispozitivele. Aplicația funcționează întotdeauna complet offline, indiferent dacă vă conectați sau nu.",
"notConnected": "Neconectat",
"connected": "Conectat",
"connectedTo": "Conectat la {{url}}",
"lastSynced": "Ultima sincronizare {{time}}",
"neverSynced": "Nu s-a sincronizat niciodată",
"syncError": "Eroare de sincronizare: {{message}}",
"needsReauth": "Conectarea a expirat",
"connectButton": "Conectare la server",
"disconnectButton": "Deconecta",
"syncNowButton": "Sincronizează acum",
"syncing": "Se sincronizează...",
"serverUrl": "URL-ul serverului",
"enterServerUrl": "Introduceți adresa URL a serverului",
"mustIncludeProtocol": "URL-ul serverului trebuie să înceapă cu http:// sau https://",
"connectionTestFailed": "Nu s-a putut accesa un server Termix la adresa URL respectivă.",
"allowInvalidCertificate": "Permiteți certificatul nevalid",
"allowInvalidCertificateDesc": "A se utiliza numai pentru servere auto-găzduite de încredere, cu certificate autosemnate sau cu adresă IP.",
"savedServers": "Servere salvate",
"removeServer": "Elimina",
"continueButton": "Continua",
"cancelButton": "Anula",
"signInTitle": "Conectați-vă la {{url}}",
"originTitle": "Originea conexiunii",
"originDescription": "Alegeți în mod implicit de unde provin conexiunile SSH. Această opțiune poate fi ignorată pentru fiecare gazdă.",
"originLocal": "Acest dispozitiv (rețea locală)",
"originRemote": "Server la distanță",
"bannerReconnect": "Reconectați-vă",
"bannerMessage": "Sincronizarea la distanță necesită reautentificare"
},
"versionCheck": {
"error": "Eroare la verificarea versiunii",
"checkFailed": "Nu s-a putut verifica dacă există actualizări",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Suprascrierea acreditărilor de utilizator",
"overrideCredentialUsernameDesc": "Folosește numele de utilizator specificat mai sus în locul numelui de utilizator al acreditării",
"oidcUsernameHint": "Folosește $oidc.preferred_username pentru a înlocui numele de utilizator OIDC.",
"tailscaleUsernameHint": "Acesta trebuie să fie un utilizator Unix căruia îi este acordată identitatea Tailscale în ACL-ul SSH al rețelei tailnet, nu neapărat root.",
"jumpHostChain": "Lanțul gazdă Jump",
"portKnocking": "Lovitura în port",
"addKnock": "Adăugați port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Întârziere după (ms)",
"useSocks5Proxy": "Folosește proxy-ul SOCKS5",
"useSocks5ProxyDesc": "Dirijați conexiunea printr-un server proxy",
"connectionOrigin": "Originea conexiunii",
"connectionOriginDesc": "De unde provine conexiunea SSH a acestei gazde. Suprascrie setarea globală implicită a aplicației desktop.",
"connectionOriginDefault": "Utilizați implicit",
"connectionOriginLocal": "Acest dispozitiv (rețea locală)",
"connectionOriginRemote": "Server la distanță",
"proxyHost": "Gazdă proxy",
"proxyPort": "Port proxy",
"proxyUsername": "Nume de utilizator proxy",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Cian strălucitor",
"customThemeBrightWhite": "Alb strălucitor",
"customThemeResetTooltip": "Resetare la setările implicite",
"savedThemesLabel": "Teme salvate",
"saveAsGlobalTheme": "Salvați ca temă globală",
"saveGlobalThemeNamePrompt": "Introduceți un nume pentru această temă",
"saveGlobalThemeSuccess": "Tema a fost salvată",
"saveGlobalThemeError": "Nu s-a putut salva tema",
"applyGlobalThemeTooltip": "Aplică această temă",
"deleteGlobalThemeTooltip": "Ștergeți această temă",
"noSavedThemes": "Nicio temă salvată încă",
"syntaxHighlightingLabel": "Evidențierea sintaxei",
"syntaxHighlightingDesc": "Colorează ieșirea terminalului (erori, căi, IP-uri, timestamp-uri)",
"syntaxHighlightingCategories": "Evidențiați categoriile",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Lansează sau atașează automat la sesiunea tmux",
"enableSessionLogging": "Înregistrarea sesiunilor",
"enableSessionLoggingDesc": "Înregistrați ieșirea sesiunii terminale pentru o revizuire ulterioară",
"allowSessionSharing": "Permiteți partajarea sesiunii",
"allowSessionSharingDesc": "Permiteți partajarea sesiunilor live de pe această gazdă prin link sau cu alți utilizatori",
"enableCommandHistory": "Istoricul comenzilor",
"enableCommandHistoryDesc": "Înregistrați comenzile executate în acest terminal pentru istoric și completare automată",
"linkClickBehaviorLabel": "Comportamentul clicurilor pe link",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importat",
"proxmoxResultUpdated": "{{count}} actualizat",
"proxmoxResultFailed": "{{count}} a eșuat",
"proxmoxResultSkippedNoIp": "{{count}} omis (nu a fost găsită nicio adresă IP)",
"proxmoxImportComplete": "Import Proxmox finalizat: {{summary}}",
"proxmoxDiscoveryFailed": "Descoperirea a eșuat",
"proxmoxImportFailed": "Importul a eșuat",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Nu s-a putut salva acreditările",
"credentialNameRequired": "Vă rugăm să introduceți un nume pentru acreditare",
"credentialAuthRequired": "Adăugați o parolă, o cheie SSH sau ambele",
"createCredentialFromHostBtn": "Creați acreditări",
"createCredentialFromHostTitle": "Creați acreditări de la gazdă",
"createCredentialFromHostDesc": "Creați o intrare de acreditări reutilizabilă și partajabilă, preumplută cu numele de utilizator, parola și/sau cheia SSH actuale ale acestei gazde.",
"backToHosts": "Înapoi la Gazde",
"backToCredentials": "Înapoi la acreditări",
"pinned": "Fixat",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Folosește / pentru a separa niveluri și a crea foldere imbricate.",
"folderColor": "Culoare",
"folderIcon": "Pictogramă",
"folderCredential": "Acreditare",
"folderCredentialNone": "Nicio acreditare atribuită",
"folderCredentialHint": "Gazdele din acest folder care utilizează autentificarea „Credențiale stocate” fără a fi selectate propriile credențiale vor moșteni aceasta.",
"folderPreview": "Previzualizare",
"folderNameFallback": "Dosar fără titlu",
"createFolderButton": "Creați un folder",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Etichete",
"shareHost": "Gazdă partajată",
"shareHostTitle": "Distribuie: {{name}}",
"shareFolder": "Partajați folderul",
"shareFolderTitle": "Partajați folderul: {{name}}",
"folderSharedSuccessfully": "Gazdă(e) {{count}} partajată(e) în folder",
"failedToShareFolder": "Nu s-a putut partaja folderul",
"sharing": {
"loadError": "Nu s-au putut încărca datele de partajare. Vă rugăm să încercați din nou.",
"shareWithSection": "Distribuiți cu",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Distribuie ({{count}})",
"currentAccess": "Acces curent",
"noAccessEntries": "Această gazdă nu a fost încă partajată",
"folderShareSummary": "Gazdă(e) {{shared}} partajată din {{total}} în acest folder",
"grantedBy": "Acordat de",
"expires": "Expiră",
"expired": "Expirat",
@@ -1309,8 +1242,6 @@
"authMethod": "Metodă de autentificare",
"authTypeDirect": "Direct",
"authTypeCredential": "Acreditare",
"authTypeNone": "Nici unul",
"authTypeNoneDesc": "Nu sunt stocate acreditări. Vi se va solicita un nume de utilizator și o parolă de fiecare dată când vă conectați; acestea nu sunt salvate.",
"selectCredential": "Selectați o acreditare...",
"connectionSettings": "Setări de conectare",
"displaySettings": "Setări de afișare",
@@ -1328,7 +1259,6 @@
"rdpPort": "Port RDP",
"username": "Nume de utilizator",
"password": "Parolă",
"passwordSaved": "Parolă salvată, tastați pentru a o modifica",
"domain": "Domeniu",
"securityMode": "Mod de securitate",
"colorDepth": "Adâncimea culorii",
@@ -1494,60 +1424,7 @@
"expiresIn": "Expiră în {{duration}}",
"search": "Căutați conexiuni...",
"noSearchResults": "Nicio conexiune nu corespunde căutării dvs.",
"rename": "Redenumiți sesiunea",
"sectionSharedWithMe": "Distribuit cu mine",
"sharedBy": "Distribuit de {{username}}",
"join": "Alătură-te",
"sharedSessionLabel": "{{hostName}} (partajat)"
},
"sessionSharing": {
"guestView": {
"loading": "Conectare la sesiunea partajată...",
"linkInvalid": "Acest link de partajare este invalid, a expirat sau a fost revocat.",
"rateLimited": "Prea multe încercări, vă rugăm să încercați din nou în scurt timp",
"sessionEnded": "Această sesiune s-a încheiat",
"readOnlyBadge": "Doar vizualizare"
},
"modalTitle": "Partajați sesiunea",
"shareButton": "Distribuie",
"notReadyToShare": "Sesiunea nu este încă gata de partajare",
"modeTab": {
"link": "Legătură",
"user": "Utilizator"
},
"linkModeDescription": "Oricine are acest link se poate alătura, nu este necesar un cont.",
"userModeDescription": "Partajați cu un anumit utilizator care are deja acces la această gazdă. Dacă nu are încă acces, partajați mai întâi gazda cu acesta sau utilizați un link în schimb. După partajare, sesiunea apare în fila Conexiuni.",
"permissionLevel": {
"label": "Nivel de permisiune",
"readOnly": "Doar citire",
"readOnlyDescription": "Pot urmări sesiunea în direct, dar nu pot tasta sau interacționa.",
"readWrite": "Citire-scriere",
"readWriteDescription": "Poate tasta și interacționa cu sesiunea la fel ca proprietarul."
},
"expiryLabel": "Expirarea linkului",
"createLinkButton": "Creați un link",
"createShareButton": "Partajează cu utilizatorul",
"searchUsersPlaceholder": "Căutați utilizatori...",
"noUsersFound": "Nu au fost găsiți utilizatori",
"linkCreated": "Link de partajare creat",
"linkCopied": "Linkul a fost copiat în clipboard",
"copyLink": "Copiază linkul",
"shareCreated": "Sesiune partajată. Va apărea în fila Conexiuni.",
"shareFailed": "Nu s-a putut crea partajarea",
"userLacksHostAccess": "Utilizatorul respectiv nu are încă acces la această gazdă. Partajați mai întâi gazda cu acesta sau folosiți un link în schimb.",
"activeShares": "Acțiuni active",
"noActiveShares": "Nicio partajare activă pentru această sesiune",
"revoke": "Revoca",
"revokeConfirmTitle": "Revocați această partajare?",
"revokeConfirmDescription": "Oricine folosește această partajare va pierde accesul imediat.",
"revoked": "Acțiune revocată",
"revokeFailed": "Nu s-a putut revoca partajarea",
"joinCount": "{{count}} alătură-te",
"joinCount_other": "{{count}} se alătură",
"expiresAt": "Expiră la {{date}}",
"linkShareBadge": "Legătură",
"userShareBadge": "Utilizator: {{username}}",
"loadSharesFailed": "Nu s-au putut încărca partajările active"
"rename": "Redenumiți sesiunea"
},
"guacamole": {
"connecting": "Se conectează la sesiunea {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Reconectați-vă",
"retry": "Reîncercați",
"guacdUnavailable": "Serviciul desktop la distanță (guacd) nu este disponibil. Asigurați-vă că guacd rulează, este accesibil și configurat corect în setările de administrare.",
"credentialPromptTitle": "Introduceți acreditările RDP",
"credentialPromptDescription": "Această gazdă este configurată să solicite acreditări la conectare. Acestea sunt utilizate doar pentru această sesiune și nu sunt salvate.",
"connect": "Conectează-te",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Sesiune de reconectare",
"collapse": "Restrângeți bara de instrumente",
"expand": "Extindeți bara de instrumente",
"dragHandle": "Trageți pentru a repoziționa",
"switchToTrackpad": "Comutare la modul trackpad (tragere pentru mutarea cursorului, atingere pentru clic)",
"switchToTouch": "Comutare la modul tactil (atingere direct unde doriți să faceți clic)"
"dragHandle": "Trageți pentru a repoziționa"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Autentificare cu doi factori necesară",
"totpCodeLabel": "Cod de verificare",
"totpVerify": "Verifica",
"mfaPromptRequired": "Autentificare necesară",
"mfaPushRequired": "Autentificare push necesară",
"mfaMenuPlaceholder": "Introduceți răspunsul dvs.",
"mfaWaitingApproval": "Se așteaptă aprobarea pe dispozitivul dvs....",
"mfaSendRequest": "Trimiteți cererea",
"warpgateAuthRequired": "Autentificare Warpgate necesară",
"warpgateSecurityKey": "Cheie de securitate",
"warpgateAuthUrl": "URL de autentificare",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Rată limitată: Prea multe încercări de verificare. Vă rugăm să încercați din nou mai târziu.",
"resetCodeRateLimitedWithTime": "Rată limitată: Prea multe încercări de verificare. Vă rugăm să așteptați {{time}} secunde înainte de a încerca din nou.",
"authTokenSaveFailed": "Nu s-a putut salva jetonul de autentificare",
"failedToLoadServer": "Nu s-a putut încărca serverul",
"remoteServerRequired": "Este necesar un server la distanță. Conectați un server la distanță în Setări pentru a utiliza acest tip de conexiune."
"failedToLoadServer": "Nu s-a putut încărca serverul"
},
"messages": {
"registrationDisabled": "Înregistrarea unui cont nou este dezactivată în prezent de un administrator. Vă rugăm să vă conectați sau să contactați un administrator.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Setările SSL nu au putut fi salvate.",
"sslRequiresDomain": "Domeniul și adresa de e-mail sunt obligatorii",
"sslInfoNote": "După emiterea unui certificat, activați SSL în variabilele de mediu (ENABLE_SSL=true) și reporniți Termix.",
"sslManualOption": "Manual (încărcare certificat)",
"sslManualCert": "Certificat (PEM)",
"sslManualCertPlaceholder": "-----ÎNCEPERE CERTIFICAT-----",
"sslManualKey": "Cheie privată (PEM)",
"sslManualKeyPlaceholder": "-----ÎNCEPERE CHEIE PRIVATĂ-----",
"sslManualDesc": "Lipiți certificatul și cheia privată existente, inclusiv un lanț complet, dacă este necesar de către autoritatea de certificare.",
"sslManualUpload": "Încărcați și instalați certificatul",
"sslManualUploadLoading": "Se încarcă certificatul...",
"sslManualUploadSuccess": "Certificatul a fost încărcat și instalat cu succes",
"sslManualUploadFailed": "Încărcarea certificatului a eșuat",
"sslManualRequiresFields": "Certificatul și cheia privată sunt necesare",
"auditLogTotal": "{{total}} total intrări",
"auditLogEmpty": "Nu au fost găsite intrări în jurnalul de audit",
"auditLogSuccess": "Succes",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Istoricul comenzilor",
"commandHistoryEnabledDesc": "Permite înregistrarea istoricului comenzilor. Când este dezactivată, istoricul nu este salvat, indiferent de setările pentru fiecare gazdă.",
"updateCommandHistoryFailed": "Nu s-a putut actualiza setarea istoricului comenzilor",
"analyticsEnabled": "Distribuiți statistici de utilizare anonime",
"analyticsEnabledDesc": "Trimite un număr zilnic anonim de utilizatori, gazde și utilizare a funcțiilor pentru a ajuta la îmbunătățirea Termix. Nu sunt incluse niciodată date personale sau detalii de conexiune.",
"updateAnalyticsFailed": "Setările de analiză nu au putut fi actualizate",
"sessionSharingGloballyEnabled": "Permiteți partajarea sesiunii",
"sessionSharingGloballyEnabledDesc": "Permite partajarea sesiunilor live de terminal, RDP, VNC și Telnet la nivel de instanță. Suprascrie fiecare comutare de partajare per gazdă atunci când este dezactivată.",
"updateSessionSharingFailed": "Setările de partajare a sesiunii nu au putut fi actualizate.",
"sessionTimeout": "Expirare sesiune",
"hours": "ore",
"sessionTimeoutRange": "Min 1 oră · Max 720 ore",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Succes",
"executionFailed": "Eșuat"
},
"keybindings": {
"title": "Comenzi rapide de la tastatură",
"description": "Personalizați comenzile rapide de copiere, lipire și control din terminal sau asociați taste pentru a trimite text sau a rula un fragment de cod.",
"defaultsHeading": "Comenzi rapide încorporate",
"customHeading": "Comenzi rapide personalizate",
"addBinding": "Adăugați o comandă rapidă",
"addBindingTitle": "Adăugați o comandă rapidă",
"editBindingTitle": "Editare comandă rapidă",
"loading": "Încărcare...",
"noCustomBindings": "Încă nu există comenzi rapide personalizate",
"defaultBadge": "Implicit",
"customizedBadge": "Personalizat",
"customize": "Personalizare",
"resetToDefault": "Resetare la valorile implicite",
"resetAllToDefaults": "Resetați toate la valorile implicite",
"close": "Aproape",
"cancel": "Anula",
"saveBinding": "Salvați comanda rapidă",
"saveError": "Nu s-au putut salva comenzile rapide de la tastatură",
"pressKeysToRecord": "Combinație de taste",
"pressKeysPlaceholder": "Faceți clic și apăsați tastele",
"recording": "Apăsați tastele...",
"comboRequiredError": "Apăsați mai întâi o combinație de taste",
"textRequiredError": "Introduceți textul de trimis",
"controlCodeRequiredError": "Introduceți o singură literă pentru codul de control",
"snippetRequiredError": "Selectați un fragment",
"conflictWarning": "Această combinație este deja legată de {{combo}}. Salvarea va face ca ambele să se declanșeze.",
"actionLabel": "Acţiune",
"actionCopy": "Copiați selecția",
"actionPaste": "Pastă",
"actionSendControlCode": "Trimite semnal Ctrl+literă",
"actionSendText": "Trimiteți mesaj text",
"actionRunSnippet": "Execută fragmentul existent",
"controlCodeLabel": "Scrisoare",
"textLabel": "Text de trimis",
"appendEnterLabel": "Apăsați Enter după trimitere",
"snippetLabel": "Fragment",
"selectSnippetPlaceholder": "Selectați un fragment",
"orphanedSnippetWarning": "fragmentul nu a fost găsit",
"clipboardPermissionNote": "În unele browsere, este posibil să solicite permisiunea pentru clipboard la prima utilizare."
},
"userProfile": {
"donateTitle": "Menține Termix în viață",
"donateDescription": "Termix este gratuit, găzduit în regim self-hosting și construit de doar câțiva oameni în timpul lor liber. Dacă ți-a economisit timp sau bani, o donație în criptomonede ajută la menținerea activității.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Aspect",
"sectionSecurity": "Securitate",
"sectionApiKeys": "Chei API",
"sectionData": "Date",
"sectionC2sTunnels": "Tuneluri C2S",
"usernameLabel": "Nume de utilizator",
"roleLabel": "Rol",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Completare automată a comenzilor",
"commandAutocompleteDesc": "Afișează completarea automată în timpul tastării",
"keyboardShortcuts": "Comenzi rapide de la tastatură",
"keyboardShortcutsDescription": "Personalizați comenzile rapide pentru copiere, lipire și comenzi în terminal",
"manageShortcuts": "Gestionează",
"terminalLinkBehavior": "Clic pe linkul terminalului",
"terminalLinkBehaviorDesc": "Comportament implicit la clic pe linkuri în terminal",
"historyTracking": "Urmărirea istoricului",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Includeți cheia dvs. în",
"apiKeyUsageHintHeader": "antet.",
"apiKeyPermissionsHint": "Cheile moștenesc permisiunile utilizatorului care le creează.",
"exportData": "Exportați datele mele",
"exportDataDesc": "Descărcați o copie de rezervă a gazdelor, acreditărilor și setărilor pentru a o transfera pe un alt dispozitiv",
"export": "Export",
"exporting": "Exportator...",
"importData": "Importați datele mele",
"importDataDesc": "Restaurați gazdele, acreditările și setările dintr-un fișier de rezervă .sqlite",
"importDataSelected": "Selectat: {{name}}",
"selectFile": "Selectați fișierul",
"changeFile": "Schimba",
"import": "Import",
"importing": "Importare...",
"exportSuccess": "Datele au fost exportate cu succes",
"exportFailed": "Exportul datelor a eșuat",
"importSelectFile": "Vă rugăm să selectați mai întâi un fișier",
"importCompleted": "Import finalizat: {{total}} articole importate, {{skipped}} omis",
"importFailed": "Importul a eșuat: {{error}}",
"roleUser": "Utilizator",
"authMethodDual": "Autentificare duală",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PagUp",
"pageDown": "Pagină următoare",
"delete": "Șterge",
"paste": "Pastă",
"editQuickKeys": "Editați tastele rapide",
"quickKeysTitle": "Taste rapide",
"quickKeysDesc": "Atingeți × pentru a elimina. Acceptă până la 8 caractere.",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Нема сачуваних сервера",
"removeServer": "Уклони"
},
"migrationNotice": {
"title": "Termix Desktop сада ради самостално",
"body1": "Ова апликација се раније директно повезивала са вашим Termix сервером. Прерађена је тако да ради потпуно самостално, чувајући хостове и акредитиве локално како би наставила да ради ван мреже. Двосмерна синхронизација са самостално хостованим Termix сервером је сада опционална.",
"body2": "Ваши постојећи хостови и акредитиви су и даље на {{url}}. Укључите даљинску синхронизацију и поново се повежите са тим сервером да бисте их вратили и одржали синхронизацију у будућности.",
"dismiss": "Не сада",
"setUpSync": "Подешавање даљинске синхронизације"
},
"remoteSync": {
"title": "Даљинска синхронизација",
"description": "Опционо, повежите ову десктоп апликацију са самостално хостованим Termix сервером да бисте синхронизовали своје хостове, акредитиве и исечке кода на свим уређајима. Апликација увек ради потпуно ван мреже, без обзира да ли се повежете или не.",
"notConnected": "Није повезано",
"connected": "Повезано",
"connectedTo": "Повезано са {{url}}",
"lastSynced": "Последња синхронизација {{time}}",
"neverSynced": "Никада није синхронизовано",
"syncError": "Грешка у синхронизацији: {{message}}",
"needsReauth": "Пријава је истекла",
"connectButton": "Повежи се са сервером",
"disconnectButton": "Прекини везу",
"syncNowButton": "Синхронизуј сада",
"syncing": "Синхронизација...",
"serverUrl": "URL сервера",
"enterServerUrl": "Молимо вас да унесете URL адресу сервера",
"mustIncludeProtocol": "URL сервера мора да почиње са http:// или https://",
"connectionTestFailed": "Није могуће доћи до Termix сервера на тој URL адреси",
"allowInvalidCertificate": "Дозволи неважећи сертификат",
"allowInvalidCertificateDesc": "Користите само за поуздане самохостоване сервере са самопотписаним сертификатима или сертификатима за IP адресу.",
"savedServers": "Сачувани сервери",
"removeServer": "Уклони",
"continueButton": "Настави",
"cancelButton": "Откажи",
"signInTitle": "Пријавите се на {{url}}",
"originTitle": "Порекло везе",
"originDescription": "Изаберите одакле SSH везе потичу подразумевано. Ово се може заменити за сваки хост.",
"originLocal": "Овај уређај (локална мрежа)",
"originRemote": "Удаљени сервер",
"bannerReconnect": "Поново се повежите",
"bannerMessage": "Даљинска синхронизација захтева поновну аутентификацију"
},
"versionCheck": {
"error": "Грешка при провери верзије",
"checkFailed": "Провера ажурирања није успела",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Замени корисничко име акредитива",
"overrideCredentialUsernameDesc": "Користите корисничко име наведено горе уместо корисничког имена акредитива",
"oidcUsernameHint": "Користите $oidc.preferred_username да бисте заменили своје OIDC корисничко име.",
"tailscaleUsernameHint": "Ово мора бити Unix корисник чији је идентитет на Tailscale-у додељен у SSH ACL-у Tailnet-а, не нужно root.",
"jumpHostChain": "Скок ланца хоста",
"portKnocking": "Лук Нокинг",
"addKnock": "Додај порт",
@@ -690,11 +650,6 @@
"delayAfterMs": "Кашњење после (ms)",
"useSocks5Proxy": "Користите SOCKS5 прокси",
"useSocks5ProxyDesc": "Усмерите везу преко прокси сервера",
"connectionOrigin": "Порекло везе",
"connectionOriginDesc": "Одакле потиче SSH веза овог хоста. Замењује глобално подразумевано подешавање десктоп апликације.",
"connectionOriginDefault": "Користи подразумевано",
"connectionOriginLocal": "Овај уређај (локална мрежа)",
"connectionOriginRemote": "Удаљени сервер",
"proxyHost": "Прокси хост",
"proxyPort": "Прокси порт",
"proxyUsername": "Корисничко име проксија",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Светло цијан",
"customThemeBrightWhite": "Светло бела",
"customThemeResetTooltip": "Ресетуј на подразумеване вредности",
"savedThemesLabel": "Сачуване теме",
"saveAsGlobalTheme": "Сачувај као глобалну тему",
"saveGlobalThemeNamePrompt": "Унесите име за ову тему",
"saveGlobalThemeSuccess": "Тема је сачувана",
"saveGlobalThemeError": "Чување теме није успело",
"applyGlobalThemeTooltip": "Примени ову тему",
"deleteGlobalThemeTooltip": "Обриши ову тему",
"noSavedThemes": "Још нема сачуваних тема",
"syntaxHighlightingLabel": "Истицање синтаксе",
"syntaxHighlightingDesc": "Обоји излаз терминала (грешке, путање, IP адресе, временске ознаке)",
"syntaxHighlightingCategories": "Истакните категорије",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Аутоматски покрени или прикључи се tmux сесији",
"enableSessionLogging": "Записивање сесије",
"enableSessionLoggingDesc": "Снимите излаз терминалне сесије за каснији преглед",
"allowSessionSharing": "Дозволи дељење сесије",
"allowSessionSharingDesc": "Дозволите дељење сесија уживо на овом хосту путем линка или са другим корисницима",
"enableCommandHistory": "Историја команди",
"enableCommandHistoryDesc": "Снимање команди које се извршавају у овом терминалу за историју и аутоматско довршавање",
"linkClickBehaviorLabel": "Понашање при клику на линк",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} увезено",
"proxmoxResultUpdated": "{{count}} ажурирано",
"proxmoxResultFailed": "{{count}} није успело",
"proxmoxResultSkippedNoIp": "{{count}} прескочено (није пронађена IP адреса)",
"proxmoxImportComplete": "Увоз Proxmox-а завршен: {{summary}}",
"proxmoxDiscoveryFailed": "Откривање није успело",
"proxmoxImportFailed": "Увоз није успео",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Чување акредитива није успело",
"credentialNameRequired": "Унесите име за акредитив",
"credentialAuthRequired": "Додајте лозинку, SSH кључ или обоје",
"createCredentialFromHostBtn": "Креирај акредитив",
"createCredentialFromHostTitle": "Креирај акредитив од хоста",
"createCredentialFromHostDesc": "Направите унос акредитива за вишекратну употребу и дељење, унапред попуњен тренутним корисничким именом, лозинком и/или SSH кључем овог хоста.",
"backToHosts": "Назад на хостове",
"backToCredentials": "Назад на акредитиве",
"pinned": "Закачено",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Користите / да бисте раздвојили нивое и креирали угнежђене фасцикле.",
"folderColor": "Боја",
"folderIcon": "Икона",
"folderCredential": "Акредитив",
"folderCredentialNone": "Није додељен акредитив",
"folderCredentialHint": "Домаћини у овој фасцикли који користе ауторизацију „Сачувани акредитиви“ без изабраних сопствених акредитива наследиће овај.",
"folderPreview": "Преглед",
"folderNameFallback": "Неименована фасцикла",
"createFolderButton": "Направи фасциклу",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Ознаке",
"shareHost": "Дели хост",
"shareHostTitle": "Подели: {{name}}",
"shareFolder": "Дели фасциклу",
"shareFolderTitle": "Дели фасциклу: {{name}}",
"folderSharedSuccessfully": "Дељено {{count}} хост(ова) у фолдеру",
"failedToShareFolder": "Дељење фасцикле није успело",
"sharing": {
"loadError": "Учитавање података о дељењу није успело. Молимо покушајте поново.",
"shareWithSection": "Подели са",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Подели ({{count}})",
"currentAccess": "Тренутни приступ",
"noAccessEntries": "Овај хост још није дељен",
"folderShareSummary": "Дељено {{shared}} од {{total}} хост(ова) у овој фасцикли",
"grantedBy": "Додељено од стране",
"expires": "Истиче",
"expired": "Истекло",
@@ -1309,8 +1242,6 @@
"authMethod": "Метода овлашћења",
"authTypeDirect": "Директно",
"authTypeCredential": "Акредитив",
"authTypeNone": "Ниједан",
"authTypeNoneDesc": "Акредитиви се не чувају. Сваки пут када се повежете, биће вам затражено корисничко име и лозинка; они се не чувају.",
"selectCredential": "Изаберите акредитив...",
"connectionSettings": "Подешавања везе",
"displaySettings": "Подешавања екрана",
@@ -1328,7 +1259,6 @@
"rdpPort": "РДП порт",
"username": "Корисничко име",
"password": "Лозинка",
"passwordSaved": "Лозинка сачувана, унесите текст да бисте је променили",
"domain": "Домен",
"securityMode": "Безбедносни режим",
"colorDepth": "Дубина боје",
@@ -1494,60 +1424,7 @@
"expiresIn": "Истиче за {{duration}}",
"search": "Претражи везе...",
"noSearchResults": "Ниједна веза не одговара вашој претрази",
"rename": "Преименуј сесију",
"sectionSharedWithMe": "Дељено са мном",
"sharedBy": "Дели {{username}}",
"join": "Придружи се",
"sharedSessionLabel": "{{hostName}} (дељено)"
},
"sessionSharing": {
"guestView": {
"loading": "Повезивање са дељеном сесијом...",
"linkInvalid": "Овај линк за дељење је неважећи, истекао је или је опозван",
"rateLimited": "Превише покушаја, покушајте поново ускоро",
"sessionEnded": "Ова сесија је завршена",
"readOnlyBadge": "Само преглед"
},
"modalTitle": "Дели сесију",
"shareButton": "Дели",
"notReadyToShare": "Сесија још није спремна за дељење",
"modeTab": {
"link": "Линк",
"user": "Корисник"
},
"linkModeDescription": "Свако ко има овај линк може да се придружи, није потребан налог.",
"userModeDescription": "Делите са одређеним корисником који већ има приступ овом хосту. Ако још немају приступ, прво поделите хост са њима или користите линк. Након дељења, сесија се појављује на њиховој картици „Везе“.",
"permissionLevel": {
"label": "Ниво дозволе",
"readOnly": "Само за читање",
"readOnlyDescription": "Може да гледа сесију уживо, али не може да куца или да комуницира.",
"readWrite": "Читање и писање",
"readWriteDescription": "Може да куца и интерагује са сесијом баш као и власник."
},
"expiryLabel": "Истек линка",
"createLinkButton": "Направи линк",
"createShareButton": "Подели са корисником",
"searchUsersPlaceholder": "Претражи кориснике...",
"noUsersFound": "Није пронађен ниједан корисник",
"linkCreated": "Линк за дељење је направљен",
"linkCopied": "Линк је копиран у међуспремник",
"copyLink": "Копирај линк",
"shareCreated": "Сесија је дељена. Појавиће се на њиховој картици „Везе“.",
"shareFailed": "Није успело креирање дељења",
"userLacksHostAccess": "Тај корисник још увек нема приступ овом хосту. Прво поделите хост са њим или користите линк.",
"activeShares": "Активне акције",
"noActiveShares": "Нема активних дељења за ову сесију",
"revoke": "Поништи",
"revokeConfirmTitle": "Да ли желите да опозовете ово дељење?",
"revokeConfirmDescription": "Свако ко користи ово дељење ће одмах изгубити приступ.",
"revoked": "Дељење опозвано",
"revokeFailed": "Опозивање дељења није успело",
"joinCount": "{{count}} придружи се",
"joinCount_other": "{{count}} придруживања",
"expiresAt": "Истиче {{date}}",
"linkShareBadge": "Линк",
"userShareBadge": "Корисник: {{username}}",
"loadSharesFailed": "Учитавање активних дељења није успело"
"rename": "Преименуј сесију"
},
"guacamole": {
"connecting": "Повезивање са {{type}} сесијом...",
@@ -1559,9 +1436,6 @@
"reconnect": "Поново се повежите",
"retry": "Покушај поново",
"guacdUnavailable": "Услуга удаљеног рачунара (guacd) није доступна. Молимо вас да се уверите да је guacd покренут, доступан и правилно конфигурисан у администраторским подешавањима.",
"credentialPromptTitle": "Унесите RDP акредитиве",
"credentialPromptDescription": "Овај хост је подешен да тражи акредитиве при повезивању. Они се користе само за ову сесију и не чувају се.",
"connect": "Повежи се",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Поновно повезивање сесије",
"collapse": "Савиј траку са алаткама",
"expand": "Прошири траку са алаткама",
"dragHandle": "Превуците да бисте променили положај",
"switchToTrackpad": "Пребаците се у режим додирне табле (превуците да бисте померили курсор, додирните да бисте кликнули)",
"switchToTouch": "Пребаците се на режим додира (додирните директно место где желите да кликнете)"
"dragHandle": "Превуците да бисте променили положај"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Потребна је двофакторска аутентификација",
"totpCodeLabel": "Верификациони код",
"totpVerify": "Верификуј",
"mfaPromptRequired": "Потребна је аутентификација",
"mfaPushRequired": "Потребна је аутентификација путем поруке",
"mfaMenuPlaceholder": "Унесите свој одговор",
"mfaWaitingApproval": "Чекање одобрења на вашем уређају...",
"mfaSendRequest": "Пошаљи захтев",
"warpgateAuthRequired": "Потребна је аутентификација Варпгејта",
"warpgateSecurityKey": "Безбедносни кључ",
"warpgateAuthUrl": "URL адреса за аутентификацију",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Ограничена брзина: Превише покушаја верификације. Молимо покушајте поново касније.",
"resetCodeRateLimitedWithTime": "Ограничена брзина: Превише покушаја верификације. Молимо сачекајте {{time}} секунди пре него што покушате поново.",
"authTokenSaveFailed": "Чување токена за аутентификацију није успело",
"failedToLoadServer": "Учитавање сервера није успело",
"remoteServerRequired": "Потребан је удаљени сервер. Повежите се са удаљеним сервером у Подешавањима да бисте користили овај тип везе."
"failedToLoadServer": "Учитавање сервера није успело"
},
"messages": {
"registrationDisabled": "Регистрација новог налога је тренутно онемогућена од стране администратора. Молимо вас да се пријавите или контактирате администратора.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Чување SSL подешавања није успело",
"sslRequiresDomain": "Домен и имејл су обавезни",
"sslInfoNote": "Након издавања сертификата, омогућите SSL у променљивим окружења (ENABLE_SSL=true) и поново покрените Termix.",
"sslManualOption": "Ручно (отпремање сертификата)",
"sslManualCert": "Сертификат (ПЕМ)",
"sslManualCertPlaceholder": "-----ПОЧЕТАК СЕРТИФИКАТА-----",
"sslManualKey": "Приватни кључ (PEM)",
"sslManualKeyPlaceholder": "-----ПОЧЕТАК ПРИВАТНОГ КЉУЧА-----",
"sslManualDesc": "Налепите постојећи сертификат и приватни кључ, укључујући цео ланац ако то захтева ваш ЦА.",
"sslManualUpload": "Сертификат за отпремање и инсталирање",
"sslManualUploadLoading": "Отпремање сертификата...",
"sslManualUploadSuccess": "Сертификат је успешно отпремљен и инсталиран",
"sslManualUploadFailed": "Отпремање сертификата није успело",
"sslManualRequiresFields": "Потребни су сертификат и приватни кључ",
"auditLogTotal": "{{total}} укупно уноса",
"auditLogEmpty": "Нису пронађени уноси у евиденцију ревизије",
"auditLogSuccess": "Успех",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Историја команди",
"commandHistoryEnabledDesc": "Дозволи снимање историје команди. Када је онемогућено, историја се не чува без обзира на подешавања за сваки хост.",
"updateCommandHistoryFailed": "Ажурирање подешавања историје команди није успело",
"analyticsEnabled": "Делите анонимну статистику коришћења",
"analyticsEnabledDesc": "Шаље анонимни дневни број корисника, хостова и коришћења функција како би се побољшао Termix. Никада се не укључују лични подаци или детаљи везе.",
"updateAnalyticsFailed": "Ажурирање подешавања аналитике није успело",
"sessionSharingGloballyEnabled": "Дозволи дељење сесије",
"sessionSharingGloballyEnabledDesc": "Дозвољава дељење сесија уживо терминала, RDP, VNC и Telnet сесија на нивоу целе инстанце. Замењује сваки прекидач за дељење по хосту када је онемогућен.",
"updateSessionSharingFailed": "Ажурирање подешавања дељења сесије није успело",
"sessionTimeout": "Временско ограничење сесије",
"hours": "сати",
"sessionTimeoutRange": "Мин. 1 сат · Макс. 720 сати",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Успех",
"executionFailed": "Неуспешно"
},
"keybindings": {
"title": "Пречице на тастатури",
"description": "Прилагодите пречице за копирање, лепљење и контролу терминала или повежите тастере за слање текста или покретање исечка кода.",
"defaultsHeading": "Уграђене пречице",
"customHeading": "Прилагођене пречице",
"addBinding": "Додај пречицу",
"addBindingTitle": "Додај пречицу",
"editBindingTitle": "Измени пречицу",
"loading": "Учитавање...",
"noCustomBindings": "Још нема прилагођених пречица",
"defaultBadge": "Подразумевано",
"customizedBadge": "Прилагођено",
"customize": "Прилагоди",
"resetToDefault": "Ресетуј на подразумеване вредности",
"resetAllToDefaults": "Ресетуј све на подразумеване вредности",
"close": "Затвори",
"cancel": "Откажи",
"saveBinding": "Сачувај пречицу",
"saveError": "Чување пречица на тастатури није успело",
"pressKeysToRecord": "Комбинација тастера",
"pressKeysPlaceholder": "Кликните и притисните тастере",
"recording": "Притисните тастере...",
"comboRequiredError": "Прво притисните комбинацију тастера",
"textRequiredError": "Унесите текст за слање",
"controlCodeRequiredError": "Унесите једно слово за контролни код",
"snippetRequiredError": "Изаберите исечак",
"conflictWarning": "Ова комбинација је већ повезана са {{combo}}. Чување ће активирати обе.",
"actionLabel": "Акција",
"actionCopy": "Копирај избор",
"actionPaste": "Залепи",
"actionSendControlCode": "Пошаљи сигнал Ctrl+слово",
"actionSendText": "Пошаљи текст",
"actionRunSnippet": "Покрени постојећи исечак кода",
"controlCodeLabel": "Писмо",
"textLabel": "Текст за слање",
"appendEnterLabel": "Притисните Ентер након слања",
"snippetLabel": "Исечак",
"selectSnippetPlaceholder": "Изаберите исечак",
"orphanedSnippetWarning": "фрагмент није пронађен",
"clipboardPermissionNote": "Може затражити дозволу за међуспремник при првој употреби у неким прегледачима."
},
"userProfile": {
"donateTitle": "Нека Термикс остане жив",
"donateDescription": "Termix је бесплатан, самостално хостован и изградило га је само неколико људи у своје слободно време. Ако вам је уштедео време или новац, донација криптовалута помаже да се одржи.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Изглед",
"sectionSecurity": "Безбедност",
"sectionApiKeys": "API кључеви",
"sectionData": "Подаци",
"sectionC2sTunnels": "C2S тунели",
"usernameLabel": "Корисничко име",
"roleLabel": "Улога",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Терминал",
"commandAutocomplete": "Аутоматско довршавање команди",
"commandAutocompleteDesc": "Прикажи аутоматско довршавање током куцања",
"keyboardShortcuts": "Пречице на тастатури",
"keyboardShortcutsDescription": "Прилагодите пречице за копирање, лепљење и команде у терминалу",
"manageShortcuts": "Управљај",
"terminalLinkBehavior": "Кликните на линк терминала",
"terminalLinkBehaviorDesc": "Подразумевано понашање при клику на линкове у терминалу",
"historyTracking": "Праћење историје",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Укључите свој кључ у",
"apiKeyUsageHintHeader": "заглавље.",
"apiKeyPermissionsHint": "Кључеви наслеђују дозволе корисника који их креира.",
"exportData": "Извези моје податке",
"exportDataDesc": "Преузмите резервну копију својих хостова, акредитива и подешавања да бисте их пренели на други уређај",
"export": "Извоз",
"exporting": "Извоз...",
"importData": "Увези моје податке",
"importDataDesc": "Вратите своје хостове, акредитиве и подешавања из .sqlite датотеке резервне копије",
"importDataSelected": "Изабрано: {{name}}",
"selectFile": "Изаберите датотеку",
"changeFile": "Промена",
"import": "Увоз",
"importing": "Увоз...",
"exportSuccess": "Подаци су успешно извезени",
"exportFailed": "Извоз података није успео",
"importSelectFile": "Прво изаберите датотеку",
"importCompleted": "Увоз завршен: {{total}} ставки је увезено, {{skipped}} прескочено",
"importFailed": "Увоз није успео: {{error}}",
"roleUser": "Корисник",
"authMethodDual": "Двострука аутентификација",
"authMethodOidc": "ОИДЦ",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "ПгДн",
"delete": "Дел",
"paste": "Залепи",
"editQuickKeys": "Уреди брзе тастере",
"quickKeysTitle": "Брзи тастери",
"quickKeysDesc": "Додирните × да бисте уклонили. Подржава до 8 знакова.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Inga sparade servrar",
"removeServer": "Ta bort"
},
"migrationNotice": {
"title": "Termix Desktop körs nu fristående",
"body1": "Den här appen brukade ansluta direkt till din Termix-server. Den har omarbetats för att köras helt självständigt och lagrar värdar och inloggningsuppgifter lokalt så att den fortsätter att fungera offline. Tvåvägssynkronisering med en egen Termix-server är nu valfritt.",
"body2": "Dina befintliga värdar och inloggningsuppgifter finns fortfarande på {{url}}. Aktivera fjärrsynkronisering och återanslut till servern för att få tillbaka dem och hålla båda synkroniserade framöver.",
"dismiss": "Inte nu",
"setUpSync": "Konfigurera fjärrsynkronisering"
},
"remoteSync": {
"title": "Fjärrsynkronisering",
"description": "Anslut alternativt den här skrivbordsappen till en självhostad Termix-server för att synkronisera dina värdar, inloggningsuppgifter och kodavsnitt mellan enheter. Appen fungerar alltid helt offline oavsett om du ansluter eller inte.",
"notConnected": "Inte ansluten",
"connected": "Ansluten",
"connectedTo": "Ansluten till {{url}}",
"lastSynced": "Senast synkroniserad {{time}}",
"neverSynced": "Aldrig synkroniserad",
"syncError": "Synkroniseringsfel: {{message}}",
"needsReauth": "Inloggningen har upphört",
"connectButton": "Anslut till servern",
"disconnectButton": "Koppla från",
"syncNowButton": "Synkronisera nu",
"syncing": "Synkroniserar...",
"serverUrl": "Server-URL",
"enterServerUrl": "Ange en server-URL",
"mustIncludeProtocol": "Serverns URL måste börja med http:// eller https://",
"connectionTestFailed": "Kunde inte nå en Termix-server på den URL:en",
"allowInvalidCertificate": "Tillåt ogiltigt certifikat",
"allowInvalidCertificateDesc": "Använd endast för betrodda, självhostade servrar med självsignerade certifikat eller IP-adresscertifikat.",
"savedServers": "Sparade servrar",
"removeServer": "Ta bort",
"continueButton": "Fortsätta",
"cancelButton": "Avboka",
"signInTitle": "Logga in på {{url}}",
"originTitle": "Anslutningens ursprung",
"originDescription": "Välj var SSH-anslutningar kommer ifrån som standard. Detta kan åsidosättas per värd.",
"originLocal": "Den här enheten (lokalt nätverk)",
"originRemote": "Fjärrserver",
"bannerReconnect": "Återanslut",
"bannerMessage": "Fjärrsynkronisering behöver autentiseras på nytt"
},
"versionCheck": {
"error": "Versionskontrollfel",
"checkFailed": "Misslyckades med att söka efter uppdateringar",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Åsidosätt användarnamn för inloggningsuppgifter",
"overrideCredentialUsernameDesc": "Använd användarnamnet som anges ovan istället för användarnamnet för inloggningsuppgifterna",
"oidcUsernameHint": "Använd $oidc.preferred_username för att ersätta ditt OIDC-inloggningsnamn.",
"tailscaleUsernameHint": "Detta måste vara en Unix-användare som din Tailscale-identitet är beviljad i tailnätets SSH ACL, inte nödvändigtvis root.",
"jumpHostChain": "Hoppa värdkedja",
"portKnocking": "Port Knocking",
"addKnock": "Lägg till port",
@@ -690,11 +650,6 @@
"delayAfterMs": "Fördröjning efter (ms)",
"useSocks5Proxy": "Använd SOCKS5-proxy",
"useSocks5ProxyDesc": "Led anslutning via en proxyserver",
"connectionOrigin": "Anslutningens ursprung",
"connectionOriginDesc": "Varifrån den här värdens SSH-anslutning kommer. Åsidosätter skrivbordsappens globala standardvärde.",
"connectionOriginDefault": "Använd standard",
"connectionOriginLocal": "Den här enheten (lokalt nätverk)",
"connectionOriginRemote": "Fjärrserver",
"proxyHost": "Proxyvärd",
"proxyPort": "Proxyport",
"proxyUsername": "Proxy-användarnamn",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Ljus cyan",
"customThemeBrightWhite": "Klarvitt",
"customThemeResetTooltip": "Återställ till standardinställningarna",
"savedThemesLabel": "Sparade teman",
"saveAsGlobalTheme": "Spara som globalt tema",
"saveGlobalThemeNamePrompt": "Ange ett namn för detta tema",
"saveGlobalThemeSuccess": "Temat sparat",
"saveGlobalThemeError": "Misslyckades med att spara temat",
"applyGlobalThemeTooltip": "Använd detta tema",
"deleteGlobalThemeTooltip": "Ta bort det här temat",
"noSavedThemes": "Inga sparade teman ännu",
"syntaxHighlightingLabel": "Syntaxmarkering",
"syntaxHighlightingDesc": "Färglägg terminalutdata (fel, sökvägar, IP-adresser, tidsstämplar)",
"syntaxHighlightingCategories": "Markera kategorier",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Starta eller anslut automatiskt till tmux-session",
"enableSessionLogging": "Sessionsloggning",
"enableSessionLoggingDesc": "Spela in utdata från terminalsessionen för senare granskning",
"allowSessionSharing": "Tillåt sessionsdelning",
"allowSessionSharingDesc": "Tillåt att livesessioner på den här värden delas via länk eller med andra användare",
"enableCommandHistory": "Kommandohistorik",
"enableCommandHistoryDesc": "Record-kommandon körs i den här terminalen för historik och autofullständig ifyllning",
"linkClickBehaviorLabel": "Länkklickbeteende",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} importerad",
"proxmoxResultUpdated": "{{count}} uppdaterad",
"proxmoxResultFailed": "{{count}} misslyckades",
"proxmoxResultSkippedNoIp": "{{count}} hoppades över (ingen IP-adress hittades)",
"proxmoxImportComplete": "Proxmox-import klar: {{summary}}",
"proxmoxDiscoveryFailed": "Upptäckten misslyckades",
"proxmoxImportFailed": "Importen misslyckades",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Misslyckades med att spara inloggningsuppgifterna",
"credentialNameRequired": "Ange ett namn för autentiseringsuppgifterna",
"credentialAuthRequired": "Lägg till ett lösenord, en SSH-nyckel eller båda",
"createCredentialFromHostBtn": "Skapa autentiseringsuppgifter",
"createCredentialFromHostTitle": "Skapa autentiseringsuppgifter från värden",
"createCredentialFromHostDesc": "Skapa en återanvändbar, delbar autentiseringsuppgift som är förifylld med värdens nuvarande användarnamn, lösenord och/eller SSH-nyckel.",
"backToHosts": "Tillbaka till värdar",
"backToCredentials": "Tillbaka till inloggningsuppgifter",
"pinned": "Fäst",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Använd / för att separera nivåer och skapa kapslade mappar.",
"folderColor": "Färg",
"folderIcon": "Ikon",
"folderCredential": "Referenser",
"folderCredentialNone": "Ingen inloggningsuppgifter har tilldelats",
"folderCredentialHint": "Värdar i den här mappen som använder autentiseringsuppgifterna \"Lagrad autentiseringsuppgift\" utan att ha valt sina egna autentiseringsuppgifter kommer att ärva den här.",
"folderPreview": "Förhandsvisning",
"folderNameFallback": "Namnlös mapp",
"createFolderButton": "Skapa mapp",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Taggar",
"shareHost": "Dela värd",
"shareHostTitle": "Dela: {{name}}",
"shareFolder": "Dela mapp",
"shareFolderTitle": "Dela mapp: {{name}}",
"folderSharedSuccessfully": "Delade {{count}} värd(ar) i mappen",
"failedToShareFolder": "Misslyckades med att dela mappen",
"sharing": {
"loadError": "Misslyckades med att ladda delningsdata. Försök igen.",
"shareWithSection": "Dela med",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Dela ({{count}})",
"currentAccess": "Nuvarande åtkomst",
"noAccessEntries": "Denna värd har inte delats ännu",
"folderShareSummary": "Delad {{shared}} av {{total}} värd(ar) i den här mappen",
"grantedBy": "Beviljad av",
"expires": "Utgår",
"expired": "Utgången",
@@ -1309,8 +1242,6 @@
"authMethod": "Autentiseringsmetod",
"authTypeDirect": "Direkt",
"authTypeCredential": "Referenser",
"authTypeNone": "Ingen",
"authTypeNoneDesc": "Inga inloggningsuppgifter lagras. Du kommer att bli ombedd att ange ett användarnamn och lösenord varje gång du ansluter; de sparas inte.",
"selectCredential": "Välj en behörighetsinloggning...",
"connectionSettings": "Anslutningsinställningar",
"displaySettings": "Skärminställningar",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-port",
"username": "Användarnamn",
"password": "Lösenord",
"passwordSaved": "Lösenordet sparats, skriv för att ändra",
"domain": "Domän",
"securityMode": "Säkerhetsläge",
"colorDepth": "Färgdjup",
@@ -1494,60 +1424,7 @@
"expiresIn": "Utgår om {{duration}}",
"search": "Sök efter kopplingar...",
"noSearchResults": "Inga kontakter matchar din sökning",
"rename": "Byt namn på sessionen",
"sectionSharedWithMe": "Delade med mig",
"sharedBy": "Delat av {{username}}",
"join": "Ansluta sig till",
"sharedSessionLabel": "{{hostName}} (delad)"
},
"sessionSharing": {
"guestView": {
"loading": "Ansluter till delad session...",
"linkInvalid": "Den här delningslänken är ogiltig, har löpt ut eller har återkallats",
"rateLimited": "För många försök, försök igen snart",
"sessionEnded": "Den här sessionen är avslutad",
"readOnlyBadge": "Endast visning"
},
"modalTitle": "Dela session",
"shareButton": "Dela",
"notReadyToShare": "Sessionen är inte redo att delas ännu",
"modeTab": {
"link": "Länk",
"user": "Användare"
},
"linkModeDescription": "Vem som helst med den här länken kan gå med, inget konto krävs.",
"userModeDescription": "Dela med en specifik användare som redan har åtkomst till den här värden. Om de inte har åtkomst ännu, dela värden med dem först eller använd en länk istället. När sessionen har delats visas den på deras flik Anslutningar.",
"permissionLevel": {
"label": "Behörighetsnivå",
"readOnly": "Skrivskyddad",
"readOnlyDescription": "Kan titta på sessionen live men kan inte skriva eller interagera.",
"readWrite": "Läs-skriv",
"readWriteDescription": "Kan skriva och interagera med sessionen precis som ägaren."
},
"expiryLabel": "Länkens utgångsdatum",
"createLinkButton": "Skapa länk",
"createShareButton": "Dela med användaren",
"searchUsersPlaceholder": "Sök användare...",
"noUsersFound": "Inga användare hittades",
"linkCreated": "Delningslänk skapad",
"linkCopied": "Länk kopierad till urklipp",
"copyLink": "Kopiera länk",
"shareCreated": "Sessionen delade. Den kommer att visas på fliken Anslutningar.",
"shareFailed": "Misslyckades med att skapa delningen",
"userLacksHostAccess": "Den användaren har inte åtkomst till den här värden ännu. Dela värden med dem först, eller använd en länk istället.",
"activeShares": "Aktiva aktier",
"noActiveShares": "Inga aktiva delningar för den här sessionen",
"revoke": "Återkalla",
"revokeConfirmTitle": "Återkalla den här delningen?",
"revokeConfirmDescription": "Alla som använder den här delningen kommer att förlora åtkomsten omedelbart.",
"revoked": "Delning återkallad",
"revokeFailed": "Misslyckades med att återkalla delningen",
"joinCount": "{{count}} gå med",
"joinCount_other": "{{count}} ansluter sig",
"expiresAt": "Utgångsdatum {{date}}",
"linkShareBadge": "Länk",
"userShareBadge": "Användare: {{username}}",
"loadSharesFailed": "Misslyckades med att läsa in aktiva delningar"
"rename": "Byt namn på sessionen"
},
"guacamole": {
"connecting": "Ansluter till {{type}} session...",
@@ -1559,9 +1436,6 @@
"reconnect": "Återanslut",
"retry": "Försöka igen",
"guacdUnavailable": "Fjärrskrivbordstjänsten (guacd) är inte tillgänglig. Se till att guacd körs, är tillgängligt och korrekt konfigurerat i administratörsinställningarna.",
"credentialPromptTitle": "Ange RDP-inloggningsuppgifter",
"credentialPromptDescription": "Den här värden är inställd på att be om inloggningsuppgifter vid anslutning. De används endast för den här sessionen och sparas inte.",
"connect": "Ansluta",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Återanslut sessionen",
"collapse": "Komprimera verktygsfältet",
"expand": "Expandera verktygsfältet",
"dragHandle": "Dra för att flytta",
"switchToTrackpad": "Växla till styrplatteläge (dra för att flytta markören, tryck för att klicka)",
"switchToTouch": "Växla till pekläge (tryck direkt där du vill klicka)"
"dragHandle": "Dra för att flytta"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Tvåfaktorsautentisering krävs",
"totpCodeLabel": "Verifieringskod",
"totpVerify": "Kontrollera",
"mfaPromptRequired": "Autentisering krävs",
"mfaPushRequired": "Push-autentisering krävs",
"mfaMenuPlaceholder": "Ange ditt svar",
"mfaWaitingApproval": "Väntar på godkännande på din enhet...",
"mfaSendRequest": "Skicka förfrågan",
"warpgateAuthRequired": "Warpgate-autentisering krävs",
"warpgateSecurityKey": "Säkerhetsnyckel",
"warpgateAuthUrl": "Autentiserings-URL",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Begränsad hastighet: För många verifieringsförsök. Försök igen senare.",
"resetCodeRateLimitedWithTime": "Begränsad hastighet: För många verifieringsförsök. Vänta {{time}} sekunder innan du försöker igen.",
"authTokenSaveFailed": "Misslyckades med att spara autentiseringstoken",
"failedToLoadServer": "Misslyckades med att ladda servern",
"remoteServerRequired": "Fjärrserver krävs. Anslut en fjärrserver i Inställningar för att använda den här anslutningstypen."
"failedToLoadServer": "Misslyckades med att ladda servern"
},
"messages": {
"registrationDisabled": "Registrering av nytt konto är för närvarande inaktiverat av en administratör. Vänligen logga in eller kontakta en administratör.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Kunde inte spara SSL-inställningarna",
"sslRequiresDomain": "Domän och e-postadress krävs",
"sslInfoNote": "Efter att du har utfärdat ett certifikat, aktivera SSL i dina miljövariabler (ENABLE_SSL=true) och starta om Termix.",
"sslManualOption": "Manual (ladda upp certifikat)",
"sslManualCert": "Certifikat (PEM)",
"sslManualCertPlaceholder": "-----BEGIN CERTIFIKAT-----",
"sslManualKey": "Privat nyckel (PEM)",
"sslManualKeyPlaceholder": "-----BEGIN PRIVAT NYCKEL-----",
"sslManualDesc": "Klistra in ditt befintliga certifikat och din privata nyckel, inklusive en fullständig kedja om det krävs av din CA.",
"sslManualUpload": "Ladda upp och installera certifikat",
"sslManualUploadLoading": "Laddar upp certifikat...",
"sslManualUploadSuccess": "Certifikatet har laddats upp och installerats",
"sslManualUploadFailed": "Uppladdning av certifikat misslyckades",
"sslManualRequiresFields": "Certifikat och privat nyckel krävs",
"auditLogTotal": "{{total}} totala antalet poster",
"auditLogEmpty": "Inga poster i granskningsloggen hittades",
"auditLogSuccess": "Framgång",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Kommandohistorik",
"commandHistoryEnabledDesc": "Tillåt inspelning av kommandohistorik. När det är inaktiverat sparas inte historiken oavsett inställningarna per värd.",
"updateCommandHistoryFailed": "Misslyckades med att uppdatera inställningen för kommandohistorik",
"analyticsEnabled": "Dela anonym användningsstatistik",
"analyticsEnabledDesc": "Skickar en anonym daglig räkning av användare, värdar och funktionsanvändning för att förbättra Termix. Inga personuppgifter eller anslutningsdetaljer inkluderas någonsin.",
"updateAnalyticsFailed": "Det gick inte att uppdatera analysinställningen",
"sessionSharingGloballyEnabled": "Tillåt sessionsdelning",
"sessionSharingGloballyEnabledDesc": "Tillåt att liveterminal-, RDP-, VNC- och Telnet-sessioner delas instansövergripande. Åsidosätter alla delningsknappar per värd när de är inaktiverade.",
"updateSessionSharingFailed": "Det gick inte att uppdatera inställningen för sessionsdelning",
"sessionTimeout": "Sessionstimeout",
"hours": "timmar",
"sessionTimeoutRange": "Min 1 timme · Max 720 timmar",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Framgång",
"executionFailed": "Misslyckades"
},
"keybindings": {
"title": "Kortkommandon",
"description": "Anpassa kortkommandon för att kopiera, klistra in och kontrollera terminalen, eller bind tangenter för att skicka text eller köra ett kodavsnitt.",
"defaultsHeading": "Inbyggda genvägar",
"customHeading": "Anpassade genvägar",
"addBinding": "Lägg till genväg",
"addBindingTitle": "Lägg till genväg",
"editBindingTitle": "Redigera genväg",
"loading": "Belastning...",
"noCustomBindings": "Inga anpassade genvägar ännu",
"defaultBadge": "Standard",
"customizedBadge": "Anpassad",
"customize": "Anpassa",
"resetToDefault": "Återställ till standard",
"resetAllToDefaults": "Återställ alla till standardinställningarna",
"close": "Nära",
"cancel": "Avboka",
"saveBinding": "Spara genväg",
"saveError": "Kunde inte spara kortkommandon",
"pressKeysToRecord": "Tangentkombination",
"pressKeysPlaceholder": "Klicka och tryck på tangenterna",
"recording": "Tryck på tangenterna...",
"comboRequiredError": "Tryck först på en tangentkombination",
"textRequiredError": "Ange texten som ska skickas",
"controlCodeRequiredError": "Ange en enda bokstav för kontrollkoden",
"snippetRequiredError": "Välj ett utdrag",
"conflictWarning": "Denna kombination är redan bunden till {{combo}}. Om du sparar den aktiveras båda.",
"actionLabel": "Handling",
"actionCopy": "Kopiera val",
"actionPaste": "Klistra",
"actionSendControlCode": "Skicka Ctrl+bokstavssignal",
"actionSendText": "Skicka sms",
"actionRunSnippet": "Kör befintligt kodavsnitt",
"controlCodeLabel": "Brev",
"textLabel": "SMS att skicka",
"appendEnterLabel": "Tryck Enter efter att du skickat",
"snippetLabel": "Utdrag",
"selectSnippetPlaceholder": "Välj ett utdrag",
"orphanedSnippetWarning": "utdraget hittades inte",
"clipboardPermissionNote": "Kan be om behörighet för urklipp vid första användningen i vissa webbläsare."
},
"userProfile": {
"donateTitle": "Håll Termix vid liv",
"donateDescription": "Termix är gratis, självhostat och byggt av endast ett fåtal personer på sin fritid. Om det har sparat dig tid eller pengar, hjälper en kryptodonation till att hålla det igång.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Utseende",
"sectionSecurity": "Säkerhet",
"sectionApiKeys": "API-nycklar",
"sectionData": "Data",
"sectionC2sTunnels": "C2S-tunnlar",
"usernameLabel": "Användarnamn",
"roleLabel": "Roll",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Terminal",
"commandAutocomplete": "Kommando Autokomplettering",
"commandAutocompleteDesc": "Visa autokomplettering medan du skriver",
"keyboardShortcuts": "Kortkommandon",
"keyboardShortcutsDescription": "Anpassa kortkommandon för terminalkopiering, klistra in och kommandon",
"manageShortcuts": "Hantera",
"terminalLinkBehavior": "Klicka på terminallänken",
"terminalLinkBehaviorDesc": "Standardbeteende vid klick på länkar i terminalen",
"historyTracking": "Historikspårning",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Lägg till din nyckel i",
"apiKeyUsageHintHeader": "rubrik.",
"apiKeyPermissionsHint": "Nycklar ärver behörigheterna från den skapande användaren.",
"exportData": "Exportera mina data",
"exportDataDesc": "Ladda ner en säkerhetskopia av dina värdar, inloggningsuppgifter och inställningar för att överföra till en annan enhet.",
"export": "Exportera",
"exporting": "Exporterar...",
"importData": "Importera mina data",
"importDataDesc": "Återställ dina värdar, inloggningsuppgifter och inställningar från en .sqlite-säkerhetskopia",
"importDataSelected": "Vald: {{name}}",
"selectFile": "Välj fil",
"changeFile": "Ändra",
"import": "Importera",
"importing": "Importerar...",
"exportSuccess": "Data exporterades",
"exportFailed": "Dataexport misslyckades",
"importSelectFile": "Välj en fil först",
"importCompleted": "Importen är klar: {{total}} objekt har importerats, {{skipped}} hoppades över",
"importFailed": "Importen misslyckades: {{error}}",
"roleUser": "Användare",
"authMethodDual": "Dubbel autentisering",
"authMethodOidc": "OIDC",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "PgDn",
"delete": "Del",
"paste": "Klistra",
"editQuickKeys": "Redigera snabbknappar",
"quickKeysTitle": "Snabbtangenter",
"quickKeysDesc": "Tryck på × för att ta bort. Stöder upp till 8 tecken.",
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "ไม่มีเซิร์ฟเวอร์ที่บันทึกไว้",
"removeServer": "ลบ"
},
"migrationNotice": {
"title": "ตอนนี้ Termix Desktop สามารถใช้งานแบบสแตนด์อโลนได้แล้ว",
"body1": "แอปนี้เดิมทีเชื่อมต่อโดยตรงกับเซิร์ฟเวอร์ Termix ของคุณ แต่ตอนนี้ได้รับการปรับปรุงใหม่ให้ทำงานได้ด้วยตัวเองอย่างสมบูรณ์ โดยจัดเก็บโฮสต์และข้อมูลรับรองไว้ในเครื่อง ทำให้ยังคงทำงานได้แม้ในขณะออฟไลน์ การซิงค์ข้อมูลแบบสองทางกับเซิร์ฟเวอร์ Termix ที่คุณโฮสต์เองนั้นเป็นตัวเลือกเสริมแล้ว",
"body2": "โฮสต์และข้อมูลรับรองที่มีอยู่ของคุณยังคงอยู่บน {{url}}เปิดใช้งานการซิงค์ระยะไกลและเชื่อมต่อกับเซิร์ฟเวอร์นั้นอีกครั้งเพื่อนำข้อมูลกลับมาและทำให้ทั้งสองซิงค์กันต่อไป",
"dismiss": "ไม่ใช่ตอนนี้",
"setUpSync": "ตั้งค่าการซิงค์ระยะไกล"
},
"remoteSync": {
"title": "การซิงค์ระยะไกล",
"description": "คุณสามารถเชื่อมต่อแอปพลิเคชันบนเดสก์ท็อปนี้กับเซิร์ฟเวอร์ Termix ที่คุณโฮสต์เองเพื่อซิงค์โฮสต์ ข้อมูลรับรอง และโค้ดตัวอย่างระหว่างอุปกรณ์ต่างๆ ได้ แอปพลิเคชันนี้ทำงานได้อย่างสมบูรณ์ในโหมดออฟไลน์เสมอ ไม่ว่าคุณจะเชื่อมต่อหรือไม่ก็ตาม",
"notConnected": "ไม่ได้เชื่อมต่อ",
"connected": "เชื่อมต่อแล้ว",
"connectedTo": "เชื่อมต่อกับ {{url}}",
"lastSynced": "ซิงค์ครั้งล่าสุด {{time}}",
"neverSynced": "ไม่เคยซิงค์",
"syncError": "ข้อผิดพลาดในการซิงค์: {{message}}",
"needsReauth": "การเข้าสู่ระบบหมดอายุแล้ว",
"connectButton": "เชื่อมต่อกับเซิร์ฟเวอร์",
"disconnectButton": "ตัดการเชื่อมต่อ",
"syncNowButton": "ซิงค์เดี๋ยวนี้",
"syncing": "กำลังซิงค์...",
"serverUrl": "URL ของเซิร์ฟเวอร์",
"enterServerUrl": "โปรดป้อน URL ของเซิร์ฟเวอร์",
"mustIncludeProtocol": "URL ของเซิร์ฟเวอร์ต้องขึ้นต้นด้วย http:// หรือ https://",
"connectionTestFailed": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ Termix ที่ URL นั้นได้",
"allowInvalidCertificate": "อนุญาตให้ใช้ใบรับรองที่ไม่ถูกต้อง",
"allowInvalidCertificateDesc": "ใช้ได้เฉพาะกับเซิร์ฟเวอร์ที่ดูแลเองที่เชื่อถือได้ ซึ่งมีใบรับรองแบบลงนามเองหรือใบรับรองตามที่อยู่ IP เท่านั้น",
"savedServers": "เซิร์ฟเวอร์ที่บันทึกไว้",
"removeServer": "ลบ",
"continueButton": "ดำเนินการต่อ",
"cancelButton": "ยกเลิก",
"signInTitle": "เข้าสู่ระบบที่ {{url}}",
"originTitle": "จุดเริ่มต้นของการเชื่อมต่อ",
"originDescription": "เลือกต้นทางของการเชื่อมต่อ SSH ตามค่าเริ่มต้น สามารถเปลี่ยนแปลงการตั้งค่านี้ได้สำหรับแต่ละโฮสต์",
"originLocal": "อุปกรณ์นี้ (เครือข่ายท้องถิ่น)",
"originRemote": "เซิร์ฟเวอร์ระยะไกล",
"bannerReconnect": "เชื่อมต่อใหม่",
"bannerMessage": "การซิงค์ระยะไกลจำเป็นต้องยืนยันตัวตนอีกครั้ง"
},
"versionCheck": {
"error": "ข้อผิดพลาดในการตรวจสอบเวอร์ชัน",
"checkFailed": "ไม่สามารถตรวจสอบการอัปเดตได้",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "แทนที่ชื่อผู้ใช้ข้อมูลประจำตัว",
"overrideCredentialUsernameDesc": "ใช้ชื่อผู้ใช้ที่ระบุไว้ข้างต้นแทนชื่อผู้ใช้ของข้อมูลประจำตัว",
"oidcUsernameHint": "ใช้ $oidc.preferred_username แทนชื่อผู้ใช้ OIDC ของคุณ",
"tailscaleUsernameHint": "ผู้ใช้งานนี้ต้องเป็นผู้ใช้ Unix ที่ได้รับสิทธิ์การใช้งาน Tailscale ใน SSH ACL ของ tailnet ไม่จำเป็นต้องเป็น root เสมอไป",
"jumpHostChain": "ห่วงโซ่ Jump Host",
"portKnocking": "การเคาะพอร์ต",
"addKnock": "เพิ่มพอร์ต",
@@ -690,11 +650,6 @@
"delayAfterMs": "เวลาหน่วงหลังจาก (มิลลิวินาที)",
"useSocks5Proxy": "ใช้พร็อกซี SOCKS5",
"useSocks5ProxyDesc": "การเชื่อมต่อเส้นทางผ่านพร็อกซีเซิร์ฟเวอร์",
"connectionOrigin": "จุดเริ่มต้นของการเชื่อมต่อ",
"connectionOriginDesc": "ระบุตำแหน่งที่การเชื่อมต่อ SSH ของโฮสต์นี้เริ่มต้นขึ้น ซึ่งจะแทนที่ค่าเริ่มต้นทั่วไปของแอปพลิเคชันบนเดสก์ท็อป",
"connectionOriginDefault": "ใช้ค่าเริ่มต้น",
"connectionOriginLocal": "อุปกรณ์นี้ (เครือข่ายท้องถิ่น)",
"connectionOriginRemote": "เซิร์ฟเวอร์ระยะไกล",
"proxyHost": "พร็อกซีโฮสต์",
"proxyPort": "พอร์ตพร็อกซี",
"proxyUsername": "ชื่อผู้ใช้พร็อกซี",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "สีฟ้าสดใส",
"customThemeBrightWhite": "สีขาวสว่าง",
"customThemeResetTooltip": "รีเซ็ตเป็นการตั้งค่าเริ่มต้น",
"savedThemesLabel": "ธีมที่บันทึกไว้",
"saveAsGlobalTheme": "บันทึกเป็นธีมทั่วโลก",
"saveGlobalThemeNamePrompt": "ตั้งชื่อให้กับธีมนี้",
"saveGlobalThemeSuccess": "บันทึกธีมแล้ว",
"saveGlobalThemeError": "ไม่สามารถบันทึกธีมได้",
"applyGlobalThemeTooltip": "ใช้ธีมนี้",
"deleteGlobalThemeTooltip": "ลบธีมนี้",
"noSavedThemes": "ยังไม่มีธีมที่บันทึกไว้",
"syntaxHighlightingLabel": "การเน้นไวยากรณ์",
"syntaxHighlightingDesc": "ใส่สีให้กับข้อมูลที่แสดงในเทอร์มินัล (ข้อผิดพลาด เส้นทาง ที่อยู่ IP และเวลา)",
"syntaxHighlightingCategories": "หมวดหมู่เด่น",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "เปิดใช้งานหรือเชื่อมต่อกับเซสชัน tmux โดยอัตโนมัติ",
"enableSessionLogging": "การบันทึกเซสชัน",
"enableSessionLoggingDesc": "บันทึกผลลัพธ์จากเทอร์มินัลเพื่อตรวจสอบในภายหลัง",
"allowSessionSharing": "อนุญาตการแชร์เซสชัน",
"allowSessionSharingDesc": "อนุญาตให้แชร์เซสชันสดบนโฮสต์นี้ผ่านลิงก์หรือกับผู้ใช้รายอื่น",
"enableCommandHistory": "ประวัติคำสั่ง",
"enableCommandHistoryDesc": "บันทึกคำสั่งที่เรียกใช้ในเทอร์มินัลนี้เพื่อดูประวัติและฟังก์ชันเติมคำอัตโนมัติ",
"linkClickBehaviorLabel": "พฤติกรรมการคลิกลิงก์",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} นำเข้าแล้ว",
"proxmoxResultUpdated": "{{count}} อัปเดตแล้ว",
"proxmoxResultFailed": "{{count}} ล้มเหลว",
"proxmoxResultSkippedNoIp": "{{count}} ข้ามไป (ไม่พบ IP)",
"proxmoxImportComplete": "การนำเข้า Proxmox เสร็จสมบูรณ์: {{summary}}",
"proxmoxDiscoveryFailed": "การค้นพบล้มเหลว",
"proxmoxImportFailed": "การนำเข้าล้มเหลว",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "ไม่สามารถบันทึกข้อมูลประจำตัวได้",
"credentialNameRequired": "โปรดระบุชื่อสำหรับข้อมูลประจำตัว",
"credentialAuthRequired": "เพิ่มรหัสผ่าน คีย์ SSH หรือทั้งสองอย่าง",
"createCredentialFromHostBtn": "สร้างข้อมูลรับรอง",
"createCredentialFromHostTitle": "สร้างข้อมูลรับรองจากโฮสต์",
"createCredentialFromHostDesc": "สร้างรายการข้อมูลประจำตัวที่สามารถนำกลับมาใช้ใหม่และแบ่งปันได้ โดยกรอกข้อมูลล่วงหน้าด้วยชื่อผู้ใช้ รหัสผ่าน และ/หรือคีย์ SSH ปัจจุบันของโฮสต์นี้",
"backToHosts": "กลับไปที่หน้าโฮสต์",
"backToCredentials": "กลับไปที่ข้อมูลประจำตัว",
"pinned": "ปักหมุด",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "ใช้เครื่องหมาย / เพื่อแยกแต่ละระดับและสร้างโฟลเดอร์ย่อย",
"folderColor": "สี",
"folderIcon": "ไอคอน",
"folderCredential": "ใบรับรอง",
"folderCredentialNone": "ไม่มีการกำหนดข้อมูลประจำตัว",
"folderCredentialHint": "โฮสต์ในโฟลเดอร์นี้ที่ใช้การตรวจสอบสิทธิ์ \"ข้อมูลรับรองที่จัดเก็บไว้\" โดยไม่ได้เลือกข้อมูลรับรองของตนเอง จะได้รับข้อมูลรับรองนี้มาโดยอัตโนมัติ",
"folderPreview": "ตัวอย่าง",
"folderNameFallback": "โฟลเดอร์ที่ไม่มีชื่อ",
"createFolderButton": "สร้างโฟลเดอร์",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "แท็ก",
"shareHost": "แชร์โฮสต์",
"shareHostTitle": "แชร์: {{name}}",
"shareFolder": "โฟลเดอร์ที่แชร์",
"shareFolderTitle": "โฟลเดอร์ที่แชร์: {{name}}",
"folderSharedSuccessfully": "โฮสต์ที่แชร์ {{count}} ในโฟลเดอร์",
"failedToShareFolder": "ไม่สามารถแชร์โฟลเดอร์ได้",
"sharing": {
"loadError": "ไม่สามารถโหลดข้อมูลการแชร์ได้ โปรดลองอีกครั้ง",
"shareWithSection": "แบ่งปันกับ",
@@ -1287,7 +1221,6 @@
"shareWithCount": "แชร์ ({{count}})",
"currentAccess": "การเข้าถึงปัจจุบัน",
"noAccessEntries": "โฮสต์นี้ยังไม่ได้ถูกแชร์",
"folderShareSummary": "แชร์ {{shared}} ของ {{total}} โฮสต์ในโฟลเดอร์นี้",
"grantedBy": "ได้รับอนุญาตจาก",
"expires": "หมดอายุ",
"expired": "หมดอายุ",
@@ -1309,8 +1242,6 @@
"authMethod": "วิธีการตรวจสอบสิทธิ์",
"authTypeDirect": "โดยตรง",
"authTypeCredential": "ใบรับรอง",
"authTypeNone": "ไม่มี",
"authTypeNoneDesc": "ไม่มีการจัดเก็บข้อมูลประจำตัวใดๆ คุณจะต้องป้อนชื่อผู้ใช้และรหัสผ่านทุกครั้งที่เชื่อมต่อ ซึ่งจะไม่ถูกบันทึกไว้",
"selectCredential": "เลือกข้อมูลประจำตัว...",
"connectionSettings": "การตั้งค่าการเชื่อมต่อ",
"displaySettings": "การตั้งค่าการแสดงผล",
@@ -1328,7 +1259,6 @@
"rdpPort": "พอร์ต RDP",
"username": "ชื่อผู้ใช้",
"password": "รหัสผ่าน",
"passwordSaved": "บันทึกรหัสผ่านแล้ว กรุณาพิมพ์เพื่อเปลี่ยนรหัสผ่าน",
"domain": "โดเมน",
"securityMode": "โหมดความปลอดภัย",
"colorDepth": "ความลึกของสี",
@@ -1494,60 +1424,7 @@
"expiresIn": "หมดอายุใน {{duration}}",
"search": "ค้นหาการเชื่อมต่อ...",
"noSearchResults": "ไม่พบผลลัพธ์ที่ตรงกับการค้นหาของคุณ",
"rename": "เปลี่ยนชื่อเซสชัน",
"sectionSharedWithMe": "แชร์กับฉัน",
"sharedBy": "แชร์โดย {{username}}",
"join": "เข้าร่วม",
"sharedSessionLabel": "{{hostName}} (แชร์)"
},
"sessionSharing": {
"guestView": {
"loading": "กำลังเชื่อมต่อกับเซสชันที่ใช้ร่วมกัน...",
"linkInvalid": "ลิงก์แชร์นี้ไม่ถูกต้อง หมดอายุ หรือถูกยกเลิกแล้ว",
"rateLimited": "พยายามหลายครั้งเกินไป โปรดลองใหม่อีกครั้งในภายหลัง",
"sessionEnded": "การประชุมครั้งนี้สิ้นสุดลงแล้ว",
"readOnlyBadge": "ดูได้อย่างเดียว"
},
"modalTitle": "เซสชั่นการแบ่งปัน",
"shareButton": "แบ่งปัน",
"notReadyToShare": "เซสชั่นยังไม่พร้อมให้แชร์",
"modeTab": {
"link": "ลิงก์",
"user": "ผู้ใช้"
},
"linkModeDescription": "ใครก็ตามที่มีลิงก์นี้สามารถเข้าร่วมได้ ไม่จำเป็นต้องมีบัญชีผู้ใช้",
"userModeDescription": "แชร์กับผู้ใช้เฉพาะรายที่มีสิทธิ์เข้าถึงโฮสต์นี้อยู่แล้ว หากพวกเขายังไม่มีสิทธิ์เข้าถึง ให้แชร์โฮสต์กับพวกเขาก่อน หรือใช้ลิงก์แทน เมื่อแชร์แล้ว เซสชันจะปรากฏในแท็บการเชื่อมต่อของพวกเขา",
"permissionLevel": {
"label": "ระดับการอนุญาต",
"readOnly": "อ่านอย่างเดียว",
"readOnlyDescription": "สามารถรับชมการประชุมสดได้ แต่ไม่สามารถพิมพ์หรือโต้ตอบได้",
"readWrite": "อ่าน-เขียน",
"readWriteDescription": "สามารถพิมพ์และโต้ตอบกับเซสชันได้เหมือนกับเจ้าของเซสชัน"
},
"expiryLabel": "ลิงก์หมดอายุ",
"createLinkButton": "สร้างลิงก์",
"createShareButton": "แชร์กับผู้ใช้",
"searchUsersPlaceholder": "ค้นหาผู้ใช้...",
"noUsersFound": "ไม่พบผู้ใช้งาน",
"linkCreated": "สร้างลิงก์สำหรับแชร์แล้ว",
"linkCopied": "คัดลอกลิงก์ไปยังคลิปบอร์ดแล้ว",
"copyLink": "คัดลิงก์",
"shareCreated": "แชร์เซสชันแล้ว เซสชันนั้นจะปรากฏในแท็บการเชื่อมต่อของพวกเขา",
"shareFailed": "ไม่สามารถสร้างการแชร์ได้",
"userLacksHostAccess": "ผู้ใช้รายนั้นยังไม่มีสิทธิ์เข้าถึงโฮสต์นี้ โปรดแชร์โฮสต์ให้พวกเขาก่อน หรือใช้ลิงก์แทน",
"activeShares": "หุ้นที่ใช้งานอยู่",
"noActiveShares": "ไม่มีหุ้นที่เปิดซื้อขายในรอบนี้",
"revoke": "ถอน",
"revokeConfirmTitle": "ต้องการยกเลิกการแชร์นี้หรือไม่?",
"revokeConfirmDescription": "ใครก็ตามที่ใช้ลิงก์นี้จะเสียสิทธิ์การเข้าถึงทันที",
"revoked": "หุ้นถูกเพิกถอน",
"revokeFailed": "ไม่สามารถเพิกถอนหุ้นได้",
"joinCount": "{{count}} เข้าร่วม",
"joinCount_other": "{{count}} เข้าร่วม",
"expiresAt": "หมดอายุ {{date}}",
"linkShareBadge": "ลิงก์",
"userShareBadge": "ผู้ใช้: {{username}}",
"loadSharesFailed": "ไม่สามารถโหลดหุ้นที่ใช้งานอยู่ได้"
"rename": "เปลี่ยนชื่อเซสชัน"
},
"guacamole": {
"connecting": "กำลังเชื่อมต่อกับเซสชัน {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "เชื่อมต่อใหม่",
"retry": "ลองใหม่อีกครั้ง",
"guacdUnavailable": "บริการเดสก์ท็อประยะไกล (guacd) ไม่พร้อมใช้งาน โปรดตรวจสอบให้แน่ใจว่า guacd กำลังทำงาน สามารถเข้าถึงได้ และกำหนดค่าอย่างถูกต้องในการตั้งค่าผู้ดูแลระบบ",
"credentialPromptTitle": "ป้อนข้อมูลรับรอง RDP",
"credentialPromptDescription": "โฮสต์นี้ตั้งค่าให้ขอข้อมูลรับรองเมื่อเชื่อมต่อ ข้อมูลเหล่านี้จะใช้สำหรับเซสชันนี้เท่านั้นและจะไม่ถูกบันทึกไว้",
"connect": "เชื่อมต่อ",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "เซสชั่นเชื่อมต่อใหม่",
"collapse": "ยุบแถบเครื่องมือ",
"expand": "ขยายแถบเครื่องมือ",
"dragHandle": "ลากเพื่อจัดตำแหน่งใหม่",
"switchToTrackpad": "เปลี่ยนไปใช้โหมดแทร็กแพด (ลากเพื่อเลื่อนเคอร์เซอร์ แตะเพื่อคลิก)",
"switchToTouch": "เปลี่ยนเป็นโหมดสัมผัส (แตะตรงจุดที่คุณต้องการคลิก)"
"dragHandle": "ลากเพื่อจัดตำแหน่งใหม่"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "ต้องใช้การยืนยันตัวตนสองขั้นตอน",
"totpCodeLabel": "รหัสยืนยัน",
"totpVerify": "ตรวจสอบ",
"mfaPromptRequired": "ต้องยืนยันตัวตน",
"mfaPushRequired": "ต้องมีการตรวจสอบสิทธิ์แบบพุช",
"mfaMenuPlaceholder": "กรุณาป้อนคำตอบของคุณ",
"mfaWaitingApproval": "กำลังรอการอนุมัติบนอุปกรณ์ของคุณ...",
"mfaSendRequest": "ส่งคำขอ",
"warpgateAuthRequired": "ต้องยืนยันตัวตนผ่านวาร์ปเกต",
"warpgateSecurityKey": "รหัสความปลอดภัย",
"warpgateAuthUrl": "URL สำหรับการตรวจสอบสิทธิ์",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "จำกัดจำนวนครั้งในการยืนยัน: มีการพยายามยืนยันมากเกินไป โปรดลองใหม่อีกครั้งในภายหลัง",
"resetCodeRateLimitedWithTime": "จำกัดจำนวนครั้ง: มีการพยายามยืนยันมากเกินไป โปรดรอ {{time}} วินาทีก่อนลองอีกครั้ง",
"authTokenSaveFailed": "ไม่สามารถบันทึกโทเค็นการตรวจสอบสิทธิ์ได้",
"failedToLoadServer": "ไม่สามารถโหลดเซิร์ฟเวอร์ได้",
"remoteServerRequired": "จำเป็นต้องใช้เซิร์ฟเวอร์ระยะไกล เชื่อมต่อเซิร์ฟเวอร์ระยะไกลในการตั้งค่าเพื่อใช้ประเภทการเชื่อมต่อนี้"
"failedToLoadServer": "ไม่สามารถโหลดเซิร์ฟเวอร์ได้"
},
"messages": {
"registrationDisabled": "ขณะนี้ผู้ดูแลระบบได้ปิดใช้งานการลงทะเบียนบัญชีใหม่แล้ว โปรดเข้าสู่ระบบหรือติดต่อผู้ดูแลระบบ",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "ไม่สามารถบันทึกการตั้งค่า SSL ได้",
"sslRequiresDomain": "ต้องระบุโดเมนและอีเมล",
"sslInfoNote": "หลังจากออกใบรับรองแล้ว ให้เปิดใช้งาน SSL ในตัวแปรสภาพแวดล้อมของคุณ (ENABLE_SSL=true) และรีสตาร์ท Termix",
"sslManualOption": "คู่มือ (อัปโหลดใบรับรอง)",
"sslManualCert": "ใบรับรอง (PEM)",
"sslManualCertPlaceholder": "-----เริ่มต้นใบรับรอง-----",
"sslManualKey": "กุญแจส่วนตัว (PEM)",
"sslManualKeyPlaceholder": "-----เริ่มรหัสส่วนตัว-----",
"sslManualDesc": "โปรดวางใบรับรองและรหัสส่วนตัวที่มีอยู่ของคุณ รวมถึงห่วงโซ่ใบรับรองแบบเต็มหากหน่วยงานออกใบรับรอง (CA) ของคุณต้องการ",
"sslManualUpload": "อัปโหลดและติดตั้งใบรับรอง",
"sslManualUploadLoading": "กำลังอัปโหลดใบรับรอง...",
"sslManualUploadSuccess": "อัปโหลดและติดตั้งใบรับรองเรียบร้อยแล้ว",
"sslManualUploadFailed": "การอัปโหลดใบรับรองล้มเหลว",
"sslManualRequiresFields": "ต้องใช้ใบรับรองและรหัสส่วนตัว",
"auditLogTotal": "{{total}} จำนวนรายการทั้งหมด",
"auditLogEmpty": "ไม่พบรายการบันทึกการตรวจสอบใดๆ",
"auditLogSuccess": "ความสำเร็จ",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "ประวัติคำสั่ง",
"commandHistoryEnabledDesc": "อนุญาตให้บันทึกประวัติคำสั่ง เมื่อปิดใช้งาน ประวัติจะไม่ถูกบันทึกไม่ว่าการตั้งค่าต่อโฮสต์จะเป็นอย่างไรก็ตาม",
"updateCommandHistoryFailed": "ไม่สามารถอัปเดตการตั้งค่าประวัติคำสั่งได้",
"analyticsEnabled": "แบ่งปันสถิติการใช้งานแบบไม่ระบุตัวตน",
"analyticsEnabledDesc": "ส่งรายงานจำนวนผู้ใช้ โฮสต์ และการใช้งานฟีเจอร์รายวันแบบไม่ระบุตัวตน เพื่อช่วยปรับปรุง Termix ไม่มีข้อมูลส่วนบุคคลหรือรายละเอียดการเชื่อมต่อใดๆ รวมอยู่ด้วย",
"updateAnalyticsFailed": "ไม่สามารถอัปเดตการตั้งค่าการวิเคราะห์ได้",
"sessionSharingGloballyEnabled": "อนุญาตการแชร์เซสชัน",
"sessionSharingGloballyEnabledDesc": "อนุญาตให้แชร์เซสชันเทอร์มินัลสด, RDP, VNC และ Telnet ทั่วทั้งอินสแตนซ์ การตั้งค่านี้จะแทนที่การตั้งค่าการแชร์ต่อโฮสต์ทั้งหมดเมื่อปิดใช้งานอยู่",
"updateSessionSharingFailed": "ไม่สามารถอัปเดตการตั้งค่าการแชร์เซสชันได้",
"sessionTimeout": "หมดเวลาเซสชัน",
"hours": "ชั่วโมง",
"sessionTimeoutRange": "ขั้นต่ำ 1 ชั่วโมง · ขั้นสูงสุด 720 ชั่วโมง",
@@ -3270,47 +3119,6 @@
"executionSuccess": "ความสำเร็จ",
"executionFailed": "ล้มเหลว"
},
"keybindings": {
"title": "แป้นพิมพ์ลัด",
"description": "ปรับแต่งปุ่มลัดสำหรับการคัดลอก วาง และควบคุมในเทอร์มินัล หรือกำหนดปุ่มเพื่อส่งข้อความหรือเรียกใช้โค้ดตัวอย่าง",
"defaultsHeading": "ทางลัดในตัว",
"customHeading": "ทางลัดแบบกำหนดเอง",
"addBinding": "เพิ่มทางลัด",
"addBindingTitle": "เพิ่มทางลัด",
"editBindingTitle": "แก้ไขทางลัด",
"loading": "กำลังโหลด...",
"noCustomBindings": "ยังไม่มีปุ่มลัดที่กำหนดเอง",
"defaultBadge": "ค่าเริ่มต้น",
"customizedBadge": "ปรับแต่งตามต้องการ",
"customize": "ปรับแต่ง",
"resetToDefault": "รีเซ็ตเป็นค่าเริ่มต้น",
"resetAllToDefaults": "รีเซ็ตทุกอย่างเป็นการตั้งค่าเริ่มต้น",
"close": "ปิด",
"cancel": "ยกเลิก",
"saveBinding": "บันทึกทางลัด",
"saveError": "ไม่สามารถบันทึกปุ่มลัดได้",
"pressKeysToRecord": "ปุ่มกด",
"pressKeysPlaceholder": "คลิกและกดปุ่ม",
"recording": "กดปุ่ม...",
"comboRequiredError": "กดปุ่มคีย์ผสมก่อน",
"textRequiredError": "ป้อนข้อความที่จะส่ง",
"controlCodeRequiredError": "ป้อนตัวอักษรตัวเดียวสำหรับรหัสควบคุม",
"snippetRequiredError": "เลือกส่วนย่อย",
"conflictWarning": "การรวมกันนี้ถูกผูกไว้กับ {{combo}}แล้ว การบันทึกจะทำให้ทั้งสองอย่างทำงาน",
"actionLabel": "การกระทำ",
"actionCopy": "คัดลอกการเลือก",
"actionPaste": "แปะ",
"actionSendControlCode": "ส่งสัญญาณ Ctrl+ตัวอักษร",
"actionSendText": "ส่งข้อความ",
"actionRunSnippet": "เรียกใช้โค้ดตัวอย่างที่มีอยู่",
"controlCodeLabel": "จดหมาย",
"textLabel": "ข้อความที่จะส่ง",
"appendEnterLabel": "กด Enter หลังจากส่งแล้ว",
"snippetLabel": "ส่วนหนึ่ง",
"selectSnippetPlaceholder": "เลือกส่วนย่อย",
"orphanedSnippetWarning": "ไม่พบส่วนย่อย",
"clipboardPermissionNote": "ในบางเบราว์เซอร์ อาจขออนุญาตคัดลอกและวางข้อมูลลงในคลิปบอร์ดเมื่อใช้งานครั้งแรก"
},
"userProfile": {
"donateTitle": "รักษา Termix ให้คงอยู่ต่อไป",
"donateDescription": "Termix เป็นโปรแกรมฟรี ติดตั้งบนเซิร์ฟเวอร์ของคุณเอง และสร้างขึ้นโดยคนเพียงไม่กี่คนในเวลาว่าง หากโปรแกรมนี้ช่วยประหยัดเวลาหรือเงินของคุณ การบริจาคด้วยคริปโตเคอร์เรนซีจะช่วยให้โปรแกรมนี้ดำเนินต่อไปได้",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "รูปร่าง",
"sectionSecurity": "ความปลอดภัย",
"sectionApiKeys": "คีย์ API",
"sectionData": "ข้อมูล",
"sectionC2sTunnels": "อุโมงค์ C2S",
"usernameLabel": "ชื่อผู้ใช้",
"roleLabel": "บทบาท",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "เทอร์มินัล",
"commandAutocomplete": "การเติมคำสั่งอัตโนมัติ",
"commandAutocompleteDesc": "แสดงคำแนะนำอัตโนมัติขณะพิมพ์",
"keyboardShortcuts": "แป้นพิมพ์ลัด",
"keyboardShortcutsDescription": "ปรับแต่งการคัดลอก วาง และปุ่มลัดคำสั่งในเทอร์มินัล",
"manageShortcuts": "จัดการ",
"terminalLinkBehavior": "คลิกลิงก์เทอร์มินัล",
"terminalLinkBehaviorDesc": "พฤติกรรมเริ่มต้นเมื่อคลิกลิงก์ในเทอร์มินัล",
"historyTracking": "การติดตามประวัติ",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "ใส่กุญแจของคุณลงไปด้วย",
"apiKeyUsageHintHeader": "ส่วนหัว",
"apiKeyPermissionsHint": "คีย์จะได้รับสิทธิ์การใช้งานจากผู้ใช้ที่สร้างคีย์นั้นขึ้นมา",
"exportData": "ส่งออกข้อมูลของฉัน",
"exportDataDesc": "ดาวน์โหลดไฟล์สำรองข้อมูลโฮสต์ ข้อมูลประจำตัว และการตั้งค่าของคุณเพื่อถ่ายโอนไปยังอุปกรณ์อื่น",
"export": "ส่งออก",
"exporting": "กำลังส่งออก...",
"importData": "นำเข้าข้อมูลของฉัน",
"importDataDesc": "กู้คืนข้อมูลโฮสต์ ข้อมูลประจำตัว และการตั้งค่าของคุณจากไฟล์สำรองข้อมูล .sqlite",
"importDataSelected": "เลือกแล้ว: {{name}}",
"selectFile": "เลือกไฟล์",
"changeFile": "เปลี่ยน",
"import": "นำเข้า",
"importing": "กำลังนำเข้า...",
"exportSuccess": "ส่งออกข้อมูลสำเร็จแล้ว",
"exportFailed": "การส่งออกข้อมูลล้มเหลว",
"importSelectFile": "โปรดเลือกไฟล์ก่อน",
"importCompleted": "การนำเข้าเสร็จสมบูรณ์: นำเข้า {{total}} รายการ ข้าม {{skipped}} รายการ",
"importFailed": "การนำเข้าล้มเหลว: {{error}}",
"roleUser": "ผู้ใช้",
"authMethodDual": "การตรวจสอบสิทธิ์แบบคู่",
"authMethodOidc": "โอไอดีซี",
@@ -3591,7 +3379,6 @@
"pageUp": "พีจีอัพ",
"pageDown": "พีจีดีเอ็น",
"delete": "เดล",
"paste": "แปะ",
"editQuickKeys": "แก้ไขปุ่มลัด",
"quickKeysTitle": "ปุ่มลัด",
"quickKeysDesc": "แตะ × เพื่อลบ รองรับได้สูงสุด 8 ตัวอักษร",
File diff suppressed because it is too large Load Diff
+3 -216
View File
@@ -389,45 +389,6 @@
"noSavedServers": "Немає збережених серверів",
"removeServer": "Видалити"
},
"migrationNotice": {
"title": "Termix Desktop тепер працює автономно",
"body1": "Цей додаток раніше підключався безпосередньо до вашого сервера Termix. Його було перероблено, щоб він працював повністю самостійно, зберігаючи хости та облікові дані локально, щоб він міг працювати офлайн. Двостороння синхронізація з власним сервером Termix тепер необов'язкова.",
"body2": "Ваші існуючі хости та облікові дані все ще на {{url}}. Увімкніть віддалену синхронізацію та повторно підключіться до цього сервера, щоб відновити їх та підтримувати синхронізацію надалі.",
"dismiss": "Не зараз",
"setUpSync": "Налаштування віддаленої синхронізації"
},
"remoteSync": {
"title": "Віддалена синхронізація",
"description": "За бажанням, підключіть цей настільний додаток до окремого сервера Termix, щоб синхронізувати свої хости, облікові дані та фрагменти коду на всіх пристроях. Додаток завжди працює повністю офлайн, незалежно від того, чи підключено ви його до мережі.",
"notConnected": "Не підключено",
"connected": "Підключено",
"connectedTo": "Підключено до {{url}}",
"lastSynced": "Остання синхронізація {{time}}",
"neverSynced": "Ніколи не синхронізувалося",
"syncError": "Помилка синхронізації: {{message}}",
"needsReauth": "Термін дії входу минув",
"connectButton": "Підключення до сервера",
"disconnectButton": "Відключитися",
"syncNowButton": "Синхронізувати зараз",
"syncing": "Синхронізація...",
"serverUrl": "URL-адреса сервера",
"enterServerUrl": "Будь ласка, введіть URL-адресу сервера",
"mustIncludeProtocol": "URL-адреса сервера має починатися з http:// або https://",
"connectionTestFailed": "Не вдалося зв'язатися із сервером Termix за цією URL-адресою",
"allowInvalidCertificate": "Дозволити недійсний сертифікат",
"allowInvalidCertificateDesc": "Використовуйте лише для довірених серверів із власним хостингом та самопідписаними сертифікатами або сертифікатами IP-адреси.",
"savedServers": "Збережені сервери",
"removeServer": "Видалити",
"continueButton": "Продовжити",
"cancelButton": "Скасувати",
"signInTitle": "Увійти до {{url}}",
"originTitle": "Джерело з'єднання",
"originDescription": "Виберіть, звідки за замовчуванням створюються SSH-з’єднання. Це можна змінити для кожного хоста окремо.",
"originLocal": "Цей пристрій (локальна мережа)",
"originRemote": "Віддалений сервер",
"bannerReconnect": "Знову підключитися",
"bannerMessage": "Віддалена синхронізація потребує повторної автентифікації"
},
"versionCheck": {
"error": "Помилка перевірки версії",
"checkFailed": "Не вдалося перевірити наявність оновлень",
@@ -641,7 +602,6 @@
"overrideCredentialUsername": "Перезаписати ім'я користувача облікових даних",
"overrideCredentialUsernameDesc": "Використовуйте вказане вище ім'я користувача замість імені користувача облікових даних",
"oidcUsernameHint": "Використовуйте $oidc.preferred_username замість вашого імені користувача OIDC.",
"tailscaleUsernameHint": "Це має бути користувач Unix, якому надано ідентифікатор Tailscale у SSH ACL tailnet, не обов'язково root.",
"jumpHostChain": "Ланцюг хоста стрибка",
"portKnocking": "Порт-Нокінґ",
"addKnock": "Додати порт",
@@ -690,11 +650,6 @@
"delayAfterMs": "Затримка після (мс)",
"useSocks5Proxy": "Використовуйте проксі-сервер SOCKS5",
"useSocks5ProxyDesc": "Маршрутизувати з'єднання через проксі-сервер",
"connectionOrigin": "Джерело з'єднання",
"connectionOriginDesc": "Звідки походить SSH-з’єднання цього хоста. Замінює глобальне налаштування за замовчуванням для програми для робочого столу.",
"connectionOriginDefault": "Використовувати за замовчуванням",
"connectionOriginLocal": "Цей пристрій (локальна мережа)",
"connectionOriginRemote": "Віддалений сервер",
"proxyHost": "Проксі-хост",
"proxyPort": "Проксі-порт",
"proxyUsername": "Ім'я користувача проксі-сервера",
@@ -807,14 +762,6 @@
"customThemeBrightCyan": "Яскравий блакитний",
"customThemeBrightWhite": "Яскраво-білий",
"customThemeResetTooltip": "Скинути до налаштувань за замовчуванням",
"savedThemesLabel": "Збережені теми",
"saveAsGlobalTheme": "Зберегти як глобальну тему",
"saveGlobalThemeNamePrompt": "Введіть назву для цієї теми",
"saveGlobalThemeSuccess": "Тему збережено",
"saveGlobalThemeError": "Не вдалося зберегти тему",
"applyGlobalThemeTooltip": "Застосувати цю тему",
"deleteGlobalThemeTooltip": "Видалити цю тему",
"noSavedThemes": "Ще немає збережених тем",
"syntaxHighlightingLabel": "Підсвічування синтаксису",
"syntaxHighlightingDesc": "Розфарбувати вивід терміналу (помилки, шляхи, IP-адреси, позначки часу)",
"syntaxHighlightingCategories": "Виділіть категорії",
@@ -844,8 +791,6 @@
"enableAutoTmuxDesc": "Автоматично запускати або приєднуватися до сеансу tmux",
"enableSessionLogging": "Ведення журналу сеансу",
"enableSessionLoggingDesc": "Запис виводу сеансу терміналу для подальшого перегляду",
"allowSessionSharing": "Дозволити спільний доступ до сеансу",
"allowSessionSharingDesc": "Дозволити ділитися прямими трансляціями на цьому хості через посилання або з іншими користувачами",
"enableCommandHistory": "Історія команд",
"enableCommandHistoryDesc": "Запис команд, що виконуються в цьому терміналі, для історії та автозаповнення",
"linkClickBehaviorLabel": "Поведінка кліків посилань",
@@ -950,7 +895,6 @@
"proxmoxResultImported": "{{count}} імпортовано",
"proxmoxResultUpdated": "{{count}} оновлено",
"proxmoxResultFailed": "{{count}} не вдалося",
"proxmoxResultSkippedNoIp": "{{count}} пропущено (IP-адресу не знайдено)",
"proxmoxImportComplete": "Імпорт Proxmox завершено: {{summary}}",
"proxmoxDiscoveryFailed": "Не вдалося знайти",
"proxmoxImportFailed": "Імпорт не вдалося",
@@ -1017,9 +961,6 @@
"failedToSaveCredential": "Не вдалося зберегти облікові дані",
"credentialNameRequired": "Будь ласка, введіть ім'я для облікових даних",
"credentialAuthRequired": "Додайте пароль, ключ SSH або обидва",
"createCredentialFromHostBtn": "Створити облікові дані",
"createCredentialFromHostTitle": "Створити облікові дані від хоста",
"createCredentialFromHostDesc": "Створіть багаторазовий, спільний запис облікових даних, попередньо заповнений поточним іменем користувача, паролем та/або ключем SSH цього хоста.",
"backToHosts": "Назад до хостів",
"backToCredentials": "Повернутися до облікових даних",
"pinned": "Закріплено",
@@ -1059,9 +1000,6 @@
"folderNestingHint": "Використовуйте / для розділення рівнів та створення вкладених папок.",
"folderColor": "Колір",
"folderIcon": "Значок",
"folderCredential": "Посвідчення",
"folderCredentialNone": "Облікові дані не призначено",
"folderCredentialHint": "Хости в цій папці, які використовують автентифікацію \"Збережені облікові дані\" без вибраних власних облікових даних, успадкують ці дані.",
"folderPreview": "Попередній перегляд",
"folderNameFallback": "Папка без назви",
"createFolderButton": "Створити папку",
@@ -1243,10 +1181,6 @@
"filterTagsGroup": "Теги",
"shareHost": "Спільний хост",
"shareHostTitle": "Поділитися: {{name}}",
"shareFolder": "Спільний доступ до папки",
"shareFolderTitle": "Папка для спільного доступу: {{name}}",
"folderSharedSuccessfully": "Спільні хости {{count}} у папці",
"failedToShareFolder": "Не вдалося поділитися папкою",
"sharing": {
"loadError": "Не вдалося завантажити дані для спільного доступу. Спробуйте ще раз.",
"shareWithSection": "Поділитися з",
@@ -1287,7 +1221,6 @@
"shareWithCount": "Поділитися ({{count}})",
"currentAccess": "Поточний доступ",
"noAccessEntries": "Цей хост ще не був спільним",
"folderShareSummary": "Спільні хости {{shared}} з {{total}} у цій папці",
"grantedBy": "Надано",
"expires": "Термін дії закінчується",
"expired": "Термін дії минув",
@@ -1309,8 +1242,6 @@
"authMethod": "Метод автентифікації",
"authTypeDirect": "Прямий",
"authTypeCredential": "Посвідчення",
"authTypeNone": "Жоден",
"authTypeNoneDesc": "Облікові дані не зберігаються. Вам буде запропоновано ввести ім’я користувача та пароль щоразу під час підключення; вони не зберігаються.",
"selectCredential": "Виберіть облікові дані...",
"connectionSettings": "Налаштування підключення",
"displaySettings": "Налаштування дисплея",
@@ -1328,7 +1259,6 @@
"rdpPort": "RDP-порт",
"username": "Ім'я користувача",
"password": "Пароль",
"passwordSaved": "Пароль збережено, введіть, щоб змінити",
"domain": "Домен",
"securityMode": "Режим безпеки",
"colorDepth": "Глибина кольору",
@@ -1494,60 +1424,7 @@
"expiresIn": "Термін дії закінчується через {{duration}}",
"search": "Пошук з'єднань...",
"noSearchResults": "Немає з'єднань, що відповідають вашому пошуку",
"rename": "Перейменувати сеанс",
"sectionSharedWithMe": "Поділилися зі мною",
"sharedBy": "Поділився користувачем {{username}}",
"join": "Приєднатися",
"sharedSessionLabel": "{{hostName}} (спільний доступ)"
},
"sessionSharing": {
"guestView": {
"loading": "Підключення до спільного сеансу...",
"linkInvalid": "Це посилання для спільного доступу недійсне, термін його дії минув або його було відкликано",
"rateLimited": "Забагато спроб, спробуйте ще раз пізніше",
"sessionEnded": "Цей сеанс завершився",
"readOnlyBadge": "Тільки перегляд"
},
"modalTitle": "Поділитися сеансом",
"shareButton": "Поділитися",
"notReadyToShare": "Сеанс ще не готовий до поширення",
"modeTab": {
"link": "Посилання",
"user": "Користувач"
},
"linkModeDescription": "Приєднатися може кожен, хто має це посилання, обліковий запис не потрібен.",
"userModeDescription": "Надайте доступ певному користувачеві, який уже має доступ до цього хоста. Якщо у нього ще немає доступу, спочатку надайте йому доступ до хоста або скористайтеся посиланням. Після надання доступу сеанс відображається на його вкладці «Підключення».",
"permissionLevel": {
"label": "Рівень дозволів",
"readOnly": "Тільки для читання",
"readOnlyDescription": "Можна дивитися сеанс у прямому ефірі, але не можна друкувати чи взаємодіяти.",
"readWrite": "Читання-запис",
"readWriteDescription": "Можна друкувати та взаємодіяти з сеансом так само, як і його власник."
},
"expiryLabel": "Термін дії посилання",
"createLinkButton": "Створити посилання",
"createShareButton": "Поділитися з користувачем",
"searchUsersPlaceholder": "Пошук користувачів...",
"noUsersFound": "Користувачів не знайдено",
"linkCreated": "Посилання для спільного доступу створено",
"linkCopied": "Посилання скопійовано в буфер обміну",
"copyLink": "Копіювати посилання",
"shareCreated": "Сеанс спільний. Він відображатиметься на вкладці «Зв’язки».",
"shareFailed": "Не вдалося створити спільний доступ",
"userLacksHostAccess": "Цей користувач ще не має доступу до цього хоста. Спочатку надайте йому доступ до хоста або скористайтеся посиланням.",
"activeShares": "Активні акції",
"noActiveShares": "Немає активних поширень для цього сеансу",
"revoke": "Скасувати",
"revokeConfirmTitle": "Скасувати цей спільний доступ?",
"revokeConfirmDescription": "Будь-хто, хто користується цим ресурсом спільного доступу, негайно втратить доступ.",
"revoked": "Акції анульовано",
"revokeFailed": "Не вдалося скасувати спільний доступ",
"joinCount": "{{count}} приєднатися",
"joinCount_other": "{{count}} приєднується",
"expiresAt": "Термін дії закінчується {{date}}",
"linkShareBadge": "Посилання",
"userShareBadge": "Користувач: {{username}}",
"loadSharesFailed": "Не вдалося завантажити активні спільні ресурси"
"rename": "Перейменувати сеанс"
},
"guacamole": {
"connecting": "Підключення до сеансу {{type}}...",
@@ -1559,9 +1436,6 @@
"reconnect": "Знову підключитися",
"retry": "Повторити спробу",
"guacdUnavailable": "Служба віддаленого робочого столу (guacd) недоступна. Переконайтеся, що guacd запущено, доступно та правильно налаштовано в налаштуваннях адміністратора.",
"credentialPromptTitle": "Введіть облікові дані RDP",
"credentialPromptDescription": "Цей хост налаштовано на запит облікових даних під час підключення. Вони використовуються лише для цього сеансу та не зберігаються.",
"connect": "Підключитися",
"ctrlAltDel": "Ctrl+Alt+Del",
"toolbar": {
"ctrlAltDel": "Ctrl+Alt+Del",
@@ -1587,9 +1461,7 @@
"reconnect": "Сеанс повторного підключення",
"collapse": "Згорнути панель інструментів",
"expand": "Розгорнути панель інструментів",
"dragHandle": "Перетягніть, щоб змінити положення",
"switchToTrackpad": "Перехід у режим трекпада (перетягування для переміщення курсора, торкання для клацання)",
"switchToTouch": "Перемикання в сенсорний режим (торкайтеся безпосередньо там, де потрібно клацнути)"
"dragHandle": "Перетягніть, щоб змінити положення"
}
},
"terminal": {
@@ -1636,11 +1508,6 @@
"totpRequired": "Потрібна двофакторна автентифікація",
"totpCodeLabel": "Код підтвердження",
"totpVerify": "Перевірити",
"mfaPromptRequired": "Потрібна автентифікація",
"mfaPushRequired": "Потрібна автентифікація push-повідомлень",
"mfaMenuPlaceholder": "Введіть свою відповідь",
"mfaWaitingApproval": "Очікування схвалення на вашому пристрої...",
"mfaSendRequest": "Надіслати запит",
"warpgateAuthRequired": "Потрібна автентифікація Warpgate",
"warpgateSecurityKey": "Ключ безпеки",
"warpgateAuthUrl": "URL-адреса автентифікації",
@@ -2410,8 +2277,7 @@
"resetCodeRateLimited": "Обмеження швидкості: Забагато спроб перевірки. Будь ласка, спробуйте пізніше.",
"resetCodeRateLimitedWithTime": "Обмеження швидкості: Забагато спроб перевірки. Будь ласка, зачекайте {{time}} секунд, перш ніж повторити спробу.",
"authTokenSaveFailed": "Не вдалося зберегти токен автентифікації",
"failedToLoadServer": "Не вдалося завантажити сервер",
"remoteServerRequired": "Потрібен віддалений сервер. Підключіться до віддаленого сервера в налаштуваннях, щоб використовувати цей тип підключення."
"failedToLoadServer": "Не вдалося завантажити сервер"
},
"messages": {
"registrationDisabled": "Реєстрацію нового облікового запису наразі вимкнено адміністратором. Будь ласка, увійдіть або зверніться до адміністратора.",
@@ -2778,17 +2644,6 @@
"sslSaveFailed": "Не вдалося зберегти налаштування SSL",
"sslRequiresDomain": "Домен та електронна адреса обов'язкові",
"sslInfoNote": "Після видачі сертифіката увімкніть SSL у змінних середовища (ENABLE_SSL=true) та перезапустіть Termix.",
"sslManualOption": "Інструкція (сертифікат завантаження)",
"sslManualCert": "Сертифікат (PEM)",
"sslManualCertPlaceholder": "-----ПОЧАТИ СЕРТИФІКАТ-----",
"sslManualKey": "Закритий ключ (PEM)",
"sslManualKeyPlaceholder": "-----ПОЧАТИ ПРИВАТНИЙ КЛЮЧ-----",
"sslManualDesc": "Вставте свій існуючий сертифікат і закритий ключ, включаючи повний ланцюжок, якщо цього вимагає ваш центр сертифікації.",
"sslManualUpload": "Сертифікат завантаження та встановлення",
"sslManualUploadLoading": "Завантаження сертифіката...",
"sslManualUploadSuccess": "Сертифікат успішно завантажено та встановлено",
"sslManualUploadFailed": "Не вдалося завантажити сертифікат",
"sslManualRequiresFields": "Потрібні сертифікат та закритий ключ",
"auditLogTotal": "{{total}} всього записів",
"auditLogEmpty": "Записів журналу аудиту не знайдено",
"auditLogSuccess": "Успіх",
@@ -2817,12 +2672,6 @@
"commandHistoryEnabled": "Історія команд",
"commandHistoryEnabledDesc": "Дозволити запис історії команд. Якщо вимкнено, історія не зберігається незалежно від налаштувань для кожного хоста.",
"updateCommandHistoryFailed": "Не вдалося оновити налаштування історії команд",
"analyticsEnabled": "Поділитися анонімною статистикою використання",
"analyticsEnabledDesc": "Надсилає анонімний щоденний підрахунок користувачів, хостів та використання функцій, щоб допомогти покращити Termix. Жодні особисті дані чи деталі з’єднання ніколи не включаються.",
"updateAnalyticsFailed": "Не вдалося оновити налаштування аналітики",
"sessionSharingGloballyEnabled": "Дозволити спільний доступ до сеансу",
"sessionSharingGloballyEnabledDesc": "Дозволити спільний доступ до сеансів терміналу, RDP, VNC та Telnet для всього екземпляра. Перевизначає кожен перемикач спільного доступу для кожного хоста, якщо його вимкнено.",
"updateSessionSharingFailed": "Не вдалося оновити налаштування спільного доступу до сеансу",
"sessionTimeout": "Час очікування сеансу",
"hours": "години",
"sessionTimeoutRange": "Мін. 1 год · Макс. 720 год",
@@ -3270,47 +3119,6 @@
"executionSuccess": "Успіх",
"executionFailed": "Не вдалося"
},
"keybindings": {
"title": "Комбінації клавіш",
"description": "Налаштуйте комбінації клавіш для копіювання, вставки та керування терміналом або прив’яжіть клавіші для надсилання тексту чи запуску фрагмента коду.",
"defaultsHeading": "Вбудовані комбінації клавіш",
"customHeading": "Користувацькі скорочення",
"addBinding": "Додати ярлик",
"addBindingTitle": "Додати ярлик",
"editBindingTitle": "Редагувати скорочення",
"loading": "Завантаження...",
"noCustomBindings": "Поки що немає користувацьких комбінацій клавіш",
"defaultBadge": "За замовчуванням",
"customizedBadge": "Налаштовані",
"customize": "Налаштувати",
"resetToDefault": "Скинути до заводських налаштувань",
"resetAllToDefaults": "Скинути всі налаштування до заводських",
"close": "Закрити",
"cancel": "Скасувати",
"saveBinding": "Зберегти ярлик",
"saveError": "Не вдалося зберегти комбінації клавіш",
"pressKeysToRecord": "Комбінація клавіш",
"pressKeysPlaceholder": "Клацайте та натискайте клавіші",
"recording": "Натискайте клавіші...",
"comboRequiredError": "Спочатку натисніть комбінацію клавіш",
"textRequiredError": "Введіть текст для надсилання",
"controlCodeRequiredError": "Введіть одну літеру для контрольного коду",
"snippetRequiredError": "Виберіть фрагмент",
"conflictWarning": "Ця комбінація вже пов'язана з {{combo}}. Збереження призведе до спрацювання обох.",
"actionLabel": "Дія",
"actionCopy": "Копіювати виділення",
"actionPaste": "Вставити",
"actionSendControlCode": "Надіслати сигнал Ctrl+літера",
"actionSendText": "Надіслати текст",
"actionRunSnippet": "Запустити існуючий фрагмент коду",
"controlCodeLabel": "Лист",
"textLabel": "Текст для надсилання",
"appendEnterLabel": "Натисніть Enter після відправлення",
"snippetLabel": "Фрагмент",
"selectSnippetPlaceholder": "Виберіть фрагмент",
"orphanedSnippetWarning": "фрагмент не знайдено",
"clipboardPermissionNote": "У деяких браузерах може запитуватися дозвіл на використання буфера обміну під час першого використання."
},
"userProfile": {
"donateTitle": "Збережи Термікс живим",
"donateDescription": "Termix безкоштовний, розміщується самостійно та створений лише кількома людьми у вільний час. Якщо це заощадило вам час або гроші, пожертва у криптовалюті допоможе йому продовжувати роботу.",
@@ -3326,7 +3134,6 @@
"sectionAppearance": "Зовнішній вигляд",
"sectionSecurity": "Безпека",
"sectionApiKeys": "Ключі API",
"sectionData": "Дані",
"sectionC2sTunnels": "Тунелі C2S",
"usernameLabel": "Ім'я користувача",
"roleLabel": "Роль",
@@ -3355,9 +3162,6 @@
"settingsTerminal": "Термінал",
"commandAutocomplete": "Автозаповнення команд",
"commandAutocompleteDesc": "Показувати автозаповнення під час введення тексту",
"keyboardShortcuts": "Комбінації клавіш",
"keyboardShortcutsDescription": "Налаштування скорочень команд, копіювання, вставки та інших команд у терміналі",
"manageShortcuts": "Керувати",
"terminalLinkBehavior": "Натисніть посилання терміналу",
"terminalLinkBehaviorDesc": "Поведінка за замовчуванням під час натискання посилань у терміналі",
"historyTracking": "Відстеження історії",
@@ -3424,22 +3228,6 @@
"apiKeyUsageHint": "Додайте свій ключ до",
"apiKeyUsageHintHeader": "заголовок.",
"apiKeyPermissionsHint": "Ключі успадковують дозволи користувача, який їх створює.",
"exportData": "Експорт моїх даних",
"exportDataDesc": "Завантажте резервну копію своїх хостів, облікових даних та налаштувань для перенесення на інший пристрій",
"export": "Експорт",
"exporting": "Експорт...",
"importData": "Імпортувати мої дані",
"importDataDesc": "Відновіть свої хости, облікові дані та налаштування з резервного файлу .sqlite",
"importDataSelected": "Вибрано: {{name}}",
"selectFile": "Виберіть файл",
"changeFile": "Зміна",
"import": "Імпорт",
"importing": "Імпорт...",
"exportSuccess": "Дані успішно експортовано",
"exportFailed": "Не вдалося експортувати дані",
"importSelectFile": "Спочатку виберіть файл",
"importCompleted": "Імпорт завершено: імпортовано {{total}} елементів, пропущено {{skipped}}",
"importFailed": "Помилка імпорту: {{error}}",
"roleUser": "Користувач",
"authMethodDual": "Подвійна автентифікація",
"authMethodOidc": "ОІДК",
@@ -3591,7 +3379,6 @@
"pageUp": "PgUp",
"pageDown": "ПгДн",
"delete": "Дел",
"paste": "Вставити",
"editQuickKeys": "Редагувати швидкі клавіші",
"quickKeysTitle": "Швидкі клавіші",
"quickKeysDesc": "Натисніть ×, щоб видалити. Підтримує до 8 символів.",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+124 -23
View File
@@ -646,6 +646,12 @@ function isDev(): boolean {
const apiHost = import.meta.env.VITE_API_HOST || "localhost";
export interface ServerConfig {
serverUrl: string;
lastUpdated: string;
allowInvalidCertificate?: boolean;
}
interface AxiosRequestConfigExtended extends AxiosRequestConfig {
startTime?: number;
requestId?: string;
@@ -656,6 +662,70 @@ interface AxiosErrorExtended extends AxiosError {
config?: AxiosRequestConfigExtended;
}
/**
* Reads the remote-sync connection record (repurposed from the old
* server-config.json startup-gate file). Not used to route the app's own
* API instances anymore -- see getApiUrl, which always targets the
* embedded backend in Electron. This is only consumed by the Remote Sync
* settings panel (Phase 3).
*/
export async function getServerConfig(): Promise<ServerConfig | null> {
if (!isElectron()) return null;
try {
const result = await (
window as Window &
typeof globalThis & {
IS_ELECTRON?: boolean;
electronAPI?: unknown;
}
).electronAPI?.invoke("get-server-config");
return result;
} catch (error) {
console.error("Failed to get server config:", error);
return null;
}
}
export async function saveServerConfig(config: ServerConfig): Promise<boolean> {
if (!isElectron()) return false;
try {
const result = await (
window as Window &
typeof globalThis & {
IS_ELECTRON?: boolean;
electronAPI?: unknown;
}
).electronAPI?.invoke("save-server-config", config);
return !!result?.success;
} catch (error) {
console.error("Failed to save server config:", error);
return false;
}
}
export async function testServerConnection(
serverUrl: string,
): Promise<{ success: boolean; error?: string }> {
if (!isElectron())
return { success: false, error: "Not in Electron environment" };
try {
const result = await (
window as Window &
typeof globalThis & {
IS_ELECTRON?: boolean;
electronAPI?: unknown;
}
).electronAPI?.invoke("test-server-connection", serverUrl);
return result;
} catch (error) {
console.error("Failed to test server connection:", error);
return { success: false, error: "Connection test failed" };
}
}
export async function checkElectronUpdate(): Promise<{
success: boolean;
status?: "up_to_date" | "requires_update" | "beta";
@@ -690,6 +760,36 @@ export async function checkElectronUpdate(): Promise<{
}
}
/**
* Reports whether the local embedded backend process is alive. The desktop
* app always runs the embedded backend now, so this is purely a liveness
* check -- it no longer reflects a "mode" the app could be in.
*/
export async function getEmbeddedServerStatus(): Promise<{
running: boolean;
dataDir: string | null;
} | null> {
if (!isElectron()) return null;
try {
const result = await (
window as Window &
typeof globalThis & {
IS_ELECTRON?: boolean;
electronAPI?: {
invoke: (channel: string, ...args: unknown[]) => Promise<unknown>;
};
}
).electronAPI?.invoke("get-embedded-server-status");
return result as {
running: boolean;
dataDir: string | null;
} | null;
} catch {
return null;
}
}
function getApiUrl(path: string, defaultPort: number): string {
const devMode = isDev();
const electronMode = isElectron();
@@ -1601,6 +1701,25 @@ export async function loginUser(
rememberMe,
});
const isInIframe =
typeof window !== "undefined" && window.self !== window.top;
if (isInIframe && isElectron() && response.data.success) {
try {
window.parent.postMessage(
{
type: "AUTH_SUCCESS",
source: "login_api",
platform: "desktop",
timestamp: Date.now(),
},
window.location.origin,
);
} catch (e) {
console.error("[main-axios] Error posting message to parent:", e);
}
}
if (response.data.token) {
localStorage.setItem("jwt", response.data.token);
}
@@ -1767,18 +1886,7 @@ export async function getSetupRequired(): Promise<{ setup_required: boolean }> {
// second mint silently invalidates whichever token the app already started
// using, surfacing as a spurious "session expired/revoked" on the very
// next request.
let desktopAutoSessionRequest: Promise<DesktopAutoSessionOutcome> | null = null;
// A 403 from the auto-session endpoint means the backend positively
// evaluated the request and declined (not loopback, or not exactly one
// local user) -- that verdict won't change by retrying. Any other failure
// (connection refused, timeout, 5xx) most likely means the embedded
// backend process hasn't finished booting yet, which is routine on a cold
// first launch, so it's worth retrying rather than treated as final.
export type DesktopAutoSessionOutcome =
| { kind: "success"; data: AuthResponse }
| { kind: "declined" }
| { kind: "retry" };
let desktopAutoSessionRequest: Promise<AuthResponse | null> | null = null;
/**
* Electron-only, non-iframed local login: exchanges the embedded backend's
@@ -1787,7 +1895,7 @@ export type DesktopAutoSessionOutcome =
* or otherwise multi-user install never satisfies this, and falls through
* to a normal login screen instead).
*/
export async function requestDesktopAutoSession(): Promise<DesktopAutoSessionOutcome> {
export async function requestDesktopAutoSession(): Promise<AuthResponse | null> {
if (desktopAutoSessionRequest) return desktopAutoSessionRequest;
desktopAutoSessionRequest = (async () => {
@@ -1798,17 +1906,10 @@ export async function requestDesktopAutoSession(): Promise<DesktopAutoSessionOut
}
if (response.data?.success) {
markUserAuthenticated();
return { kind: "success" as const, data: response.data };
}
return { kind: "declined" as const };
} catch (err) {
const status =
(err as { status?: number; response?: { status?: number } })?.status ??
(err as { response?: { status?: number } })?.response?.status;
if (status === 403) {
return { kind: "declined" as const };
}
return { kind: "retry" as const };
return response.data;
} catch {
return null;
}
})();
+24 -56
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Server, RefreshCw, Loader2 } from "lucide-react";
import { Button } from "@/components/button.tsx";
@@ -29,11 +29,7 @@ interface RemoteSyncStatus {
type DesktopSettings = { defaultConnectionOrigin: "local" | "remote" };
interface RemoteSyncPanelProps {
initialServerUrl?: string;
}
export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
export function RemoteSyncPanel() {
const { t } = useTranslation();
const [config, setConfig] = useState<RemoteSyncConfig | null>(null);
const [status, setStatus] = useState<RemoteSyncStatus | null>(null);
@@ -43,7 +39,6 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
const [step, setStep] = useState<"idle" | "picker" | "login">("idle");
const [pendingServerUrl, setPendingServerUrl] = useState("");
const [syncingNow, setSyncingNow] = useState(false);
const consumedInitialServerUrl = useRef(false);
const refresh = useCallback(async () => {
const [cfg, st, settings] = await Promise.all([
@@ -70,14 +65,6 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
return () => unsubscribe?.();
}, [refresh]);
useEffect(() => {
if (consumedInitialServerUrl.current) return;
if (!initialServerUrl || config?.serverUrl) return;
consumedInitialServerUrl.current = true;
setPendingServerUrl(initialServerUrl);
setStep("picker");
}, [initialServerUrl, config]);
const handleConnectClick = () => setStep("picker");
const handleServerConfigured = (serverUrl: string) => {
@@ -112,18 +99,6 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
};
const isConnected = !!config?.serverUrl;
const connectedToText = config?.serverUrl
? t("remoteSync.connectedTo", { url: config.serverUrl })
: "";
const connectionSubtitleText = status?.needsReauth
? t("remoteSync.needsReauth")
: status?.lastError
? t("remoteSync.syncError", { message: status.lastError })
: status?.lastSyncedAt
? t("remoteSync.lastSynced", {
time: new Date(status.lastSyncedAt).toLocaleString(),
})
: t("remoteSync.neverSynced");
return (
<div className="flex flex-col gap-4">
@@ -136,21 +111,27 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
{t("remoteSync.description")}
</p>
<div className="flex flex-col gap-2 border-t border-border pt-3">
<div className="flex flex-col gap-0.5 min-w-0">
<div className="flex items-center justify-between border-t border-border pt-3">
<div className="flex flex-col gap-0.5">
{isConnected ? (
<>
<span
className="text-xs font-medium break-all"
title={connectedToText}
>
{connectedToText}
<span className="text-xs font-medium">
{t("remoteSync.connectedTo", { url: config.serverUrl })}
</span>
<span
className="text-[10px] text-muted-foreground break-words"
title={connectionSubtitleText}
>
{connectionSubtitleText}
<span className="text-[10px] text-muted-foreground">
{status?.needsReauth
? t("remoteSync.needsReauth")
: status?.lastError
? t("remoteSync.syncError", {
message: status.lastError,
})
: status?.lastSyncedAt
? t("remoteSync.lastSynced", {
time: new Date(
status.lastSyncedAt,
).toLocaleString(),
})
: t("remoteSync.neverSynced")}
</span>
</>
) : (
@@ -160,7 +141,7 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
)}
</div>
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-2 shrink-0">
{isConnected ? (
<>
{status?.needsReauth && (
@@ -250,7 +231,7 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
open={step === "picker"}
onOpenChange={(open) => !open && setStep("idle")}
>
<DialogContent className="bg-card border border-border sm:max-w-md">
<DialogContent className="bg-card border border-border max-w-md">
<DialogHeader>
<DialogTitle className="sr-only">
{t("remoteSync.title")}
@@ -259,7 +240,6 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
<RemoteSyncServerPicker
onServerConfigured={handleServerConfigured}
onCancel={() => setStep("idle")}
initialServerUrl={initialServerUrl}
/>
</DialogContent>
</Dialog>
@@ -268,20 +248,8 @@ export function RemoteSyncPanel({ initialServerUrl }: RemoteSyncPanelProps) {
open={step === "login"}
onOpenChange={(open) => !open && setStep("idle")}
>
<DialogContent
className="bg-card border border-border sm:max-w-4xl h-[80vh] flex flex-col p-0 gap-0"
// The entire dialog body is a cross-document iframe (the remote
// server's own login page). Radix's default "outside interaction"
// dismiss can't attribute clicks inside a different document to
// this dialog's content tree, so without this it treats a click
// on the iframe's own login button as an outside click and closes
// the dialog instantly, before any auth round-trip even starts --
// this is what looked like the login button silently doing
// nothing. Escape and the explicit X button still close it.
onPointerDownOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => e.preventDefault()}
>
<DialogHeader className="sr-only">
<DialogContent className="bg-card border border-border max-w-4xl h-[80vh] flex flex-col p-0">
<DialogHeader className="p-4 pb-0">
<DialogTitle>
{t("remoteSync.signInTitle", { url: pendingServerUrl })}
</DialogTitle>
+1 -19
View File
@@ -6,7 +6,6 @@ import { Alert, AlertTitle, AlertDescription } from "@/components/alert.tsx";
import { Switch } from "@/components/switch.tsx";
import { useTranslation } from "react-i18next";
import { ChevronDown, X } from "lucide-react";
import { toast } from "sonner";
const SAVED_URLS_KEY = "termix_saved_server_urls";
const MAX_SAVED_URLS = 5;
@@ -38,16 +37,14 @@ function removeSavedUrl(url: string) {
interface RemoteSyncServerPickerProps {
onServerConfigured: (serverUrl: string) => void;
onCancel: () => void;
initialServerUrl?: string;
}
export function RemoteSyncServerPicker({
onServerConfigured,
onCancel,
initialServerUrl,
}: RemoteSyncServerPickerProps) {
const { t } = useTranslation();
const [serverUrl, setServerUrl] = useState(initialServerUrl ?? "");
const [serverUrl, setServerUrl] = useState("");
const [allowInvalidCertificate, setAllowInvalidCertificate] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -95,21 +92,6 @@ export function RemoteSyncServerPicker({
return;
}
const testResult = (await window.electronAPI?.invoke?.(
"test-server-connection",
normalizedUrl,
)) as { success?: boolean; error?: string; warning?: string } | undefined;
if (!testResult?.success) {
setError(testResult?.error || t("remoteSync.connectionTestFailed"));
setLoading(false);
return;
}
if (testResult.warning) {
toast.warning(testResult.warning);
}
const result = await window.electronAPI?.invoke?.(
"save-remote-sync-config",
{
+6 -18
View File
@@ -196,21 +196,15 @@ export function createHostEditorForm(
host?.quickActions ?? ([] as { name: string; snippetId: string }[]),
rdpCredentialId: host?.rdpCredentialId ?? "",
rdpUser: host?.rdpUser ?? "",
rdpPassword: host?.hasRdpPassword
? "existing_rdp_password"
: (host?.rdpPassword ?? ""),
rdpPassword: host?.rdpPassword ?? "",
domain: host?.domain ?? "",
security: host?.security ?? "",
ignoreCert: host?.ignoreCert ?? false,
vncCredentialId: host?.vncCredentialId ?? "",
vncPassword: host?.hasVncPassword
? "existing_vnc_password"
: (host?.vncPassword ?? ""),
vncPassword: host?.vncPassword ?? "",
vncUser: host?.vncUser ?? "",
telnetUser: host?.telnetUser ?? "",
telnetPassword: host?.hasTelnetPassword
? "existing_telnet_password"
: (host?.telnetPassword ?? ""),
telnetPassword: host?.telnetPassword ?? "",
telnetCredentialId:
host?.telnetCredentialId != null ? String(host.telnetCredentialId) : "",
rdpAuthType: (host?.rdpAuthType ??
@@ -354,9 +348,7 @@ export function buildHostEditorPayload(
? form.rdpUser || null
: null,
rdpPassword:
protocols.enableRdp &&
form.rdpAuthType === "direct" &&
form.rdpPassword !== "existing_rdp_password"
protocols.enableRdp && form.rdpAuthType === "direct"
? form.rdpPassword || null
: null,
rdpDomain: form.domain || null,
@@ -370,9 +362,7 @@ export function buildHostEditorPayload(
? Number(form.vncCredentialId)
: null,
vncPassword:
protocols.enableVnc &&
form.vncAuthType === "direct" &&
form.vncPassword !== "existing_vnc_password"
protocols.enableVnc && form.vncAuthType === "direct"
? form.vncPassword || null
: null,
vncUser:
@@ -391,9 +381,7 @@ export function buildHostEditorPayload(
? form.telnetUser || null
: null,
telnetPassword:
protocols.enableTelnet &&
form.telnetAuthType === "direct" &&
form.telnetPassword !== "existing_telnet_password"
protocols.enableTelnet && form.telnetAuthType === "direct"
? form.telnetPassword || null
: null,
jumpHosts: form.jumpHosts,

Some files were not shown because too many files have changed in this diff Show More