feat: move the terminal fully into the ssh-terminal plugin (B9)

This commit is contained in:
LukeGus committed 2026-09-23 21:41:22 -05:00
1 parent 535126fd1b
commit 79d77678c9
309 files changed
+41789 -14958

No files matched your search

+1 -10
View File
@@ -161,7 +161,7 @@ http {
root /app/html;
index index.html index.htm;
expires off;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-zKdkhhPi/Ts2hmKgRCOB9Lfl+DkWJeRhR+a0sSotsBc='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-OJgSdz/tftu3bBUxP1DqbBkfBm/fs2VjcBuZm3txHlk='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
@@ -303,15 +303,6 @@ http {
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/terminal(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/plugins(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
+1 -10
View File
@@ -142,7 +142,7 @@ http {
root /app/html;
index index.html index.htm;
expires off;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-zKdkhhPi/Ts2hmKgRCOB9Lfl+DkWJeRhR+a0sSotsBc='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-OJgSdz/tftu3bBUxP1DqbBkfBm/fs2VjcBuZm3txHlk='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
@@ -283,15 +283,6 @@ http {
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/terminal(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/plugins(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
+5
View File
@@ -0,0 +1,5 @@
-- command_history moved to the ssh-terminal plugin, whose first migration
-- renames it to p_ssh_terminal_command_history. Dropping it here would lose
-- every saved command, so this only stops drizzle tracking it. SELECT 1
-- because MySQL rejects an empty query.
SELECT 1;
File diff suppressed because it is too large. Load diff
+7
View File
@@ -267,6 +267,13 @@
"when": 1790201005569,
"tag": "0037_foamy_norman_osborn",
"breakpoints": true
},
{
"idx": 38,
"version": "5",
"when": 1790208810478,
"tag": "0038_quiet_karnak",
"breakpoints": true
}
]
}
@@ -0,0 +1,5 @@
-- command_history moved to the ssh-terminal plugin, whose first migration
-- renames it to p_ssh_terminal_command_history. Dropping it here would lose
-- every saved command, so this only stops drizzle tracking it. SELECT 1
-- because MySQL rejects an empty query.
SELECT 1;
File diff suppressed because it is too large. Load diff
+7
View File
@@ -274,6 +274,13 @@
"when": 1790201004950,
"tag": "0038_cute_vermin",
"breakpoints": true
},
{
"idx": 39,
"version": "7",
"when": 1790208809860,
"tag": "0039_violet_firedrake",
"breakpoints": true
}
]
}
@@ -0,0 +1,5 @@
-- command_history moved to the ssh-terminal plugin, whose first migration
-- renames it to p_ssh_terminal_command_history. Dropping it here would lose
-- every saved command, so this only stops drizzle tracking it. SELECT 1
-- because MySQL rejects an empty query.
SELECT 1;
File diff suppressed because it is too large. Load diff
+7
View File
@@ -246,6 +246,13 @@
"when": 1790201004314,
"tag": "0034_curious_molecule_man",
"breakpoints": true
},
{
"idx": 35,
"version": "6",
"when": 1790208809221,
"tag": "0035_striped_strong_guy",
"breakpoints": true
}
]
}
+109 -18
View File
@@ -248,6 +248,20 @@ arrives with a share token), the Docker console (per-message auth), and the
Guacamole display (a single-use encrypted connection token in the query, minted
by an authenticated route).
**B9** added `optionalAuth` for a public route: core still verifies a token
when the client sent one and hands the handler that user, else an empty id.
The terminal uses it, because one socket serves both signed-in users and
share-link or room guests, who arrive with a share token instead. The route is
public so a guest can reach it at all; guest auth itself stays in the plugin's
handler (noted for D2). Every route connection now also carries `clientIp`,
`requestOrigin` and `isDataUnlocked()`, the data-key check a long-lived socket
repeats per message so an expired session stops being served.
Socket and ssh2 event listeners fire outside the upgrade's async context, so a
plugin that calls privileged ctx members from them binds them with Node's
`AsyncResource.bind` while the actor is still set; the terminal does this for
every listener it registers.
### 7. Settings
Plugins declare settings fields in the manifest with scope `admin`, `user` or
@@ -426,8 +440,10 @@ to list manager cards plugins added to `host-metrics.managers` next to its
own). The SDK has no runtime dependencies: the hooks delegate to a host
bridge core installs.
Slots core owns: `terminal.toolbar`, `terminal.dock`, `terminal.overlay`
(declared by ssh-terminal), `onboarding.steps`, `onboarding.features`,
Slots core owns: `terminal.toolbar`, `terminal.toolbarStatus`,
`terminal.sidePanel`, `terminal.overlay` (declared by ssh-terminal; **B9**
renamed `terminal.dock` to `terminal.sidePanel` and added the status slot,
where host-metrics puts its CPU, memory and disk bars), `onboarding.steps`, `onboarding.features`,
`onboarding.workflow`, `hosts.importMenu`, `hosts.panel`, `proxmox.hostEditor`
and `session.remoteDisplay`. `host-metrics.managers` (component slot,
**B3**) is host-metrics's own: tailscale contributes its manager card there
@@ -438,6 +454,29 @@ plugin actions the same way when a core view shows a plugin's data:
`files.openHost`, and **B7**'s `tunnels.statuses` and `tunnels.open` behind the
dashboard's active tunnel counter, which reads as zero while tunnels is off.
**B9** moved the terminal itself into ssh-terminal, which adds
`terminal.open(host, { path?, joinSharedSessionId?, joinShareId?, label? })`
(the file manager's "open terminal here", the connections panel's "join a
shared session") and replaces `ShellApi.openTerminalTab`, which the shell no
longer has.
**Components by id.** `app.registerComponent(id, component)` offers a
component to other plugins and core, which render it with `PluginComponent`
from `@termix/plugin-sdk/ui` (a fallback while the owner is off) or
`usePluginComponent(id)`. ssh-terminal registers `terminal.view`, the
embeddable terminal the collab room, the homepage SSH widget, the tmux
monitor and the file manager's window render instead of importing it; its
handle comes back through a `handleRef` prop, since a registered component
cannot take a React ref.
**Session tabs.** `TabOptions` gained `commandTarget` (history, macros and SSH
tools act on the last one focused; `PanelProps.targetTab` hands a panel that
tab), `ownBackground` (the shell leaves the frame transparent) and
`multiInstance` (each open is a new tab). `TabHandle` documents what a session
tab puts on `handleRef`: focus, reconnect, disconnect and, for sharing,
`getShareTarget()`, which the shell's own share dialog uses. These replaced
every check of the `terminal` and `local-terminal` tab types in the shell.
**B2** added a live-terminal-session surface, for a plugin that needs to push
resolved text into an open SSH session rather than just fill a slot with a
button: ssh-terminal registers `terminal.listSessions()` (open sessions with
@@ -466,12 +505,28 @@ skipped, and it stays in the next snapshot (`isUnregisteredPluginTabType` in
`src/ui/plugin-host/sdk-ui.ts`. Adding to it is a contract change; removing
from it is a breaking one. Today it exports: alert, alert-dialog, badge,
button, card, checkbox, dialog, dropdown-menu, input, label, password-input,
select, select2, separator, switch, textarea, tooltip, section-card,
select, select2, separator, switch, textarea, tooltip, sheet, section-card,
metric-card, charts, the card grid, `ConnectionScreen` and the connection
status helpers, `SnippetVariablesDialog`, `FullScreenAppWrapper`, the
connection log context, `TOTPDialog`, `SSHAuthDialog`, `WarpgateDialog`,
`useTabs`/`useTabsSafe`, `ActionSlot`, `ComponentSlot` and `FOLDER_COLORS` (the
colour swatches folders and workspaces pick from). Publishing its
`PassphraseDialog`, `OPKSSHDialog` (until opkssh moves in Phase C),
`HostKeyVerificationDialog`, `useTabs`/`useTabsSafe`, `ActionSlot`,
`ComponentSlot`, `PluginComponent` and `FOLDER_COLORS` (the colour swatches
folders and workspaces pick from).
**B9** added what a terminal-like surface needs from the shell: `cn`,
`useConfirmation`, `useIsMobile`, `runAdaptivePolling`; the terminal look
(`TERMINAL_THEMES`, `TERMINAL_FONTS`, `DEFAULT_TERMINAL_CONFIG`,
`resolveTermixThemeColors`, `ensureTerminalFontsLoaded`, the font zoom limits,
the clipboard helpers and `useAppTheme`, the full theme id rather than light or
dark), shared by the SSH and local terminals, the docker console and serial
from `src/ui/lib/terminal-look/`; keyboard handling shared with the shell
(`findMatchingKeybinding`, `globalShortcutHandler`, `isTabJumpHotkey`); the
connection helpers (`isElectron`, `resolveConnectionOrigin`, `pluginWsUrl`,
`hydrateLocalSharedHostAuth`, `useConnectionDefaults`); and a few core APIs
(`logActivity`, `getHostPassword`, `patchOpenTab`, `getUserPreferences`,
`parseCustomKeybindings`, `setHostAutoTmux`, `getCookie`). The APIs are a
stopgap in the right place: D1 turns them into typed bridge members. Publishing its
`.d.ts` for plugins outside this repo is a follow-up for the repo split.
#### Strings
@@ -520,11 +575,34 @@ every enabled plugin that declares `contributes.settings.host`, it looks up
`"<id>.hostImportNormalizer"` and, if the plugin registered one, calls it with
the raw imported row and writes back whatever it returns (or nothing, for
`null`). This is not part of the SDK's typed `ctx` surface - it is a plain
`ctx.registry` convention, the same mechanism `terminal.sessions` and
`remote-desktop.sessions` already use, just with a name core's own code knows
`ctx.registry` convention, the same mechanism `remote-desktop.sessions`
already uses, just with a name core's own code knows
to look for. web-endpoint (`src/backend/host-import.ts`) is the first plugin
to register one.
**B9**'s terminal provides `sessions.live` (live SSH sessions: look up, end,
remove a share's participants, hand a room share's control over, subscribe to
output, write) and `terminal.history` (the actor's command history, which the
AI assistant reads). It consumes three optional services that later steps
provide: `tmux.sessions` (B10; without it the terminal skips tmux attach),
`sessions.sharing` (B12; member joins and collab room events) and
`recordings.writer` (B13; without it nothing is recorded). The terminal keeps
the asciicast formatting and the 300 ms batched flush (issue #1049) and hands
the provider one `append` per batch. Service names need a dot, so "tmux" and
"recordings" became `tmux.sessions` and `recordings.writer`.
A service call runs a permission check for its actor, so a share-link guest,
who has no user, cannot make one. Guest link resolution is therefore
published on `ctx.registry` as `sessions.sharing.guests` instead: the token is
the authority there.
**Core consuming a plugin service.** Session sharing, collab and the open
tabs route are still core and reach live terminal sessions through
`getServiceImplementation(service, range)` in `service-registry.ts`
(`hosts/live-terminal-sessions.ts` wraps it). Core already authorized the
request, so this skips the per-call check and returns the same object
plugins get, or undefined while no compatible provider runs.
### 10. Lifecycle
`activate(ctx)` creates all state. Everything registered through `ctx` or `app`
@@ -1082,6 +1160,7 @@ Built per plugin in `src/backend/plugins/ctx.ts` and passed to `activate`.
| `ctx.notify.*` | `notify:send` | A6 |
| `ctx.auth.*` | `auth:provide` | **A8** |
| `ctx.desktop.openIsolatedWindow` | `desktop:window` | **B8** |
| `ctx.audit.record` | none, the actor is the runtime's | **B9** |
| `ctx.fetch` | `network:outbound` | B |
`ctx.ssh` was pulled forward from B so plugin transports go through core's
@@ -1107,6 +1186,15 @@ connect pipeline instead of importing ssh2 helpers from core:
since the file-manager plugin's interactive connect route and its dedicated
transfer sessions both need their own keepalive/timeout defaults rather than
falling back to the generic `"plugin"` purpose.
- **B9** added what an interactive transport needs, for the terminal:
`resolveHost(hostId, { syncId? })` (the host with secrets, by sync id first,
audited), the `"terminal"` purpose, `interactive` and `hostKeySocket` on
`prepare` (so a changed host key can be accepted over the socket), the
provider's `onBanner` and `onAuthFailed` hooks bound on its result,
`openTransport(..., { resolveDns, log })`, and `startInteraction` /
`cancelInteraction` for the browser sign-ins (opkssh, step-ca, vault), which
the terminal used to reach by import. Providers gained an optional
`cancelInteraction`.
- **B7** added `sock` to the connect options: an already-open stream to the
host, such as a `forwardOut` channel through another host. `connect` then
skips the transport step (port knocking, proxy, jump hosts, DNS) and runs
@@ -1165,6 +1253,12 @@ existing ones (proxmox's discovery and sync flow is the first caller):
detail" surface those calls need to read back, not a wider read grant than
`hosts:read` gives through `list`/`get`.
**B9** added `trackSession(hostId)` (the online indicator, counted with every
other feature's sessions; synchronous, so only the declaration is checked, like
`ctx.http.router`) and `recordActivity(hostId, type, hostName)` (the same rate
limit and access rule as the dashboard's route, which now shares
`services/recent-activity.ts` with it). Both need `hosts:read`.
`PluginHostRecord` carries no secret auth material (password, key, vault
token): a plugin that creates a host picks an `authType` and, for
`"credential"`, a `credentialId` it does not need to see the contents of.
@@ -1318,7 +1412,8 @@ and that is bundled into its output.
**Backend** (plus every node builtin): `@termix/plugin-sdk`, `express`, `ssh2`,
`ws`, `multer`, `cookie-parser`, `axios`, `jszip`, `guacamole-lite`,
`@anthropic-ai/sdk`, `drizzle-orm`.
`@anthropic-ai/sdk`, `drizzle-orm`, `sharp` (native, so it cannot be bundled;
the terminal's image upload loads it on first use).
**Frontend**: `react`, `react-dom`, `react-dom/client`, `react/jsx-runtime`,
`i18next`, `react-i18next`, `sonner`, `@termix/plugin-sdk/frontend`,
@@ -1666,8 +1761,8 @@ What the lint fence enforces today, in `eslint.config.mjs`:
| Core importing a plugin backend | **Error** | 0 | - |
| A plugin backend importing frontend code or `@/` | **Error** | 0 | - |
| The shell importing plugin code | **Error** | 0 | - |
| A plugin frontend importing core through `@/` | Warning | 135 files | D1 |
| A plugin importing core by relative path | Warning | 67 files | D1 |
| A plugin frontend importing core through `@/` | Warning | 130 files | D1 |
| A plugin importing core by relative path | Warning | 66 files | D1 |
| A plugin importing another plugin's source | Warning | 3 files | B18 |
A warning does not fail a build, so the counts are held by
@@ -1702,14 +1797,10 @@ Known specifics:
**B6** moved file-manager off port 30004 onto `/plugin-api/file-manager/`,
and **B7** moved tunnels off port 30003 onto `/plugin-api/tunnels/` and
`/plugin-ws/tunnels/c2s/stream`.
- The terminal component and `TerminalTabContent` stay in core until the
terminal's Phase B step (session manager, split view). ssh-terminal
registers them through the legacy alias; **B6** switched every core caller
of the file manager over to `invokeAction("files.openHost"/"files.openEditor")`
instead of the `openFileManager`/`openFileInEditor` shell callbacks it
removed, so `TerminalWindow.tsx`'s embedding of the core terminal component
is now the only file-manager-adjacent legacy-core import left, tracked the
same way as any other plugin depending on an unconverted core component.
- **B9** moved the terminal, its session manager, the local terminal and the
command history panel into ssh-terminal, which imports nothing from core.
The file manager's terminal window renders the `terminal.view` component
instead of importing the core terminal.
- Host data columns (`enableDocker`, `enableRdp`, `guacamoleConfig` and so on)
stay in core types until Phase B moves them. Only UI branches on them left
the shell.
+88
View File
@@ -144,3 +144,91 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite
to ask Electron's main process for something, extend
`BACKEND_REQUEST_HANDLERS` there rather than building a parallel channel.
No owner needed unless a second caller appears.
- **B9 (ssh-terminal):** session sharing has no provider yet. The terminal
consumes `sessions.sharing` v1 (member joins through `authorizeJoin` and
`recordJoin`, collab room events through `subscribeRoom`/`unsubscribeRoom`)
and reads guest link resolution from `ctx.registry` as
`sessions.sharing.guests` (`resolve`, `recordJoin`), all typed in
`plugins/ssh-terminal/src/backend/services.ts`. Until something provides
them, share-link and room guests are refused and in-app joins fail. Core's
session-sharing, collab and open-tabs code reach the terminal through
`sessions.live` already. Owner: B12, which provides both from the
session-sharing plugin (the lookups are the old code in
`hosts/session-sharing` and `hosts/collab`: share repo, room repo, guest
rate limit, `canJoinRoomStageShare`, `session_sharing_globally_enabled`,
host `allowSessionSharing`, `getAuditUsername` for labels, `collabRoomHub`).
B12 also moves `eventsWsPath` in `collab/routes.ts` and the share dialog the
shell opens from a tab's `getShareTarget()`.
- **B9 (ssh-terminal), for D2:** the terminal socket is a public route with
`optionalAuth`, because share-link and room guests authenticate inside the
handler with their token. Guest auth itself is unchanged; review it with the
rest of the public routes.
- **B9 (ssh-terminal):** tmux has no provider yet. The terminal calls the
optional `tmux.sessions` v1 (`detect`, `attachOrCreate`, `waitForSession`)
and skips tmux attach without it (auto-tmux, the tmux monitor's attach, the
session picker). The toolbar's old host action list only ever held the
Tmux Monitor button, which is gone with it. `TmuxSessionPicker` lives in
ssh-terminal now. Owner: B10, which provides the service (the helpers are
`hosts/tmux/helper.ts`) and contributes its toolbar button to
`terminal.toolbar`.
- **B9 (ssh-terminal):** recording has no provider yet. The terminal calls the
optional `recordings.writer` v1 `open(meta)` and gets a sink with
`append(chunk)` (one call per 300 ms batch, the first starting with the
asciicast header), `persist(summary)` and `discard()`. Without it nothing is
recorded and no `session_recordings` row is written. Owner: B13, which
writes the file under `DATA_DIR/session_logs/<user>/<session>.cast` (mkdir
and writeFile on the first append, appendFile after, never per chunk, see
issue #1049) and creates or updates the row as the old session manager did.
- **B9 (ssh-terminal):** `enable_terminal`, `enable_terminal_toolbar` and
`enable_command_history` are still live `ssh_data` columns. The copy into
ssh-terminal host settings shipped (`ssh-terminal-settings-migration.ts`)
and the terminal reads its settings, but core host routes, types, the
export/import sample and `HostEditorData` still carry the columns.
`enable_terminal_toolbar` also drives the RDP/VNC/Telnet toolbar
(`remote-desktop/GuacamoleApp.tsx`), whose host editor switch stays in the
General tab, so remote-desktop needs its own host setting and copy first.
Then drop all three in lockstep. Owner: remote-desktop's step for its copy,
then D0.
- **B9 (ssh-terminal):** the legacy `settings` rows for the terminal
(`terminal_session_timeout_minutes`, `terminal_session_persistence_enabled`,
`command_history_enabled`, `touch_input_settings`, `terminal_image_*`) are
left in place for a release after the move; nothing reads them. Delete them
in 3.0.0. `sessionPersistence` is carried over but, as before, nothing acts
on it. Owner: D0 to decide.
- **B9 (ssh-terminal):** the image storage paths used to be rejected at save
when not absolute; the schema-driven settings form accepts any string and
the resolver ignores a bad one with a warning. A custom field or a validate
hook on plugin settings would restore the save-time error. Owner: D1.
- **B9 (ssh-terminal):** `@termix/plugin-sdk/ui` now re-exports a handful of
core APIs the terminal needs (`logActivity`, `getHostPassword`,
`patchOpenTab`, `getUserPreferences`, `parseCustomKeybindings`,
`setHostAutoTmux`, `getCookie`) and the connection helpers. They belong on
the typed host bridge, which is also where B7's `app.logActivity` line
above lands. Owner: D1.
- **B9 (ssh-terminal):** `OPKSSHDialog` is exported from
`@termix/plugin-sdk/ui` so the terminal can show the opkssh sign-in. It
leaves the ui entry when opkssh becomes a plugin that draws its own UI in
`terminal.overlay`. Owner: Phase C (opkssh).
- **B9 (host-metrics):** the terminal toolbar's CPU/memory/disk bars
(`TerminalMetricsStatus`) poll the local backend only; on the desktop app a
host that lives on a connected remote server shows no bars. Same fix as the
B7 remote-origin line above. Owner: D1.
- **B9:** the Hosts panel's "Terminal" feature filter was removed along with
the column reads, the same as B7's tunnel filter. Owner: D1, with the
generic host-settings filter.
- **B9:** the startup purge of expired open tabs in `db/index.ts` read
`terminal_session_timeout_minutes` from core settings; it was removed and
expired tabs are dropped when the tab list is read, against the terminal's
own timeout (`GET /open-tabs/session-timeout`, 30 while the terminal is
off). No owner unless the table growth matters.
- **Pre-existing (B8), found in B9:** `node scripts/check-shell-plugin-ids.cjs`
fails on `src/ui/lib/host-to-ssh-host.ts` spelling `web-endpoint` (the
`pluginSettings` lookup B8 added). Not allowlisted and not introduced here.
Owner: D0 (read it through a registry or allowlist it with a reason).
## Manual checks after 2.9.0
- SSH terminal: password auth, key auth (and an encrypted key's passphrase
prompt), TOTP keyboard-interactive, a jump host, Warpgate, split view,
snippets sent to a terminal, reconnect after a network drop and after a
page reload (session reattach), and the local terminal in the desktop app.
@@ -23,6 +23,8 @@ export const BACKEND_EXTERNALS = [
"@anthropic-ai/sdk",
"drizzle-orm",
"drizzle-orm/*",
// Native image processing, loaded on first use by the terminal's image upload.
"sharp",
];
/**
+131 -5
View File
@@ -292,6 +292,16 @@ export interface PluginWebSocketConnection {
readonly request: unknown;
/** The ws WebSocket. Binary frames and backpressure work as usual. */
readonly socket: unknown;
/** The client address, behind a trusted proxy when one is configured. */
readonly clientIp: string;
/** The public origin the request came in on, for browser redirects. */
readonly requestOrigin: string;
/**
* Whether the user's data key is still unlocked. False for a guest and
* once a session expires, so a long-lived socket can refuse work it can no
* longer decrypt for.
*/
isDataUnlocked: () => boolean;
}
export type PluginWebSocketHandler = (
@@ -305,6 +315,12 @@ export interface PluginWebSocketOptions {
* guest or a token-in-query protocol needs.
*/
public?: boolean;
/**
* With `public`, still resolves a token when the client sent one, so one
* route can serve signed-in users and a guest arriving with a token of its
* own. The handler gets the user id, or an empty one for a guest.
*/
optionalAuth?: boolean;
}
/**
@@ -551,6 +567,22 @@ export interface PluginHosts {
listUsers: () => Promise<PluginShareableUser[]>;
/** Non-system roles the caller may pick as a share target. */
listRoles: () => Promise<PluginShareableRole[]>;
/**
* Marks a live session on a host, for the online indicator. Counted with
* every other feature's sessions; call the returned function once when the
* session ends. Needs hosts:read.
*/
trackSession: (hostId: number) => () => void;
/**
* Adds a recent activity entry for the acting user, rate limited and
* refused for a host they cannot reach, like the dashboard's own route.
* Needs hosts:read.
*/
recordActivity: (
hostId: number,
type: string,
hostName: string,
) => Promise<void>;
}
/**
@@ -579,7 +611,8 @@ export type PluginSshPurpose =
| "remote-desktop"
| "tunnel"
| "file-manager"
| "file-transfer";
| "file-transfer"
| "terminal";
export type PluginSshAuthOutcome =
| { status: "ready" }
@@ -717,6 +750,18 @@ export interface PluginSsh {
/** The pool key withConnection would use, for clearing it. */
poolKey: (pool: string, host: PluginSshHost) => string;
/**
* The host as the acting user may connect to it: RBAC, owner-key
* decryption, shared-host overrides and external secret references
* resolved, secrets included. By sync id when given, since a numeric id
* only names a host on the database that issued it. Null when the host is
* not on this server or the user cannot reach it.
*/
resolveHost: (
hostId: number,
options?: { syncId?: string | null },
) => Promise<PluginSshHost | null>;
/**
* Lower level: fills an ssh2 config for a client the plugin drives itself,
* for transports with their own prompt flow. Never connects.
@@ -729,18 +774,49 @@ export interface PluginSsh {
/** Server-side host id when it differs from host.id. */
serverHostId?: number;
log?: (level: "info" | "warning" | "error", message: string) => void;
/** A person is at the keyboard, so providers may prompt. */
interactive?: boolean;
/**
* The ws socket the host key verifier asks on when a key is new or has
* changed. Without one a changed key is refused.
*/
hostKeySocket?: unknown;
},
) => Promise<{
config: Record<string, unknown>;
outcome: PluginSshAuthOutcome;
}>;
) => Promise<PluginSshPrepared>;
/** Lower level: port knocking, jump hosts or proxy; sets config.sock. */
openTransport: (
host: PluginSshHost,
config: Record<string, unknown>,
options?: {
/** False when the caller already resolved DNS, or a jump host will. */
resolveDns?: boolean;
log?: (level: "info" | "warning" | "error", message: string) => void;
},
) => Promise<{ jumpClient: unknown | null; via: string }>;
/**
* Starts the browser step behind an `<interaction>_auth_required` message.
* The provider for the host's own auth type wins, else the first provider
* that owns the interaction. Rejects with PluginSshInteractionError when
* none can.
*/
startInteraction: (
interaction: string,
request: {
hostId: number;
socket: unknown;
requestOrigin: string;
payload?: Record<string, unknown>;
},
) => Promise<void>;
/** Cancels a pending browser step on every provider that owns it. */
cancelInteraction: (
interaction: string,
request: { hostId?: number; requestId?: string },
) => Promise<void>;
/** What a keyboard-interactive round is. Pure; no capability needed. */
classifyKeyboardInteractive: (
round: {
@@ -764,6 +840,31 @@ export interface PluginSsh {
supportsBackground: (authType: string) => boolean;
}
/** What ctx.ssh.prepare built, plus the provider hooks bound to it. */
export interface PluginSshPrepared {
config: Record<string, unknown>;
outcome: PluginSshAuthOutcome;
/** The auth type whose provider prepared it, null when none exists. */
authType: string | null;
/** The provider's banner hook, bound to this host and connection. */
onBanner?: (banner: string) => PluginSshBannerDecision | undefined;
/** The provider's auth failure hook, bound to this host and connection. */
onAuthFailed?: (context: {
error: Error;
retries: number;
canRetry: boolean;
methodNotAvailable: boolean;
}) => PluginSshAuthOutcome | undefined;
}
/** Thrown by ctx.ssh.startInteraction when no provider can start it. */
export class PluginSshInteractionError extends Error {
constructor(message: string) {
super(message);
this.name = "PluginSshInteractionError";
}
}
export interface PluginSshAuthEnv {
/** The ssh2 Client about to connect; certificate auth patches it. */
client: unknown;
@@ -845,6 +946,12 @@ export interface PluginSshAuthProvider {
requestOrigin: string;
payload: Record<string, unknown>;
}) => Promise<void>;
/** Cancels a pending browser step. */
cancelInteraction?: (request: {
userId: string;
hostId?: number;
requestId?: string;
}) => void | Promise<void>;
}
/**
@@ -986,6 +1093,23 @@ export interface PluginDesktop {
) => Promise<{ success: true }>;
}
/**
* An audit line under an action name of the plugin's choosing (an SSH login),
* for the acting user, next to the plugin_* line every privileged ctx call
* already writes.
*/
export interface PluginAudit {
record: (entry: {
action: string;
resourceType?: string;
resourceId?: string;
resourceName?: string;
details?: string;
success: boolean;
errorMessage?: string;
}) => Promise<void>;
}
export interface PluginContext {
readonly pluginId: string;
readonly manifest: PluginManifest;
@@ -1010,6 +1134,8 @@ export interface PluginContext {
readonly auth: PluginAuth;
/** Opens Electron windows outside the main renderer. Needs desktop:window. */
readonly desktop: PluginDesktop;
/** Audit lines under the plugin's own action names. */
readonly audit: PluginAudit;
/**
* Runs `fn` with `userId` as the acting user, for background work that has
+87 -5
View File
@@ -49,6 +49,13 @@ export interface PluginTabRecord {
label: string;
host?: PluginHostRecord;
data?: Record<string, unknown>;
/** Stable across restores, for a session tab to reattach by. */
instanceId?: string;
/** A backend session a session tab should reattach to. */
restoredSessionId?: string | null;
/** A live shared session this tab joins instead of connecting its own. */
joinSharedSessionId?: string | null;
joinShareId?: string | null;
[key: string]: unknown;
}
@@ -76,7 +83,11 @@ export interface ShellApi {
openSingletonTab: (type: string, options?: OpenTabOptions) => void;
closeTab: (tabId: string) => void;
renameTab: (tabId: string, label: string) => void;
openTerminalTab: (host: PluginHostRecord, path?: string) => void;
/** Saves a quick-connect tab's host as a real host. Absent in some shells. */
saveQuickConnect?: (
tab: PluginTabRecord,
host: PluginHostRecord,
) => Promise<void>;
/** Opens a rail view in the left sidebar. */
openRailView: (id: string) => void;
/** Closes a rail view wherever it is shown. */
@@ -141,6 +152,11 @@ export interface RailItemContribution {
}
export interface PanelProps {
/**
* The command-target tab (see TabOptions.commandTarget) the user is working
* in: the active one, else the one focused last. For panels that act on it.
*/
targetTab?: PluginTabRecord;
/** Whether the panel is the one currently shown. */
active: boolean;
shell: ShellApi;
@@ -157,6 +173,33 @@ export interface PanelOptions {
keepMounted?: boolean;
}
/**
* What a session tab hands the shell through `handleRef`, so split view, tab
* close and reconnect-all can drive it without knowing what kind it is.
*/
export interface TabHandle {
focus?: () => void;
fit?: () => void;
reconnect?: () => void;
disconnect?: () => void;
isConnected?: () => boolean;
sendInput?: (data: string) => void;
paste?: (text: string) => void;
refresh?: () => void;
notifyResize?: () => void;
/** A tab that shows its own share dialog. */
canShare?: () => boolean;
openShareModal?: () => void;
/** A tab whose session the shell's share dialog can share. */
getShareTarget?: () => {
hostId: number;
sessionId: string;
protocol: "ssh";
tabInstanceId?: string;
} | null;
[key: string]: unknown;
}
export interface TabProps {
tab: PluginTabRecord;
host?: PluginHostRecord;
@@ -203,6 +246,15 @@ export interface TabOptions {
panelFrame?: boolean;
/** False keeps the tab out of saved layouts and workspaces. Default true. */
inLayouts?: boolean;
/**
* Its session takes typed commands: the history, macros and SSH tools
* panels act on the one focused last.
*/
commandTarget?: boolean;
/** Paints its own background, so the shell leaves its frame transparent. */
ownBackground?: boolean;
/** Every open is a new tab, labelled "<title> (2)" and so on. */
multiInstance?: boolean;
/** Warms the tab's code before it is opened. */
preload?: () => Promise<unknown>;
}
@@ -504,6 +556,16 @@ export interface TermixApp extends TermixAppInfo {
contribution: SlotContribution,
) => Disposer;
invokeAction: (id: string, ...args: unknown[]) => Promise<unknown>;
/**
* Offers a component to other plugins and to core by id, rendered where
* they choose with `PluginComponent` from @termix/plugin-sdk/ui or
* `usePluginComponent`. The id should start with a name the plugin owns
* ("terminal.view"). The props are the owner's contract; document them.
*/
registerComponent: (
id: string,
component: ComponentType<Record<string, unknown>>,
) => Disposer;
registerSshAuthEditor: (editor: SshAuthEditorContribution) => Disposer;
registerLoginMethod: (method: LoginMethodContribution) => Disposer;
registerSecondFactorUI: (factor: SecondFactorContribution) => Disposer;
@@ -609,7 +671,13 @@ export interface PluginHostBridge {
useTabs: () => TabsApi;
invokeAction: (id: string, ...args: unknown[]) => Promise<unknown>;
useSshAuthTypes: () => { types: SshAuthTypeInfo[]; loaded: boolean };
useSlotContributions: (slotId: string) => SlotContribution[];
useSlotContributions: (
slotId: string,
context?: Record<string, unknown>,
) => SlotContribution[];
usePluginComponent: (
id: string,
) => ComponentType<Record<string, unknown>> | undefined;
}
let host: PluginHostBridge | null = null;
@@ -717,10 +785,24 @@ export function useSshAuthTypes(): {
* metadata (id, titleKey, component). For an owner that needs to build a
* catalog from what was contributed rather than just render it in place, e.g.
* host-metrics listing manager cards plugins added to "host-metrics.managers"
* next to its own. Same permission and `when` filtering as ComponentSlot.
* next to its own. Same permission and `when` filtering as ComponentSlot;
* `context` is what each contribution's `when` sees.
*/
export function useSlotContributions(slotId: string): SlotContribution[] {
return requireHost().useSlotContributions(slotId);
export function useSlotContributions(
slotId: string,
context?: Record<string, unknown>,
): SlotContribution[] {
return requireHost().useSlotContributions(slotId, context);
}
/**
* The component another plugin registered under `id` with
* app.registerComponent, or undefined while that plugin is off.
*/
export function usePluginComponent(
id: string,
): ComponentType<Record<string, unknown>> | undefined {
return requireHost().usePluginComponent(id);
}
/**
+85 -3
View File
@@ -62,6 +62,8 @@ export interface FakeContextOptions {
/** Users and roles ctx.hosts.listUsers/listRoles answer with. */
shareableUsers?: PluginShareableUser[];
shareableRoles?: PluginShareableRole[];
/** Hosts ctx.ssh.resolveHost answers with, secrets included. */
sshHosts?: PluginSshHost[];
}
export interface FakeAuthRegistrations {
@@ -116,6 +118,18 @@ export interface FakePluginContext {
title?: string;
ignoreCert?: boolean;
}>;
/** Every ctx.audit.record entry, in order. */
audits: Array<{ action: string; success: boolean; [key: string]: unknown }>;
/** Every ctx.hosts.recordActivity call, in order. */
activities: Array<{ hostId: number; type: string; hostName: string }>;
/** Host ids with a live ctx.hosts.trackSession, one entry per session. */
trackedSessions: number[];
/** Every ctx.ssh.startInteraction / cancelInteraction call, in order. */
interactions: Array<{
action: "start" | "cancel";
interaction: string;
request: Record<string, unknown>;
}>;
/** Changes the acting user, as core's request middleware would. */
setActor: (userId: string | undefined) => void;
/** Implementations provided through ctx.services.provide, by service name. */
@@ -187,6 +201,10 @@ export function createFakeContext(
const sshConnections: FakePluginContext["sshConnections"] = [];
const hostShares: FakePluginContext["hostShares"] = [];
const desktopWindows: FakePluginContext["desktopWindows"] = [];
const audits: FakePluginContext["audits"] = [];
const activities: FakePluginContext["activities"] = [];
const trackedSessions: number[] = [];
const interactions: FakePluginContext["interactions"] = [];
const hostsById = new Map<number, PluginHostSummary>(
(options.hosts ?? []).map((h) => [h.id, h]),
);
@@ -486,6 +504,25 @@ export function createFakeContext(
},
listUsers: async () => options.shareableUsers ?? [],
listRoles: async () => options.shareableRoles ?? [],
trackSession: (hostId) => {
trackedSessions.push(hostId);
let active = true;
return () => {
if (!active) return;
active = false;
const index = trackedSessions.indexOf(hostId);
if (index >= 0) trackedSessions.splice(index, 1);
};
},
recordActivity: async (hostId, type, hostName) => {
activities.push({ hostId, type, hostName });
},
},
audit: {
record: async (entry) => {
audits.push({ ...entry });
},
},
ssh: {
@@ -512,8 +549,24 @@ export function createFakeContext(
}),
poolKey: (pool, host) =>
`${pool}:${host.userId}:${host.ip}:${host.port}:${host.username}`,
prepare: async () => ({ config: {}, outcome: { status: "ready" } }),
resolveHost: async (hostId, resolveOptions) =>
(options.sshHosts ?? []).find((host) =>
resolveOptions?.syncId
? host.syncId === resolveOptions.syncId
: host.id === hostId,
) ?? null,
prepare: async () => ({
config: {},
outcome: { status: "ready" },
authType: null,
}),
openTransport: async () => ({ jumpClient: null, via: "direct" }),
startInteraction: async (interaction, request) => {
interactions.push({ action: "start", interaction, request });
},
cancelInteraction: async (interaction, request) => {
interactions.push({ action: "cancel", interaction, request });
},
classifyKeyboardInteractive: ({ prompts }, host) => ({
kind: "auto",
responses: prompts.map((p) =>
@@ -586,6 +639,10 @@ export function createFakeContext(
hostShares,
auth,
desktopWindows,
audits,
activities,
trackedSessions,
interactions,
setActor: (userId) => {
actor = userId;
},
@@ -616,6 +673,8 @@ export interface MockContextOptions {
permissions?: string[];
/** Hosts ctx.hosts.list/get/checkAccess serve. See FakeContextOptions. */
hosts?: PluginHostSummary[];
/** Hosts ctx.ssh.resolveHost serves. See FakeContextOptions. */
sshHosts?: PluginSshHost[];
}
export interface MockPluginContext extends FakePluginContext {
@@ -650,6 +709,7 @@ export function createMockCtx(
router: options.router,
permissions: options.permissions,
hosts: options.hosts,
sshHosts: options.sshHosts,
manifest: {
capabilities: options.capabilities ?? [],
...options.manifest,
@@ -795,6 +855,14 @@ export function createMockCtx(
require("hosts:write");
return ctx.hosts.listRoles();
},
trackSession: (hostId) => {
require("hosts:read");
return ctx.hosts.trackSession(hostId);
},
recordActivity: async (hostId, type, hostName) => {
require("hosts:read");
return ctx.hosts.recordActivity(hostId, type, hostName);
},
},
ssh: {
@@ -814,9 +882,23 @@ export function createMockCtx(
require("credentials:use");
return ctx.ssh.prepare(host, prepareOptions);
},
openTransport: async (host, config) => {
openTransport: async (host, config, transportOptions) => {
require("ssh:connect");
return ctx.ssh.openTransport(host, config);
return ctx.ssh.openTransport(host, config, transportOptions);
},
resolveHost: async (hostId, resolveOptions) => {
require("ssh:connect");
require("credentials:use");
return ctx.ssh.resolveHost(hostId, resolveOptions);
},
startInteraction: async (interaction, request) => {
require("ssh:connect");
require("credentials:use");
return ctx.ssh.startInteraction(interaction, request);
},
cancelInteraction: async (interaction, request) => {
require("ssh:connect");
return ctx.ssh.cancelInteraction(interaction, request);
},
jumpChain: async (jumpHosts, chainOptions) => {
require("ssh:connect");
+1
View File
@@ -112,6 +112,7 @@ const offline = () =>
loaded: true,
}),
useSlotContributions: () => [],
usePluginComponent: () => undefined,
};
afterEach(() => {
+7 -1
View File
@@ -33,7 +33,8 @@
"workspaces": "^1.0.0",
"network-topology": "^1.0.0",
"snippets": "^1.0.0",
"fleets": "^1.0.0"
"fleets": "^1.0.0",
"ssh-terminal": "^1.0.0"
},
"requires": [
{
@@ -55,6 +56,11 @@
"service": "fleets.access",
"versionRange": "^1.0.0",
"optional": true
},
{
"service": "terminal.history",
"versionRange": "^1.0.0",
"optional": true
}
],
"providesSecret": [
+27
View File
@@ -221,3 +221,30 @@ export async function deleteSnippet(
): Promise<boolean> {
return requireSnippetsAccess(userId).remove(id);
}
/** The terminal's command history, as ctx.services.get("terminal.history") returns it. */
interface TerminalHistory {
list: (
hostId: number,
limit?: number,
) => Promise<Array<{ command: string; executedAt: string }>>;
}
/**
* The user's recent commands on one host, or null when the ssh-terminal
* plugin is off or the user may not read its history.
*/
export async function listCommandHistory(
userId: string,
hostId: number,
limit: number,
): Promise<Array<{ command: string; executedAt: string }> | null> {
if (!current) return null;
try {
return await current
.get<TerminalHistory>("terminal.history", { userId })
.list(hostId, limit);
} catch {
return null;
}
}
+4 -8
View File
@@ -1,7 +1,6 @@
import {
createCurrentNotificationChannelRepository,
createCurrentAutomationRepository,
createCurrentCommandHistoryRepository,
createCurrentHomepageItemRepository,
createCurrentHostRepository,
} from "../../../../../src/backend/database/repositories/factory.js";
@@ -11,6 +10,7 @@ import {
getNetworkTopology,
listSnippets,
listFleets,
listCommandHistory,
} from "../services.js";
/**
@@ -244,13 +244,9 @@ export const readTools: AiTool[] = [
);
if (!host) return { error: "Host not found" };
const commands =
await createCurrentCommandHistoryRepository().listCommandsForHost(
context.userId,
hostId,
limit,
);
return { commands };
const history = await listCommandHistory(context.userId, hostId, limit);
if (history === null) return { commands: [], unavailable: true };
return { commands: history.map((entry) => entry.command) };
},
},
{
+14 -12
View File
@@ -6,9 +6,9 @@ import type {
TermixApp,
} from "@termix/plugin-sdk/frontend";
import type {
TerminalDockProps,
TerminalSidePanelProps,
TerminalSlotApi,
} from "@/features/terminal/terminal-slots";
} from "./terminal/terminal-slot-types";
import { AiPanel } from "./AiPanel";
import { TerminalAiPanel } from "./terminal/TerminalAiPanel";
import { AiAssistantStep } from "./AiAssistantStep";
@@ -18,7 +18,7 @@ import { getAiStatus } from "./ai-api";
import { AI_STATUS_CHANGED_EVENT } from "./use-ai-availability";
const ASSISTANT_ACTION = "ai.openWithContext";
const DOCK_ID = "ai.assistant";
const SIDE_PANEL_ID = "ai.assistant";
function AssistantPanel({ activeTabType }: PanelProps) {
return (
@@ -32,15 +32,15 @@ function AssistantTab() {
return <AiPanel />;
}
/** The assistant docked beside a terminal. */
function TerminalDock({
/** The assistant beside a terminal, in its "terminal.sidePanel" slot. */
function TerminalSidePanel({
host,
hostId,
hostLabel,
dockProps,
panelProps,
onClose,
onRunInTerminal,
}: TerminalDockProps) {
}: TerminalSidePanelProps) {
if (!hostId) return null;
return (
<TerminalAiPanel
@@ -48,7 +48,7 @@ function TerminalDock({
hostId={hostId}
activeTab={`terminal:${host?.name || host?.ip || ""}`}
initialContext={
typeof dockProps.context === "string" ? dockProps.context : ""
typeof panelProps.context === "string" ? panelProps.context : ""
}
onClose={onClose}
onRunInTerminal={onRunInTerminal}
@@ -114,12 +114,12 @@ export function activate(app: TermixApp): void {
}),
);
surface.push(
app.registerSlotContribution("terminal.dock", {
actionId: DOCK_ID,
app.registerSlotContribution("terminal.sidePanel", {
actionId: SIDE_PANEL_ID,
titleKey: "ai.assistant",
icon: Bot,
kind: "component",
component: TerminalDock as unknown as ComponentType<
component: TerminalSidePanel as unknown as ComponentType<
Record<string, unknown>
>,
when: offeredOnHost,
@@ -165,7 +165,9 @@ export function activate(app: TermixApp): void {
app.registerAction(
ASSISTANT_ACTION,
(terminal: TerminalSlotApi) =>
terminal?.openDock(DOCK_ID, { context: terminal.getBufferText() }),
terminal?.openSidePanel(SIDE_PANEL_ID, {
context: terminal.getBufferText(),
}),
{ permission: "services.use" },
);
@@ -18,7 +18,7 @@ import {
persistAiPanelPosition,
readStoredAiPanelPosition,
type ToolbarPosition,
} from "@/features/terminal/toolbar-geometry";
} from "./panel-geometry";
interface HistoryEntry {
role: "user" | "assistant";
@@ -0,0 +1,90 @@
/**
* Where the docked assistant panel sits over a terminal. Same clamping rules
* as the terminal's own toolbar, kept here so the panel owns its geometry.
*/
export interface ToolbarPosition {
x: number;
y: number;
}
export interface RectLike {
left: number;
right: number;
top: number;
bottom: number;
width: number;
height: number;
}
const AI_PANEL_POSITION_STORAGE_KEY = "termix-terminal-ai-panel-position";
const TOOLBAR_MARGIN = 8;
const RECOVERY_SIZE = 44;
function sanitizeToolbarPosition(value: unknown): ToolbarPosition {
if (!value || typeof value !== "object") return { x: 0, y: 0 };
const candidate = value as Partial<ToolbarPosition>;
return Number.isFinite(candidate.x) && Number.isFinite(candidate.y)
? { x: Number(candidate.x), y: Number(candidate.y) }
: { x: 0, y: 0 };
}
export function readStoredAiPanelPosition(): ToolbarPosition {
if (typeof window === "undefined") return { x: 0, y: 0 };
try {
return sanitizeToolbarPosition(
JSON.parse(
window.localStorage.getItem(AI_PANEL_POSITION_STORAGE_KEY) ?? "null",
),
);
} catch {
return { x: 0, y: 0 };
}
}
export function persistAiPanelPosition(position: ToolbarPosition): void {
try {
window.localStorage.setItem(
AI_PANEL_POSITION_STORAGE_KEY,
JSON.stringify(sanitizeToolbarPosition(position)),
);
} catch {
// Storage may be unavailable in hardened browser contexts.
}
}
export function clampToolbarPosition(
position: ToolbarPosition,
toolbarRect: RectLike,
hostRect: RectLike,
renderedPosition: ToolbarPosition = position,
): ToolbarPosition {
if (
!toolbarRect.width ||
!toolbarRect.height ||
!hostRect.width ||
!hostRect.height
)
return sanitizeToolbarPosition(position);
const left = hostRect.left + TOOLBAR_MARGIN;
const right = hostRect.right - TOOLBAR_MARGIN;
const top = hostRect.top + TOOLBAR_MARGIN;
const bottom = hostRect.bottom - TOOLBAR_MARGIN;
const baseLeft = toolbarRect.left - renderedPosition.x;
const baseRight = toolbarRect.right - renderedPosition.x;
const baseTop = toolbarRect.top - renderedPosition.y;
const baseBottom = toolbarRect.bottom - renderedPosition.y;
const clamp = (min: number, max: number, value: number) =>
Math.min(max, Math.max(min, value));
const xMin =
toolbarRect.width <= right - left
? left - baseLeft
: left + RECOVERY_SIZE - baseRight;
const xMax = right - baseRight;
const yMin =
toolbarRect.height <= bottom - top
? top - baseTop
: top + RECOVERY_SIZE - baseBottom;
const yMax = bottom - baseBottom;
return { x: clamp(xMin, xMax, position.x), y: clamp(yMin, yMax, position.y) };
}
@@ -0,0 +1,31 @@
/**
* What the ssh-terminal plugin hands its slots, as that plugin documents it.
* Kept as a local copy: a plugin never imports another plugin's source.
*/
interface TerminalHost {
id: number | string;
name?: string;
ip?: string;
enableAiAssistant?: boolean;
[key: string]: unknown;
}
/** What a "terminal.toolbar" action is invoked with. */
export interface TerminalSlotApi {
host: TerminalHost | undefined;
getBufferText: () => string;
sessionId: () => string | null;
openSidePanel: (panelId: string, props?: Record<string, unknown>) => void;
runCommand: (command: string) => void;
}
/** What a "terminal.sidePanel" component is rendered with. */
export interface TerminalSidePanelProps {
host: TerminalHost | undefined;
hostId: number | undefined;
hostLabel: string;
panelProps: Record<string, unknown>;
onClose: () => void;
onRunInTerminal: (command: string) => void;
}
@@ -0,0 +1,39 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
listCommandHistory,
setPluginServices,
} from "../../src/backend/services.js";
afterEach(() => setPluginServices(null));
describe("listCommandHistory", () => {
it("reads the user's history through terminal.history", async () => {
const list = vi.fn(async () => [
{ command: "ls", executedAt: "2026-01-01T00:00:00.000Z" },
]);
const get = vi.fn(() => ({ list }));
setPluginServices({ get, provide: vi.fn() } as never);
const history = await listCommandHistory("user-1", 4, 25);
expect(get).toHaveBeenCalledWith("terminal.history", { userId: "user-1" });
expect(list).toHaveBeenCalledWith(4, 25);
expect(history).toEqual([
{ command: "ls", executedAt: "2026-01-01T00:00:00.000Z" },
]);
});
it("answers null while the terminal is off or refuses the user", async () => {
expect(await listCommandHistory("user-1", 4, 25)).toBeNull();
setPluginServices({
get: () => ({
list: async () => {
throw new Error("unavailable");
},
}),
provide: vi.fn(),
} as never);
expect(await listCommandHistory("user-1", 4, 25)).toBeNull();
});
});
@@ -19,9 +19,9 @@ import {
useTranslation,
useConnectionRetry,
} from "@termix/plugin-sdk/frontend";
import { resolveTermixThemeColors } from "@/features/terminal/terminal-theme";
import { resolveTermixThemeColors } from "@/lib/terminal-look/terminal-theme";
import { DEFAULT_TERMINAL_CONFIG, TERMINAL_FONTS } from "@/lib/terminal-themes";
import { ensureTerminalFontsLoaded } from "@/features/terminal/terminal-global-styles";
import { ensureTerminalFontsLoaded } from "@/lib/terminal-look/terminal-global-styles";
import { useTheme } from "@/components/theme-provider";
import { ConnectionScreen } from "@/components/connection/ConnectionScreen.tsx";
import {
@@ -1,13 +1,9 @@
import React from "react";
import { DraggableWindow } from "./DraggableWindow.tsx";
import {
Terminal,
type TerminalHandle,
type TerminalHostConfig,
} from "@/features/terminal/Terminal.tsx";
import type { TabHandle as TerminalHandle } from "@termix/plugin-sdk/frontend";
import { PluginComponent } from "@termix/plugin-sdk/ui";
import { useWindowManager } from "./WindowManager.tsx";
import { useTranslation } from "react-i18next";
import { CommandHistoryProvider } from "@/features/terminal/command-history/CommandHistoryContext.tsx";
import type { SSHHost } from "@/types/index.ts";
import { ExternalLink } from "lucide-react";
@@ -96,7 +92,7 @@ export function TerminalWindow({
: t("terminal.terminalTitle", { host: hostConfig.name });
return (
<CommandHistoryProvider>
<>
<DraggableWindow
title={terminalTitle}
initialX={initialX}
@@ -126,15 +122,16 @@ export function TerminalWindow({
) : null
}
>
<Terminal
ref={terminalRef}
hostConfig={hostConfig as TerminalHostConfig}
<PluginComponent
id="terminal.view"
handleRef={terminalRef}
hostConfig={hostConfig}
isVisible={!currentWindow.isMinimized}
initialPath={initialPath}
executeCommand={executeCommand}
onClose={handleClose}
/>
</DraggableWindow>
</CommandHistoryProvider>
</>
);
}
+10 -7
View File
@@ -1,10 +1,11 @@
import type { ComponentType } from "react";
import { FolderSearch, ArrowLeftRight } from "lucide-react";
import type {
PluginHostRecord,
StandaloneViewProps,
TabProps,
TermixApp,
import {
invokeAction,
type PluginHostRecord,
type StandaloneViewProps,
type TabProps,
type TermixApp,
} from "@termix/plugin-sdk/frontend";
import { GRID_SIZE } from "@/types/homepage-types";
import { FileManager } from "./FileManager.tsx";
@@ -15,7 +16,7 @@ import { FileManagerWidget } from "./homepage/FileManagerWidget.tsx";
import { FileManagerWidgetEditForm } from "./homepage/FileManagerWidgetEditForm.tsx";
import { startTransferMonitor } from "./TransferMonitor.tsx";
function FilesTab({ tab, host, sshHost, isVisible, shell }: TabProps) {
function FilesTab({ tab, host, sshHost, isVisible }: TabProps) {
const data = tab.data as
{ initialFilePath?: string; initialPath?: string } | undefined;
return (
@@ -25,7 +26,9 @@ function FilesTab({ tab, host, sshHost, isVisible, shell }: TabProps) {
initialPath={data?.initialPath}
isVisible={isVisible}
onOpenTerminalTab={
host ? (path) => shell.openTerminalTab(host, path) : undefined
host
? (path) => void invokeAction("terminal.open", host, { path })
: undefined
}
/>
);
+3
View File
@@ -239,5 +239,8 @@
"showCurrentValue": "Show Current Value",
"chartMetric": "Metric",
"metricRange": "Range"
},
"terminalStatus": {
"unavailable": "Live stats unavailable"
}
}
@@ -0,0 +1,174 @@
import { useEffect, useState } from "react";
import { usePluginApi, useTranslation } from "@termix/plugin-sdk/frontend";
import {
cn,
getPollingEnvironmentMultiplier,
runAdaptivePolling,
} from "@termix/plugin-sdk/ui";
/** What the ssh-terminal plugin hands a "terminal.toolbarStatus" component. */
interface TerminalToolbarStatusProps {
host: { id: number | string };
isConnected: boolean;
/** Visible on a desktop viewport and connected: poll only while true. */
active: boolean;
}
interface Sample {
cpu: number | null;
memory: number | null;
disk: number | null;
}
function StatBar({
label,
percent,
}: {
label: string;
percent: number | null;
}) {
const value = percent == null ? null : Math.max(0, Math.min(100, percent));
return (
<div className="flex items-center gap-1.5 px-1.5">
<span className="w-12 shrink-0 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
{label}
</span>
<div className="h-1.5 w-12 shrink-0 overflow-hidden rounded-full bg-muted">
{value != null && (
<div
className={cn(
"h-full rounded-full transition-all",
value >= 90
? "bg-red-500"
: value >= 70
? "bg-yellow-500"
: "bg-accent-brand",
)}
style={{ width: `${value}%` }}
/>
)}
</div>
<span className="w-7 shrink-0 text-right text-[10px] tabular-nums text-muted-foreground">
{value == null ? "--" : `${Math.round(value)}%`}
</span>
</div>
);
}
/** CPU, memory and disk bars in the terminal toolbar's expanded view. */
export function TerminalMetricsStatus(props: Record<string, unknown>) {
const { host, active } = props as unknown as TerminalToolbarStatusProps;
const { t } = useTranslation();
const api = usePluginApi();
const [metrics, setMetrics] = useState<Sample | null>(null);
const [available, setAvailable] = useState(true);
const hostId = host?.id ? Number(host.id) : null;
useEffect(() => {
if (!active || !hostId) {
setMetrics(null);
return;
}
let cancelled = false;
let stopPolling: (() => void) | undefined;
let viewerSessionId: string | undefined;
let previous: Sample | null = null;
setAvailable(true);
const poll = async () => {
try {
const response = await api.get<{
cpu?: { percent?: number };
memory?: { percent?: number };
disk?: { percent?: number };
} | null>(`/metrics/${hostId}`, {
validateStatus: (status: number) => status === 200 || status === 404,
});
const data = response.data;
if (cancelled || !data) return;
const next: Sample = {
cpu: data.cpu?.percent ?? null,
memory: data.memory?.percent ?? null,
disk: data.disk?.percent ?? null,
};
const changed =
!previous ||
(["cpu", "memory", "disk"] as const).some((key) => {
const before = previous?.[key];
const now = next[key];
return before == null || now == null || Math.abs(now - before) >= 2;
});
previous = next;
setMetrics(next);
return changed;
} catch {
// Keep the previous sample after a transient polling error.
return false;
}
};
const start = async () => {
try {
const { data } = await api.post<{
requires_totp?: boolean;
viewerSessionId?: string;
}>(`/metrics/start/${hostId}`);
if (cancelled) {
if (data.viewerSessionId) {
void api
.post(`/metrics/stop/${hostId}`, {
viewerSessionId: data.viewerSessionId,
})
.catch(() => {});
}
return;
}
if (data.requires_totp) {
setAvailable(false);
return;
}
viewerSessionId = data.viewerSessionId;
stopPolling = runAdaptivePolling(
poll,
{
minIntervalMs: 5_000,
maxIntervalMs: 20_000,
stablePollsPerStep: 2,
maxRequestDutyCycle: 0.2,
},
{ intervalMultiplier: getPollingEnvironmentMultiplier },
);
} catch {
if (!cancelled) setAvailable(false);
}
};
void start();
return () => {
cancelled = true;
stopPolling?.();
void api
.post(`/metrics/stop/${hostId}`, { viewerSessionId })
.catch(() => {});
};
}, [active, hostId, api]);
if (!available) {
return (
<span role="status" className="px-2 py-1 text-xs text-muted-foreground">
{t("terminalStatus.unavailable")}
</span>
);
}
return (
<>
<StatBar label={t("hostMetrics.cpu")} percent={metrics?.cpu ?? null} />
<StatBar
label={t("hostMetrics.memory")}
percent={metrics?.memory ?? null}
/>
<StatBar label={t("hostMetrics.disk")} percent={metrics?.disk ?? null} />
</>
);
}
@@ -13,6 +13,7 @@ import HostMetricsApp from "./HostMetricsApp";
import { HostStatsTab } from "./HostEditorStatsTab";
import { metricsChartWidget } from "./MetricsChartWidget";
import { MetricsRetentionSetting } from "./MetricsRetentionSetting";
import { TerminalMetricsStatus } from "./TerminalMetricsStatus";
type SectionSetField = Parameters<typeof HostStatsTab>[0]["setField"];
@@ -90,6 +91,17 @@ export function activate(app: TermixApp): void {
app.registerSettingsComponent("retention", MetricsRetentionSetting);
// Live CPU, memory and disk bars in the terminal toolbar's expanded view.
app.registerSlotContribution("terminal.toolbarStatus", {
actionId: "host-metrics.terminalStatus",
titleKey: "nav.hostMetrics",
kind: "component",
component: TerminalMetricsStatus,
when: (context) =>
(context.host as { statsConfig?: { metricsEnabled?: boolean } })
?.statsConfig?.metricsEnabled !== false,
});
app.registerSlotContribution("onboarding.features", {
actionId: "host-metrics.feature",
titleKey: "onboarding.feature_metrics",
@@ -0,0 +1,80 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
const api = vi.hoisted(() => ({
get: vi.fn(),
post: vi.fn(),
}));
vi.mock("@termix/plugin-sdk/frontend", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
usePluginApi: () => api,
useTranslation: () => ({ t: (key: string) => key, language: "en" }),
}));
vi.mock("@termix/plugin-sdk/ui", () => ({
cn: (...classes: unknown[]) => classes.filter(Boolean).join(" "),
getPollingEnvironmentMultiplier: () => 1,
runAdaptivePolling: (poll: () => Promise<unknown>) => {
void poll();
return () => {};
},
}));
import { TerminalMetricsStatus } from "../../src/frontend/TerminalMetricsStatus";
afterEach(() => {
cleanup();
vi.clearAllMocks();
});
describe("TerminalMetricsStatus", () => {
it("polls the host's metrics while active and shows the bars", async () => {
api.post.mockResolvedValue({ data: { viewerSessionId: "viewer-1" } });
api.get.mockResolvedValue({
data: {
cpu: { percent: 12 },
memory: { percent: 40 },
disk: { percent: 91 },
},
});
render(<TerminalMetricsStatus host={{ id: 7 }} isConnected active />);
await waitFor(() => expect(screen.getByText("91%")).toBeInTheDocument());
expect(api.post).toHaveBeenCalledWith("/metrics/start/7");
expect(screen.getByText("12%")).toBeInTheDocument();
expect(screen.getByText("hostMetrics.cpu")).toBeInTheDocument();
});
it("does not poll while inactive", () => {
render(
<TerminalMetricsStatus host={{ id: 7 }} isConnected active={false} />,
);
expect(api.post).not.toHaveBeenCalled();
});
it("says stats are unavailable when the host needs a second factor", async () => {
api.post.mockResolvedValue({ data: { requires_totp: true } });
render(<TerminalMetricsStatus host={{ id: 7 }} isConnected active />);
await waitFor(() =>
expect(
screen.getByText("terminalStatus.unavailable"),
).toBeInTheDocument(),
);
});
it("stops its viewer on unmount", async () => {
api.post.mockResolvedValue({ data: { viewerSessionId: "viewer-2" } });
api.get.mockResolvedValue({ data: null });
const view = render(
<TerminalMetricsStatus host={{ id: 7 }} isConnected active />,
);
await waitFor(() => expect(api.get).toHaveBeenCalled());
view.unmount();
expect(api.post).toHaveBeenLastCalledWith("/metrics/stop/7", {
viewerSessionId: "viewer-2",
});
});
});
+3 -1
View File
@@ -2,4 +2,6 @@
## 1.0.0
Bundled with Termix 2.9.0.
Bundled with Termix 2.9.0. The terminal, its session manager, the desktop
app's local terminal and command history live in this plugin now, with their
settings under the plugin's own admin and host settings.
-70
View File
@@ -1,70 +0,0 @@
# SSH Terminal
The interactive SSH terminal, packaged as a first-party plugin.
## Why this plugin runs in-process
Every other plugin runs in a `worker_threads` worker and reaches the server
through a structured-clone `postMessage` boundary. That boundary is what makes
the capability gate and the audit trail mean anything: a worker plugin cannot
hold a db handle, an ssh2 `Client` or a live socket, so every privileged action
has to go through the broker.
This plugin cannot work that way. It _is_ the SSH transport rather than a
consumer of one:
- it owns a `WebSocketServer` on port 30002,
- it holds long-lived `ssh2.Client` objects and PTY streams,
- it needs plaintext host credentials to connect at all.
None of those can cross a structured-clone boundary, and `ctx.hosts` returns a
13-field allowlist that deliberately excludes `password`, `key` and
`keyPassword`. So `ssh-terminal` runs on the main thread instead, under the
`process:transport-owner` tier.
Membership in that tier is a hardcoded allowlist in
`src/backend/plugins/first-party.ts`. A manifest cannot ask for it: declaring
`process:transport-owner` without being on the list is a validation error. The
tier has no capability gate and no audit interception, which is only defensible
because nothing installable can enter it.
## What lives where
The terminal's implementation stays in `src/backend/hosts/terminal/`. Only the
lifecycle lives here. Two reasons it was not copied in:
- `session-manager.ts` has six consumers outside the terminal (terminal routes,
open-tabs, user-image-storage, collab, and session-sharing twice). It is
shared infrastructure, not plugin-private code.
- host key verification (`host-key-verifier.ts`), jump host chaining
(`jump-host-chain.ts`) and host resolution (`host-resolver.ts`) each have
around eight consumers already. They keep working by _not_ moving.
The SSH connection pool also stays in core. The terminal never used it: it
calls `new Client()` directly. The pool's consumers are metrics, tmux, tunnel,
fleet routes, AI tools, automations and `ctx.ssh` itself, so moving it here
would have made all of them depend on the terminal being enabled.
Only `SSHAuthManager` is terminal-exclusive.
## WebSocket: still port 30002, not /plugin-api/
The terminal WS was deliberately left on its own port and path.
`/plugin-api` has no auth middleware, so routing terminal auth through it would
be a regression. Beyond that, `/ssh/websocket/` is mapped in both nginx configs,
three frontend call sites build that URL (`Terminal.tsx`, `SharedSessionView`,
`CollabRoomTab`), Electron's `buildOriginWsUrl` pins `localPort: 30002`, and the
8-attempt reconnect plus detach/reattach buffering all depend on the current
path.
The plugin owns the port's lifecycle instead: enabling it calls
`startTerminalServer()`, disabling it calls `stopTerminalServer()`, which closes
live sessions and frees the port.
## Disabling
Disabling behaves like any other plugin. No confirmation dialog, no special
casing. The terminal tab stops being offered from the host list, command
palette and quick connect, the port stops listening, and every other feature
(metrics, docker, tunnels, file manager) keeps working off its own connections.
+226 -1
View File
@@ -1 +1,226 @@
{}
{
"mobileKeyboard": {
"addQuickKey": "Add",
"arrowDown": "Arrow Down",
"arrowLeft": "Arrow Left",
"arrowRight": "Arrow Right",
"arrowUp": "Arrow Up",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Done",
"editQuickKeys": "Edit quick keys",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Paste",
"quickKeyPlaceholder": "e.g. sudo ",
"quickKeysDesc": "Tap × to remove. Supports up to 8 characters.",
"quickKeysTitle": "Quick Keys",
"removeQuickKey": "Remove",
"resetDefaults": "Reset to defaults",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Failed to enable Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux enabled for {{host}}. It applies from the next connection.",
"clipboardHttpWarning": "Couldn't read the clipboard. Some browsers restrict clipboard access outside HTTPS - try Ctrl+V instead, or serve Termix over HTTPS.",
"closeTab": "Close",
"connecting": "Connecting...",
"connectionLost": "Connection lost",
"connectionRejected": "Connection rejected by server. Please check your authentication and network configuration.",
"connectionTimeout": "Connection timeout",
"enableAutoTmuxAction": "Enable Auto-Tmux",
"hostKeyRejected": "SSH host key verification rejected. Connection cancelled.",
"linkDialogCopy": "Copy",
"linkDialogOpen": "Open",
"linkDialogTitle": "Open Link",
"maxReconnectAttemptsReached": "Maximum reconnection attempts reached",
"noHostSelected": "No host selected",
"opksshAuthFailed": "Authentication failed. Please check your credentials and try again.",
"opksshTimeout": "Authentication timed out. Please try again.",
"passwordPromptFillTitle": "Fill the saved password into this prompt?",
"reconnect": "Reconnect",
"reconnected": "Reconnected successfully",
"reconnecting": "Reconnecting... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Match Case",
"searchClose": "Close (Escape)",
"searchNext": "Next Match (Enter)",
"searchNoResults": "No results",
"searchPlaceholder": "Find",
"searchPrevious": "Previous Match (Shift+Enter)",
"searchRegex": "Use Regular Expression",
"searchResultCount": "{{index}} of {{count}}",
"searchWholeWord": "Match Whole Word",
"sessionExpiredNotice": "Your previous session on {{host}} expired after {{minutes}} minutes disconnected; the shell and anything running in it are gone. Enable Auto-Tmux on this host to keep sessions alive.",
"sessionExpiredNoticeNoMinutes": "Your previous session on {{host}} expired while disconnected; the shell and anything running in it are gone. Enable Auto-Tmux on this host to keep sessions alive.",
"sessionTakenOver": "Session was opened in another tab. Reconnecting...",
"tmuxAttached": "Attached clients",
"tmuxAttachedCount": "{{count}} attached",
"tmuxCopyHint": "Adjust selection and press Enter to copy to clipboard",
"tmuxCreateNew": "Start new session",
"tmuxDetached": "Detached from tmux session",
"tmuxLastActivity": "Last activity",
"tmuxSessionAttached": "tmux session attached: {{name}}",
"tmuxSessionCreated": "tmux session created: {{name}}",
"tmuxSessionPickerDesc": "Existing tmux sessions found on this host. Select one to reattach or create a new session.",
"tmuxSessionPickerTitle": "tmux Sessions",
"tmuxTimeDays": "{{count}}d ago",
"tmuxTimeHours": "{{count}}h ago",
"tmuxTimeJustNow": "just now",
"tmuxTimeMinutes": "{{count}}m ago",
"tmuxUnavailable": "tmux is not installed on the remote host, falling back to standard shell",
"tmuxWindowCount": "{{count}} window",
"tmuxWindows": "Windows",
"unknownError": "Unknown error occurred",
"vaultAuthDescription": "A window has opened to sign in to HashiCorp Vault. Complete the sign-in there; this connection will continue automatically.",
"vaultAuthFailed": "Vault authentication failed. Please try again.",
"vaultAuthTitle": "Vault sign-in required",
"vaultReopen": "Reopen sign-in window",
"websocketAbnormalClose": "Connection closed unexpectedly. This may be due to a reverse proxy or SSL configuration issue. Please check server logs.",
"websocketError": "WebSocket connection error"
},
"terminalToolbar": {
"detachTmux": "Detach tmux",
"detachTmuxDescription": "Detach tmux session",
"dismissError": "Dismiss image error",
"hideToolbar": "Hide toolbar",
"image": "Image actions (images only)",
"imageActionFailed": "Image action failed",
"layout": "Toolbar display mode",
"layoutExpanded": "Expanded with metrics",
"layoutIcon": "Icon only",
"layoutLabeled": "Icons and labels",
"moveToolbar": "Move toolbar",
"moveToolbarHint": "Drag to reposition the toolbar",
"paste": "Paste image",
"pasteImage": "Paste image from clipboard (images only)",
"retry": "Retry",
"showToolbar": "Expand toolbar",
"upload": "Upload image",
"uploadImage": "Upload an image to the host",
"uploadingImage": "Uploading..."
},
"history": {
"noTerminalSelected": "No terminal selected",
"noTerminalSelectedHint": "Open an SSH terminal tab to view its command history",
"searchPlaceholder": "Search history...",
"clearAll": "Clear All",
"noHistoryEntries": "No history entries",
"trackingDisabled": "History tracking is disabled",
"trackingDisabledHint": "Turn on Command History for this host in its SSH Terminal plugin settings.",
"commandCount_one": "{{count}} command",
"commandCount_other": "{{count}} commands"
},
"touchInput": {
"enabled": "Enable Touch Input",
"enabledDesc": "Allow one-finger touch scrolling in terminals",
"momentumEnabled": "Enable Momentum",
"momentumEnabledDesc": "Continue scrolling briefly after a swipe",
"advanced": "Advanced",
"resetDefaults": "Reset to defaults",
"dragThreshold": "Drag threshold",
"maxWheelDelta": "Maximum wheel delta",
"momentumSampleWindow": "Momentum sample window",
"releaseGracePeriod": "Release grace period",
"minimumVelocity": "Minimum velocity",
"maximumVelocity": "Maximum velocity",
"maximumDuration": "Maximum duration",
"maximumTravel": "Maximum travel",
"decayTime": "Decay time",
"pixelsPerTick": "Pixels per tick",
"maximumFrameInterval": "Maximum frame interval",
"maximumTicksPerFrame": "Maximum ticks per frame"
},
"imageStorage": {
"instanceId": "Terminal session instance ID",
"instanceIdDesc": "Instance ID of one of your connected terminal tabs, used to test visibility.",
"selectedModeUnavailable": "Unavailable",
"test": "Test",
"testing": "Testing...",
"testFailed": "Failed to test image storage",
"testConnected": "Terminal session is connected.",
"testNotConnected": "No connected terminal session with this instance ID.",
"testSelectedMode": "An upload would use: {{mode}}",
"testLocalVisible": "The local directory is visible from the connected host.",
"testLocalNotVisible": "The local directory is NOT visible from the connected host."
},
"settings": {
"admin": {
"group": {
"touch": "Touch Input",
"images": "Image Storage",
"sessions": "Sessions"
},
"sessionTimeoutMinutes": {
"label": "Terminal Session Persistence",
"description": "How long a disconnected terminal is kept alive on the server before it is closed. Min 1 · Max 1440 minutes. For sessions that must outlive this, enable Auto-Tmux on the host."
},
"commandHistoryEnabled": {
"label": "Command History",
"description": "Allow command history recording. When disabled, history is not saved regardless of per-host settings."
},
"imageStorageMode": {
"label": "Storage Mode",
"description": "Where pasted and uploaded terminal images are written. Auto picks by connection, Local and Remote (SFTP) are fixed.",
"auto": "Auto",
"local": "Local",
"remoteSftp": "Remote (SFTP)"
},
"imageLocalDir": {
"label": "Local directory",
"description": "Absolute path on the Termix backend where local mode writes images. Never shown after saving. Empty uses TERMIX_IMAGE_DIR."
},
"imageHostPath": {
"label": "Host path",
"description": "The host-side view of the local directory that terminal agents receive."
},
"imageTtlMs": {
"label": "TTL (ms)",
"description": "How long an uploaded image is kept, in milliseconds. Empty uses TERMIX_IMAGE_TTL_MS or one hour."
},
"imageMaxCount": {
"label": "Max image count",
"description": "Most images kept at once. Empty uses TERMIX_MAX_IMAGE_COUNT or 100."
},
"imageMaxBytes": {
"label": "Max storage bytes",
"description": "Most bytes of images kept at once. Empty uses TERMIX_MAX_IMAGE_STORAGE_BYTES or 5 GiB."
},
"sessionPersistence": {
"label": "Keep sessions after disconnect",
"description": "Keep a disconnected terminal alive on the server for the time above, so reopening its tab picks up where it left off."
}
},
"host": {
"enableTerminalToolbar": {
"label": "Enable Terminal Toolbar",
"description": "Show a quick-access toolbar docked to the active terminal for this host's tools and live stats"
},
"enableCommandHistory": {
"label": "Command History",
"description": "Record commands run in this terminal for history and autocomplete"
},
"enableTerminal": {
"label": "Enable Terminal",
"description": "Offer an SSH terminal for this host."
}
}
},
"palette": {
"localTerminal": "Local Terminal"
},
"permissions": {
"sessions": {
"title": "Use live terminal sessions",
"description": "Lets other features, such as session sharing and recording, reach your live terminal sessions."
},
"history": {
"title": "Read command history",
"description": "Lets other features, such as the AI assistant, read your command history."
}
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Voeg by",
"arrowDown": "Pyltjie Af",
"arrowLeft": "Pyltjie links",
"arrowRight": "Pyltjie Regs",
"arrowUp": "Pyltjie Op",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Klaar",
"editQuickKeys": "Wysig vinnige sleutels",
"end": "Einde",
"esc": "Esc",
"home": "Tuis",
"pageDown": "PgDn",
"pageUp": "Bladsy Op",
"paste": "Plak",
"quickKeyPlaceholder": "bv. sudo ",
"quickKeysDesc": "Tik × om te verwyder. Ondersteun tot 8 karakters.",
"quickKeysTitle": "Vinnige sleutels",
"removeQuickKey": "Verwyder",
"resetDefaults": "Stel terug na verstekwaardes",
"shift": "Skof",
"tab": "Oortjie"
},
"terminal": {
"autoTmuxEnableFailed": "Kon nie Auto-Tmux aktiveer nie",
"autoTmuxEnabled": "Auto-Tmux geaktiveer vir {{host}}. Dit geld vanaf die volgende verbinding.",
"clipboardHttpWarning": "Kon nie die knipbord lees nie. Sommige blaaiers beperk toegang tot die knipbord buite HTTPS - probeer eerder Ctrl+V, of bedien Termix oor HTTPS.",
"closeTab": "Maak toe",
"connecting": "Verbind...",
"connectionLost": "Verbinding verloor",
"connectionRejected": "Verbinding deur bediener verwerp. Kontroleer asseblief u verifikasie en netwerkkonfigurasie.",
"connectionTimeout": "Verbindingstydverstryking",
"enableAutoTmuxAction": "Aktiveer Auto-Tmux",
"hostKeyRejected": "SSH-gasheersleutelverifikasie verwerp. Verbinding gekanselleer.",
"linkDialogCopy": "Kopieer",
"linkDialogOpen": "Oop",
"linkDialogTitle": "Maak skakel oop",
"maxReconnectAttemptsReached": "Maksimum herverbindingspogings bereik",
"noHostSelected": "Geen gasheer gekies nie",
"opksshAuthFailed": "Verifikasie het misluk. Kontroleer asseblief jou geloofsbriewe en probeer weer.",
"opksshTimeout": "Verifikasie het verstryk. Probeer asseblief weer.",
"passwordPromptFillTitle": "Vul die gestoorde wagwoord in hierdie aanwysingsprompt in?",
"reconnect": "Herkoppel",
"reconnected": "Herverbind suksesvol",
"reconnecting": "Herverbind... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Pasmaat",
"searchClose": "Maak toe (Escape)",
"searchNext": "Volgende Wedstryd (Enter)",
"searchNoResults": "Geen resultate nie",
"searchPlaceholder": "Vind",
"searchPrevious": "Vorige Wedersydse (Shift+Enter)",
"searchRegex": "Gebruik Gereelde Uitdrukking",
"searchResultCount": "{{index}} van {{count}}",
"searchWholeWord": "Pas die hele woord by",
"sessionExpiredNotice": "Jou vorige sessie op {{host}} het verval nadat {{minutes}} minute ontkoppel was; die dop en enigiets wat daarin loop, is weg. Aktiveer Auto-Tmux op hierdie gasheer om sessies aan die gang te hou.",
"sessionExpiredNoticeNoMinutes": "Jou vorige sessie op {{host}} het verval terwyl dit ontkoppel was; die dop en enigiets wat daarin loop, is weg. Aktiveer Auto-Tmux op hierdie gasheer om sessies aan die gang te hou.",
"sessionTakenOver": "Sessie is in 'n ander oortjie oopgemaak. Herverbind tans...",
"tmuxAttached": "Aangehegte kliënte",
"tmuxAttachedCount": "{{count}} aangeheg",
"tmuxCopyHint": "Pas seleksie aan en druk Enter om na knipbord te kopieer",
"tmuxCreateNew": "Begin nuwe sessie",
"tmuxDetached": "Losgemaak van tmux-sessie",
"tmuxLastActivity": "Laaste aktiwiteit",
"tmuxSessionAttached": "tmux-sessie aangeheg: {{name}}",
"tmuxSessionCreated": "tmux-sessie geskep: {{name}}",
"tmuxSessionPickerDesc": "Bestaande tmux-sessies is op hierdie gasheer gevind. Kies een om weer aan te heg of skep 'n nuwe sessie.",
"tmuxSessionPickerTitle": "tmux-sessies",
"tmuxTimeDays": "{{count}}dae gelede",
"tmuxTimeHours": "{{count}}uur gelede",
"tmuxTimeJustNow": "nou net",
"tmuxTimeMinutes": "{{count}}m gelede",
"tmuxUnavailable": "tmux is nie op die afgeleë gasheer geïnstalleer nie, en val terug na die standaard dop.",
"tmuxWindowCount": "{{count}} venster",
"tmuxWindows": "Vensters",
"unknownError": "Onbekende fout het voorgekom",
"vaultAuthDescription": "'n Venster het oopgemaak om by HashiCorp Vault aan te meld. Voltooi die aanmelding daar; hierdie verbinding sal outomaties voortgaan.",
"vaultAuthFailed": "Kluisverifikasie het misluk. Probeer asseblief weer.",
"vaultAuthTitle": "Kluisaanmelding vereis",
"vaultReopen": "Maak aanmeldvenster weer oop",
"websocketAbnormalClose": "Verbinding het onverwags gesluit. Dit kan wees as gevolg van 'n omgekeerde proxy- of SSL-konfigurasieprobleem. Gaan asseblief bedienerlogboeke na.",
"websocketError": "WebSocket-verbindingsfout"
},
"terminalToolbar": {
"detachTmux": "Ontkoppel tmux",
"detachTmuxDescription": "Ontkoppel tmux-sessie",
"dismissError": "Maak beeldfout toe",
"hideToolbar": "Versteek nutsbalk",
"image": "Beeldaksies (slegs beelde)",
"imageActionFailed": "Beeldaksie het misluk",
"layout": "Nutsbalk-vertoonmodus",
"layoutExpanded": "Uitgebrei met statistieke",
"layoutIcon": "Slegs ikoon",
"layoutLabeled": "Ikone en etikette",
"moveToolbar": "Skuif nutsbalk",
"moveToolbarHint": "Sleep om die nutsbalk te herposisioneer",
"paste": "Plak beeld",
"pasteImage": "Plak beeld vanaf knipbord (slegs beelde)",
"retry": "Probeer weer",
"showToolbar": "Vou die nutsbalk uit",
"upload": "Laai beeld op",
"uploadImage": "Laai 'n beeld op na die gasheer",
"uploadingImage": "Laai tans op..."
},
"history": {
"noTerminalSelected": "Geen terminaal gekies nie",
"noTerminalSelectedHint": "Maak 'n SSH-terminaal-oortjie oop om die opdraggeskiedenis te sien",
"searchPlaceholder": "Soekgeskiedenis...",
"clearAll": "Vee alles uit",
"noHistoryEntries": "Geen geskiedenisinskrywings nie",
"trackingDisabled": "Geskiedenisopsporing is gedeaktiveer"
},
"touchInput": {
"enabled": "Aktiveer Aanraakinvoer",
"enabledDesc": "Laat eenvinger-aanraakblaai in terminale toe",
"momentumEnabled": "Aktiveer Momentum",
"momentumEnabledDesc": "Gaan voort met blaai kortliks na 'n swipe",
"advanced": "Gevorderd",
"resetDefaults": "Stel terug na verstekwaardes",
"dragThreshold": "Sleepdrempel",
"maxWheelDelta": "Maksimum wieldelta",
"momentumSampleWindow": "Momentum voorbeeldvenster",
"releaseGracePeriod": "Vrystellingsgrasietydperk",
"minimumVelocity": "Minimum snelheid",
"maximumVelocity": "Maksimum snelheid",
"maximumDuration": "Maksimum duur",
"maximumTravel": "Maksimum reis",
"decayTime": "Vervaltyd",
"pixelsPerTick": "Piksels per tik",
"maximumFrameInterval": "Maksimum raaminterval",
"maximumTicksPerFrame": "Maksimum aantal tikkies per raam"
},
"imageStorage": {
"instanceId": "Terminale sessie-instansie-ID",
"instanceIdDesc": "Instansie-ID van een van jou gekoppelde terminaal-oortjies, wat gebruik word om sigbaarheid te toets.",
"selectedModeUnavailable": "Nie beskikbaar nie",
"test": "Toets",
"testing": "Toetsing...",
"testFailed": "Kon nie beeldberging toets nie",
"testConnected": "Terminaalsessie is gekoppel.",
"testNotConnected": "Geen gekoppelde terminaalsessie met hierdie instansie-ID nie.",
"testSelectedMode": "'n Oplaai sal gebruik: {{mode}}",
"testLocalVisible": "Die plaaslike gids is sigbaar vanaf die gekoppelde gasheer.",
"testLocalNotVisible": "Die plaaslike gids is NIE sigbaar vanaf die gekoppelde gasheer nie."
},
"settings": {
"admin": {
"group": {
"touch": "Aanraakinvoer",
"images": "Beeldberging"
},
"sessionTimeoutMinutes": {
"label": "Terminale Sessie Persistensie",
"description": "Hoe lank 'n ontkoppelde terminaal op die bediener aan die gang gehou word voordat dit gesluit word. Min 1 · Maks 1440 minute. Vir sessies wat dit moet oorleef, aktiveer Auto-Tmux op die gasheer."
},
"commandHistoryEnabled": {
"label": "Opdraggeskiedenis",
"description": "Laat die opname van opdraggeskiedenis toe. Wanneer dit gedeaktiveer is, word geskiedenis nie gestoor nie, ongeag die instellings per gasheer."
},
"imageStorageMode": {
"label": "Bergingsmodus",
"description": "Waar geplakte en opgelaaide terminaalbeelde geskryf word. Outomatiese seleksies per verbinding, Plaaslik en Afgeleë (SFTP) is reggestel.",
"auto": "Outomaties",
"local": "Plaaslik",
"remoteSftp": "Afstandsbediening (SFTP)"
},
"imageLocalDir": {
"label": "Plaaslike gids"
},
"imageHostPath": {
"label": "Gasheerpad",
"description": "Die gasheer-aansig van die plaaslike gids wat terminaalagente ontvang."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Maksimum beeldtelling"
},
"imageMaxBytes": {
"label": "Maksimum stoorplekgrepe"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Aktiveer Terminale-nutsbalk",
"description": "Wys 'n vinnige toegangsbalk wat aan die aktiewe terminaal gekoppel is vir hierdie gasheer se gereedskap en regstreekse statistieke."
},
"enableCommandHistory": {
"label": "Opdraggeskiedenis",
"description": "Rekordopdragte word in hierdie terminaal uitgevoer vir geskiedenis en outovoltooiing"
},
"enableTerminal": {
"label": "Aktiveer Terminaal"
}
}
},
"palette": {
"localTerminal": "Plaaslike Terminaal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "إضافة",
"arrowDown": "Arrow Down",
"arrowLeft": "Arrow Left",
"arrowRight": "Arrow Right",
"arrowUp": "Arrow Up",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "تم",
"editQuickKeys": "تحرير المفاتيح السريعة",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "لصق",
"quickKeyPlaceholder": "مثال: sudo ",
"quickKeysDesc": "اضغط × للإزالة. يدعم حتى 8 أحرف.",
"quickKeysTitle": "المفاتيح السريعة",
"removeQuickKey": "إزالة",
"resetDefaults": "استعادة الإعدادات الافتراضية",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "فشل تفعيل خاصية Auto-Tmux",
"autoTmuxEnabled": "تم تفعيل خاصية Auto-Tmux لـ {{host}}. وسيتم تطبيقها بدءًا من الاتصال التالي.",
"clipboardHttpWarning": "تعذر قراءة الحافظة. بعض المتصفحات تقيّد الوصول إلى الحافظة خارج بروتوكول HTTPS - جرّب Ctrl+V بدلاً من ذلك، أو شغّل Termix عبر HTTPS.",
"closeTab": "إغلاق",
"connecting": "جارٍ الاتصال...",
"connectionLost": "انقطع الاتصال",
"connectionRejected": "تم رفض الاتصال من قبل الخادم. يرجى التحقق من المصادقة وإعدادات الشبكة.",
"connectionTimeout": "انتهت مهلة الاتصال",
"enableAutoTmuxAction": "تفعيل خاصية Auto-Tmux",
"hostKeyRejected": "تم رفض التحقق من مفتاح مضيف SSH. تم إلغاء الاتصال.",
"linkDialogCopy": "نسخ",
"linkDialogOpen": "فتح",
"linkDialogTitle": "فتح الرابط",
"maxReconnectAttemptsReached": "تم الوصول إلى الحد الأقصى لمحاولات إعادة الاتصال",
"noHostSelected": "لم يتم تحديد مضيف",
"opksshAuthFailed": "فشلت المصادقة. يرجى التحقق من بيانات الاعتماد والمحاولة مرة أخرى.",
"opksshTimeout": "انتهت مهلة المصادقة. يرجى المحاولة مرة أخرى.",
"passwordPromptFillTitle": "هل تريد ملء كلمة المرور المحفوظة في هذه النافذة؟",
"reconnect": "إعادة الاتصال",
"reconnected": "تمت إعادة الاتصال بنجاح",
"reconnecting": "جارٍ إعادة الاتصال... ({{attempt}}/{{max}})",
"searchCaseSensitive": "علبة كبريت",
"searchClose": "إغلاق (الهروب)",
"searchNext": "المباراة التالية (إدخال)",
"searchNoResults": "لا توجد نتائج",
"searchPlaceholder": "يجد",
"searchPrevious": "المباراة السابقة (Shift+Enter)",
"searchRegex": "استخدم التعبير النمطي",
"searchResultCount": "{{index}} من {{count}}",
"searchWholeWord": "تطابق الكلمة الكاملة",
"sessionExpiredNotice": "انتهت صلاحية جلستك السابقة على {{host}} بعد {{minutes}} دقيقة من قطع الاتصال؛ تم إغلاق نافذة الأوامر وأي شيء يعمل بداخلها. فعّل خاصية Auto-Tmux على هذا المضيف للحفاظ على استمرارية الجلسات.",
"sessionExpiredNoticeNoMinutes": "انتهت صلاحية جلستك السابقة على {{host}} أثناء انقطاع الاتصال؛ تم حذف واجهة سطر الأوامر وأي شيء كان يعمل بداخلها. فعّل خاصية Auto-Tmux على هذا المضيف للحفاظ على استمرارية الجلسات.",
"sessionTakenOver": "تم فتح الجلسة في علامة تبويب أخرى. جارٍ إعادة الاتصال...",
"tmuxAttached": "العملاء المتصلون",
"tmuxAttachedCount": "{{count}} متصل",
"tmuxCopyHint": "اضبط التحديد واضغط Enter للنسخ إلى الحافظة",
"tmuxCreateNew": "بدء جلسة جديدة",
"tmuxDetached": "تم الفصل من جلسة tmux",
"tmuxLastActivity": "آخر نشاط",
"tmuxSessionAttached": "تم الالتحاق بجلسة tmux: {{name}}",
"tmuxSessionCreated": "تم إنشاء جلسة tmux: {{name}}",
"tmuxSessionPickerDesc": "تم العثور على جلسات tmux موجودة على هذا المضيف. اختر واحدة لإعادة الاتصال أو أنشئ جلسة جديدة.",
"tmuxSessionPickerTitle": "جلسات tmux",
"tmuxTimeDays": "منذ {{count}}ي",
"tmuxTimeHours": "منذ {{count}}س",
"tmuxTimeJustNow": "الآن فقط",
"tmuxTimeMinutes": "منذ {{count}}د",
"tmuxUnavailable": "tmux غير مثبّت على المضيف البعيد، جارٍ الرجوع إلى الصدفة القياسية",
"tmuxWindowCount": "{{count}} نافذة",
"tmuxWindows": "النوافذ",
"unknownError": "حدث خطأ غير معروف",
"vaultAuthDescription": "تم فتح نافذة لتسجيل الدخول إلى HashiCorp Vault. أكمل تسجيل الدخول هناك؛ سيستمر هذا الاتصال تلقائيًا.",
"vaultAuthFailed": "فشلت مصادقة Vault. يرجى المحاولة مرة أخرى.",
"vaultAuthTitle": "مطلوب تسجيل الدخول إلى Vault",
"vaultReopen": "أعد فتح نافذة تسجيل الدخول",
"websocketAbnormalClose": "أُغلق الاتصال بشكل غير متوقع. قد يكون السبب مشكلة في الوكيل العكسي أو إعدادات SSL. يرجى مراجعة سجلات الخادم.",
"websocketError": "خطأ في اتصال WebSocket"
},
"terminalToolbar": {
"detachTmux": "افصل tmux",
"detachTmuxDescription": "فصل جلسة tmux",
"dismissError": "تجاهل خطأ الصورة",
"hideToolbar": "إخفاء شريط الأدوات",
"image": "إجراءات الصور (للصور فقط)",
"imageActionFailed": "فشلت عملية معالجة الصورة",
"layout": "وضع عرض شريط الأدوات",
"layoutExpanded": "تم توسيعه بمقاييس",
"layoutIcon": "أيقونة فقط",
"layoutLabeled": "الرموز والملصقات",
"moveToolbar": "نقل شريط الأدوات",
"moveToolbarHint": "اسحب لإعادة وضع شريط الأدوات",
"paste": "لصق الصورة",
"pasteImage": "لصق الصورة من الحافظة (الصور فقط)",
"retry": "إعادة المحاولة",
"showToolbar": "توسيع شريط الأدوات",
"upload": "تحميل صورة",
"uploadImage": "قم بتحميل صورة إلى المضيف",
"uploadingImage": "جارٍ التحميل..."
},
"history": {
"noTerminalSelected": "لا توجد طرفية محددة",
"noTerminalSelectedHint": "افتح علامة تبويب طرفية SSH لعرض سجل الأوامر الخاص بها",
"searchPlaceholder": "ابحث في السجل...",
"clearAll": "مسح الكل",
"noHistoryEntries": "لا توجد مدخلات سجل",
"trackingDisabled": "تتبع السجل معطل"
},
"touchInput": {
"enabled": "تفعيل إدخال اللمس",
"enabledDesc": "السماح بالتمرير بلمسة إصبع واحدة في المحطات الطرفية",
"momentumEnabled": "تفعيل الزخم",
"momentumEnabledDesc": "استمر في التمرير لفترة وجيزة بعد التمرير",
"advanced": "متقدم",
"resetDefaults": "إعادة ضبط الإعدادات الافتراضية",
"dragThreshold": "عتبة السحب",
"maxWheelDelta": "أقصى دلتا للعجلة",
"momentumSampleWindow": "نافذة عينة الزخم",
"releaseGracePeriod": "فترة السماح للإفراج",
"minimumVelocity": "الحد الأدنى للسرعة",
"maximumVelocity": "أقصى سرعة",
"maximumDuration": "أقصى مدة",
"maximumTravel": "أقصى قدر من السفر",
"decayTime": "وقت التحلل",
"pixelsPerTick": "بكسل لكل نبضة",
"maximumFrameInterval": "أقصى فاصل زمني بين الإطارات",
"maximumTicksPerFrame": "الحد الأقصى للنبضات لكل إطار"
},
"imageStorage": {
"instanceId": "معرّف مثيل جلسة الطرفية",
"instanceIdDesc": "معرف مثيل إحدى علامات تبويب الطرفية المتصلة، المستخدمة لاختبار الرؤية.",
"selectedModeUnavailable": "غير متاح",
"test": "امتحان",
"testing": "جارٍ الاختبار...",
"testFailed": "فشل اختبار تخزين الصور",
"testConnected": "تم الاتصال بجلسة الطرفية.",
"testNotConnected": "لا توجد جلسة طرفية متصلة بهذا المعرف.",
"testSelectedMode": "سيتم استخدام الأمر التالي للتحميل: {{mode}}",
"testLocalVisible": "الدليل المحلي مرئي من الجهاز المضيف المتصل.",
"testLocalNotVisible": "الدليل المحلي غير مرئي من الجهاز المضيف المتصل."
},
"settings": {
"admin": {
"group": {
"touch": "إدخال اللمس",
"images": "تخزين الصور"
},
"sessionTimeoutMinutes": {
"label": "استمرار جلسة الطرفية",
"description": "مدة بقاء طرفية غير متصلة على الخادم قبل إغلاقها. الحد الأدنى: دقيقة واحدة، الحد الأقصى: 1440 دقيقة. بالنسبة للجلسات التي يجب أن تستمر لفترة أطول من ذلك، فعّل خاصية Auto-Tmux على المضيف."
},
"commandHistoryEnabled": {
"label": "سجل الأوامر",
"description": "السماح بتسجيل سجل الأوامر. عند التعطيل، لا يتم حفظ السجل بغض النظر عن إعدادات كل جهاز."
},
"imageStorageMode": {
"label": "وضع التخزين",
"description": "يتم حفظ الصور الطرفية الملصقة والمرفوعة في هذا المكان. يتم تثبيت عمليات الاختيار التلقائي حسب الاتصال، سواء كان محليًا أو عن بُعد (SFTP).",
"auto": "آلي",
"local": "محلي",
"remoteSftp": "عن بعد (SFTP)"
},
"imageLocalDir": {
"label": "الدليل المحلي"
},
"imageHostPath": {
"label": "مسار المضيف",
"description": "عرض الدليل المحلي من جانب المضيف الذي تتلقاه وكلاء المحطة الطرفية."
},
"imageTtlMs": {
"label": "مدة البقاء (بالمللي ثانية)"
},
"imageMaxCount": {
"label": "الحد الأقصى لعدد الصور"
},
"imageMaxBytes": {
"label": "الحد الأقصى لحجم التخزين بالبايت"
}
},
"host": {
"enableTerminalToolbar": {
"label": "تفعيل شريط أدوات الطرفية",
"description": "اعرض شريط أدوات سريع الوصول مثبتًا على الطرفية النشطة لأدوات هذا المضيف وإحصائياته المباشرة."
},
"enableCommandHistory": {
"label": "سجل الأوامر",
"description": "تسجيل الأوامر المنفذة في هذه الطرفية للسجل والإكمال التلقائي"
},
"enableTerminal": {
"label": "تمكين الطرفية"
}
}
},
"palette": {
"localTerminal": "المحطة المحلية"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Добавяне",
"arrowDown": "Стрелка надолу",
"arrowLeft": "Стрелка наляво",
"arrowRight": "Стрелка надясно",
"arrowUp": "Стрелка нагоре",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Дел",
"done": "Готово",
"editQuickKeys": "Редактиране на бързи клавиши",
"end": "Край",
"esc": "Esc",
"home": "Дом",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Паста",
"quickKeyPlaceholder": "например sudo ",
"quickKeysDesc": "Докоснете × за премахване. Поддържа до 8 знака.",
"quickKeysTitle": "Бързи клавиши",
"removeQuickKey": "Премахване",
"resetDefaults": "Възстановяване на фабричните настройки",
"shift": "Изместване",
"tab": "Раздел"
},
"terminal": {
"autoTmuxEnableFailed": "Неуспешно активиране на Auto-Tmux",
"autoTmuxEnabled": "Автоматичното Tmux управление е активирано за {{host}}. Прилага се от следващата връзка.",
"clipboardHttpWarning": "Не можах да прочета клипборда. Някои браузъри ограничават достъпа до клипборда извън HTTPS - опитайте Ctrl+V вместо това или използвайте Termix през HTTPS.",
"closeTab": "Затвори",
"connecting": "Свързване...",
"connectionLost": "Връзката е прекъсната",
"connectionRejected": "Връзката е отхвърлена от сървъра. Моля, проверете удостоверяването и мрежовата си конфигурация.",
"connectionTimeout": "Време за изчакване на връзката",
"enableAutoTmuxAction": "Активиране на Auto-Tmux",
"hostKeyRejected": "Проверката на SSH ключа за хост е отхвърлена. Връзката е прекратена.",
"linkDialogCopy": "Копиране",
"linkDialogOpen": "Отворено",
"linkDialogTitle": "Отвори линка",
"maxReconnectAttemptsReached": "Достигнат е максималният брой опити за повторно свързване",
"noHostSelected": "Няма избран хост",
"opksshAuthFailed": "Удостоверяването не бе успешно. Моля, проверете идентификационните си данни и опитайте отново.",
"opksshTimeout": "Времето за изчакване на удостоверяването изтече. Моля, опитайте отново.",
"passwordPromptFillTitle": "Да се попълни ли запазената парола в този ред?",
"reconnect": "Свържете се отново",
"reconnected": "Успешно повторно свързване",
"reconnecting": "Повторно свързване... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Съвпадение на регистъра",
"searchClose": "Затвори (Escape)",
"searchNext": "Следващ мач (Enter)",
"searchNoResults": "Няма резултати",
"searchPlaceholder": "Намерете",
"searchPrevious": "Предишно съвпадение (Shift+Enter)",
"searchRegex": "Използвайте регулярни изрази",
"searchResultCount": "{{index}} от {{count}}",
"searchWholeWord": "Съвпадение на цяла дума",
"sessionExpiredNotice": "Предишната ви сесия на {{host}} изтече след {{minutes}} минути прекъснато свързване; обвивката и всичко, което се изпълнява в нея, са изчезнали. Активирайте Auto-Tmux на този хост, за да поддържате сесиите активни.",
"sessionExpiredNoticeNoMinutes": "Предишната ви сесия на {{host}} изтече, докато бяхте прекъснати; обвивката и всичко, което се изпълняваше в нея, са изчезнали. Активирайте Auto-Tmux на този хост, за да поддържате сесиите активни.",
"sessionTakenOver": "Сесията беше отворена в друг раздел. Повторно свързване...",
"tmuxAttached": "Прикачени клиенти",
"tmuxAttachedCount": "{{count}} прикачен",
"tmuxCopyHint": "Променете селекцията и натиснете Enter, за да я копирате в клипборда",
"tmuxCreateNew": "Започнете нова сесия",
"tmuxDetached": "Отделен от tmux сесията",
"tmuxLastActivity": "Последна активност",
"tmuxSessionAttached": "Прикачена tmux сесия: {{name}}",
"tmuxSessionCreated": "tmux сесия, създадена: {{name}}",
"tmuxSessionPickerDesc": "На този хост са открити съществуващи tmux сесии. Изберете една, за да я свържете отново или да създадете нова сесия.",
"tmuxSessionPickerTitle": "tmux сесии",
"tmuxTimeDays": "преди {{count}}дни",
"tmuxTimeHours": "преди {{count}}ч",
"tmuxTimeJustNow": "точно сега",
"tmuxTimeMinutes": "преди {{count}}мин.",
"tmuxUnavailable": "tmux не е инсталиран на отдалечения хост, връща се към стандартната обвивка",
"tmuxWindowCount": "{{count}} прозорец",
"tmuxWindows": "Прозорци",
"unknownError": "Възникна неизвестна грешка",
"vaultAuthDescription": "Отвори се прозорец за влизане в HashiCorp Vault. Завършете влизането там; тази връзка ще продължи автоматично.",
"vaultAuthFailed": "Удостоверяването на трезора не бе успешно. Моля, опитайте отново.",
"vaultAuthTitle": "Изисква се влизане в трезора",
"vaultReopen": "Отваряне отново на прозореца за влизане",
"websocketAbnormalClose": "Връзката се прекъсна неочаквано. Това може да се дължи на проблем с обратен прокси или SSL конфигурация. Моля, проверете лог файловете на сървъра.",
"websocketError": "Грешка при свързване с WebSocket"
},
"terminalToolbar": {
"detachTmux": "Отделяне на tmux",
"detachTmuxDescription": "Отделяне на tmux сесия",
"dismissError": "Грешка при отхвърляне на изображението",
"hideToolbar": "Скриване на лентата с инструменти",
"image": "Действия с изображения (само с изображения)",
"imageActionFailed": "Действието с изображението не беше успешно",
"layout": "Режим на показване на лентата с инструменти",
"layoutExpanded": "Разширено с показатели",
"layoutIcon": "Само икона",
"layoutLabeled": "Икони и етикети",
"moveToolbar": "Преместване на лентата с инструменти",
"moveToolbarHint": "Плъзнете, за да препозиционирате лентата с инструменти",
"paste": "Поставяне на изображение",
"pasteImage": "Поставяне на изображение от клипборда (само за изображения)",
"retry": "Опитай отново",
"showToolbar": "Разгъване на лентата с инструменти",
"upload": "Качване на изображение",
"uploadImage": "Качете изображение на хоста",
"uploadingImage": "Качване..."
},
"history": {
"noTerminalSelected": "Няма избран терминал",
"noTerminalSelectedHint": "Отворете раздел на SSH терминал, за да видите историята на командите му",
"searchPlaceholder": "История на търсенията...",
"clearAll": "Изчисти всички",
"noHistoryEntries": "Няма записи в историята",
"trackingDisabled": "Проследяването на историята е деактивирано"
},
"touchInput": {
"enabled": "Активиране на сензорно въвеждане",
"enabledDesc": "Разрешаване на превъртане с един пръст в терминали",
"momentumEnabled": "Активиране на Momentum",
"momentumEnabledDesc": "Продължете да превъртате за кратко след плъзване",
"advanced": "Разширено",
"resetDefaults": "Възстановяване на фабричните настройки",
"dragThreshold": "Праг на плъзгане",
"maxWheelDelta": "Максимална делта на колелото",
"momentumSampleWindow": "Прозорец за пример на импулса",
"releaseGracePeriod": "Гратисен период за освобождаване",
"minimumVelocity": "Минимална скорост",
"maximumVelocity": "Максимална скорост",
"maximumDuration": "Максимална продължителност",
"maximumTravel": "Максимален ход",
"decayTime": "Време на разпад",
"pixelsPerTick": "Пиксели на такт",
"maximumFrameInterval": "Максимален интервал на кадрите",
"maximumTicksPerFrame": "Максимален брой отметки на кадър"
},
"imageStorage": {
"instanceId": "Идентификатор на екземпляр на терминална сесия",
"instanceIdDesc": "Идентификатор на екземпляр на един от свързаните ви терминални раздели, използван за тестване на видимостта.",
"selectedModeUnavailable": "Не е налично",
"test": "Тест",
"testing": "Тестване...",
"testFailed": "Тестването на съхранението на изображения не бе успешно",
"testConnected": "Терминалната сесия е свързана.",
"testNotConnected": "Няма свързана терминална сесия с този идентификатор на екземпляр.",
"testSelectedMode": "Качването ще използва: {{mode}}",
"testLocalVisible": "Локалната директория е видима от свързания хост.",
"testLocalNotVisible": "Локалната директория НЕ е видима от свързания хост."
},
"settings": {
"admin": {
"group": {
"touch": "Сензорно въвеждане",
"images": "Съхранение на изображения"
},
"sessionTimeoutMinutes": {
"label": "Устойчивост на терминална сесия",
"description": "Колко дълго един прекъснат терминал се поддържа активен на сървъра, преди да бъде затворен. Мин. 1 · Макс. 1440 минути. За сесии, които трябва да преживеят това, активирайте Auto-Tmux на хоста."
},
"commandHistoryEnabled": {
"label": "История на командите",
"description": "Разрешаване на записване на историята на командите. Когато е деактивирано, историята не се запазва, независимо от настройките за всеки хост."
},
"imageStorageMode": {
"label": "Режим на съхранение",
"description": "Където се записват поставените и качени изображения на терминала. Автоматичното избиране по връзка, локално и отдалечено (SFTP) е поправено.",
"auto": "Автоматично",
"local": "Местно",
"remoteSftp": "Отдалечено (SFTP)"
},
"imageLocalDir": {
"label": "Локален указател"
},
"imageHostPath": {
"label": "Път на хоста",
"description": "Изгледът от страна на хоста на локалната директория, която получават терминалните агенти."
},
"imageTtlMs": {
"label": "TTL (мсек)"
},
"imageMaxCount": {
"label": "Максимален брой изображения"
},
"imageMaxBytes": {
"label": "Максимален брой байтове за съхранение"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Активиране на лентата с инструменти на терминала",
"description": "Показване на лента с инструменти за бърз достъп, закачена към активния терминал, за инструментите и статистиката в реално време на този хост"
},
"enableCommandHistory": {
"label": "История на командите",
"description": "Запис на команди, изпълнявани в този терминал, за история и автоматично довършване"
},
"enableTerminal": {
"label": "Активиране на терминала"
}
}
},
"palette": {
"localTerminal": "Местен терминал"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "যোগ করুন",
"arrowDown": "অ্যারো ডাউন",
"arrowLeft": "বাম তীর",
"arrowRight": "ডান তীরচিহ্ন",
"arrowUp": "তীর ঊর্ধ্বমুখী",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "ডেল",
"done": "সম্পন্ন হয়েছে",
"editQuickKeys": "দ্রুত কী সম্পাদনা করুন",
"end": "শেষ",
"esc": "এস্ক",
"home": "হোম",
"pageDown": "পিজিডিএন",
"pageUp": "পিজিআপ",
"paste": "পেস্ট",
"quickKeyPlaceholder": "যেমন sudo ",
"quickKeysDesc": "মুছে ফেলতে × চাপুন। সর্বোচ্চ ৮টি অক্ষর সমর্থন করে।",
"quickKeysTitle": "দ্রুত চাবি",
"removeQuickKey": "অপসারণ করুন",
"resetDefaults": "ডিফল্টে রিসেট করুন",
"shift": "শিফট",
"tab": "ট্যাব"
},
"terminal": {
"autoTmuxEnableFailed": "অটো-টিমাক্স সক্রিয় করতে ব্যর্থ হয়েছে",
"autoTmuxEnabled": "{{host}}এর জন্য অটো-টিমাক্স সক্রিয় করা হয়েছে। এটি পরবর্তী সংযোগ থেকে প্রযোজ্য হবে।",
"clipboardHttpWarning": "ক্লিপবোর্ডটি পড়া যায়নি। কিছু ব্রাউজার HTTPS-এর বাইরে ক্লিপবোর্ড অ্যাক্সেস সীমাবদ্ধ করে - এর পরিবর্তে Ctrl+V ব্যবহার করে দেখুন, অথবা HTTPS-এর মাধ্যমে Termix পরিবেশন করুন।",
"closeTab": "বন্ধ করুন",
"connecting": "সংযোগ স্থাপন...",
"connectionLost": "সংযোগ বিচ্ছিন্ন",
"connectionRejected": "সার্ভার কর্তৃক সংযোগ প্রত্যাখ্যাত হয়েছে। অনুগ্রহ করে আপনার প্রমাণীকরণ এবং নেটওয়ার্ক কনফিগারেশন যাচাই করুন।",
"connectionTimeout": "সংযোগের সময়সীমা শেষ",
"enableAutoTmuxAction": "অটো-Tmux সক্রিয় করুন",
"hostKeyRejected": "SSH হোস্ট কী যাচাইকরণ প্রত্যাখ্যাত হয়েছে। সংযোগ বাতিল করা হয়েছে।",
"linkDialogCopy": "অনুলিপি",
"linkDialogOpen": "খোলা",
"linkDialogTitle": "লিঙ্ক খুলুন",
"maxReconnectAttemptsReached": "পুনঃসংযোগের সর্বোচ্চ প্রচেষ্টা শেষ হয়েছে",
"noHostSelected": "কোন হোস্ট নির্বাচন করা হয়নি",
"opksshAuthFailed": "প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আপনার পরিচয়পত্র যাচাই করে আবার চেষ্টা করুন।",
"opksshTimeout": "প্রমাণীকরণের সময়সীমা শেষ হয়ে গেছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
"passwordPromptFillTitle": "সংরক্ষিত পাসওয়ার্ডটি এই প্রম্পটে পূরণ করুন?",
"reconnect": "পুনরায় সংযোগ করুন",
"reconnected": "সফলভাবে পুনরায় সংযুক্ত হয়েছে",
"reconnecting": "পুনরায় সংযোগ স্থাপন করা হচ্ছে... ({{attempt}}/{{max}})",
"searchCaseSensitive": "দেশলাইয়ের বাক্স",
"searchClose": "বন্ধ করুন (পলায়ন)",
"searchNext": "পরবর্তী ম্যাচ (প্রবেশ করুন)",
"searchNoResults": "কোন ফলাফল নেই",
"searchPlaceholder": "খুঁজুন",
"searchPrevious": "পূর্ববর্তী ম্যাচ (Shift+Enter)",
"searchRegex": "রেগুলার এক্সপ্রেশন ব্যবহার করুন",
"searchResultCount": "{{index}} এর {{count}}",
"searchWholeWord": "সম্পূর্ণ শব্দ মেলান",
"sessionExpiredNotice": "{{host}} -এ আপনার পূর্ববর্তী সেশনটি {{minutes}} মিনিট পর সংযোগ বিচ্ছিন্ন হয়ে মেয়াদোত্তীর্ণ হয়েছে; শেল এবং এর মধ্যে চলমান সবকিছু চলে গেছে। সেশনগুলো সচল রাখতে এই হোস্টে অটো-টিমাক্স (Auto-Tmux) সক্রিয় করুন।",
"sessionExpiredNoticeNoMinutes": "{{host}} -এ আপনার পূর্ববর্তী সেশনটি সংযোগ বিচ্ছিন্ন থাকাকালীন মেয়াদোত্তীর্ণ হয়ে গেছে; শেল এবং এর মধ্যে চলমান সবকিছু মুছে গেছে। সেশনগুলো সচল রাখতে এই হোস্টে অটো-টিমাক্স (Auto-Tmux) সক্রিয় করুন।",
"sessionTakenOver": "সেশনটি অন্য একটি ট্যাবে খোলা হয়েছিল। পুনরায় সংযোগ করা হচ্ছে...",
"tmuxAttached": "সংযুক্ত ক্লায়েন্ট",
"tmuxAttachedCount": "{{count}} সংযুক্ত",
"tmuxCopyHint": "নির্বাচনটি ঠিক করুন এবং ক্লিপবোর্ডে কপি করতে এন্টার চাপুন।",
"tmuxCreateNew": "নতুন সেশন শুরু করুন",
"tmuxDetached": "tmux সেশন থেকে বিচ্ছিন্ন",
"tmuxLastActivity": "শেষ কার্যকলাপ",
"tmuxSessionAttached": "tmux সেশন সংযুক্ত হয়েছে: {{name}}",
"tmuxSessionCreated": "tmux সেশন তৈরি হয়েছে: {{name}}",
"tmuxSessionPickerDesc": "এই হোস্টে বিদ্যমান tmux সেশন পাওয়া গেছে। পুনরায় সংযুক্ত হতে বা একটি নতুন সেশন তৈরি করতে যেকোনো একটি নির্বাচন করুন।",
"tmuxSessionPickerTitle": "tmux সেশন",
"tmuxTimeDays": "{{count}}দিন আগে",
"tmuxTimeHours": "{{count}}ঘণ্টা আগে",
"tmuxTimeJustNow": "এইমাত্র",
"tmuxTimeMinutes": "{{count}}মিনিট আগে",
"tmuxUnavailable": "রিমোট হোস্টে tmux ইনস্টল করা নেই, তাই স্ট্যান্ডার্ড শেল ব্যবহার করা হচ্ছে।",
"tmuxWindowCount": "{{count}} জানালা",
"tmuxWindows": "উইন্ডোজ",
"unknownError": "অজানা ত্রুটি ঘটেছে",
"vaultAuthDescription": "হ্যাশিকর্প ভল্টে সাইন ইন করার জন্য একটি উইন্ডো খুলেছে। সেখানে সাইন-ইন সম্পন্ন করুন; এই সংযোগটি স্বয়ংক্রিয়ভাবে চলতে থাকবে।",
"vaultAuthFailed": "ভল্ট প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
"vaultAuthTitle": "ভল্টে সাইন-ইন করা আবশ্যক",
"vaultReopen": "সাইন-ইন উইন্ডো পুনরায় খুলুন",
"websocketAbnormalClose": "সংযোগটি অপ্রত্যাশিতভাবে বন্ধ হয়ে গেছে। এটি রিভার্স প্রক্সি বা SSL কনফিগারেশন সংক্রান্ত সমস্যার কারণে হতে পারে। অনুগ্রহ করে সার্ভার লগ পরীক্ষা করুন।",
"websocketError": "ওয়েবসকেট সংযোগ ত্রুটি"
},
"terminalToolbar": {
"detachTmux": "tmux বিচ্ছিন্ন করুন",
"detachTmuxDescription": "tmux সেশন বিচ্ছিন্ন করুন",
"dismissError": "ছবির ত্রুটি খারিজ করুন",
"hideToolbar": "টুলবার লুকান",
"image": "ছবির কার্যকলাপ (শুধুমাত্র ছবি)",
"imageActionFailed": "ছবির অ্যাকশন ব্যর্থ হয়েছে",
"layout": "টুলবার প্রদর্শন মোড",
"layoutExpanded": "মেট্রিক্স দিয়ে প্রসারিত",
"layoutIcon": "শুধুমাত্র আইকন",
"layoutLabeled": "আইকন এবং লেবেল",
"moveToolbar": "টুলবার সরান",
"moveToolbarHint": "টুলবারের অবস্থান পরিবর্তন করতে টেনে আনুন",
"paste": "ছবি পেস্ট করুন",
"pasteImage": "ক্লিপবোর্ড থেকে ছবি পেস্ট করুন (শুধুমাত্র ছবি)",
"retry": "পুনরায় চেষ্টা করুন",
"showToolbar": "টুলবার প্রসারিত করুন",
"upload": "ছবি আপলোড করুন",
"uploadImage": "হোস্টে একটি ছবি আপলোড করুন",
"uploadingImage": "আপলোড হচ্ছে..."
},
"history": {
"noTerminalSelected": "কোন টার্মিনাল নির্বাচন করা হয়নি",
"noTerminalSelectedHint": "এর কমান্ড হিস্ট্রি দেখতে একটি SSH টার্মিনাল ট্যাব খুলুন।",
"searchPlaceholder": "অনুসন্ধানের ইতিহাস...",
"clearAll": "সব পরিষ্কার করুন",
"noHistoryEntries": "ইতিহাসের কোন এন্ট্রি নেই",
"trackingDisabled": "ইতিহাস ট্র্যাকিং নিষ্ক্রিয় করা হয়েছে"
},
"touchInput": {
"enabled": "টাচ ইনপুট সক্রিয় করুন",
"enabledDesc": "টার্মিনালে এক আঙুলের স্পর্শে স্ক্রোলিং করার অনুমতি দিন",
"momentumEnabled": "গতি সঞ্চার করুন",
"momentumEnabledDesc": "সোয়াইপ করার পর কিছুক্ষণ স্ক্রোল করতে থাকুন।",
"advanced": "উন্নত",
"resetDefaults": "ডিফল্টে রিসেট করুন",
"dragThreshold": "ড্র্যাগ থ্রেশহোল্ড",
"maxWheelDelta": "সর্বোচ্চ চাকার ডেল্টা",
"momentumSampleWindow": "ভরবেগ নমুনা উইন্ডো",
"releaseGracePeriod": "মুক্তির জন্য অতিরিক্ত সময়সীমা",
"minimumVelocity": "সর্বনিম্ন বেগ",
"maximumVelocity": "সর্বোচ্চ বেগ",
"maximumDuration": "সর্বোচ্চ সময়কাল",
"maximumTravel": "সর্বোচ্চ ভ্রমণ",
"decayTime": "ক্ষয়ের সময়",
"pixelsPerTick": "প্রতি টিক পিক্সেল",
"maximumFrameInterval": "সর্বোচ্চ ফ্রেম ব্যবধান",
"maximumTicksPerFrame": "প্রতি ফ্রেমে সর্বোচ্চ টিক"
},
"imageStorage": {
"instanceId": "টার্মিনাল সেশন ইনস্ট্যান্স আইডি",
"instanceIdDesc": "আপনার সংযুক্ত টার্মিনাল ট্যাবগুলোর একটির ইনস্ট্যান্স আইডি, যা দৃশ্যমানতা পরীক্ষা করতে ব্যবহৃত হয়।",
"selectedModeUnavailable": "অনুপলব্ধ",
"test": "পরীক্ষা",
"testing": "পরীক্ষা চলছে...",
"testFailed": "ইমেজ স্টোরেজ পরীক্ষা করতে ব্যর্থ হয়েছে",
"testConnected": "টার্মিনাল সেশন সংযুক্ত আছে।",
"testNotConnected": "এই ইনস্ট্যান্স আইডির সাথে কোনো সংযুক্ত টার্মিনাল সেশন নেই।",
"testSelectedMode": "আপলোড করতে ব্যবহৃত হবে: {{mode}}",
"testLocalVisible": "সংযুক্ত হোস্ট থেকে স্থানীয় ডিরেক্টরিটি দেখা যায়।",
"testLocalNotVisible": "সংযুক্ত হোস্ট থেকে স্থানীয় ডিরেক্টরিটি দেখা যাচ্ছে না।"
},
"settings": {
"admin": {
"group": {
"touch": "টাচ ইনপুট",
"images": "ইমেজ স্টোরেজ"
},
"sessionTimeoutMinutes": {
"label": "টার্মিনাল সেশন স্থায়িত্ব",
"description": "একটি সংযোগ বিচ্ছিন্ন টার্মিনাল বন্ধ হওয়ার আগে সার্ভারে কতক্ষণ চালু থাকে। সর্বনিম্ন ১ · সর্বোচ্চ ১৪৪০ মিনিট। যে সেশনগুলোকে এই সময়ের চেয়ে বেশি সময় ধরে চালু রাখতে হবে, সেগুলোর জন্য হোস্টে অটো-টিমাক্স (Auto-Tmux) সক্রিয় করুন।"
},
"commandHistoryEnabled": {
"label": "কমান্ডের ইতিহাস",
"description": "কমান্ড হিস্ট্রি রেকর্ডিংয়ের অনুমতি দিন। এটি নিষ্ক্রিয় করা থাকলে, হোস্ট-ভিত্তিক সেটিংস নির্বিশেষে হিস্ট্রি সংরক্ষিত হয় না।"
},
"imageStorageMode": {
"label": "স্টোরেজ মোড",
"description": "যেখানে পেস্ট করা এবং আপলোড করা টার্মিনাল ইমেজগুলো লেখা হয়। কানেকশন অনুযায়ী অটো পিক, লোকাল এবং রিমোট (SFTP) নির্দিষ্ট করা আছে।",
"auto": "অটো",
"local": "স্থানীয়",
"remoteSftp": "রিমোট (এসএফটিপি)"
},
"imageLocalDir": {
"label": "স্থানীয় ডিরেক্টরি"
},
"imageHostPath": {
"label": "হোস্ট পাথ",
"description": "টার্মিনাল এজেন্টরা স্থানীয় ডিরেক্টরির যে হোস্ট-সাইড ভিউটি পায়।"
},
"imageTtlMs": {
"label": "টিটিএল (মিলিসেকেন্ড)"
},
"imageMaxCount": {
"label": "সর্বোচ্চ ছবির সংখ্যা"
},
"imageMaxBytes": {
"label": "সর্বোচ্চ স্টোরেজ বাইট"
}
},
"host": {
"enableTerminalToolbar": {
"label": "টার্মিনাল টুলবার সক্রিয় করুন",
"description": "এই হোস্টের টুলস এবং লাইভ পরিসংখ্যানের জন্য সক্রিয় টার্মিনালে ডক করা একটি কুইক-অ্যাক্সেস টুলবার দেখান।"
},
"enableCommandHistory": {
"label": "কমান্ডের ইতিহাস",
"description": "ইতিহাস এবং স্বয়ংক্রিয় সম্পূর্ণতার জন্য এই টার্মিনালে চালানো কমান্ডগুলো রেকর্ড করুন।"
},
"enableTerminal": {
"label": "টার্মিনাল সক্রিয় করুন"
}
}
},
"palette": {
"localTerminal": "স্থানীয় টার্মিনাল"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Afegeix",
"arrowDown": "Fletxa avall",
"arrowLeft": "Fletxa esquerra",
"arrowRight": "Fletxa dreta",
"arrowUp": "Fletxa amunt",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Eliminar",
"done": "Fet",
"editQuickKeys": "Edita les tecles ràpides",
"end": "Final",
"esc": "Esc",
"home": "Inici",
"pageDown": "Av Pàg",
"pageUp": "Re Pàg",
"paste": "Enganxa",
"quickKeyPlaceholder": "p. ex. sudo ",
"quickKeysDesc": "Toca × per eliminar. Admet fins a 8 caràcters.",
"quickKeysTitle": "Tecles ràpides",
"removeQuickKey": "Elimina",
"resetDefaults": "Restableix els valors predeterminats",
"shift": "Majúscules",
"tab": "Pestanya"
},
"terminal": {
"autoTmuxEnableFailed": "No s'ha pogut habilitar Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux habilitat per a {{host}}. S'aplica a partir de la següent connexió.",
"clipboardHttpWarning": "No s'ha pogut llegir el porta-retalls. Alguns navegadors restringeixen l'accés al porta-retalls fora d'HTTPS; proveu Ctrl+V o serviu Termix a través d'HTTPS.",
"closeTab": "Tanca",
"connecting": "Connectant...",
"connectionLost": "Connexió perduda",
"connectionRejected": "Connexió rebutjada pel servidor. Si us plau, comproveu l'autenticació i la configuració de xarxa.",
"connectionTimeout": "Temps d'espera de connexió",
"enableAutoTmuxAction": "Activa la transmissió automàtica",
"hostKeyRejected": "Verificació de la clau de l'amfitrió SSH rebutjada. Connexió cancel·lada.",
"linkDialogCopy": "Còpia",
"linkDialogOpen": "Obre",
"linkDialogTitle": "Obre l'enllaç",
"maxReconnectAttemptsReached": "S'ha arribat al màxim d'intents de reconnexió",
"noHostSelected": "No s'ha seleccionat cap amfitrió",
"opksshAuthFailed": "L'autenticació ha fallat. Si us plau, comproveu les vostres credencials i torneu-ho a intentar.",
"opksshTimeout": "S'ha esgotat el temps d'autenticació. Torna-ho a provar.",
"passwordPromptFillTitle": "Voleu introduir la contrasenya desada en aquesta sol·licitud?",
"reconnect": "Reconnecta",
"reconnected": "S'ha reconnectat correctament",
"reconnecting": "Reconnectant... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Coincideix amb el cas",
"searchClose": "Tanca (Escapada)",
"searchNext": "Pròxima coincidència (Intro)",
"searchNoResults": "Sense resultats",
"searchPlaceholder": "Troba",
"searchPrevious": "Coincidència anterior (Maj+Intro)",
"searchRegex": "Utilitza l'expressió regular",
"searchResultCount": "{{index}} de {{count}}",
"searchWholeWord": "Coincideix amb la paraula sencera",
"sessionExpiredNotice": "La sessió anterior a {{host}} ha caducat després de {{minutes}} minuts de desconnexió; l'intèrpret d'ordres i tot el que s'hi executa ha desaparegut. Activeu Auto-Tmux en aquest amfitrió per mantenir les sessions actives.",
"sessionExpiredNoticeNoMinutes": "La sessió anterior a {{host}} ha caducat mentre estava desconnectada; l'intèrpret d'ordres i tot el que s'hi executa ha desaparegut. Activeu Auto-Tmux en aquest host per mantenir les sessions actives.",
"sessionTakenOver": "La sessió s'ha obert en una altra pestanya. S'està tornant a connectar...",
"tmuxAttached": "Clients adjunts",
"tmuxAttachedCount": "{{count}} adjunt",
"tmuxCopyHint": "Ajusta la selecció i prem Intro per copiar al porta-retalls",
"tmuxCreateNew": "Inicia una nova sessió",
"tmuxDetached": "Desconnectat de la sessió tmux",
"tmuxLastActivity": "Darrera activitat",
"tmuxSessionAttached": "sessió tmux adjunta: {{name}}",
"tmuxSessionCreated": "Sessió tmux creada: {{name}}",
"tmuxSessionPickerDesc": "S'han trobat sessions tmux existents en aquest amfitrió. Seleccioneu-ne una per tornar-la a adjuntar o crear una sessió nova.",
"tmuxSessionPickerTitle": "Sessions de tmux",
"tmuxTimeDays": "fa {{count}}dies",
"tmuxTimeHours": "fa {{count}}h",
"tmuxTimeJustNow": "ara mateix",
"tmuxTimeMinutes": "fa {{count}}minuts",
"tmuxUnavailable": "tmux no està instal·lat a l'amfitrió remot, tornant a l'intèrpret d'ordres estàndard",
"tmuxWindowCount": "finestra {{count}}",
"tmuxWindows": "Finestres",
"unknownError": "S'ha produït un error desconegut",
"vaultAuthDescription": "S'ha obert una finestra per iniciar la sessió a HashiCorp Vault. Completeu l'inici de sessió allà; aquesta connexió continuarà automàticament.",
"vaultAuthFailed": "L'autenticació de la caixa forta ha fallat. Torna-ho a provar.",
"vaultAuthTitle": "Cal iniciar sessió a la caixa forta",
"vaultReopen": "Torna a obrir la finestra d'inici de sessió",
"websocketAbnormalClose": "La connexió s'ha tancat inesperadament. Això pot ser degut a un problema de configuració del proxy invers o de l'SSL. Si us plau, reviseu els registres del servidor.",
"websocketError": "Error de connexió de WebSocket"
},
"terminalToolbar": {
"detachTmux": "Desconnecta tmux",
"detachTmuxDescription": "Desconnecta la sessió tmux",
"dismissError": "Error d'ignorar la imatge",
"hideToolbar": "Amaga la barra d'eines",
"image": "Accions d'imatge (només imatges)",
"imageActionFailed": "L'acció de la imatge ha fallat",
"layout": "Mode de visualització de la barra d'eines",
"layoutExpanded": "Ampliat amb mètriques",
"layoutIcon": "Només icona",
"layoutLabeled": "Icones i etiquetes",
"moveToolbar": "Mou la barra d'eines",
"moveToolbarHint": "Arrossega per reposicionar la barra d'eines",
"paste": "Enganxa la imatge",
"pasteImage": "Enganxa la imatge del porta-retalls (només imatges)",
"retry": "Torna-ho a intentar",
"showToolbar": "Expandeix la barra d'eines",
"upload": "Puja una imatge",
"uploadImage": "Puja una imatge a l'amfitrió",
"uploadingImage": "S'està carregant..."
},
"history": {
"noTerminalSelected": "No s'ha seleccionat cap terminal",
"noTerminalSelectedHint": "Obriu una pestanya de terminal SSH per veure el seu historial d'ordres",
"searchPlaceholder": "Historial de cerca...",
"clearAll": "Esborra-ho tot",
"noHistoryEntries": "Sense entrades a l'historial",
"trackingDisabled": "El seguiment de l'historial està desactivat"
},
"touchInput": {
"enabled": "Activa l'entrada tàctil",
"enabledDesc": "Permetre el desplaçament tàctil amb un dit als terminals",
"momentumEnabled": "Activa l'impuls",
"momentumEnabledDesc": "Continua desplaçant-te breument després de lliscar el dit",
"advanced": "Avançat",
"resetDefaults": "Restableix els valors predeterminats",
"dragThreshold": "Llindar d'arrossegament",
"maxWheelDelta": "Delta màxim de roda",
"momentumSampleWindow": "Finestra de mostra de momentum",
"releaseGracePeriod": "Període de gràcia de llançament",
"minimumVelocity": "Velocitat mínima",
"maximumVelocity": "Velocitat màxima",
"maximumDuration": "Durada màxima",
"maximumTravel": "Recorregut màxim",
"decayTime": "Temps de decadència",
"pixelsPerTick": "Píxels per clic",
"maximumFrameInterval": "Interval màxim de fotograma",
"maximumTicksPerFrame": "Màxims de tics per fotograma"
},
"imageStorage": {
"instanceId": "ID d'instància de sessió de terminal",
"instanceIdDesc": "ID d'instància d'una de les pestanyes del terminal connectat, que s'utilitza per provar la visibilitat.",
"selectedModeUnavailable": "No disponible",
"test": "Prova",
"testing": "Proves...",
"testFailed": "No s'ha pogut provar l'emmagatzematge d'imatges",
"testConnected": "La sessió de terminal està connectada.",
"testNotConnected": "No hi ha cap sessió de terminal connectada amb aquest ID d'instància.",
"testSelectedMode": "Una càrrega utilitzaria: {{mode}}",
"testLocalVisible": "El directori local és visible des de l'amfitrió connectat.",
"testLocalNotVisible": "El directori local NO és visible des de l'amfitrió connectat."
},
"settings": {
"admin": {
"group": {
"touch": "Entrada tàctil",
"images": "Emmagatzematge d'imatges"
},
"sessionTimeoutMinutes": {
"label": "Persistència de la sessió terminal",
"description": "Quant de temps es manté actiu un terminal desconnectat al servidor abans de tancar-lo. Mín 1 · Màx 1440 minuts. Per a sessions que han de durar més temps, activeu Auto-Tmux a l'amfitrió."
},
"commandHistoryEnabled": {
"label": "Historial de comandaments",
"description": "Permet l'enregistrament de l'historial d'ordres. Quan està desactivat, l'historial no es desa independentment de la configuració de cada host."
},
"imageStorageMode": {
"label": "Mode d'emmagatzematge",
"description": "On s'escriuen les imatges de terminal enganxades i carregades. Les seleccions automàtiques per connexió, Local i Remota (SFTP) estan corregides.",
"auto": "Automàtic",
"local": "Local",
"remoteSftp": "Remot (SFTP)"
},
"imageLocalDir": {
"label": "Directori local"
},
"imageHostPath": {
"label": "Camí d'amfitrió",
"description": "La vista del costat de l'amfitrió del directori local que reben els agents del terminal."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Nombre màxim d'imatges"
},
"imageMaxBytes": {
"label": "Bytes d'emmagatzematge màxims"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Habilita la barra d'eines del terminal",
"description": "Mostra una barra d'eines d'accés ràpid acoblada al terminal actiu per a les eines i les estadístiques en directe d'aquest amfitrió"
},
"enableCommandHistory": {
"label": "Historial de comandaments",
"description": "Enregistra les ordres executades en aquest terminal per a l'historial i l'autocompleció"
},
"enableTerminal": {
"label": "Habilita el terminal"
}
}
},
"palette": {
"localTerminal": "Terminal local"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Přidat",
"arrowDown": "Šipka dolů",
"arrowLeft": "Šipka vlevo",
"arrowRight": "Šipka vpravo",
"arrowUp": "Šipka nahoru",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Hotovo",
"editQuickKeys": "Upravit rychlé klávesy",
"end": "Konec",
"esc": "Esc",
"home": "Domov",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Pasta",
"quickKeyPlaceholder": "např. sudo ",
"quickKeysDesc": "Klepnutím na × odstraníte. Podporuje až 8 znaků.",
"quickKeysTitle": "Rychlé klávesy",
"removeQuickKey": "Odstranit",
"resetDefaults": "Obnovit na výchozí nastavení",
"shift": "Posun",
"tab": "Záložka"
},
"terminal": {
"autoTmuxEnableFailed": "Nepodařilo se povolit Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux povolen pro {{host}}. Platí od dalšího připojení.",
"clipboardHttpWarning": "Nelze přečíst schránku. Některé prohlížeče omezují přístup ke schránce mimo HTTPS – zkuste Ctrl+V nebo přes HTTPS poskytněte Termix.",
"closeTab": "Blízko",
"connecting": "Spojovací...",
"connectionLost": "Spojení ztraceno",
"connectionRejected": "Server odmítl připojení. Zkontrolujte prosím ověření a konfiguraci sítě.",
"connectionTimeout": "Časový limit připojení",
"enableAutoTmuxAction": "Povolit Auto-Tmux",
"hostKeyRejected": "Ověření klíče hostitele SSH odmítnuto. Připojení zrušeno.",
"linkDialogCopy": "Kopie",
"linkDialogOpen": "OTEVŘENO",
"linkDialogTitle": "Otevřít odkaz",
"maxReconnectAttemptsReached": "Dosažen maximální počet pokusů o opětovné připojení",
"noHostSelected": "Nebyl vybrán žádný hostitel",
"opksshAuthFailed": "Ověření se nezdařilo. Zkontrolujte prosím své přihlašovací údaje a zkuste to znovu.",
"opksshTimeout": "Časový limit ověření vypršel. Zkuste to prosím znovu.",
"passwordPromptFillTitle": "Vyplňte uložené heslo do tohoto řádku?",
"reconnect": "Znovu se připojte",
"reconnected": "Úspěšné opětovné připojení",
"reconnecting": "Znovu se připojuji... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Zápasová karta",
"searchClose": "Zavřít (Escape)",
"searchNext": "Další zápas (Enter)",
"searchNoResults": "Žádné výsledky",
"searchPlaceholder": "Nalézt",
"searchPrevious": "Předchozí shoda (Shift+Enter)",
"searchRegex": "Použít regulární výraz",
"searchResultCount": "{{index}} z {{count}}",
"searchWholeWord": "Shoda celého slova",
"sessionExpiredNotice": "Vaše předchozí relace na {{host}} vypršela po {{minutes}} minutách odpojení; shell a vše, co v něm běží, je pryč. Povolte na tomto hostiteli Auto-Tmux, aby relace zůstaly aktivní.",
"sessionExpiredNoticeNoMinutes": "Vaše předchozí relace na {{host}} vypršela během odpojení; shell a vše, co v něm běží, je pryč. Povolte na tomto hostiteli Auto-Tmux, aby relace zůstaly aktivní.",
"sessionTakenOver": "Relace byla otevřena na jiné kartě. Znovu se připojuji...",
"tmuxAttached": "Připojení klienti",
"tmuxAttachedCount": "{{count}} připojen",
"tmuxCopyHint": "Upravte výběr a stiskněte Enter pro zkopírování do schránky",
"tmuxCreateNew": "Zahájit novou relaci",
"tmuxDetached": "Odpojeno od relace tmux",
"tmuxLastActivity": "Poslední aktivita",
"tmuxSessionAttached": "připojena relace tmux: {{name}}",
"tmuxSessionCreated": "Vytvořena relace tmux: {{name}}",
"tmuxSessionPickerDesc": "Na tomto hostiteli byly nalezeny existující relace tmux. Vyberte jednu, kterou chcete znovu připojit, nebo vytvořte novou relaci.",
"tmuxSessionPickerTitle": "Sezení tmux",
"tmuxTimeDays": "Před {{count}}dny",
"tmuxTimeHours": "{{count}}před hodinami",
"tmuxTimeJustNow": "právě teď",
"tmuxTimeMinutes": "{{count}}před m",
"tmuxUnavailable": "tmux není nainstalován na vzdáleném hostiteli, vrací se ke standardnímu shellu.",
"tmuxWindowCount": "{{count}} okno",
"tmuxWindows": "Okna",
"unknownError": "Došlo k neznámé chybě",
"vaultAuthDescription": "Otevřelo se okno pro přihlášení do HashiCorp Vault. Dokončete přihlášení tam; toto připojení bude automaticky pokračovat.",
"vaultAuthFailed": "Ověření v trezoru se nezdařilo. Zkuste to prosím znovu.",
"vaultAuthTitle": "Vyžaduje se přihlášení do trezoru",
"vaultReopen": "Znovu otevřít přihlašovací okno",
"websocketAbnormalClose": "Spojení se neočekávaně ukončilo. Může to být způsobeno problémem s reverzní proxy nebo konfigurací SSL. Zkontrolujte prosím protokoly serveru.",
"websocketError": "Chyba připojení k WebSocketu"
},
"terminalToolbar": {
"detachTmux": "Odpojit tmux",
"detachTmuxDescription": "Odpojení relace tmux",
"dismissError": "Chyba při zavírání obrázku",
"hideToolbar": "Skrýt panel nástrojů",
"image": "Akce s obrázky (pouze obrázky)",
"imageActionFailed": "Akce s obrázkem se nezdařila",
"layout": "Režim zobrazení panelu nástrojů",
"layoutExpanded": "Rozšířeno o metriky",
"layoutIcon": "Pouze ikona",
"layoutLabeled": "Ikony a popisky",
"moveToolbar": "Přesunout panel nástrojů",
"moveToolbarHint": "Přetažením změňte umístění panelu nástrojů",
"paste": "Vložit obrázek",
"pasteImage": "Vložit obrázek ze schránky (pouze obrázky)",
"retry": "Zkusit znovu",
"showToolbar": "Rozbalit panel nástrojů",
"upload": "Nahrát obrázek",
"uploadImage": "Nahrajte obrázek na hostitele",
"uploadingImage": "Nahrávání..."
},
"history": {
"noTerminalSelected": "Není vybrán žádný terminál",
"noTerminalSelectedHint": "Otevření karty terminálu SSH pro zobrazení historie jejích příkazů",
"searchPlaceholder": "Historie vyhledávání...",
"clearAll": "Vymazat vše",
"noHistoryEntries": "Žádné záznamy v historii",
"trackingDisabled": "Sledování historie je zakázáno"
},
"touchInput": {
"enabled": "Povolit dotykový vstup",
"enabledDesc": "Povolit posouvání jedním prstem v terminálech",
"momentumEnabled": "Povolit hybnost",
"momentumEnabledDesc": "Po přejetí prstem krátce pokračujte v rolování",
"advanced": "Moderní",
"resetDefaults": "Obnovit na výchozí nastavení",
"dragThreshold": "Práh přetažení",
"maxWheelDelta": "Maximální delta kola",
"momentumSampleWindow": "Okno vzorku hybnosti",
"releaseGracePeriod": "Dodatečná lhůta",
"minimumVelocity": "Minimální rychlost",
"maximumVelocity": "Maximální rychlost",
"maximumDuration": "Maximální doba trvání",
"maximumTravel": "Maximální zdvih",
"decayTime": "Doba rozpadu",
"pixelsPerTick": "Pixely na tik",
"maximumFrameInterval": "Maximální interval snímků",
"maximumTicksPerFrame": "Maximální počet tiků na snímek"
},
"imageStorage": {
"instanceId": "ID instance terminálové relace",
"instanceIdDesc": "ID instance jedné z vašich připojených karet terminálu, používané k testování viditelnosti.",
"selectedModeUnavailable": "Nedostupné",
"test": "Test",
"testing": "Testování...",
"testFailed": "Nepodařilo se otestovat úložiště obrázků",
"testConnected": "Terminálová relace je připojena.",
"testNotConnected": "S tímto ID instance není připojena žádná terminálová relace.",
"testSelectedMode": "Nahrávání by použilo: {{mode}}",
"testLocalVisible": "Lokální adresář je viditelný z připojeného hostitele.",
"testLocalNotVisible": "Lokální adresář NENÍ viditelný z připojeného hostitele."
},
"settings": {
"admin": {
"group": {
"touch": "Dotykový vstup",
"images": "Ukládání obrázků"
},
"sessionTimeoutMinutes": {
"label": "Perzistence terminálové relace",
"description": "Jak dlouho je odpojený terminál udržován aktivní na serveru, než je uzavřen. Min. 1 · Max. 1440 minut. Pro relace, které musí tuto dobu přetrvávat, povolte na hostiteli Auto-Tmux."
},
"commandHistoryEnabled": {
"label": "Historie příkazů",
"description": "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."
},
"imageStorageMode": {
"label": "Režim úložiště",
"description": "Kam se zapisují vložené a nahrané obrazy terminálu. Automatické výběry podle připojení, lokální a vzdálené (SFTP) jsou opraveny.",
"auto": "Auto",
"local": "Místní",
"remoteSftp": "Vzdálené (SFTP)"
},
"imageLocalDir": {
"label": "Místní adresář"
},
"imageHostPath": {
"label": "Cesta hostitele",
"description": "Pohled na lokální adresář, který termináloví agenti přijímají, na straně hostitele."
},
"imageTtlMs": {
"label": "Doba trvání (ms)"
},
"imageMaxCount": {
"label": "Maximální počet obrázků"
},
"imageMaxBytes": {
"label": "Maximální počet bajtů úložiště"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Povolit panel nástrojů terminálu",
"description": "Zobrazit panel nástrojů pro rychlý přístup ukotvený v aktivním terminálu pro nástroje a aktuální statistiky tohoto hostitele"
},
"enableCommandHistory": {
"label": "Historie příkazů",
"description": "Zaznamenávejte příkazy spouštěné v tomto terminálu pro historii a automatické dokončování"
},
"enableTerminal": {
"label": "Povolit terminál"
}
}
},
"palette": {
"localTerminal": "Místní terminál"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Tilføje",
"arrowDown": "Pil ned",
"arrowLeft": "Pil til venstre",
"arrowRight": "Pil til højre",
"arrowUp": "Pil op",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Færdig",
"editQuickKeys": "Rediger genvejstaster",
"end": "Ende",
"esc": "Esc",
"home": "Hjem",
"pageDown": "PgDn",
"pageUp": "Side op",
"paste": "Indsæt",
"quickKeyPlaceholder": "f.eks. sudo ",
"quickKeysDesc": "Tryk på × for at fjerne. Understøtter op til 8 tegn.",
"quickKeysTitle": "Hurtigtaster",
"removeQuickKey": "Fjerne",
"resetDefaults": "Nulstil til standardindstillinger",
"shift": "Flytte",
"tab": "Faneblad"
},
"terminal": {
"autoTmuxEnableFailed": "Kunne ikke aktivere Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux aktiveret for {{host}}. Det gælder fra den næste forbindelse.",
"clipboardHttpWarning": "Kunne ikke læse udklipsholderen. Nogle browsere begrænser adgang til udklipsholderen uden for HTTPS - prøv Ctrl+V i stedet, eller server Termix via HTTPS.",
"closeTab": "Tæt",
"connecting": "Forbinder...",
"connectionLost": "Forbindelse tabt",
"connectionRejected": "Forbindelsen blev afvist af serveren. Kontroller din godkendelse og netværkskonfiguration.",
"connectionTimeout": "Forbindelsestimeout",
"enableAutoTmuxAction": "Aktivér Auto-Tmux",
"hostKeyRejected": "SSH-værtsnøglebekræftelse afvist. Forbindelse afbrudt.",
"linkDialogCopy": "Kopi",
"linkDialogOpen": "Åben",
"linkDialogTitle": "Åbn link",
"maxReconnectAttemptsReached": "Maksimalt antal forsøg på genoprettelse nået",
"noHostSelected": "Ingen vært valgt",
"opksshAuthFailed": "Godkendelse mislykkedes. Tjek dine loginoplysninger, og prøv igen.",
"opksshTimeout": "Godkendelsen udløb. Prøv igen.",
"passwordPromptFillTitle": "Udfyld den gemte adgangskode i denne prompt?",
"reconnect": "Genopret forbindelse",
"reconnected": "Genoprettet forbindelse",
"reconnecting": "Genopretter forbindelse... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Match-sag",
"searchClose": "Luk (Escape)",
"searchNext": "Næste kamp (Enter)",
"searchNoResults": "Ingen resultater",
"searchPlaceholder": "Finde",
"searchPrevious": "Forrige match (Shift+Enter)",
"searchRegex": "Brug regulært udtryk",
"searchResultCount": "{{index}} af {{count}}",
"searchWholeWord": "Match hele ordet",
"sessionExpiredNotice": "Din forrige session på {{host}} udløb efter {{minutes}} minutters afbrydelse; shell'en og alt, der kører i den, er væk. Aktiver Auto-Tmux på denne vært for at holde sessionerne aktive.",
"sessionExpiredNoticeNoMinutes": "Din forrige session på {{host}} udløb, mens forbindelsen var afbrudt; skallen og alt, der kørte i den, er væk. Aktiver Auto-Tmux på denne vært for at holde sessionerne aktive.",
"sessionTakenOver": "Sessionen blev åbnet i en anden fane. Genopretter forbindelse...",
"tmuxAttached": "Tilknyttede klienter",
"tmuxAttachedCount": "{{count}} vedhæftet",
"tmuxCopyHint": "Juster valget, og tryk på Enter for at kopiere til udklipsholderen",
"tmuxCreateNew": "Start en ny session",
"tmuxDetached": "Afkoblet fra tmux-session",
"tmuxLastActivity": "Sidste aktivitet",
"tmuxSessionAttached": "tmux-session vedhæftet: {{name}}",
"tmuxSessionCreated": "tmux-session oprettet: {{name}}",
"tmuxSessionPickerDesc": "Eksisterende tmux-sessioner fundet på denne vært. Vælg en for at gentilknytte eller opret en ny session.",
"tmuxSessionPickerTitle": "tmux-sessioner",
"tmuxTimeDays": "for {{count}}dage siden",
"tmuxTimeHours": "For {{count}}timer siden",
"tmuxTimeJustNow": "lige nu",
"tmuxTimeMinutes": "For {{count}}minutter siden",
"tmuxUnavailable": "tmux er ikke installeret på den eksterne vært, og bruger standardshell",
"tmuxWindowCount": "{{count}} vindue",
"tmuxWindows": "Vinduer",
"unknownError": "Der opstod en ukendt fejl",
"vaultAuthDescription": "Et vindue er åbnet, hvor du kan logge ind på HashiCorp Vault. Fuldfør loginprocessen der; forbindelsen fortsætter automatisk.",
"vaultAuthFailed": "Godkendelse af arkiv mislykkedes. Prøv igen.",
"vaultAuthTitle": "Login til Vault kræves",
"vaultReopen": "Genåbn loginvinduet",
"websocketAbnormalClose": "Forbindelsen blev lukket uventet. Dette kan skyldes et problem med reverse proxy eller SSL-konfigurationen. Tjek venligst serverlogfilerne.",
"websocketError": "WebSocket-forbindelsesfejl"
},
"terminalToolbar": {
"detachTmux": "Afmonter tmux",
"detachTmuxDescription": "Afbryd tmux-session",
"dismissError": "Afvis billedfejl",
"hideToolbar": "Skjul værktøjslinjen",
"image": "Billedhandlinger (kun billeder)",
"imageActionFailed": "Billedhandling mislykkedes",
"layout": "Værktøjslinjevisningstilstand",
"layoutExpanded": "Udvidet med metrikker",
"layoutIcon": "Kun ikon",
"layoutLabeled": "Ikoner og etiketter",
"moveToolbar": "Flyt værktøjslinjen",
"moveToolbarHint": "Træk for at flytte værktøjslinjen",
"paste": "Indsæt billede",
"pasteImage": "Indsæt billede fra udklipsholder (kun billeder)",
"retry": "Prøv igen",
"showToolbar": "Udvid værktøjslinjen",
"upload": "Upload billede",
"uploadImage": "Upload et billede til værten",
"uploadingImage": "Uploader..."
},
"history": {
"noTerminalSelected": "Ingen terminal valgt",
"noTerminalSelectedHint": "Åbn en SSH-terminalfane for at se dens kommandohistorik",
"searchPlaceholder": "Søgehistorik...",
"clearAll": "Ryd alt",
"noHistoryEntries": "Ingen historikposter",
"trackingDisabled": "Historiksporing er deaktiveret"
},
"touchInput": {
"enabled": "Aktivér berøringsinput",
"enabledDesc": "Tillad scrolling med én finger i terminaler",
"momentumEnabled": "Aktiver momentum",
"momentumEnabledDesc": "Fortsæt med at scrolle kort efter et swipe",
"advanced": "Fremskreden",
"resetDefaults": "Nulstil til standardindstillinger",
"dragThreshold": "Trækgrænse",
"maxWheelDelta": "Maksimal hjuldelta",
"momentumSampleWindow": "Momentum-eksempelvindue",
"releaseGracePeriod": "Frigivelsesperiode",
"minimumVelocity": "Minimumshastighed",
"maximumVelocity": "Maksimal hastighed",
"maximumDuration": "Maksimal varighed",
"maximumTravel": "Maksimal rejse",
"decayTime": "Henfaldstid",
"pixelsPerTick": "Pixels pr. tick",
"maximumFrameInterval": "Maksimalt billedinterval",
"maximumTicksPerFrame": "Maksimalt antal ticks pr. frame"
},
"imageStorage": {
"instanceId": "Terminalsessionsinstans-ID",
"instanceIdDesc": "Instans-ID for en af dine tilsluttede terminalfaner, der bruges til at teste synligheden.",
"selectedModeUnavailable": "Ikke tilgængelig",
"test": "Prøve",
"testing": "Testning...",
"testFailed": "Kunne ikke teste billedlagring",
"testConnected": "Terminalsessionen er forbundet.",
"testNotConnected": "Ingen forbundet terminalsession med dette instans-ID.",
"testSelectedMode": "En upload ville bruge: {{mode}}",
"testLocalVisible": "Den lokale mappe er synlig fra den tilsluttede vært.",
"testLocalNotVisible": "Den lokale mappe er IKKE synlig fra den tilsluttede vært."
},
"settings": {
"admin": {
"group": {
"touch": "Berøringsinput",
"images": "Billedlagring"
},
"sessionTimeoutMinutes": {
"label": "Terminalsessionens persistens",
"description": "Hvor længe en frakoblet terminal holdes aktiv på serveren, før den lukkes. Min. 1 · Maks. 1440 minutter. For sessioner, der skal overleve dette, skal du aktivere Auto-Tmux på værten."
},
"commandHistoryEnabled": {
"label": "Kommandohistorik",
"description": "Tillad optagelse af kommandohistorik. Når den er deaktiveret, gemmes historikken ikke uanset indstillingerne for den enkelte vært."
},
"imageStorageMode": {
"label": "Lagringstilstand",
"description": "Hvor indsatte og uploadede terminalbilleder skrives. Automatiske valg efter forbindelse, lokal og fjern (SFTP) er rettet.",
"auto": "Bil",
"local": "Lokal",
"remoteSftp": "Fjernbetjening (SFTP)"
},
"imageLocalDir": {
"label": "Lokal mappe"
},
"imageHostPath": {
"label": "Værtssti",
"description": "Værtsvisningen af den lokale mappe, som terminalagenter modtager."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Maksimalt antal billeder"
},
"imageMaxBytes": {
"label": "Maks. lagerplads i bytes"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Aktivér Terminalværktøjslinjen",
"description": "Vis en værktøjslinje med hurtig adgang, der er docket til den aktive terminal for denne værts værktøjer og live statistikker"
},
"enableCommandHistory": {
"label": "Kommandohistorik",
"description": "Optagelseskommandoer køres i denne terminal for historik og autofuldførelse"
},
"enableTerminal": {
"label": "Aktivér terminal"
}
}
},
"palette": {
"localTerminal": "Lokal terminal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Hinzufügen",
"arrowDown": "Pfeil nach unten",
"arrowLeft": "Pfeil nach links",
"arrowRight": "Pfeil nach rechts",
"arrowUp": "Pfeil nach oben",
"backTab": "⇥",
"ctrl": "Strg",
"delete": "Entf",
"done": "Fertig",
"editQuickKeys": "Schnelltasten bearbeiten",
"end": "Ende",
"esc": "Esc",
"home": "Pos1",
"pageDown": "Bild ab",
"pageUp": "Bild auf",
"paste": "Paste",
"quickKeyPlaceholder": "z. B. sudo ",
"quickKeysDesc": "Tippen Sie auf × zum Entfernen. Unterstützt bis zu 8 Zeichen.",
"quickKeysTitle": "Schnelltasten",
"removeQuickKey": "Entfernen",
"resetDefaults": "Auf Standard zurücksetzen",
"shift": "Umschalt",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Auto-Tmux konnte nicht aktiviert werden.",
"autoTmuxEnabled": "Auto-Tmux ist für {{host}}aktiviert. Es gilt ab der nächsten Verbindung.",
"clipboardHttpWarning": "Die Zwischenablage konnte nicht gelesen werden. Einige Browser beschränken den Zugriff auf die Zwischenablage außerhalb von HTTPS – versuchen Sie stattdessen Strg+V oder verwenden Sie für Termix eine HTTPS-Verbindung.",
"closeTab": "Schließen",
"connecting": "Verbinde...",
"connectionLost": "Verbindung verloren",
"connectionRejected": "Verbindung vom Server abgelehnt. Bitte überprüfen Sie Ihre Authentifizierung und Netzwerkkonfiguration.",
"connectionTimeout": "Verbindungs-Timeout",
"enableAutoTmuxAction": "Auto-Tmux aktivieren",
"hostKeyRejected": "SSH-Hostschlüssel-Überprüfung abgelehnt. Verbindung abgebrochen.",
"linkDialogCopy": "Kopieren",
"linkDialogOpen": "Öffnen",
"linkDialogTitle": "Link öffnen",
"maxReconnectAttemptsReached": "Maximale Wiederverbindungsversuche erreicht",
"noHostSelected": "Kein Host ausgewählt",
"opksshAuthFailed": "Authentifizierung fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldeinformationen und versuchen Sie es erneut.",
"opksshTimeout": "Authentifizierung abgelaufen. Bitte versuchen Sie es erneut.",
"passwordPromptFillTitle": "Gespeichertes Passwort in diese Eingabeaufforderung einfügen?",
"reconnect": "Erneut verbinden",
"reconnected": "Erfolgreich wieder verbunden",
"reconnecting": "Wiederverbinden... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Streichholzschachtel",
"searchClose": "Schließen (Flucht)",
"searchNext": "Nächstes Spiel (Eingabe)",
"searchNoResults": "Keine Ergebnisse",
"searchPlaceholder": "Finden",
"searchPrevious": "Vorheriges Spiel (Umschalt+Eingabe)",
"searchRegex": "Regulären Ausdruck verwenden",
"searchResultCount": "{{index}} von {{count}}",
"searchWholeWord": "Gleiches Wort aus",
"sessionExpiredNotice": "Ihre vorherige Sitzung auf {{host}} ist nach {{minutes}} Minuten Verbindungsabbruch abgelaufen; die Shell und alle darin laufenden Prozesse wurden gelöscht. Aktivieren Sie Auto-Tmux auf diesem Host, um Sitzungen aufrechtzuerhalten.",
"sessionExpiredNoticeNoMinutes": "Ihre vorherige Sitzung auf {{host}} ist während der Trennung abgelaufen; die Shell und alle darin laufenden Prozesse wurden gelöscht. Aktivieren Sie Auto-Tmux auf diesem Host, um Sitzungen aufrechtzuerhalten.",
"sessionTakenOver": "Sitzung wurde in einem anderen Tab geöffnet. Verbindung wird wiederhergestellt...",
"tmuxAttached": "Verbundene Clients",
"tmuxAttachedCount": "{{count}} verbunden",
"tmuxCopyHint": "Auswahl anpassen und Enter drücken, um in die Zwischenablage zu kopieren",
"tmuxCreateNew": "Neue Sitzung starten",
"tmuxDetached": "Von tmux-Sitzung getrennt",
"tmuxLastActivity": "Letzte Aktivität",
"tmuxSessionAttached": "tmux-Sitzung verbunden: {{name}}",
"tmuxSessionCreated": "tmux-Sitzung erstellt: {{name}}",
"tmuxSessionPickerDesc": "Bestehende tmux-Sitzungen auf diesem Host gefunden. Wählen Sie eine aus, um sie erneut zu verbinden, oder erstellen Sie eine neue.",
"tmuxSessionPickerTitle": "tmux-Sitzungen",
"tmuxTimeDays": "vor {{count}} Tagen",
"tmuxTimeHours": "vor {{count}} Std.",
"tmuxTimeJustNow": "Soeben",
"tmuxTimeMinutes": "vor {{count}} Min.",
"tmuxUnavailable": "tmux ist auf dem Remote-Host nicht installiert, Fallback auf die Standard-Shell",
"tmuxWindowCount": "{{count}} Fenster",
"tmuxWindows": "Fenster",
"unknownError": "Unbekannter Fehler aufgetreten",
"vaultAuthDescription": "Ein Fenster zur Anmeldung bei HashiCorp Vault wurde geöffnet. Schließen Sie die Anmeldung dort ab; diese Verbindung wird automatisch fortgesetzt.",
"vaultAuthFailed": "Vault-Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"vaultAuthTitle": "Vault-Anmeldung erforderlich",
"vaultReopen": "Anmeldefenster erneut öffnen",
"websocketAbnormalClose": "Verbindung unerwartet geschlossen. Dies kann auf ein Problem mit dem Reverse-Proxy oder der SSL-Konfiguration zurückzuführen sein. Bitte überprüfen Sie die Server-Logs.",
"websocketError": "WebSocket-Verbindungsfehler"
},
"terminalToolbar": {
"detachTmux": "tmux trennen",
"detachTmuxDescription": "tmux-Sitzung trennen",
"dismissError": "Bildfehler ignorieren",
"hideToolbar": "Werkzeugleiste ausblenden",
"image": "Bildaktionen (nur Bilder)",
"imageActionFailed": "Bildaktion fehlgeschlagen",
"layout": "Symbolleistenanzeigemodus",
"layoutExpanded": "Erweitert um Kennzahlen",
"layoutIcon": "Nur Symbol",
"layoutLabeled": "Symbole und Beschriftungen",
"moveToolbar": "Verschieben Sie die Werkzeugleiste",
"moveToolbarHint": "Ziehen Sie, um die Symbolleiste neu zu positionieren.",
"paste": "Bild einfügen",
"pasteImage": "Bild aus der Zwischenablage einfügen (nur Bilder)",
"retry": "Wiederholen",
"showToolbar": "Symbolleiste erweitern",
"upload": "Bild hochladen",
"uploadImage": "Laden Sie ein Bild auf den Host hoch.",
"uploadingImage": "Wird hochgeladen..."
},
"history": {
"noTerminalSelected": "Kein Terminal ausgewählt",
"noTerminalSelectedHint": "Öffnen Sie einen SSH-Terminal-Tab, um den Befehlsverlauf anzuzeigen.",
"searchPlaceholder": "Verlauf durchsuchen...",
"clearAll": "Alle löschen",
"noHistoryEntries": "Keine Verlaufseinträge",
"trackingDisabled": "Verlaufsaufzeichnung ist deaktiviert"
},
"touchInput": {
"enabled": "Touch-Eingabe aktivieren",
"enabledDesc": "Ein-Finger-Touch-Scrollen in Terminals ermöglichen",
"momentumEnabled": "Dynamik aktivieren",
"momentumEnabledDesc": "Nach dem Wischen kurz weiterscrollen.",
"advanced": "Fortschrittlich",
"resetDefaults": "Auf Standardeinstellungen zurücksetzen",
"dragThreshold": "Widerstandsschwelle",
"maxWheelDelta": "Maximale Rad-Delta",
"momentumSampleWindow": "Momentum-Abtastfenster",
"releaseGracePeriod": "Freigabefrist",
"minimumVelocity": "Minimale Geschwindigkeit",
"maximumVelocity": "Maximale Geschwindigkeit",
"maximumDuration": "Maximale Dauer",
"maximumTravel": "Maximale Reisegeschwindigkeit",
"decayTime": "Zerfallszeit",
"pixelsPerTick": "Pixel pro Tick",
"maximumFrameInterval": "Maximales Frame-Intervall",
"maximumTicksPerFrame": "Maximale Anzahl an Ticks pro Frame"
},
"imageStorage": {
"instanceId": "Terminal-Sitzungsinstanz-ID",
"instanceIdDesc": "Instanz-ID eines Ihrer verbundenen Terminal-Tabs, die zum Testen der Sichtbarkeit verwendet wird.",
"selectedModeUnavailable": "Nicht verfügbar",
"test": "Prüfen",
"testing": "Testen...",
"testFailed": "Fehler beim Testen der Bildspeicherung",
"testConnected": "Die Terminal-Sitzung ist verbunden.",
"testNotConnected": "Es besteht keine verbundene Terminal-Sitzung mit dieser Instanz-ID.",
"testSelectedMode": "Ein Upload würde Folgendes verwenden: {{mode}}",
"testLocalVisible": "Das lokale Verzeichnis ist vom verbundenen Host aus sichtbar.",
"testLocalNotVisible": "Das lokale Verzeichnis ist vom verbundenen Host aus nicht sichtbar."
},
"settings": {
"admin": {
"group": {
"touch": "Berührungseingabe",
"images": "Bildspeicherung"
},
"sessionTimeoutMinutes": {
"label": "Terminal-Sitzungspersistenz",
"description": "Wie lange ein getrenntes Terminal auf dem Server aktiv bleibt, bevor es geschlossen wird. Min. 1 Minute · Max. 1440 Minuten. Für Sitzungen, die länger als diese Zeitspanne bestehen bleiben sollen, aktivieren Sie Auto-Tmux auf dem Host."
},
"commandHistoryEnabled": {
"label": "Befehlsverlauf",
"description": "Aufzeichnung des Befehlsverlaufs erlauben. Wenn deaktiviert, wird der Verlauf unabhängig von Host-Einstellungen nicht gespeichert."
},
"imageStorageMode": {
"label": "Speichermodus",
"description": "Hier werden eingefügte und hochgeladene Terminalbilder gespeichert. Die automatische Auswahl anhand der Verbindung (lokal und remote (SFTP)) ist festgelegt.",
"auto": "Auto",
"local": "Lokal",
"remoteSftp": "Remote (SFTP)"
},
"imageLocalDir": {
"label": "Lokales Verzeichnis"
},
"imageHostPath": {
"label": "Hostpfad",
"description": "Die hostseitige Ansicht des lokalen Verzeichnisses, die Terminalagenten empfangen."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Maximale Bildanzahl"
},
"imageMaxBytes": {
"label": "Maximale Speicherkapazität in Bytes"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Terminal-Symbolleiste aktivieren",
"description": "Zeigt eine Schnellzugriffsleiste an, die am aktiven Terminal angedockt ist und die Tools und Live-Statistiken dieses Hosts anzeigt."
},
"enableCommandHistory": {
"label": "Befehlsverlauf",
"description": "In diesem Terminal ausgeführte Befehle für Verlauf und Autovervollständigung aufzeichnen"
},
"enableTerminal": {
"label": "Terminal aktivieren"
}
}
},
"palette": {
"localTerminal": "Lokales Terminal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Προσθέτω",
"arrowDown": "Βέλος προς τα κάτω",
"arrowLeft": "Βέλος αριστερά",
"arrowRight": "Βέλος δεξιά",
"arrowUp": "Βέλος προς τα πάνω",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Διαγραφή",
"done": "Γινώμενος",
"editQuickKeys": "Επεξεργασία γρήγορων πλήκτρων",
"end": "Τέλος",
"esc": "Esc",
"home": "Σπίτι",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Πάστα",
"quickKeyPlaceholder": "π.χ. sudo ",
"quickKeysDesc": "Πατήστε × για κατάργηση. Υποστηρίζει έως και 8 χαρακτήρες.",
"quickKeysTitle": "Γρήγορα πλήκτρα",
"removeQuickKey": "Αφαιρώ",
"resetDefaults": "Επαναφορά στις προεπιλογές",
"shift": "Αλλαγή",
"tab": "Αυτί"
},
"terminal": {
"autoTmuxEnableFailed": "Αποτυχία ενεργοποίησης του Auto-Tmux",
"autoTmuxEnabled": "Το Auto-Tmux είναι ενεργοποιημένο για {{host}}. Εφαρμόζεται από την επόμενη σύνδεση.",
"clipboardHttpWarning": "Δεν ήταν δυνατή η ανάγνωση του πρόχειρου. Ορισμένα προγράμματα περιήγησης περιορίζουν την πρόσβαση στο πρόχειρο εκτός HTTPS - δοκιμάστε Ctrl+V ή προβάλετε το Termix μέσω HTTPS.",
"closeTab": "Κοντά",
"connecting": "Συνδετικός...",
"connectionLost": "Η σύνδεση χάθηκε",
"connectionRejected": "Η σύνδεση απορρίφθηκε από τον διακομιστή. Ελέγξτε τον έλεγχο ταυτότητας και τη διαμόρφωση δικτύου.",
"connectionTimeout": "Λήξη χρονικού ορίου σύνδεσης",
"enableAutoTmuxAction": "Ενεργοποίηση Auto-Tmux",
"hostKeyRejected": "Η επαλήθευση του κλειδιού κεντρικού υπολογιστή SSH απορρίφθηκε. Η σύνδεση ακυρώθηκε.",
"linkDialogCopy": "Αντίγραφο",
"linkDialogOpen": "Ανοιχτό",
"linkDialogTitle": "Άνοιγμα συνδέσμου",
"maxReconnectAttemptsReached": "Επιτεύχθηκε ο μέγιστος αριθμός προσπαθειών επανασύνδεσης",
"noHostSelected": "Δεν έχει επιλεγεί κεντρικός υπολογιστής",
"opksshAuthFailed": "Ο έλεγχος ταυτότητας απέτυχε. Ελέγξτε τα διαπιστευτήριά σας και προσπαθήστε ξανά.",
"opksshTimeout": "Το χρονικό όριο ελέγχου ταυτότητας έληξε. Δοκιμάστε ξανά.",
"passwordPromptFillTitle": "Συμπληρώστε τον αποθηκευμένο κωδικό πρόσβασης σε αυτήν την ερώτηση;",
"reconnect": "Επανασύνδεση",
"reconnected": "Επανασύνδεση με επιτυχία",
"reconnecting": "Επανασύνδεση... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Ταίριασμα υπόθεσης",
"searchClose": "Κλείσιμο (Escape)",
"searchNext": "Επόμενος αγώνας (Είσοδος)",
"searchNoResults": "Δεν υπάρχουν αποτελέσματα",
"searchPlaceholder": "Εύρημα",
"searchPrevious": "Προηγούμενος αγώνας (Shift+Enter)",
"searchRegex": "Χρήση κανονικής έκφρασης",
"searchResultCount": "{{index}} από {{count}}",
"searchWholeWord": "Ταίριασμα ολόκληρης λέξης",
"sessionExpiredNotice": "Η προηγούμενη συνεδρία σας στο {{host}} έληξε μετά από {{minutes}} λεπτά αποσύνδεσης. Το κέλυφος και οτιδήποτε εκτελείται σε αυτό έχουν εξαφανιστεί. Ενεργοποιήστε το Auto-Tmux σε αυτόν τον κεντρικό υπολογιστή για να διατηρήσετε ενεργές τις συνεδρίες.",
"sessionExpiredNoticeNoMinutes": "Η προηγούμενη συνεδρία σας στο {{host}} έληξε κατά την αποσύνδεση. Το κέλυφος και οτιδήποτε εκτελείται σε αυτό έχουν χαθεί. Ενεργοποιήστε το Auto-Tmux σε αυτόν τον κεντρικό υπολογιστή για να διατηρήσετε ενεργές τις συνεδρίες.",
"sessionTakenOver": "Η συνεδρία άνοιξε σε άλλη καρτέλα. Επανασύνδεση...",
"tmuxAttached": "Συνδεδεμένοι πελάτες",
"tmuxAttachedCount": "{{count}} επισυνάπτεται",
"tmuxCopyHint": "Προσαρμόστε την επιλογή και πατήστε Enter για αντιγραφή στο πρόχειρο",
"tmuxCreateNew": "Έναρξη νέας συνεδρίας",
"tmuxDetached": "Αποσπάστηκε από την περίοδο λειτουργίας tmux",
"tmuxLastActivity": "Τελευταία δραστηριότητα",
"tmuxSessionAttached": "συνεδρία tmux επισυνάπτεται: {{name}}",
"tmuxSessionCreated": "δημιουργήθηκε η συνεδρία tmux: {{name}}",
"tmuxSessionPickerDesc": "Βρέθηκαν υπάρχουσες συνεδρίες tmux σε αυτόν τον κεντρικό υπολογιστή. Επιλέξτε μία για να επανασυνδέσετε ή να δημιουργήσετε μια νέα συνεδρία.",
"tmuxSessionPickerTitle": "Συνεδρίες tmux",
"tmuxTimeDays": "{{count}}πριν από ημέρες",
"tmuxTimeHours": "{{count}}πριν από ώρες",
"tmuxTimeJustNow": "μόλις τώρα",
"tmuxTimeMinutes": "{{count}}πριν από λεπτά",
"tmuxUnavailable": "Το tmux δεν είναι εγκατεστημένο στον απομακρυσμένο κεντρικό υπολογιστή, επιστρέφοντας στο τυπικό κέλυφος",
"tmuxWindowCount": "παράθυρο {{count}}",
"tmuxWindows": "Παράθυρα",
"unknownError": "Παρουσιάστηκε άγνωστο σφάλμα",
"vaultAuthDescription": "Έχει ανοίξει ένα παράθυρο για να συνδεθείτε στο HashiCorp Vault. Ολοκληρώστε τη σύνδεση εκεί. Αυτή η σύνδεση θα συνεχιστεί αυτόματα.",
"vaultAuthFailed": "Ο έλεγχος ταυτότητας του vault απέτυχε. Δοκιμάστε ξανά.",
"vaultAuthTitle": "Απαιτείται σύνδεση στο Vault",
"vaultReopen": "Επανάνοιγμα παραθύρου σύνδεσης",
"websocketAbnormalClose": "Η σύνδεση έκλεισε απροσδόκητα. Αυτό μπορεί να οφείλεται σε πρόβλημα αντίστροφης μεσολάβησης ή ρύθμισης παραμέτρων SSL. Ελέγξτε τα αρχεία καταγραφής διακομιστή.",
"websocketError": "Σφάλμα σύνδεσης WebSocket"
},
"terminalToolbar": {
"detachTmux": "Αποσύνδεση tmux",
"detachTmuxDescription": "Αποσύνδεση συνεδρίας tmux",
"dismissError": "Σφάλμα απόρριψης εικόνας",
"hideToolbar": "Απόκρυψη γραμμής εργαλείων",
"image": "Ενέργειες εικόνας (μόνο εικόνες)",
"imageActionFailed": "Η ενέργεια εικόνας απέτυχε",
"layout": "Λειτουργία εμφάνισης γραμμής εργαλείων",
"layoutExpanded": "Επεκτάθηκε με μετρήσεις",
"layoutIcon": "Μόνο εικονίδιο",
"layoutLabeled": "Εικονίδια και ετικέτες",
"moveToolbar": "Μετακίνηση γραμμής εργαλείων",
"moveToolbarHint": "Σύρετε για να αλλάξετε τη θέση της γραμμής εργαλείων",
"paste": "Επικόλληση εικόνας",
"pasteImage": "Επικόλληση εικόνας από το πρόχειρο (μόνο εικόνες)",
"retry": "Δοκιμάζω πάλι",
"showToolbar": "Ανάπτυξη γραμμής εργαλείων",
"upload": "Μεταφόρτωση εικόνας",
"uploadImage": "Μεταφορτώστε μια εικόνα στον κεντρικό υπολογιστή",
"uploadingImage": "Μεταφόρτωση..."
},
"history": {
"noTerminalSelected": "Δεν έχει επιλεγεί τερματικό",
"noTerminalSelectedHint": "Ανοίξτε μια καρτέλα τερματικού SSH για να δείτε το ιστορικό εντολών του",
"searchPlaceholder": "Ιστορικό αναζήτησης...",
"clearAll": "Εκκαθάριση όλων",
"noHistoryEntries": "Δεν υπάρχουν καταχωρήσεις ιστορικού",
"trackingDisabled": "Η παρακολούθηση ιστορικού είναι απενεργοποιημένη"
},
"touchInput": {
"enabled": "Ενεργοποίηση εισαγωγής αφής",
"enabledDesc": "Επιτρέψτε την κύλιση με ένα δάχτυλο σε τερματικά",
"momentumEnabled": "Ενεργοποίηση ορμής",
"momentumEnabledDesc": "Συνεχίστε σύντομα την κύλιση μετά από μια σάρωση",
"advanced": "Προχωρημένος",
"resetDefaults": "Επαναφορά στις προεπιλογές",
"dragThreshold": "Όριο οπισθέλκουσας",
"maxWheelDelta": "Μέγιστο δέλτα τροχού",
"momentumSampleWindow": "Παράθυρο δείγματος ορμής",
"releaseGracePeriod": "Περίοδος χάριτος αποδέσμευσης",
"minimumVelocity": "Ελάχιστη ταχύτητα",
"maximumVelocity": "Μέγιστη ταχύτητα",
"maximumDuration": "Μέγιστη διάρκεια",
"maximumTravel": "Μέγιστη διαδρομή",
"decayTime": "Χρόνος αποσύνθεσης",
"pixelsPerTick": "Εικονοστοιχεία ανά τικ",
"maximumFrameInterval": "Μέγιστο διάστημα καρέ",
"maximumTicksPerFrame": "Μέγιστος αριθμός τικ ανά καρέ"
},
"imageStorage": {
"instanceId": "Αναγνωριστικό παρουσίας περιόδου λειτουργίας τερματικού",
"instanceIdDesc": "Αναγνωριστικό παρουσίας μίας από τις συνδεδεμένες καρτέλες τερματικού σας, που χρησιμοποιείται για τον έλεγχο της ορατότητας.",
"selectedModeUnavailable": "Δυσεύρετος",
"test": "Δοκιμή",
"testing": "Δοκιμές...",
"testFailed": "Αποτυχία δοκιμής χώρου αποθήκευσης εικόνων",
"testConnected": "Η συνεδρία τερματικού είναι συνδεδεμένη.",
"testNotConnected": "Δεν υπάρχει συνδεδεμένη συνεδρία τερματικού με αυτό το αναγνωριστικό παρουσίας.",
"testSelectedMode": "Μια μεταφόρτωση θα χρησιμοποιούσε: {{mode}}",
"testLocalVisible": "Ο τοπικός κατάλογος είναι ορατός από τον συνδεδεμένο κεντρικό υπολογιστή.",
"testLocalNotVisible": "Ο τοπικός κατάλογος ΔΕΝ είναι ορατός από τον συνδεδεμένο κεντρικό υπολογιστή."
},
"settings": {
"admin": {
"group": {
"touch": "Εισαγωγή με άγγιγμα",
"images": "Αποθήκευση εικόνων"
},
"sessionTimeoutMinutes": {
"label": "Διατήρηση Συνεδρίας Τερματικού",
"description": "Για πόσο χρονικό διάστημα διατηρείται ενεργό ένα αποσυνδεδεμένο τερματικό στον διακομιστή πριν κλείσει. Ελάχιστο 1 · Μέγιστο 1440 λεπτά. Για συνεδρίες που πρέπει να διαρκέσουν περισσότερο από αυτό, ενεργοποιήστε το Auto-Tmux στον κεντρικό υπολογιστή."
},
"commandHistoryEnabled": {
"label": "Ιστορικό εντολών",
"description": "Να επιτρέπεται η καταγραφή ιστορικού εντολών. Όταν είναι απενεργοποιημένη, το ιστορικό δεν αποθηκεύεται ανεξάρτητα από τις ρυθμίσεις ανά κεντρικό υπολογιστή."
},
"imageStorageMode": {
"label": "Λειτουργία αποθήκευσης",
"description": "Πού εγγράφονται επικολλημένες και μεταφορτωμένες εικόνες τερματικού. Οι αυτόματες επιλογές ανά σύνδεση, Τοπικές και Απομακρυσμένες (SFTP) έχουν διορθωθεί.",
"auto": "Αυτο",
"local": "Τοπικός",
"remoteSftp": "Απομακρυσμένο (SFTP)"
},
"imageLocalDir": {
"label": "Τοπικός κατάλογος"
},
"imageHostPath": {
"label": "Διαδρομή κεντρικού υπολογιστή",
"description": "Η προβολή από την πλευρά του κεντρικού υπολογιστή του τοπικού καταλόγου που λαμβάνουν οι πράκτορες τερματικού."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Μέγιστος αριθμός εικόνων"
},
"imageMaxBytes": {
"label": "Μέγιστος αριθμός byte αποθήκευσης"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Ενεργοποίηση γραμμής εργαλείων τερματικού",
"description": "Εμφάνιση μιας γραμμής εργαλείων γρήγορης πρόσβασης συνδεδεμένης στο ενεργό τερματικό για τα εργαλεία και τα ζωντανά στατιστικά αυτού του κεντρικού υπολογιστή"
},
"enableCommandHistory": {
"label": "Ιστορικό εντολών",
"description": "Οι εντολές εγγραφής εκτελούνται σε αυτό το τερματικό για ιστορικό και αυτόματη συμπλήρωση"
},
"enableTerminal": {
"label": "Ενεργοποίηση τερματικού"
}
}
},
"palette": {
"localTerminal": "Τοπικό τερματικό"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Añadir",
"arrowDown": "Flecha abajo",
"arrowLeft": "Flecha izquierda",
"arrowRight": "Flecha derecha",
"arrowUp": "Flecha arriba",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Supr",
"done": "Hecho",
"editQuickKeys": "Editar teclas rápidas",
"end": "Fin",
"esc": "Esc",
"home": "Inicio",
"pageDown": "Re Pág",
"pageUp": "Av Pág",
"paste": "Pasta",
"quickKeyPlaceholder": "p. ej. sudo ",
"quickKeysDesc": "Toca × para eliminar. Admite hasta 8 caracteres.",
"quickKeysTitle": "Teclas rápidas",
"removeQuickKey": "Eliminar",
"resetDefaults": "Restablecer",
"shift": "Mayús",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "No se pudo habilitar Auto-Tmux.",
"autoTmuxEnabled": "Auto-Tmux habilitado para {{host}}. Se aplica a partir de la siguiente conexión.",
"clipboardHttpWarning": "No se pudo leer el portapapeles. Algunos navegadores restringen el acceso al portapapeles fuera de HTTPS; intente usar Ctrl+V o ejecute Termix a través de HTTPS.",
"closeTab": "Cerrar",
"connecting": "Conectando...",
"connectionLost": "Conexión perdida",
"connectionRejected": "Conexión rechazada por el servidor. Revisa tu autenticación y configuración de red.",
"connectionTimeout": "Timeout de conexión",
"enableAutoTmuxAction": "Habilitar Auto-Tmux",
"hostKeyRejected": "Verificación de clave de host SSH rechazada. Conexión cancelada.",
"linkDialogCopy": "Copiar",
"linkDialogOpen": "Abrir",
"linkDialogTitle": "Abrir enlace",
"maxReconnectAttemptsReached": "Número máximo de intentos de reconexión alcanzado",
"noHostSelected": "Ningún host seleccionado",
"opksshAuthFailed": "La autenticación falló. Revisa tus credenciales e inténtalo de nuevo.",
"opksshTimeout": "La autenticación expiró. Inténtalo de nuevo.",
"passwordPromptFillTitle": "¿Rellenar este campo con la contraseña guardada?",
"reconnect": "Reconectar",
"reconnected": "Reconectado correctamente",
"reconnecting": "Reconectando... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Caja de cerillas",
"searchClose": "Cerrar (Escape)",
"searchNext": "Siguiente partido (Entrar)",
"searchNoResults": "No hay resultados",
"searchPlaceholder": "Encontrar",
"searchPrevious": "Coincidencia anterior (Shift+Enter)",
"searchRegex": "Utilizar expresiones regulares",
"searchResultCount": "{{index}} de {{count}}",
"searchWholeWord": "Emparejar palabra completa",
"sessionExpiredNotice": "Tu sesión anterior en {{host}} expiró después de {{minutes}} minutos desconectada; la consola y todo lo que se ejecutaba en ella se han perdido. Habilita Auto-Tmux en este host para mantener las sesiones activas.",
"sessionExpiredNoticeNoMinutes": "Tu sesión anterior en {{host}} caducó mientras estabas desconectado; la consola y todo lo que se ejecutaba en ella se han perdido. Habilita Auto-Tmux en este host para mantener las sesiones activas.",
"sessionTakenOver": "La sesión se abrió en otra pestaña. Reconectando...",
"tmuxAttached": "Clientes adjuntos",
"tmuxAttachedCount": "{{count}} adjunto",
"tmuxCopyHint": "Ajuste la selección y pulse Intro para copiar al portapapeles",
"tmuxCreateNew": "Iniciar nueva sesión",
"tmuxDetached": "Desconectado de la sesión tmux",
"tmuxLastActivity": "Última actividad",
"tmuxSessionAttached": "Sesión tmux adjuntada: {{name}}",
"tmuxSessionCreated": "Sesión tmux creada: {{name}}",
"tmuxSessionPickerDesc": "Se encontraron sesiones tmux existentes en este host. Seleccione una para readjuntar o cree una nueva sesión.",
"tmuxSessionPickerTitle": "Sesiones tmux",
"tmuxTimeDays": "hace {{count}}d",
"tmuxTimeHours": "hace {{count}}h",
"tmuxTimeJustNow": "ahora mismo",
"tmuxTimeMinutes": "hace {{count}}m",
"tmuxUnavailable": "tmux no está instalado en el host remoto, recurriendo al intérprete de comandos estándar",
"tmuxWindowCount": "{{count}} ventana",
"tmuxWindows": "Ventanas",
"unknownError": "Se ha producido un error desconocido.",
"vaultAuthDescription": "Se ha abierto una ventana para iniciar sesión en HashiCorp Vault. Completa el inicio de sesión allí; esta conexión continuará automáticamente.",
"vaultAuthFailed": "La autenticación de Vault falló. Inténtalo de nuevo.",
"vaultAuthTitle": "Inicio de sesión en Vault requerido",
"vaultReopen": "Reabrir ventana de inicio de sesión",
"websocketAbnormalClose": "La conexión se cerró de forma inesperada. Esto puede deberse a un problema con el proxy inverso o la configuración SSL. Revisa los registros del servidor.",
"websocketError": "Error de conexión WebSocket"
},
"terminalToolbar": {
"detachTmux": "Desvincular tmux",
"detachTmuxDescription": "Desvincular sesión de tmux",
"dismissError": "Descartar error de imagen",
"hideToolbar": "Ocultar barra de herramientas",
"image": "Acciones de imagen (solo imágenes)",
"imageActionFailed": "La acción de la imagen falló.",
"layout": "Modo de visualización de la barra de herramientas",
"layoutExpanded": "Ampliado con métricas",
"layoutIcon": "Solo icono",
"layoutLabeled": "Iconos y etiquetas",
"moveToolbar": "Mover barra de herramientas",
"moveToolbarHint": "Arrastra para cambiar la posición de la barra de herramientas.",
"paste": "Pegar imagen",
"pasteImage": "Pegar imagen desde el portapapeles (solo imágenes)",
"retry": "Rever",
"showToolbar": "Expandir barra de herramientas",
"upload": "Subir imagen",
"uploadImage": "Sube una imagen al servidor.",
"uploadingImage": "Subiendo..."
},
"history": {
"noTerminalSelected": "Ningún terminal seleccionado",
"noTerminalSelectedHint": "Abra una pestaña de terminal SSH para ver su historial de comandos",
"searchPlaceholder": "Buscar en el historial...",
"clearAll": "Borrar todo",
"noHistoryEntries": "Sin entradas de historial",
"trackingDisabled": "El seguimiento del historial está desactivado"
},
"touchInput": {
"enabled": "Habilitar la entrada táctil",
"enabledDesc": "Permitir el desplazamiento táctil con un solo dedo en terminales.",
"momentumEnabled": "Habilitar el impulso",
"momentumEnabledDesc": "Continúa desplazándote brevemente después de deslizar el dedo.",
"advanced": "Avanzado",
"resetDefaults": "Restablecer valores predeterminados",
"dragThreshold": "Umbral de arrastre",
"maxWheelDelta": "Delta máximo de rueda",
"momentumSampleWindow": "Ventana de muestreo de Momentum",
"releaseGracePeriod": "Período de gracia de liberación",
"minimumVelocity": "Velocidad mínima",
"maximumVelocity": "Velocidad máxima",
"maximumDuration": "Duración máxima",
"maximumTravel": "Viaje máximo",
"decayTime": "Tiempo de decaimiento",
"pixelsPerTick": "Píxeles por tick",
"maximumFrameInterval": "Intervalo máximo de fotogramas",
"maximumTicksPerFrame": "Número máximo de ticks por fotograma"
},
"imageStorage": {
"instanceId": "ID de instancia de sesión de terminal",
"instanceIdDesc": "ID de instancia de una de las pestañas de terminal conectadas, utilizada para probar la visibilidad.",
"selectedModeUnavailable": "Indisponible",
"test": "Prueba",
"testing": "Pruebas...",
"testFailed": "No se pudo probar el almacenamiento de imágenes.",
"testConnected": "La sesión de terminal está conectada.",
"testNotConnected": "No existe ninguna sesión de terminal conectada con este ID de instancia.",
"testSelectedMode": "Una carga usaría: {{mode}}",
"testLocalVisible": "El directorio local es visible desde el host conectado.",
"testLocalNotVisible": "El directorio local NO es visible desde el host conectado."
},
"settings": {
"admin": {
"group": {
"touch": "Entrada táctil",
"images": "Almacenamiento de imágenes"
},
"sessionTimeoutMinutes": {
"label": "Persistencia de sesión terminal",
"description": "¿Cuánto tiempo permanece activa una terminal desconectada en el servidor antes de cerrarse? Mínimo 1 minuto · Máximo 1440 minutos. Para sesiones que deban durar más tiempo, active Auto-Tmux en el host."
},
"commandHistoryEnabled": {
"label": "Historial de comandos",
"description": "Permitir el registro del historial de comandos. Si se desactiva, el historial no se guarda independientemente de la configuración por host."
},
"imageStorageMode": {
"label": "Modo de almacenamiento",
"description": "Donde se escriben las imágenes de terminal pegadas y cargadas. La selección automática por conexión, local y remota (SFTP) es fija.",
"auto": "Auto",
"local": "Local",
"remoteSftp": "Remoto (SFTP)"
},
"imageLocalDir": {
"label": "Directorio local"
},
"imageHostPath": {
"label": "Ruta del host",
"description": "La vista del directorio local que reciben los agentes de terminal desde el lado del host."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Número máximo de imágenes"
},
"imageMaxBytes": {
"label": "Bytes de almacenamiento máximo"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Habilitar la barra de herramientas del terminal",
"description": "Mostrar una barra de herramientas de acceso rápido acoplada al terminal activo para las herramientas y estadísticas en tiempo real de este host."
},
"enableCommandHistory": {
"label": "Historial de comandos",
"description": "Registrar los comandos ejecutados en este terminal para el historial y autocompletado"
},
"enableTerminal": {
"label": "Activar terminal"
}
}
},
"palette": {
"localTerminal": "Terminal local"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Lisätä",
"arrowDown": "Nuoli alas",
"arrowLeft": "Nuoli vasemmalle",
"arrowRight": "Nuoli oikealle",
"arrowUp": "Nuoli ylös",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Poista",
"done": "Tehty",
"editQuickKeys": "Muokkaa pikanäppäimiä",
"end": "Loppu",
"esc": "Esc-näppäin",
"home": "Kotiin",
"pageDown": "PgDn",
"pageUp": "Sivu ylös",
"paste": "Liitä",
"quickKeyPlaceholder": "esim. sudo ",
"quickKeysDesc": "Poista napauttamalla ×. Tukee enintään 8 merkkiä.",
"quickKeysTitle": "Pikanäppäimet",
"removeQuickKey": "Poistaa",
"resetDefaults": "Palauta oletusasetukset",
"shift": "Siirtää",
"tab": "Välilehti"
},
"terminal": {
"autoTmuxEnableFailed": "Automaattisen Tmuxin käyttöönotto epäonnistui",
"autoTmuxEnabled": "Auto-Tmux käytössä kohteelle {{host}}. Se on voimassa seuraavasta yhteydestä alkaen.",
"clipboardHttpWarning": "Leikepöydän lukeminen epäonnistui. Jotkin selaimet rajoittavat leikepöydän käyttöä HTTPS-yhteyden ulkopuolella – kokeile Ctrl+V-näppäinyhdistelmää tai käytä Termixiä HTTPS-yhteyden kautta.",
"closeTab": "Lähellä",
"connecting": "Yhdistetään...",
"connectionLost": "Yhteys katkesi",
"connectionRejected": "Palvelin hylkäsi yhteyden. Tarkista todennus ja verkkoasetukset.",
"connectionTimeout": "Yhteyden aikakatkaisu",
"enableAutoTmuxAction": "Ota automaattinen Tmux käyttöön",
"hostKeyRejected": "SSH-isännän avaimen vahvistus hylättiin. Yhteys katkaistu.",
"linkDialogCopy": "Kopioida",
"linkDialogOpen": "Avata",
"linkDialogTitle": "Avaa linkki",
"maxReconnectAttemptsReached": "Yhteyden muodostamisen uudelleenyritysten enimmäismäärä saavutettu",
"noHostSelected": "Ei valittua isäntää",
"opksshAuthFailed": "Todennus epäonnistui. Tarkista tunnistetietosi ja yritä uudelleen.",
"opksshTimeout": "Todennus aikakatkaistiin. Yritä uudelleen.",
"passwordPromptFillTitle": "Täytä tallennettu salasana tähän kehotteeseen?",
"reconnect": "Yhdistä uudelleen",
"reconnected": "Yhdistäminen uudelleen onnistui",
"reconnecting": "Yhdistetään uudelleen... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Ottelukotelo",
"searchClose": "Sulje (Esc)",
"searchNext": "Seuraava osuma (Enter)",
"searchNoResults": "Ei tuloksia",
"searchPlaceholder": "Löytää",
"searchPrevious": "Edellinen osuma (Vaihto+Enter)",
"searchRegex": "Käytä säännöllistä lauseketta",
"searchResultCount": "{{index}} / {{count}}",
"searchWholeWord": "Koko sanan täsmäys",
"sessionExpiredNotice": "Edellinen istuntosi kohdassa {{host}} päättyi {{minutes}} minuutin katkoksen jälkeen; komentotulkki ja kaikki siinä käynnissä oleva on poistettu. Ota automaattinen Tmux käyttöön tällä koneella pitääksesi istunnot toiminnassa.",
"sessionExpiredNoticeNoMinutes": "Edellinen istuntosi koneella {{host}} päättyi yhteyden katkettua; komentotulkki ja kaikki siinä käynnissä oleva on poissa. Ota automaattinen Tmux käyttöön tällä koneella pitääksesi istunnot toiminnassa.",
"sessionTakenOver": "Istunto avattiin toisella välilehdellä. Yhdistetään uudelleen...",
"tmuxAttached": "Liitetyt asiakkaat",
"tmuxAttachedCount": "{{count}} liitteenä",
"tmuxCopyHint": "Säädä valintaa ja kopioi leikepöydälle painamalla Enter",
"tmuxCreateNew": "Aloita uusi istunto",
"tmuxDetached": "Irrotettu tmux-istunnosta",
"tmuxLastActivity": "Viimeisin aktiviteetti",
"tmuxSessionAttached": "tmux-istunto liitetty: {{name}}",
"tmuxSessionCreated": "tmux-istunto luotu: {{name}}",
"tmuxSessionPickerDesc": "Tältä isännältä löytyi olemassa olevia tmux-istuntoja. Valitse yksi liittääksesi sen uudelleen tai luodaksesi uuden istunnon.",
"tmuxSessionPickerTitle": "tmux-istunnot",
"tmuxTimeDays": "{{count}}d sitten",
"tmuxTimeHours": "{{count}}tuntia sitten",
"tmuxTimeJustNow": "juuri nyt",
"tmuxTimeMinutes": "{{count}}minuuttia sitten",
"tmuxUnavailable": "tmuxia ei ole asennettu etäkoneeseen, vaan käytetään vakiokuorta.",
"tmuxWindowCount": "{{count}} ikkuna",
"tmuxWindows": "Ikkunat",
"unknownError": "Tuntematon virhe tapahtui",
"vaultAuthDescription": "Ikkuna HashiCorp-holviin kirjautumista varten on avautunut. Kirjaudu sisään siinä. Yhteys jatkuu automaattisesti.",
"vaultAuthFailed": "Holvin todennus epäonnistui. Yritä uudelleen.",
"vaultAuthTitle": "Holviin kirjautuminen vaaditaan",
"vaultReopen": "Avaa kirjautumisikkuna uudelleen",
"websocketAbnormalClose": "Yhteys katkesi odottamatta. Tämä voi johtua käänteisestä välityspalvelimesta tai SSL-määritysongelmasta. Tarkista palvelimen lokit.",
"websocketError": "WebSocket-yhteysvirhe"
},
"terminalToolbar": {
"detachTmux": "Irrota tmux",
"detachTmuxDescription": "Irrota tmux-istunto",
"dismissError": "Hylkää kuvavirhe",
"hideToolbar": "Piilota työkalupalkki",
"image": "Kuvatoiminnot (vain kuvat)",
"imageActionFailed": "Kuvatoiminto epäonnistui",
"layout": "Työkalurivin näyttötila",
"layoutExpanded": "Laajennettu mittareilla",
"layoutIcon": "Vain kuvake",
"layoutLabeled": "Kuvakkeet ja otsikot",
"moveToolbar": "Siirrä työkalupalkkia",
"moveToolbarHint": "Vedä työkalupalkin sijaintia",
"paste": "Liitä kuva",
"pasteImage": "Liitä kuva leikepöydältä (vain kuvat)",
"retry": "Yritä uudelleen",
"showToolbar": "Laajenna työkalupalkkia",
"upload": "Lataa kuva",
"uploadImage": "Lataa kuva isännöintipalveluun",
"uploadingImage": "Ladataan..."
},
"history": {
"noTerminalSelected": "Ei valittua päätettä",
"noTerminalSelectedHint": "Avaa SSH-päätevälilehti nähdäksesi sen komentohistorian",
"searchPlaceholder": "Hakuhistoria...",
"clearAll": "Tyhjennä kaikki",
"noHistoryEntries": "Ei historiamerkintöjä",
"trackingDisabled": "Historian seuranta on poistettu käytöstä"
},
"touchInput": {
"enabled": "Ota kosketussyöttö käyttöön",
"enabledDesc": "Salli yhden sormen kosketusvieritys päätteissä",
"momentumEnabled": "Ota vauhti käyttöön",
"momentumEnabledDesc": "Jatka vierittämistä lyhyesti pyyhkäisyn jälkeen",
"advanced": "Edistynyt",
"resetDefaults": "Palauta oletusasetukset",
"dragThreshold": "Vetokynnys",
"maxWheelDelta": "Suurin pyörän delta",
"momentumSampleWindow": "Momentumin näyteikkuna",
"releaseGracePeriod": "Vapautuksen lisäaika",
"minimumVelocity": "Miniminopeus",
"maximumVelocity": "Suurin nopeus",
"maximumDuration": "Enimmäiskesto",
"maximumTravel": "Maksimaalinen liikerata",
"decayTime": "Hajoamisaika",
"pixelsPerTick": "Pikseleitä rastia kohden",
"maximumFrameInterval": "Suurin kehysväli",
"maximumTicksPerFrame": "Maksimi tikit kehystä kohden"
},
"imageStorage": {
"instanceId": "Pääteistunnon instanssin tunnus",
"instanceIdDesc": "Yhdistetyn päätevälilehden instanssi-ID, jota käytetään näkyvyyden testaamiseen.",
"selectedModeUnavailable": "Ei saatavilla",
"test": "Testata",
"testing": "Testaus...",
"testFailed": "Kuvan tallennuksen testaus epäonnistui",
"testConnected": "Pääteistunto on yhdistetty.",
"testNotConnected": "Tällä instanssi-ID:llä ei ole yhdistettyä pääteistuntoa.",
"testSelectedMode": "Lataus käyttäisi seuraavaa: {{mode}}",
"testLocalVisible": "Paikallinen hakemisto on näkyvissä yhdistetyssä isännässä.",
"testLocalNotVisible": "Paikallinen hakemisto EI ole näkyvissä yhdistetyssä isännässä."
},
"settings": {
"admin": {
"group": {
"touch": "Kosketussyöttö",
"images": "Kuvien tallennus"
},
"sessionTimeoutMinutes": {
"label": "Pääteistunnon pysyvyys",
"description": "Kuinka kauan irrotettua päätelaitetta pidetään toiminnassa palvelimella ennen sen sulkemista. Min 1 · Max 1440 minuuttia. Jos istunnon on kestettävä tätä pidempään, ota automaattinen Tmux käyttöön isännässä."
},
"commandHistoryEnabled": {
"label": "Komentohistoria",
"description": "Salli komentohistorian tallennus. Kun tämä on poistettu käytöstä, historiaa ei tallenneta isäntäkohtaisista asetuksista riippumatta."
},
"imageStorageMode": {
"label": "Tallennustila",
"description": "Mihin liitetyt ja ladatut päätekuvat kirjoitetaan. Automaattiset valinnat yhteyden, paikallisen ja etäyhteyden (SFTP) mukaan on korjattu.",
"auto": "Auto",
"local": "Paikallinen",
"remoteSftp": "Etäkäyttö (SFTP)"
},
"imageLocalDir": {
"label": "Paikallinen hakemisto"
},
"imageHostPath": {
"label": "Isäntäpolku",
"description": "Pääteagenttien vastaanottaman paikallisen hakemiston isäntäpuolen näkymä."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Kuvien enimmäismäärä"
},
"imageMaxBytes": {
"label": "Tallennustilan enimmäismäärä tavuja"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Ota käyttöön päätetyökalurivi",
"description": "Näytä aktiiviseen terminaaliin kiinnitetty pikakäyttöinen työkalupalkki tämän isännän työkaluille ja reaaliaikaisille tilastoille"
},
"enableCommandHistory": {
"label": "Komentohistoria",
"description": "Tallenna tässä terminaalissa suoritettavat komennot historiaa ja automaattista täydennystä varten"
},
"enableTerminal": {
"label": "Ota terminaali käyttöön"
}
}
},
"palette": {
"localTerminal": "Paikallinen terminaali"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Ajouter",
"arrowDown": "Flèche bas",
"arrowLeft": "Flèche gauche",
"arrowRight": "Flèche droite",
"arrowUp": "Flèche haut",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Suppr",
"done": "Terminé",
"editQuickKeys": "Modifier les touches rapides",
"end": "Fin",
"esc": "Échap",
"home": "Début",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Coller",
"quickKeyPlaceholder": "ex. sudo ",
"quickKeysDesc": "Appuyez sur × pour supprimer. Prend en charge jusqu’à 8 caractères.",
"quickKeysTitle": "Touches rapides",
"removeQuickKey": "Supprimer",
"resetDefaults": "Rétablir les paramètres par défaut",
"shift": "Maj",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Échec de l'activation d'Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux activé pour {{host}}. Cela s'appliquera à partir de la prochaine connexion.",
"clipboardHttpWarning": "Impossible de lire le contenu du presse-papiers. Certains navigateurs restreignent l'accès au presse-papiers en dehors du protocole HTTPS ; essayez plutôt Ctrl+V ou utilisez Termix via HTTPS.",
"closeTab": "Fermer",
"connecting": "Connexion en cours...",
"connectionLost": "Connexion perdue",
"connectionRejected": "Connexion rejetée par le serveur. Veuillez vérifier votre authentification et votre configuration réseau.",
"connectionTimeout": "Délai de connexion dépassé",
"enableAutoTmuxAction": "Activer Auto-Tmux",
"hostKeyRejected": "Vérification de la clé de l'hôte SSH rejetée. Connexion annulée.",
"linkDialogCopy": "Copier",
"linkDialogOpen": "Ouvrir",
"linkDialogTitle": "Ouvrir le lien",
"maxReconnectAttemptsReached": "Nombre maximal de tentatives de reconnexion atteint",
"noHostSelected": "Aucun hôte sélectionné",
"opksshAuthFailed": "Échec de l'authentification. Veuillez vérifier vos identifiants et réessayer.",
"opksshTimeout": "L'authentification a expiré. Veuillez réessayer.",
"passwordPromptFillTitle": "Remplir ce champ avec le mot de passe enregistré ?",
"reconnect": "Reconnecter",
"reconnected": "Reconnexion réussie",
"reconnecting": "Reconnexion... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Boîte d'allumettes",
"searchClose": "Fermer (Échap)",
"searchNext": "Prochain match (Entrée)",
"searchNoResults": "Aucun résultat",
"searchPlaceholder": "Trouver",
"searchPrevious": "Match précédent (Maj+Entrée)",
"searchRegex": "Utilisez une expression régulière",
"searchResultCount": "{{index}} de {{count}}",
"searchWholeWord": "Associer le mot entier",
"sessionExpiredNotice": "Votre session précédente sur {{host}} a expiré après {{minutes}} minutes de déconnexion ; le shell et tous les processus en cours d'exécution ont été supprimés. Activez Auto-Tmux sur cet hôte pour maintenir les sessions actives.",
"sessionExpiredNoticeNoMinutes": "Votre session précédente sur {{host}} a expiré pendant la déconnexion ; le shell et tous les processus en cours d'exécution ont été supprimés. Activez Auto-Tmux sur cet hôte pour maintenir les sessions actives.",
"sessionTakenOver": "La session a été ouverte dans un autre onglet. Reconnexion en cours...",
"tmuxAttached": "Clients attachés",
"tmuxAttachedCount": "{{count}} attaché(s)",
"tmuxCopyHint": "Ajustez la sélection et appuyez sur Entrée pour copier dans le presse-papiers",
"tmuxCreateNew": "Créer une nouvelle session",
"tmuxDetached": "Détaché de la session tmux",
"tmuxLastActivity": "Dernière activité",
"tmuxSessionAttached": "Session tmux attachée : {{name}}",
"tmuxSessionCreated": "Session tmux créée : {{name}}",
"tmuxSessionPickerDesc": "Des sessions tmux existantes ont été trouvées sur cet hôte. Sélectionnez-en une pour vous y rattacher ou créez une nouvelle session.",
"tmuxSessionPickerTitle": "Sessions tmux",
"tmuxTimeDays": "il y a {{count}} j",
"tmuxTimeHours": "il y a {{count}} h",
"tmuxTimeJustNow": "à l'instant",
"tmuxTimeMinutes": "il y a {{count}} min",
"tmuxUnavailable": "tmux n'est pas installé sur l'hôte distant, retour au shell standard",
"tmuxWindowCount": "{{count}} fenêtre(s)",
"tmuxWindows": "Fenêtres",
"unknownError": "Une erreur inconnue s'est produite",
"vaultAuthDescription": "Une fenêtre s'est ouverte pour vous connecter à HashiCorp Vault. Terminez la connexion dans cette fenêtre ; cette connexion continuera automatiquement.",
"vaultAuthFailed": "Échec de l'authentification Vault. Veuillez réessayer.",
"vaultAuthTitle": "Connexion à Vault requise",
"vaultReopen": "Rouvrir la fenêtre de connexion",
"websocketAbnormalClose": "Connexion fermée de manière inattendue. Cela peut être dû à un problème de proxy inverse ou de configuration SSL. Veuillez vérifier les journaux du serveur.",
"websocketError": "Erreur de connexion WebSocket"
},
"terminalToolbar": {
"detachTmux": "Détacher tmux",
"detachTmuxDescription": "Détacher la session tmux",
"dismissError": "Erreur d'image",
"hideToolbar": "Masquer la barre d'outils",
"image": "Actions sur les images (images uniquement)",
"imageActionFailed": "L'action sur l'image a échoué",
"layout": "mode d'affichage de la barre d'outils",
"layoutExpanded": "Développé avec des indicateurs",
"layoutIcon": "Icône uniquement",
"layoutLabeled": "Icônes et étiquettes",
"moveToolbar": "Déplacer la barre d'outils",
"moveToolbarHint": "Faites glisser pour repositionner la barre d'outils",
"paste": "Coller l'image",
"pasteImage": "Coller une image depuis le presse-papiers (images uniquement)",
"retry": "Réessayer",
"showToolbar": "Barre d'outils Développer",
"upload": "Télécharger une image",
"uploadImage": "Téléversez une image sur l'hôte",
"uploadingImage": "Chargement en cours..."
},
"history": {
"noTerminalSelected": "Aucun terminal sélectionné",
"noTerminalSelectedHint": "Ouvrez un onglet de terminal SSH pour voir son historique de commandes",
"searchPlaceholder": "Rechercher dans l'historique...",
"clearAll": "Tout effacer",
"noHistoryEntries": "Aucune entrée d'historique",
"trackingDisabled": "Le suivi de l'historique est désactivé"
},
"touchInput": {
"enabled": "Activer l'entrée tactile",
"enabledDesc": "Autoriser le défilement tactile à un doigt dans les terminaux",
"momentumEnabled": "Activer l'élan",
"momentumEnabledDesc": "Continuez à faire défiler brièvement après un glissement.",
"advanced": "Avancé",
"resetDefaults": "Réinitialiser les paramètres par défaut",
"dragThreshold": "Seuil de glissement",
"maxWheelDelta": "Delta de roue maximal",
"momentumSampleWindow": "fenêtre d'échantillonnage de l'impulsion",
"releaseGracePeriod": "période de grâce pour la libération",
"minimumVelocity": "Vitesse minimale",
"maximumVelocity": "Vitesse maximale",
"maximumDuration": "Durée maximale",
"maximumTravel": "Voyage maximal",
"decayTime": "Temps de dégradation",
"pixelsPerTick": "Pixels par tick",
"maximumFrameInterval": "Intervalle d'images maximal",
"maximumTicksPerFrame": "Nombre maximal de ticks par image"
},
"imageStorage": {
"instanceId": "ID d'instance de session du terminal",
"instanceIdDesc": "ID d'instance de l'un de vos onglets de terminal connectés, utilisé pour tester la visibilité.",
"selectedModeUnavailable": "Indisponible",
"test": "Test",
"testing": "Essai...",
"testFailed": "Échec du test du stockage d'images",
"testConnected": "La session terminal est connectée.",
"testNotConnected": "Aucune session de terminal connectée avec cet ID d'instance.",
"testSelectedMode": "Un téléchargement utiliserait : {{mode}}",
"testLocalVisible": "Le répertoire local est visible depuis l'hôte connecté.",
"testLocalNotVisible": "Le répertoire local n'est PAS visible depuis l'hôte connecté."
},
"settings": {
"admin": {
"group": {
"touch": "Entrée tactile",
"images": "Stockage d'images"
},
"sessionTimeoutMinutes": {
"label": "Persistance de session terminale",
"description": "Durée de vie d'un terminal déconnecté sur le serveur avant sa fermeture. Minimum : 1 minute ; maximum : 1 440 minutes. Pour les sessions qui doivent durer plus longtemps, activez Auto-Tmux sur l'hôte."
},
"commandHistoryEnabled": {
"label": "Historique des commandes",
"description": "Autoriser l'enregistrement de l'historique des commandes. Lorsque désactivé, l'historique n'est pas sauvegardé, indépendamment des paramètres par hôte."
},
"imageStorageMode": {
"label": "Mode de stockage",
"description": "Emplacement où sont écrites les images du terminal collées et téléchargées. La sélection automatique par connexion (locale et distante [SFTP]) est fixe.",
"auto": "Auto",
"local": "Locale",
"remoteSftp": "Accès distant (SFTP)"
},
"imageLocalDir": {
"label": "Répertoire local"
},
"imageHostPath": {
"label": "Chemin hôte",
"description": "Vue côté hôte du répertoire local reçu par les agents terminaux."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Nombre maximal d'images"
},
"imageMaxBytes": {
"label": "Capacité de stockage maximale"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Activer la barre d'outils du terminal",
"description": "Afficher une barre d'outils d'accès rapide ancrée au terminal actif pour les outils et les statistiques en direct de cet hôte."
},
"enableCommandHistory": {
"label": "Historique des commandes",
"description": "Enregistrer les commandes exécutées dans ce terminal pour l'historique et l'autocomplétion"
},
"enableTerminal": {
"label": "Activer le terminal"
}
}
},
"palette": {
"localTerminal": "Terminal local"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "לְהוֹסִיף",
"arrowDown": "חץ למטה",
"arrowLeft": "חץ שמאלה",
"arrowRight": "חץ ימינה",
"arrowUp": "חץ למעלה",
"backTab": "⇥",
"ctrl": "קונטרול",
"delete": "דל",
"done": "נַעֲשָׂה",
"editQuickKeys": "עריכת מקשי מהירים",
"end": "סוֹף",
"esc": "Esc",
"home": "בַּיִת",
"pageDown": "PgDn",
"pageUp": "עמוד למעלה",
"paste": "לְהַדבִּיק",
"quickKeyPlaceholder": "למשל סודו ",
"quickKeysDesc": "הקש על × כדי להסיר. תומך בעד 8 תווים.",
"quickKeysTitle": "מקשי מהירים",
"removeQuickKey": "לְהַסִיר",
"resetDefaults": "איפוס להגדרות ברירת מחדל",
"shift": "מִשׁמֶרֶת",
"tab": "טאב"
},
"terminal": {
"autoTmuxEnableFailed": "נכשל בהפעלת Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux מופעל עבור {{host}}. זה חל מהחיבור הבא.",
"clipboardHttpWarning": "לא ניתן היה לקרוא את הלוח. חלק מהדפדפנים מגבילים את הגישה ללוח מחוץ ל-HTTPS - נסה Ctrl+V במקום זאת, או שלח את Termix דרך HTTPS.",
"closeTab": "לִסְגוֹר",
"connecting": "מְקַשֵׁר...",
"connectionLost": "החיבור אבד",
"connectionRejected": "החיבור נדחה על ידי השרת. אנא בדוק את האימות ותצורת הרשת שלך.",
"connectionTimeout": "זמן קצוב לחיבור",
"enableAutoTmuxAction": "הפעל את Auto-Tmux",
"hostKeyRejected": "אימות מפתח מארח SSH נדחה. החיבור בוטל.",
"linkDialogCopy": "לְהַעְתִיק",
"linkDialogOpen": "לִפְתוֹחַ",
"linkDialogTitle": "פתח קישור",
"maxReconnectAttemptsReached": "הגעת למספר המקסימלי של ניסיונות חיבור מחדש",
"noHostSelected": "לא נבחר מארח",
"opksshAuthFailed": "האימות נכשל. אנא בדוק את פרטי הגישה שלך ונסה שוב.",
"opksshTimeout": "הזמן שהוקצב לאימות הסתיים. אנא נסה שוב.",
"passwordPromptFillTitle": "למלא את הסיסמה השמורה בשורת הפקודה הזו?",
"reconnect": "התחבר מחדש",
"reconnected": "התחבר מחדש בהצלחה",
"reconnecting": "מתחבר מחדש... ({{attempt}}/{{max}})",
"searchCaseSensitive": "מארז התאמה",
"searchClose": "סגור (Escape)",
"searchNext": "המשחק הבא (Enter)",
"searchNoResults": "אין תוצאות",
"searchPlaceholder": "לִמצוֹא",
"searchPrevious": "התאמה קודמת (Shift+Enter)",
"searchRegex": "השתמש בביטוי רגולרי",
"searchResultCount": "{{index}} מתוך {{count}}",
"searchWholeWord": "התאמת מילה שלמה",
"sessionExpiredNotice": "הסשן הקודם שלך ב- {{host}} פג תוקפו לאחר {{minutes}} דקות של ניתוק; המעטפת וכל מה שפועל בה נעלם. הפעל את Auto-Tmux במארח זה כדי לשמור על הסשנים פעילים.",
"sessionExpiredNoticeNoMinutes": "הסשן הקודם שלך ב- {{host}} פג תוקפו בזמן ניתוק; המעטפת וכל מה שפועל בה נעלם. הפעל את Auto-Tmux במארח זה כדי לשמור על הסשנים פעילים.",
"sessionTakenOver": "הסשן נפתח בכרטיסייה אחרת. מתחבר מחדש...",
"tmuxAttached": "לקוחות מצורפים",
"tmuxAttachedCount": "{{count}} מצורף",
"tmuxCopyHint": "התאם את הבחירה ולחץ על Enter כדי להעתיק ללוח",
"tmuxCreateNew": "התחל סשן חדש",
"tmuxDetached": "מנותק מסשן tmux",
"tmuxLastActivity": "פעילות אחרונה",
"tmuxSessionAttached": "סשן tmux מצורף: {{name}}",
"tmuxSessionCreated": "נוצרה סשן tmux: {{name}}",
"tmuxSessionPickerDesc": "נמצאו הפעלות tmux קיימות במארח זה. בחר אחת לחיבור מחדש או צור הפעלות חדשה.",
"tmuxSessionPickerTitle": "מפגשי tmux",
"tmuxTimeDays": "{{count}}d ago",
"tmuxTimeHours": "{{count}}h ago",
"tmuxTimeJustNow": "זֶה עַתָה",
"tmuxTimeMinutes": "לפני {{count}}דקות",
"tmuxUnavailable": "tmux אינו מותקן על המארח המרוחק, חוזר למעטפת סטנדרטית",
"tmuxWindowCount": "חלון {{count}}",
"tmuxWindows": "חלונות",
"unknownError": "אירעה שגיאה לא ידועה",
"vaultAuthDescription": "נפתח חלון לכניסה לכספת HashiCorp. השלם את תהליך הכניסה שם; חיבור זה ימשיך אוטומטית.",
"vaultAuthFailed": "אימות הכספת נכשל. אנא נסה שוב.",
"vaultAuthTitle": "נדרשת כניסה לכספת",
"vaultReopen": "פתח מחדש את חלון הכניסה",
"websocketAbnormalClose": "החיבור נסגר באופן בלתי צפוי. ייתכן שהדבר נובע מבעיית פרוקסי הפוך או מבעיית תצורת SSL. אנא בדוק את יומני השרת.",
"websocketError": "שגיאת חיבור WebSocket"
},
"terminalToolbar": {
"detachTmux": "ניתוק tmux",
"detachTmuxDescription": "ניתוק סשן tmux",
"dismissError": "סגור שגיאת תמונה",
"hideToolbar": "הסתר את סרגל הכלים",
"image": "פעולות תמונה (תמונות בלבד)",
"imageActionFailed": "פעולת התמונה נכשלה",
"layout": "מצב תצוגה של סרגל הכלים",
"layoutExpanded": "מורחב עם מדדים",
"layoutIcon": "סמל בלבד",
"layoutLabeled": "סמלים ותוויות",
"moveToolbar": "הזזת סרגל הכלים",
"moveToolbarHint": "גרור כדי לשנות את מיקום סרגל הכלים",
"paste": "הדבקת תמונה",
"pasteImage": "הדבקת תמונה מהלוח (תמונות בלבד)",
"retry": "נסה שוב",
"showToolbar": "הרחב את סרגל הכלים",
"upload": "העלאת תמונה",
"uploadImage": "העלה תמונה למארח",
"uploadingImage": "מעלה..."
},
"history": {
"noTerminalSelected": "לא נבחר טרמינל",
"noTerminalSelectedHint": "פתח כרטיסיית מסוף SSH כדי להציג את היסטוריית הפקודות שלה",
"searchPlaceholder": "היסטוריית חיפוש...",
"clearAll": "נקה הכל",
"noHistoryEntries": "אין רשומות היסטוריה",
"trackingDisabled": "מעקב אחר היסטוריה מושבת"
},
"touchInput": {
"enabled": "הפעל קלט מגע",
"enabledDesc": "אפשר גלילה באמצעות מגע באצבע אחת במסופים",
"momentumEnabled": "לאפשר מומנטום",
"momentumEnabledDesc": "המשך גלילה קצרה לאחר החלקה",
"advanced": "מִתקַדֵם",
"resetDefaults": "איפוס להגדרות ברירת מחדל",
"dragThreshold": "סף גרירה",
"maxWheelDelta": "דלתא גלגל מקסימלית",
"momentumSampleWindow": "חלון דוגמת מומנטום",
"releaseGracePeriod": "תקופת חסד לשחרור",
"minimumVelocity": "מהירות מינימלית",
"maximumVelocity": "מהירות מקסימלית",
"maximumDuration": "משך זמן מקסימלי",
"maximumTravel": "נסיעה מקסימלית",
"decayTime": "זמן דעיכה",
"pixelsPerTick": "פיקסלים לכל טיק",
"maximumFrameInterval": "מרווח פריימים מקסימלי",
"maximumTicksPerFrame": "מספר מקסימלי של טיקים למסגרת"
},
"imageStorage": {
"instanceId": "מזהה מופע של סשן מסוף",
"instanceIdDesc": "מזהה מופע של אחת מכרטיסיות הטרמינל המחוברות שלך, המשמש לבדיקת נראות.",
"selectedModeUnavailable": "לא זמין",
"test": "מִבְחָן",
"testing": "בּוֹחֵן...",
"testFailed": "נכשלה בדיקת אחסון התמונה",
"testConnected": "סשן הטרמינל מחובר.",
"testNotConnected": "אין סשן מסוף מחובר עם מזהה מופע זה.",
"testSelectedMode": "העלאה תשתמש ב: {{mode}}",
"testLocalVisible": "הספרייה המקומית גלויה מהמארח המחובר.",
"testLocalNotVisible": "הספרייה המקומית אינה גלויה מהמארח המחובר."
},
"settings": {
"admin": {
"group": {
"touch": "קלט מגע",
"images": "אחסון תמונות"
},
"sessionTimeoutMinutes": {
"label": "התמדה של סשן מסוף",
"description": "כמה זמן מסוף מנותק נשאר פעיל בשרת לפני שהוא נסגר. מינימום 1 · מקסימום 1440 דקות. עבור סשנים שחייבים לשרוד יותר מ-10 דקות, הפעל את Auto-Tmux במארח."
},
"commandHistoryEnabled": {
"label": "היסטוריית פקודות",
"description": "אפשר רישום היסטוריית פקודות. כאשר האפשרות מושבתת, ההיסטוריה לא נשמרת ללא קשר להגדרות של כל מחשב מארח."
},
"imageStorageMode": {
"label": "מצב אחסון",
"description": "היכן נכתבות תמונות טרמינל שהודבקו והועלו. בחירות אוטומטיות לפי חיבור, מקומי ומרוחק (SFTP) תוקנו.",
"auto": "אוטומטי",
"local": "מְקוֹמִי",
"remoteSftp": "מרחוק (SFTP)"
},
"imageLocalDir": {
"label": "ספרייה מקומית"
},
"imageHostPath": {
"label": "נתיב המארח",
"description": "תצוגת המארח של הספרייה המקומית שמקבלים סוכני הטרמינל."
},
"imageTtlMs": {
"label": "TTL (אלפיות שנייה)"
},
"imageMaxCount": {
"label": "ספירת תמונות מקסימלית"
},
"imageMaxBytes": {
"label": "מספר בייטים מקסימלי לאחסון"
}
},
"host": {
"enableTerminalToolbar": {
"label": "הפעלת סרגל הכלים של הטרמינל",
"description": "הצג סרגל כלים לגישה מהירה המעוגן למסוף הפעיל עבור הכלים והסטטיסטיקות החיות של מארח זה"
},
"enableCommandHistory": {
"label": "היסטוריית פקודות",
"description": "פקודות הקלטה פועלות בטרמינל זה לצורך היסטוריה והשלמה אוטומטית"
},
"enableTerminal": {
"label": "הפעלת טרמינל"
}
}
},
"palette": {
"localTerminal": "מסוף מקומי"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "जोड़ें",
"arrowDown": "नीचे तीर",
"arrowLeft": "बाएँ तीर",
"arrowRight": "दाएँ तीर",
"arrowUp": "ऊपर तीर",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "पूर्ण",
"editQuickKeys": "त्वरित कुंजियाँ संपादित करें",
"end": "एंड",
"esc": "Esc",
"home": "होम",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "पेस्ट करें",
"quickKeyPlaceholder": "उदा. sudo ",
"quickKeysDesc": "हटाने के लिए × पर टैप करें। अधिकतम 8 वर्ण समर्थित।",
"quickKeysTitle": "त्वरित कुंजियाँ",
"removeQuickKey": "हटाएँ",
"resetDefaults": "डिफ़ॉल्ट पर रीसेट करें",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "ऑटो-टीमक्स को सक्षम करने में विफल",
"autoTmuxEnabled": "{{host}}के लिए ऑटो-Tmux सक्षम किया गया है। यह अगले कनेक्शन से लागू होगा।",
"clipboardHttpWarning": "क्लिपबोर्ड पढ़ा नहीं जा सका। कुछ ब्राउज़र HTTPS के बाहर क्लिपबोर्ड एक्सेस को प्रतिबंधित करते हैं - इसके बजाय Ctrl+V का उपयोग करके देखें, या Termix को HTTPS पर चलाएं।",
"closeTab": "बंद करें",
"connecting": "कनेक्ट हो रहा है...",
"connectionLost": "कनेक्शन खो गया",
"connectionRejected": "सर्वर द्वारा कनेक्शन अस्वीकृत। कृपया अपनी प्रमाणीकरण और नेटवर्क कॉन्फ़िगरेशन जाँचें।",
"connectionTimeout": "कनेक्शन टाइमआउट",
"enableAutoTmuxAction": "ऑटो-टीमक्स सक्षम करें",
"hostKeyRejected": "SSH होस्ट कुंजी सत्यापन अस्वीकृत। कनेक्शन रद्द किया गया।",
"linkDialogCopy": "कॉपी करें",
"linkDialogOpen": "खोलें",
"linkDialogTitle": "लिंक खोलें",
"maxReconnectAttemptsReached": "अधिकतम पुनः कनेक्ट प्रयास पूर्ण हो गए",
"noHostSelected": "कोई होस्ट चयनित नहीं",
"opksshAuthFailed": "प्रमाणीकरण विफल। कृपया अपने क्रेडेंशियल जाँचें और पुनः प्रयास करें।",
"opksshTimeout": "प्रमाणीकरण का समय समाप्त हो गया। कृपया पुनः प्रयास करें।",
"passwordPromptFillTitle": "क्या सहेजे गए पासवर्ड को इस प्रांप्ट में भरें?",
"reconnect": "पुनः कनेक्ट करें",
"reconnected": "पुनः कनेक्ट हुआ",
"reconnecting": "पुनः कनेक्ट हो रहा है... ({{attempt}}/{{max}})",
"searchCaseSensitive": "मामले मिलाएं",
"searchClose": "बंद करें (एस्केप)",
"searchNext": "अगला मैच (प्रवेश करें)",
"searchNoResults": "कोई परिणाम नहीं",
"searchPlaceholder": "खोजो",
"searchPrevious": "पिछला मैच (शिफ्ट+एंटर)",
"searchRegex": "नियमित अभिव्यक्ति का उपयोग करें",
"searchResultCount": "{{index}} का {{count}}",
"searchWholeWord": "पूरे शब्द का मिलान करें",
"sessionExpiredNotice": "{{host}} पर आपका पिछला सत्र {{minutes}} मिनट बाद समाप्त हो गया; शेल और उसमें चल रही हर चीज़ समाप्त हो गई है। सत्रों को सक्रिय रखने के लिए इस होस्ट पर ऑटो-ट्मक्स सक्षम करें।",
"sessionExpiredNoticeNoMinutes": "{{host}} पर आपका पिछला सत्र डिस्कनेक्ट होने के दौरान समाप्त हो गया; शेल और उसमें चल रही हर चीज़ नष्ट हो गई है। सत्रों को सक्रिय रखने के लिए इस होस्ट पर ऑटो-ट्मक्स सक्षम करें।",
"sessionTakenOver": "सत्र किसी अन्य टैब में खोला गया था। पुनः कनेक्ट हो रहा है...",
"tmuxAttached": "अटैच किए गए क्लाइंट",
"tmuxAttachedCount": "{{count}} अटैच किए गए",
"tmuxCopyHint": "चयन समायोजित करें और क्लिपबोर्ड पर कॉपी करने के लिए Enter दबाएं",
"tmuxCreateNew": "नया सत्र प्रारंभ करें",
"tmuxDetached": "tmux सत्र से डिटैच हो गया",
"tmuxLastActivity": "अंतिम गतिविधि",
"tmuxSessionAttached": "tmux सत्र अटैच किया गया: {{name}}",
"tmuxSessionCreated": "tmux सत्र बनाया गया: {{name}}",
"tmuxSessionPickerDesc": "इस होस्ट पर मौजूदा tmux सत्र पाए गए। पुनः अटैच करने के लिए एक का चयन करें या नया सत्र बनाएं।",
"tmuxSessionPickerTitle": "tmux सत्र",
"tmuxTimeDays": "{{count}} दिन पहले",
"tmuxTimeHours": "{{count}} घंटे पहले",
"tmuxTimeJustNow": "अभी-अभी",
"tmuxTimeMinutes": "{{count}} मिनट पहले",
"tmuxUnavailable": "tmux रिमोट होस्ट पर स्थापित नहीं है, मानक शेल पर वापस लौट रहा है",
"tmuxWindowCount": "{{count}} विंडो",
"tmuxWindows": "विंडो",
"unknownError": "अज्ञात त्रुटि हुई",
"vaultAuthDescription": "HashiCorp Vault में साइन इन करने के लिए एक विंडो खुल गई है। वहाँ साइन-इन पूरा करें; यह कनेक्शन स्वचालित रूप से जारी रहेगा।",
"vaultAuthFailed": "Vault प्रमाणीकरण विफल। कृपया पुनः प्रयास करें।",
"vaultAuthTitle": "Vault साइन-इन आवश्यक है",
"vaultReopen": "साइन-इन विंडो फिर से खोलें",
"websocketAbnormalClose": "कनेक्शन अप्रत्याशित रूप से बंद हो गया। यह रिवर्स प्रॉक्सी या SSL कॉन्फ़िगरेशन समस्या के कारण हो सकता है। कृपया सर्वर लॉग जाँचें।",
"websocketError": "WebSocket कनेक्शन त्रुटि"
},
"terminalToolbar": {
"detachTmux": "tmux को अलग करें",
"detachTmuxDescription": "tmux सत्र को अलग करें",
"dismissError": "छवि त्रुटि को खारिज करें",
"hideToolbar": "उपकरण पट्टी छिपाओ",
"image": "छवि क्रियाएँ (केवल छवियाँ)",
"imageActionFailed": "छवि संबंधी कार्रवाई विफल रही",
"layout": "टूलबार डिस्प्ले मोड",
"layoutExpanded": "मैट्रिक्स के साथ विस्तारित",
"layoutIcon": "केवल आइकन",
"layoutLabeled": "आइकन और लेबल",
"moveToolbar": "टूलबार को स्थानांतरित करें",
"moveToolbarHint": "टूलबार की स्थिति बदलने के लिए उसे खींचें",
"paste": "छवि चिपकाएँ",
"pasteImage": "क्लिपबोर्ड से छवि पेस्ट करें (केवल छवियां)",
"retry": "पुन: प्रयास करें",
"showToolbar": "टूलबार का विस्तार करें",
"upload": "तस्विर अपलोड करना",
"uploadImage": "होस्ट पर एक छवि अपलोड करें",
"uploadingImage": "अपलोड हो रहा है..."
},
"history": {
"noTerminalSelected": "कोई टर्मिनल चयनित नहीं",
"noTerminalSelectedHint": "कमांड इतिहास देखने के लिए SSH टर्मिनल टैब खोलें",
"searchPlaceholder": "इतिहास खोजें...",
"clearAll": "सभी साफ़ करें",
"noHistoryEntries": "कोई इतिहास प्रविष्टियाँ नहीं",
"trackingDisabled": "इतिहास ट्रैकिंग अक्षम है"
},
"touchInput": {
"enabled": "टच इनपुट सक्षम करें",
"enabledDesc": "टर्मिनलों में एक उंगली से स्क्रॉल करने की सुविधा दें",
"momentumEnabled": "गति को सक्षम करें",
"momentumEnabledDesc": "स्वाइप करने के बाद थोड़ी देर स्क्रॉल करते रहें",
"advanced": "विकसित",
"resetDefaults": "डिफ़ॉल्ट पर पुनः सेट करें",
"dragThreshold": "ड्रैग थ्रेशहोल्ड",
"maxWheelDelta": "अधिकतम पहिया डेल्टा",
"momentumSampleWindow": "मोमेंटम सैंपल विंडो",
"releaseGracePeriod": "रिहाई की मोहलत",
"minimumVelocity": "न्यूनतम वेग",
"maximumVelocity": "अधिकतम वेग",
"maximumDuration": "अधिकतम अवधि",
"maximumTravel": "अधिकतम यात्रा",
"decayTime": "क्षय समय",
"pixelsPerTick": "पिक्सेल प्रति टिक",
"maximumFrameInterval": "अधिकतम फ्रेम अंतराल",
"maximumTicksPerFrame": "प्रति फ्रेम अधिकतम टिक"
},
"imageStorage": {
"instanceId": "टर्मिनल सत्र इंस्टेंस आईडी",
"instanceIdDesc": "आपके कनेक्टेड टर्मिनल टैब में से किसी एक का इंस्टेंस आईडी, जिसका उपयोग दृश्यता का परीक्षण करने के लिए किया जाता है।",
"selectedModeUnavailable": "अनुपलब्ध",
"test": "परीक्षा",
"testing": "परीक्षण...",
"testFailed": "इमेज स्टोरेज का परीक्षण विफल रहा",
"testConnected": "टर्मिनल सत्र कनेक्ट हो गया है।",
"testNotConnected": "इस इंस्टेंस आईडी के साथ कोई कनेक्टेड टर्मिनल सेशन नहीं है।",
"testSelectedMode": "अपलोड करने के लिए निम्न का उपयोग किया जाएगा: {{mode}}",
"testLocalVisible": "कनेक्टेड होस्ट से लोकल डायरेक्टरी दिखाई देती है।",
"testLocalNotVisible": "स्थानीय डायरेक्टरी कनेक्टेड होस्ट से दिखाई नहीं देती है।"
},
"settings": {
"admin": {
"group": {
"touch": "स्पर्श निवेश",
"images": "छवि संग्रहण"
},
"sessionTimeoutMinutes": {
"label": "टर्मिनल सत्र दृढ़ता",
"description": "सर्वर पर डिस्कनेक्ट होने के बाद टर्मिनल को बंद करने से पहले कितनी देर तक चालू रखा जाता है। न्यूनतम 1 मिनट · अधिकतम 1440 मिनट। इससे अधिक समय तक चलने वाले सेशन के लिए, होस्ट पर ऑटो-ट्मक्स को सक्षम करें।"
},
"commandHistoryEnabled": {
"label": "कमांड इतिहास",
"description": "कमांड इतिहास रिकॉर्डिंग की अनुमति दें। अक्षम होने पर, प्रति-होस्ट सेटिंग्स की परवाह किए बिना इतिहास सहेजा नहीं जाता है।"
},
"imageStorageMode": {
"label": "भंडारण मोड",
"description": "जहां पेस्ट और अपलोड की गई टर्मिनल इमेज लिखी जाती हैं। कनेक्शन के आधार पर ऑटो पिकिंग की सुविधा उपलब्ध है, लोकल और रिमोट (SFTP) कनेक्शन दोनों ही तय हैं।",
"auto": "ऑटो",
"local": "स्थानीय",
"remoteSftp": "रिमोट (एसएफटीपी)"
},
"imageLocalDir": {
"label": "स्थानीय निर्देशिका"
},
"imageHostPath": {
"label": "होस्ट पथ",
"description": "टर्मिनल एजेंटों को प्राप्त होने वाली स्थानीय निर्देशिका का होस्ट-साइड दृश्य।"
},
"imageTtlMs": {
"label": "टीटीएल (मिलीमीटर)"
},
"imageMaxCount": {
"label": "अधिकतम छवि संख्या"
},
"imageMaxBytes": {
"label": "अधिकतम संग्रहण बाइट्स"
}
},
"host": {
"enableTerminalToolbar": {
"label": "टर्मिनल टूलबार को सक्षम करें",
"description": "इस होस्ट के टूल्स और लाइव स्टैट्स के लिए सक्रिय टर्मिनल से जुड़ा एक त्वरित-पहुँच टूलबार दिखाएँ।"
},
"enableCommandHistory": {
"label": "कमांड इतिहास",
"description": "इतिहास और स्वतः पूर्ण के लिए इस टर्मिनल में चलाए गए कमांड रिकॉर्ड करें"
},
"enableTerminal": {
"label": "टर्मिनल सक्षम करें"
}
}
},
"palette": {
"localTerminal": "स्थानीय टर्मिनल"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Hozzáadás",
"arrowDown": "Lefelé mutató nyíl",
"arrowLeft": "Balra nyíl",
"arrowRight": "Jobbra nyíl",
"arrowUp": "Felfelé nyíl",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Kész",
"editQuickKeys": "Gyorsbillentyűk szerkesztése",
"end": "Vége",
"esc": "Esc",
"home": "Otthon",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Paszta",
"quickKeyPlaceholder": "pl. sudo ",
"quickKeysDesc": "Koppintson a × jelre az eltávolításhoz. Legfeljebb 8 karaktert támogat.",
"quickKeysTitle": "Gyorsbillentyűk",
"removeQuickKey": "Eltávolítás",
"resetDefaults": "Alapértelmezett beállítások visszaállítása",
"shift": "Váltás",
"tab": "Fül"
},
"terminal": {
"autoTmuxEnableFailed": "Nem sikerült engedélyezni az Auto-Tmux-ot",
"autoTmuxEnabled": "Auto-Tmux engedélyezve a {{host}}számára. A következő kapcsolattól érvényes.",
"clipboardHttpWarning": "Nem sikerült beolvasni a vágólapot. Néhány böngésző korlátozza a vágólap elérését HTTPS-en kívül - próbálja ki a Ctrl+V billentyűkombinációt, vagy HTTPS-en keresztül szolgáltassa ki a Termixet.",
"closeTab": "Közeli",
"connecting": "Kapcsolódás...",
"connectionLost": "Kapcsolat megszakadt",
"connectionRejected": "A szerver elutasította a kapcsolatot. Kérjük, ellenőrizze a hitelesítést és a hálózati konfigurációt.",
"connectionTimeout": "Kapcsolati időtúllépés",
"enableAutoTmuxAction": "Auto-Tmux engedélyezése",
"hostKeyRejected": "SSH host kulcs ellenőrzése elutasítva. Kapcsolat megszakítva.",
"linkDialogCopy": "Másolat",
"linkDialogOpen": "Nyitott",
"linkDialogTitle": "Link megnyitása",
"maxReconnectAttemptsReached": "Elérte az újracsatlakozási kísérletek maximális számát",
"noHostSelected": "Nincs kiválasztva gazdagép",
"opksshAuthFailed": "Sikertelen hitelesítés. Kérjük, ellenőrizze a hitelesítő adatait, és próbálja újra.",
"opksshTimeout": "A hitelesítés időtúllépést okozott. Kérjük, próbálja újra.",
"passwordPromptFillTitle": "Írja be a mentett jelszót ebbe a mezőbe?",
"reconnect": "Újracsatlakozás",
"reconnected": "Sikeresen újrakapcsolódott",
"reconnecting": "Újracsatlakozás... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Gyufa tok",
"searchClose": "Bezárás (Escape)",
"searchNext": "Következő találat (Enter)",
"searchNoResults": "Nincs találat",
"searchPlaceholder": "Lelet",
"searchPrevious": "Előző találat (Shift+Enter)",
"searchRegex": "Reguláris kifejezés használata",
"searchResultCount": "{{index}} a {{count}}-ból",
"searchWholeWord": "Egész szó egyezése",
"sessionExpiredNotice": "Az előző munkameneted a következő helyen: {{host}} lejárt {{minutes}} perc lecsatlakozás után; a shell és minden, ami benne fut, eltűnt. Kapcsold be az Auto-Tmux-ot ezen a gépen a munkamenetek életben tartásához.",
"sessionExpiredNoticeNoMinutes": "Az előző munkameneted a {{host}} gépen lejárt, miközben le voltál választva; a shell és minden benne futó dolog elveszett. Kapcsold be az Auto-Tmuxot ezen a gépen a munkamenetek életben tartásához.",
"sessionTakenOver": "A munkamenet egy másik lapon nyílt meg. Újrakapcsolódás...",
"tmuxAttached": "Csatlakoztatott ügyfelek",
"tmuxAttachedCount": "{{count}} csatolva",
"tmuxCopyHint": "Módosítsa a kijelölést, és nyomja meg az Enter billentyűt a vágólapra másoláshoz",
"tmuxCreateNew": "Új munkamenet indítása",
"tmuxDetached": "Leválasztva a tmux munkamenetről",
"tmuxLastActivity": "Utolsó tevékenység",
"tmuxSessionAttached": "tmux munkamenet csatolva: {{name}}",
"tmuxSessionCreated": "tmux munkamenet létrehozva: {{name}}",
"tmuxSessionPickerDesc": "Meglévő tmux munkamenetek találhatók ezen a gazdagépen. Válasszon ki egyet az újracsatlakozáshoz, vagy hozzon létre egy új munkamenetet.",
"tmuxSessionPickerTitle": "tmux munkamenetek",
"tmuxTimeDays": "{{count}}nappal ezelőtt",
"tmuxTimeHours": "{{count}}órával ezelőtt",
"tmuxTimeJustNow": "éppen most",
"tmuxTimeMinutes": "{{count}}perccel ezelőtt",
"tmuxUnavailable": "A tmux nincs telepítve a távoli gépre, a standard shell-t használja.",
"tmuxWindowCount": "{{count}} ablak",
"tmuxWindows": "Ablakok",
"unknownError": "Ismeretlen hiba történt",
"vaultAuthDescription": "Megnyílt egy ablak a HashiCorp Vaultba való bejelentkezéshez. Fejezze be a bejelentkezést ott; a kapcsolat automatikusan folytatódik.",
"vaultAuthFailed": "A trezor hitelesítése sikertelen. Próbálja újra.",
"vaultAuthTitle": "Bejelentkezés szükséges a széfbe",
"vaultReopen": "Bejelentkezési ablak újranyitása",
"websocketAbnormalClose": "A kapcsolat váratlanul megszakadt. Ennek oka lehet fordított proxy vagy SSL konfigurációs probléma. Kérjük, ellenőrizze a szervernaplókat.",
"websocketError": "WebSocket csatlakozási hiba"
},
"terminalToolbar": {
"detachTmux": "tmux leválasztása",
"detachTmuxDescription": "tmux munkamenet leválasztása",
"dismissError": "Képhiba elvetése",
"hideToolbar": "Eszköztár elrejtése",
"image": "Képműveletek (csak képek)",
"imageActionFailed": "Képművelet sikertelen",
"layout": "Eszköztár megjelenítési módja",
"layoutExpanded": "Metrikákkal bővítve",
"layoutIcon": "Csak ikon",
"layoutLabeled": "Ikonok és címkék",
"moveToolbar": "Eszköztár áthelyezése",
"moveToolbarHint": "Húzza az eszköztár áthelyezéséhez",
"paste": "Kép beillesztése",
"pasteImage": "Kép beillesztése a vágólapról (csak képek)",
"retry": "Újrapróbálkozás",
"showToolbar": "Eszköztár kibontása",
"upload": "Kép feltöltése",
"uploadImage": "Töltsön fel egy képet a tárhelyszolgáltatóra",
"uploadingImage": "Feltöltés..."
},
"history": {
"noTerminalSelected": "Nincs kiválasztva terminál",
"noTerminalSelectedHint": "Nyisson meg egy SSH terminálfület a parancselőzmények megtekintéséhez",
"searchPlaceholder": "Keresési előzmények...",
"clearAll": "Összes törlése",
"noHistoryEntries": "Nincsenek előzménybejegyzések",
"trackingDisabled": "Az előzmények követése le van tiltva"
},
"touchInput": {
"enabled": "Érintéses bevitel engedélyezése",
"enabledDesc": "Egyujjas görgetés engedélyezése a terminálokban",
"momentumEnabled": "Lendület engedélyezése",
"momentumEnabledDesc": "Görgessen tovább röviden egy húzás után",
"advanced": "Fejlett",
"resetDefaults": "Alapértelmezett beállítások visszaállítása",
"dragThreshold": "Húzási küszöb",
"maxWheelDelta": "Maximális kerékdelta",
"momentumSampleWindow": "Lendület mintavételi ablak",
"releaseGracePeriod": "Kiadási türelmi időszak",
"minimumVelocity": "Minimális sebesség",
"maximumVelocity": "Maximális sebesség",
"maximumDuration": "Maximális időtartam",
"maximumTravel": "Maximális mozgástartomány",
"decayTime": "Bomlási idő",
"pixelsPerTick": "Pixelek száma kullancsonként",
"maximumFrameInterval": "Maximális képkocka-intervallum",
"maximumTicksPerFrame": "Maximális tickek képkockánként"
},
"imageStorage": {
"instanceId": "Terminál munkamenet példányazonosítója",
"instanceIdDesc": "Az egyik csatlakoztatott terminálfül példányazonosítója, amelyet a láthatóság teszteléséhez használunk.",
"selectedModeUnavailable": "Nem elérhető",
"test": "Teszt",
"testing": "Tesztelés...",
"testFailed": "Nem sikerült tesztelni a képtárolást",
"testConnected": "A terminál munkamenet csatlakozva van.",
"testNotConnected": "Nincs csatlakoztatott terminál munkamenet ezzel a példányazonosítóval.",
"testSelectedMode": "Egy feltöltés ezt használná: {{mode}}",
"testLocalVisible": "A helyi könyvtár látható a csatlakoztatott gépről.",
"testLocalNotVisible": "A helyi könyvtár NEM látható a csatlakoztatott gépről."
},
"settings": {
"admin": {
"group": {
"touch": "Érintéses bevitel",
"images": "Képtárolás"
},
"sessionTimeoutMinutes": {
"label": "Terminál munkamenet-megőrző",
"description": "Mennyi ideig marad életben egy leválasztott terminál a szerveren, mielőtt bezárásra kerül. Min. 1 · Max. 1440 perc. Azoknál a munkameneteknél, amelyeknek ezt túl kell élniük, engedélyezze az Auto-Tmux funkciót a gazdagépen."
},
"commandHistoryEnabled": {
"label": "Parancsnoki előzmények",
"description": "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."
},
"imageStorageMode": {
"label": "Tárolási mód",
"description": "A beillesztett és feltöltött terminálképek írási helye. Kijavítottuk az automatikus kiválasztást kapcsolat, helyi és távoli (SFTP) alapján.",
"auto": "Autó",
"local": "Helyi",
"remoteSftp": "Távoli (SFTP)"
},
"imageLocalDir": {
"label": "Helyi címtár"
},
"imageHostPath": {
"label": "Gazdagép elérési útja",
"description": "A terminálügynökök által fogadott helyi könyvtár hosztoldali nézete."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Max. képszám"
},
"imageMaxBytes": {
"label": "Max. tárhely byte-okban"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Terminál eszköztár engedélyezése",
"description": "Gyorselérési eszköztár megjelenítése az aktív terminálhoz rögzítve a gazdagép eszközeihez és élő statisztikáihoz"
},
"enableCommandHistory": {
"label": "Parancsnoki előzmények",
"description": "A terminálon futó parancsok rögzítése előzmények és automatikus kiegészítés céljából"
},
"enableTerminal": {
"label": "Terminál engedélyezése"
}
}
},
"palette": {
"localTerminal": "Helyi terminál"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Menambahkan",
"arrowDown": "Panah ke Bawah",
"arrowLeft": "Panah Kiri",
"arrowRight": "Panah Kanan",
"arrowUp": "Panah Atas",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Selesai",
"editQuickKeys": "Edit tombol pintasan",
"end": "Akhir",
"esc": "Esc",
"home": "Rumah",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Pasta",
"quickKeyPlaceholder": "misalnya sudo ",
"quickKeysDesc": "Ketuk × untuk menghapus. Mendukung hingga 8 karakter.",
"quickKeysTitle": "Tombol Pintas",
"removeQuickKey": "Menghapus",
"resetDefaults": "Kembalikan ke pengaturan default",
"shift": "Menggeser",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Gagal mengaktifkan Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux diaktifkan untuk {{host}}. Ini berlaku mulai koneksi berikutnya.",
"clipboardHttpWarning": "Tidak dapat membaca isi clipboard. Beberapa browser membatasi akses clipboard di luar HTTPS - coba tekan Ctrl+V, atau sajikan Termix melalui HTTPS.",
"closeTab": "Menutup",
"connecting": "Menghubungkan...",
"connectionLost": "Koneksi terputus",
"connectionRejected": "Koneksi ditolak oleh server. Harap periksa otentikasi dan konfigurasi jaringan Anda.",
"connectionTimeout": "Waktu habis koneksi",
"enableAutoTmuxAction": "Aktifkan Auto-Tmux",
"hostKeyRejected": "Verifikasi kunci host SSH ditolak. Koneksi dibatalkan.",
"linkDialogCopy": "Menyalin",
"linkDialogOpen": "Membuka",
"linkDialogTitle": "Buka Tautan",
"maxReconnectAttemptsReached": "Upaya penyambungan kembali maksimum telah tercapai.",
"noHostSelected": "Tidak ada pembawa acara yang dipilih.",
"opksshAuthFailed": "Autentikasi gagal. Harap periksa kredensial Anda dan coba lagi.",
"opksshTimeout": "Autentikasi habis waktu. Silakan coba lagi.",
"passwordPromptFillTitle": "Masukkan kata sandi yang tersimpan ke dalam kolom ini?",
"reconnect": "Terhubung kembali",
"reconnected": "Berhasil terhubung kembali",
"reconnecting": "Menghubungkan kembali... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Kotak Korek Api",
"searchClose": "Tutup (Keluar)",
"searchNext": "Pertandingan Berikutnya (Masuk)",
"searchNoResults": "Tidak ada hasil",
"searchPlaceholder": "Menemukan",
"searchPrevious": "Pertandingan Sebelumnya (Shift+Enter)",
"searchRegex": "Gunakan Ekspresi Reguler",
"searchResultCount": "{{index}} dari {{count}}",
"searchWholeWord": "Cocokkan Seluruh Kata",
"sessionExpiredNotice": "Sesi Anda sebelumnya di {{host}} telah berakhir setelah {{minutes}} menit terputus; shell dan semua yang berjalan di dalamnya telah hilang. Aktifkan Auto-Tmux pada host ini untuk menjaga agar sesi tetap aktif.",
"sessionExpiredNoticeNoMinutes": "Sesi Anda sebelumnya di {{host}} telah berakhir saat terputus; shell dan semua yang berjalan di dalamnya telah hilang. Aktifkan Auto-Tmux pada host ini untuk menjaga agar sesi tetap aktif.",
"sessionTakenOver": "Sesi dibuka di tab lain. Sedang menyambungkan kembali...",
"tmuxAttached": "Klien terlampir",
"tmuxAttachedCount": "{{count}} terlampir",
"tmuxCopyHint": "Sesuaikan pilihan dan tekan Enter untuk menyalin ke papan klip.",
"tmuxCreateNew": "Mulai sesi baru",
"tmuxDetached": "Terputus dari sesi tmux",
"tmuxLastActivity": "Aktivitas terakhir",
"tmuxSessionAttached": "Sesi tmux terlampir: {{name}}",
"tmuxSessionCreated": "Sesi tmux dibuat: {{name}}",
"tmuxSessionPickerDesc": "Sesi tmux yang ada ditemukan di host ini. Pilih salah satu untuk menyambungkan kembali atau membuat sesi baru.",
"tmuxSessionPickerTitle": "Sesi tmux",
"tmuxTimeDays": "{{count}}d lalu",
"tmuxTimeHours": "{{count}}h lalu",
"tmuxTimeJustNow": "baru saja",
"tmuxTimeMinutes": "{{count}}m lalu",
"tmuxUnavailable": "tmux tidak terpasang pada host jarak jauh, sehingga kembali menggunakan shell standar.",
"tmuxWindowCount": "{{count}} jendela",
"tmuxWindows": "Windows",
"unknownError": "Terjadi kesalahan yang tidak diketahui.",
"vaultAuthDescription": "Jendela telah terbuka untuk masuk ke HashiCorp Vault. Selesaikan proses masuk di sana; koneksi ini akan berlanjut secara otomatis.",
"vaultAuthFailed": "Autentikasi Vault gagal. Silakan coba lagi.",
"vaultAuthTitle": "Login ke Vault diperlukan.",
"vaultReopen": "Buka kembali jendela masuk",
"websocketAbnormalClose": "Koneksi terputus secara tidak terduga. Ini mungkin disebabkan oleh masalah konfigurasi reverse proxy atau SSL. Silakan periksa log server.",
"websocketError": "Kesalahan koneksi WebSocket"
},
"terminalToolbar": {
"detachTmux": "Lepaskan tmux",
"detachTmuxDescription": "Lepaskan sesi tmux",
"dismissError": "Abaikan kesalahan gambar",
"hideToolbar": "Sembunyikan bilah alat",
"image": "Aksi gambar (hanya gambar)",
"imageActionFailed": "Aksi gambar gagal",
"layout": "Mode tampilan bilah alat",
"layoutExpanded": "Diperluas dengan metrik",
"layoutIcon": "Hanya ikon",
"layoutLabeled": "Ikon dan label",
"moveToolbar": "Pindahkan bilah alat",
"moveToolbarHint": "Seret untuk memposisikan ulang bilah alat",
"paste": "Tempel gambar",
"pasteImage": "Tempel gambar dari clipboard (hanya gambar)",
"retry": "Mencoba kembali",
"showToolbar": "Perluas bilah alat",
"upload": "Unggah gambar",
"uploadImage": "Unggah gambar ke host",
"uploadingImage": "Sedang mengunggah..."
},
"history": {
"noTerminalSelected": "Tidak ada terminal yang dipilih",
"noTerminalSelectedHint": "Buka tab terminal SSH untuk melihat riwayat perintahnya.",
"searchPlaceholder": "Riwayat pencarian...",
"clearAll": "Hapus Semua",
"noHistoryEntries": "Tidak ada entri riwayat.",
"trackingDisabled": "Pelacakan riwayat dinonaktifkan"
},
"touchInput": {
"enabled": "Aktifkan Input Sentuh",
"enabledDesc": "Izinkan pengguliran sentuh satu jari di terminal.",
"momentumEnabled": "Aktifkan Momentum",
"momentumEnabledDesc": "Lanjutkan menggulir sebentar setelah menggeser",
"advanced": "Canggih",
"resetDefaults": "Kembalikan ke pengaturan default",
"dragThreshold": "Ambang batas seret",
"maxWheelDelta": "Delta roda maksimum",
"momentumSampleWindow": "Jendela sampel momentum",
"releaseGracePeriod": "Masa tenggang pelepasan",
"minimumVelocity": "Kecepatan minimum",
"maximumVelocity": "Kecepatan maksimum",
"maximumDuration": "Durasi maksimum",
"maximumTravel": "Perjalanan maksimum",
"decayTime": "Waktu peluruhan",
"pixelsPerTick": "Piksel per detik",
"maximumFrameInterval": "Interval bingkai maksimum",
"maximumTicksPerFrame": "Jumlah tick maksimum per frame"
},
"imageStorage": {
"instanceId": "ID instance sesi terminal",
"instanceIdDesc": "ID instans dari salah satu tab terminal yang terhubung, digunakan untuk menguji visibilitas.",
"selectedModeUnavailable": "Tidak tersedia",
"test": "Tes",
"testing": "Pengujian...",
"testFailed": "Pengujian penyimpanan gambar gagal.",
"testConnected": "Sesi terminal terhubung.",
"testNotConnected": "Tidak ada sesi terminal yang terhubung dengan ID instance ini.",
"testSelectedMode": "Unggahan akan menggunakan: {{mode}}",
"testLocalVisible": "Direktori lokal tersebut dapat dilihat dari host yang terhubung.",
"testLocalNotVisible": "Direktori lokal TIDAK terlihat dari host yang terhubung."
},
"settings": {
"admin": {
"group": {
"touch": "Input Sentuh",
"images": "Penyimpanan Gambar"
},
"sessionTimeoutMinutes": {
"label": "Ketahanan Sesi Terminal",
"description": "Berapa lama terminal yang terputus tetap aktif di server sebelum ditutup. Minimal 1 menit · Maksimal 1440 menit. Untuk sesi yang harus bertahan lebih lama dari ini, aktifkan Auto-Tmux pada host."
},
"commandHistoryEnabled": {
"label": "Riwayat Perintah",
"description": "Izinkan perekaman riwayat perintah. Jika dinonaktifkan, riwayat tidak akan disimpan terlepas dari pengaturan per host."
},
"imageStorageMode": {
"label": "Mode Penyimpanan",
"description": "Di sinilah gambar terminal yang disalin dan diunggah ditulis. Pemilihan otomatis berdasarkan koneksi, Lokal dan Jarak Jauh (SFTP) telah ditetapkan.",
"auto": "Mobil",
"local": "Lokal",
"remoteSftp": "Jarak Jauh (SFTP)"
},
"imageLocalDir": {
"label": "Direktori lokal"
},
"imageHostPath": {
"label": "Jalur host",
"description": "Tampilan sisi host dari direktori lokal yang diterima oleh agen terminal."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Jumlah gambar maksimum"
},
"imageMaxBytes": {
"label": "Byte penyimpanan maksimum"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Aktifkan Toolbar Terminal",
"description": "Tampilkan bilah alat akses cepat yang terpasang pada terminal aktif untuk alat dan statistik langsung host ini."
},
"enableCommandHistory": {
"label": "Riwayat Perintah",
"description": "Rekam perintah yang dijalankan di terminal ini untuk riwayat dan pelengkapan otomatis."
},
"enableTerminal": {
"label": "Aktifkan Terminal"
}
}
},
"palette": {
"localTerminal": "Terminal Lokal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Aggiungi",
"arrowDown": "Freccia Giù",
"arrowLeft": "Freccia Sinistra",
"arrowRight": "Freccia Destra",
"arrowUp": "Freccia Su",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Canc",
"done": "Fine",
"editQuickKeys": "Modifica tasti rapidi",
"end": "Fine",
"esc": "Esc",
"home": "Home",
"pageDown": "Pag Giù",
"pageUp": "Pag Su",
"paste": "Impasto",
"quickKeyPlaceholder": "ad es. sudo ",
"quickKeysDesc": "Tocca × per rimuovere. Supporta fino a 8 caratteri.",
"quickKeysTitle": "Tasti Rapidi",
"removeQuickKey": "Rimuovi",
"resetDefaults": "Ripristina impostazioni predefinite",
"shift": "Maiusc",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Impossibile abilitare Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux abilitato per {{host}}. Si applica dalla prossima connessione.",
"clipboardHttpWarning": "Impossibile leggere gli appunti. Alcuni browser limitano l'accesso agli appunti al di fuori del protocollo HTTPS: prova a usare Ctrl+V oppure a utilizzare Termix tramite HTTPS.",
"closeTab": "Chiudi",
"connecting": "Connessione in corso...",
"connectionLost": "Connessione persa",
"connectionRejected": "Connessione rifiutata dal server. Controlla l'autenticazione e la configurazione di rete.",
"connectionTimeout": "Timeout di connessione",
"enableAutoTmuxAction": "Abilita Auto-Tmux",
"hostKeyRejected": "Verifica della chiave host SSH rifiutata. Connessione annullata.",
"linkDialogCopy": "Copia",
"linkDialogOpen": "Apri",
"linkDialogTitle": "Apri collegamento",
"maxReconnectAttemptsReached": "Raggiunto il numero massimo di tentativi di riconnessione",
"noHostSelected": "Nessun host selezionato",
"opksshAuthFailed": "Autenticazione fallita. Controlla le credenziali e riprova.",
"opksshTimeout": "Autenticazione scaduta. Riprova.",
"passwordPromptFillTitle": "Inserire la password salvata in questa richiesta?",
"reconnect": "Riconnetti",
"reconnected": "Riconnesso con successo",
"reconnecting": "Riconnessione... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Scatola di fiammiferi",
"searchClose": "Chiudi (Esc)",
"searchNext": "Prossima partita (Invio)",
"searchNoResults": "Nessun risultato",
"searchPlaceholder": "Trovare",
"searchPrevious": "Partita precedente (Maiusc+Invio)",
"searchRegex": "Utilizzare le espressioni regolari",
"searchResultCount": "{{index}} di {{count}}",
"searchWholeWord": "Abbina la parola intera",
"sessionExpiredNotice": "La tua sessione precedente su {{host}} è scaduta dopo {{minutes}} minuti di disconnessione; la shell e tutto ciò che era in esecuzione al suo interno sono stati terminati. Abilita Auto-Tmux su questo host per mantenere attive le sessioni.",
"sessionExpiredNoticeNoMinutes": "La tua sessione precedente su {{host}} è scaduta mentre eri disconnesso; la shell e tutto ciò che era in esecuzione al suo interno sono stati eliminati. Abilita Auto-Tmux su questo host per mantenere attive le sessioni.",
"sessionTakenOver": "La sessione è stata aperta in un'altra scheda. Riconnessione...",
"tmuxAttached": "Client collegati",
"tmuxAttachedCount": "{{count}} collegato",
"tmuxCopyHint": "Regola la selezione e premi Invio per copiare negli appunti",
"tmuxCreateNew": "Avvia nuova sessione",
"tmuxDetached": "Scollegato dalla sessione tmux",
"tmuxLastActivity": "Ultima attività",
"tmuxSessionAttached": "Sessione tmux collegata: {{name}}",
"tmuxSessionCreated": "Sessione tmux creata: {{name}}",
"tmuxSessionPickerDesc": "Sono state trovate sessioni tmux esistenti su questo host. Selezionane una per ricollegarti o crea una nuova sessione.",
"tmuxSessionPickerTitle": "Sessioni tmux",
"tmuxTimeDays": "{{count}}d fa",
"tmuxTimeHours": "{{count}}h fa",
"tmuxTimeJustNow": "Adesso",
"tmuxTimeMinutes": "{{count}}m fa",
"tmuxUnavailable": "tmux non è installato sull'host remoto, si utilizza la shell standard",
"tmuxWindowCount": "{{count}} finestra",
"tmuxWindows": "Finestre",
"unknownError": "Si è verificato un errore sconosciuto",
"vaultAuthDescription": "Una finestra si è aperta per accedere a HashiCorp Vault. Completa l'accesso lì; la connessione continuerà automaticamente.",
"vaultAuthFailed": "Autenticazione Vault fallita. Riprova.",
"vaultAuthTitle": "Accesso a Vault richiesto",
"vaultReopen": "Riapri la finestra di accesso",
"websocketAbnormalClose": "La connessione si è chiusa in modo imprevisto. Potrebbe trattarsi di un problema di reverse proxy o configurazione SSL. Controlla i log del server.",
"websocketError": "Errore di connessione WebSocket"
},
"terminalToolbar": {
"detachTmux": "Scollega tmux",
"detachTmuxDescription": "Scollega la sessione tmux",
"dismissError": "Chiudi errore immagine",
"hideToolbar": "Nascondi la barra degli strumenti",
"image": "Azioni sulle immagini (solo immagini)",
"imageActionFailed": "L'azione sull'immagine non è riuscita.",
"layout": "Modalità di visualizzazione della barra degli strumenti",
"layoutExpanded": "Espandeto con metriche",
"layoutIcon": "Solo icona",
"layoutLabeled": "Icone ed etichette",
"moveToolbar": "Sposta la barra degli strumenti",
"moveToolbarHint": "Trascina per riposizionare la barra degli strumenti",
"paste": "Incolla l'immagine",
"pasteImage": "Incolla l'immagine dagli appunti (solo immagini)",
"retry": "Riprova",
"showToolbar": "Espandi la barra degli strumenti",
"upload": "Carica immagine",
"uploadImage": "Carica un'immagine sull'host",
"uploadingImage": "Caricamento in corso..."
},
"history": {
"noTerminalSelected": "Nessun terminale selezionato",
"noTerminalSelectedHint": "Apri una scheda di terminale SSH per visualizzare la cronologia dei comandi",
"searchPlaceholder": "Cerca nella cronologia...",
"clearAll": "Cancella tutto",
"noHistoryEntries": "Nessuna voce nella cronologia",
"trackingDisabled": "Tracciamento cronologia disabilitato"
},
"touchInput": {
"enabled": "Abilita input tattile",
"enabledDesc": "Consenti lo scorrimento con un solo dito nei terminali",
"momentumEnabled": "Abilitare lo slancio",
"momentumEnabledDesc": "Continua a scorrere brevemente dopo uno swipe",
"advanced": "Avanzato",
"resetDefaults": "Ripristina le impostazioni predefinite",
"dragThreshold": "Trascinamento",
"maxWheelDelta": "Delta massimo della ruota",
"momentumSampleWindow": "Finestra di campionamento del momento",
"releaseGracePeriod": "Periodo di grazia per il rilascio",
"minimumVelocity": "Velocità minima",
"maximumVelocity": "velocità massima",
"maximumDuration": "Durata massima",
"maximumTravel": "Viaggio massimo",
"decayTime": "tempo di decadimento",
"pixelsPerTick": "Pixel per tick",
"maximumFrameInterval": "Intervallo massimo tra i fotogrammi",
"maximumTicksPerFrame": "Numero massimo di tick per fotogramma"
},
"imageStorage": {
"instanceId": "ID sessione terminale",
"instanceIdDesc": "ID istanza di una delle schede del terminale a cui sei connesso, utilizzato per testare la visibilità.",
"selectedModeUnavailable": "Non disponibile",
"test": "Test",
"testing": "Test...",
"testFailed": "Impossibile testare l'archiviazione delle immagini",
"testConnected": "La sessione del terminale è connessa.",
"testNotConnected": "Nessuna sessione terminale connessa con questo ID istanza.",
"testSelectedMode": "Per caricare un file si utilizzerebbe: {{mode}}",
"testLocalVisible": "La directory locale è visibile dall'host connesso.",
"testLocalNotVisible": "La directory locale NON è visibile dall'host connesso."
},
"settings": {
"admin": {
"group": {
"touch": "Input tattile",
"images": "Archiviazione delle immagini"
},
"sessionTimeoutMinutes": {
"label": "Persistenza della sessione terminale",
"description": "Per quanto tempo un terminale disconnesso rimane attivo sul server prima di essere chiuso. Minimo 1 minuto · Massimo 1440 minuti. Per le sessioni che devono durare più a lungo, abilitare Auto-Tmux sull'host."
},
"commandHistoryEnabled": {
"label": "Cronologia comandi",
"description": "Consenti la registrazione della cronologia comandi. Se disabilitato, la cronologia non viene salvata indipendentemente dalle impostazioni per host."
},
"imageStorageMode": {
"label": "Modalità di archiviazione",
"description": "Dove vengono scritte le immagini del terminale incollate e caricate. Le selezioni automatiche per connessione, locale e remota (SFTP), sono fisse.",
"auto": "Auto",
"local": "Locale",
"remoteSftp": "Remoto (SFTP)"
},
"imageLocalDir": {
"label": "Cartella locale"
},
"imageHostPath": {
"label": "Percorso host",
"description": "La visualizzazione lato host della directory locale ricevuta dagli agenti terminali."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Numero massimo di immagini"
},
"imageMaxBytes": {
"label": "byte di archiviazione massimi"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Abilita la barra degli strumenti del terminale",
"description": "Mostra una barra degli strumenti ad accesso rapido ancorata al terminale attivo per gli strumenti e le statistiche in tempo reale di questo host."
},
"enableCommandHistory": {
"label": "Cronologia comandi",
"description": "Registra i comandi eseguiti in questo terminale per la cronologia e il completamento automatico"
},
"enableTerminal": {
"label": "Abilita Terminale"
}
}
},
"palette": {
"localTerminal": "Terminale locale"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "追加",
"arrowDown": "↓",
"arrowLeft": "←",
"arrowRight": "→",
"arrowUp": "↑",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "完了",
"editQuickKeys": "クイックキー編集",
"end": "エンド",
"esc": "Esc",
"home": "ホーム",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "ペースト",
"quickKeyPlaceholder": "例: sudo ",
"quickKeysDesc": "× をタップして削除します。最大8文字まで対応。",
"quickKeysTitle": "クイックキー",
"removeQuickKey": "削除",
"resetDefaults": "デフォルトにリセット",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Auto-Tmux の有効化に失敗しました",
"autoTmuxEnabled": "{{host}}に対して Auto-Tmux が有効になりました。これは次回の接続時に適用されます。",
"clipboardHttpWarning": "クリップボードを読み取れませんでした。一部のブラウザはHTTPS接続以外でのクリップボードへのアクセスを制限しています。代わりにCtrl+Vを試すか、TermixをHTTPS経由で提供してみてください。",
"closeTab": "閉じる",
"connecting": "接続中...",
"connectionLost": "接続切断",
"connectionRejected": "サーバーによって接続が拒否されました。認証設定とネットワーク構成を確認してください。",
"connectionTimeout": "接続タイムアウト",
"enableAutoTmuxAction": "Auto-Tmuxを有効にする",
"hostKeyRejected": "SSHホストキーの検証が拒否されました。接続がキャンセルされました。",
"linkDialogCopy": "コピー",
"linkDialogOpen": "開く",
"linkDialogTitle": "リンクを開く",
"maxReconnectAttemptsReached": "再接続の最大試行回数に達しました",
"noHostSelected": "ホストが選択されていません",
"opksshAuthFailed": "認証に失敗しました。資格情報を確認して再試行してください。",
"opksshTimeout": "認証がタイムアウトしました。もう一度お試しください。",
"passwordPromptFillTitle": "保存されたパスワードをこのプロンプトに入力しますか?",
"reconnect": "再接続",
"reconnected": "再接続完了",
"reconnecting": "再接続中... ({{attempt}}/{{max}})",
"searchCaseSensitive": "マッチケース",
"searchClose": "閉じる(Escキー)",
"searchNext": "次の試合(入場)",
"searchNoResults": "検索結果なし",
"searchPlaceholder": "探す",
"searchPrevious": "前の試合 (Shift+Enter)",
"searchRegex": "正規表現を使用する",
"searchResultCount": "{{index}} / {{count}}",
"searchWholeWord": "単語全体を一致させる",
"sessionExpiredNotice": "{{host}} での前回のセッションは {{minutes}} 分後に切断され、シェルおよびそこで実行されていたすべてのものが失われました。セッションを維持するには、このホストで Auto-Tmux を有効にしてください。",
"sessionExpiredNoticeNoMinutes": "{{host}} での前回のセッションは、接続が切断された状態で期限切れになりました。シェルおよびシェル内で実行されていたすべてのプロセスは失われています。セッションを維持するには、このホストで Auto-Tmux を有効にしてください。",
"sessionTakenOver": "セッションが別のタブで開かれました。再接続中...",
"tmuxAttached": "接続クライアント",
"tmuxAttachedCount": "{{count}} 接続",
"tmuxCopyHint": "選択範囲を調整してEnterでクリップボードにコピー",
"tmuxCreateNew": "新規セッションを開始",
"tmuxDetached": "tmuxセッションからデタッチしました",
"tmuxLastActivity": "最終アクティビティ",
"tmuxSessionAttached": "tmuxセッションに接続しました: {{name}}",
"tmuxSessionCreated": "tmuxセッションが作成されました: {{name}}",
"tmuxSessionPickerDesc": "このホストに既存のtmuxセッションがあります。再アタッチするか、新しいセッションを作成してください。",
"tmuxSessionPickerTitle": "tmuxセッション",
"tmuxTimeDays": "{{count}}日前",
"tmuxTimeHours": "{{count}}時間前",
"tmuxTimeJustNow": "たった今",
"tmuxTimeMinutes": "{{count}}分前",
"tmuxUnavailable": "リモートホストにtmuxがインストールされていません。標準シェルにフォールバックします。",
"tmuxWindowCount": "{{count}} ウィンドウ",
"tmuxWindows": "ウィンドウ",
"unknownError": "不明なエラーが発生しました",
"vaultAuthDescription": "HashiCorp Vaultへのサインイン用のウィンドウが開きました。そこでサインインを完了すると、この接続は自動的に続行されます。",
"vaultAuthFailed": "Vault 認証に失敗しました。もう一度お試しください。",
"vaultAuthTitle": "Vault サインインが必要です",
"vaultReopen": "サインインウィンドウを再表示",
"websocketAbnormalClose": "接続が予期せず切断されました。リバースプロキシまたはSSLの設定に問題がある可能性があります。サーバーログを確認してください。",
"websocketError": "WebSocket接続エラー"
},
"terminalToolbar": {
"detachTmux": "tmuxをデタッチする",
"detachTmuxDescription": "tmuxセッションをデタッチする",
"dismissError": "画像エラーを非表示にする",
"hideToolbar": "ツールバーを非表示にする",
"image": "画像アクション(画像のみ)",
"imageActionFailed": "画像操作が失敗しました",
"layout": "ツールバー表示モード",
"layoutExpanded": "指標を追加して拡張",
"layoutIcon": "アイコンのみ",
"layoutLabeled": "アイコンとラベル",
"moveToolbar": "ツールバーを移動",
"moveToolbarHint": "ツールバーをドラッグして位置を変更します",
"paste": "画像を貼り付ける",
"pasteImage": "クリップボードから画像を貼り付ける(画像のみ)",
"retry": "リトライ",
"showToolbar": "ツールバーを展開する",
"upload": "画像をアップロード",
"uploadImage": "ホストに画像をアップロードする",
"uploadingImage": "アップロード中..."
},
"history": {
"noTerminalSelected": "ターミナルが選択されていません",
"noTerminalSelectedHint": "SSH ターミナルタブを開いてコマンド履歴を表示",
"searchPlaceholder": "履歴を検索...",
"clearAll": "すべてクリア",
"noHistoryEntries": "履歴エントリがありません",
"trackingDisabled": "履歴の追跡が無効です"
},
"touchInput": {
"enabled": "タッチ入力を有効にする",
"enabledDesc": "端末で指一本でのスクロールを可能にする",
"momentumEnabled": "勢いを加速させる",
"momentumEnabledDesc": "スワイプ後、少しスクロールを続ける",
"advanced": "高度な",
"resetDefaults": "初期設定に戻す",
"dragThreshold": "ドラッグしきい値",
"maxWheelDelta": "最大ホイールデルタ",
"momentumSampleWindow": "モメンタムサンプルウィンドウ",
"releaseGracePeriod": "釈放猶予期間",
"minimumVelocity": "最小速度",
"maximumVelocity": "最大速度",
"maximumDuration": "最大持続時間",
"maximumTravel": "最大移動距離",
"decayTime": "減衰時間",
"pixelsPerTick": "ピクセル/ティック",
"maximumFrameInterval": "最大フレーム間隔",
"maximumTicksPerFrame": "フレームあたりの最大ティック数"
},
"imageStorage": {
"instanceId": "端末セッションインスタンスID",
"instanceIdDesc": "表示されている可視性をテストするために使用される、接続されているターミナルタブのいずれかのインスタンスID。",
"selectedModeUnavailable": "利用不可",
"test": "テスト",
"testing": "テスト中...",
"testFailed": "画像ストレージのテストに失敗しました",
"testConnected": "端末セッションが接続されました。",
"testNotConnected": "このインスタンスIDで接続されている端末セッションはありません。",
"testSelectedMode": "アップロードには以下を使用します: {{mode}}",
"testLocalVisible": "ローカルディレクトリは、接続されているホストから参照可能です。",
"testLocalNotVisible": "ローカルディレクトリは、接続されているホストからアクセスできません。"
},
"settings": {
"admin": {
"group": {
"touch": "タッチ入力",
"images": "画像ストレージ"
},
"sessionTimeoutMinutes": {
"label": "端末セッションの永続性",
"description": "切断された端末がサーバー上で閉じられるまでの時間。最小1分、最大1440分。この時間を超えてセッションを継続する必要がある場合は、ホスト上でAuto-Tmuxを有効にしてください。"
},
"commandHistoryEnabled": {
"label": "コマンド履歴",
"description": "コマンド履歴の記録を許可します。無効にすると、ホストごとの設定に関わらず履歴は保存されません。"
},
"imageStorageMode": {
"label": "ストレージモード",
"description": "貼り付けおよびアップロードされた端末イメージが書き込まれる場所。接続による自動選択、ローカルおよびリモート(SFTP)が固定されています。",
"auto": "自動",
"local": "地元",
"remoteSftp": "リモート(SFTP)"
},
"imageLocalDir": {
"label": "ローカルディレクトリ"
},
"imageHostPath": {
"label": "ホストパス",
"description": "端末エージェントが受信するローカルディレクトリのホスト側ビュー。"
},
"imageTtlMs": {
"label": "TTL(ミリ秒)"
},
"imageMaxCount": {
"label": "最大画像数"
},
"imageMaxBytes": {
"label": "最大ストレージバイト数"
}
},
"host": {
"enableTerminalToolbar": {
"label": "ターミナルツールバーを有効にする",
"description": "このホストのツールとリアルタイム統計情報に素早くアクセスできるツールバーを、アクティブなターミナルにドッキングして表示します。"
},
"enableCommandHistory": {
"label": "コマンド履歴",
"description": "このターミナルで実行されたコマンドを履歴とオートコンプリート用に記録します"
},
"enableTerminal": {
"label": "ターミナルを有効にする"
}
}
},
"palette": {
"localTerminal": "ローカル端末"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "추가",
"arrowDown": "아래쪽 화살표",
"arrowLeft": "왼쪽 화살표",
"arrowRight": "오른쪽 화살표",
"arrowUp": "위쪽 화살표",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "완료",
"editQuickKeys": "빠른 키 편집",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "반죽",
"quickKeyPlaceholder": "예: sudo ",
"quickKeysDesc": "×를 탭하여 제거합니다. 최대 8자까지 지원합니다.",
"quickKeysTitle": "빠른 키",
"removeQuickKey": "제거",
"resetDefaults": "기본값으로 초기화",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "자동 Tmux를 활성화하는 데 실패했습니다.",
"autoTmuxEnabled": "{{host}}에 대해 자동 Tmux가 활성화되었습니다. 이는 다음 연결부터 적용됩니다.",
"clipboardHttpWarning": "클립보드를 읽을 수 없습니다. 일부 브라우저는 HTTPS 외부에서 클립보드 접근을 제한합니다. Ctrl+V를 눌러 V를 누르거나 Termix를 HTTPS로 실행해 보세요.",
"closeTab": "닫기",
"connecting": "연결 중...",
"connectionLost": "연결이 끊어졌습니다",
"connectionRejected": "서버에서 연결이 거부되었습니다. 인증 및 네트워크 구성을 확인하세요.",
"connectionTimeout": "연결 시간 초과",
"enableAutoTmuxAction": "자동 Tmux 활성화",
"hostKeyRejected": "SSH 호스트 키 검증이 거부되었습니다. 연결이 취소되었습니다.",
"linkDialogCopy": "복사",
"linkDialogOpen": "열기",
"linkDialogTitle": "링크 열기",
"maxReconnectAttemptsReached": "최대 재연결 시도 횟수에 도달했습니다.",
"noHostSelected": "선택된 호스트가 없습니다.",
"opksshAuthFailed": "인증에 실패했습니다. 자격 증명을 확인하고 다시 시도하세요.",
"opksshTimeout": "인증 시간이 초과되었습니다. 다시 시도하세요.",
"passwordPromptFillTitle": "저장된 비밀번호를 이 프롬프트에 입력하시겠습니까?",
"reconnect": "재연결",
"reconnected": "재연결 성공",
"reconnecting": "재연결 중... ({{attempt}}/{{max}})",
"searchCaseSensitive": "대소문자를 일치시키세요",
"searchClose": "닫기 (탈출)",
"searchNext": "다음 매치 (Enter)",
"searchNoResults": "결과가 없습니다",
"searchPlaceholder": "찾다",
"searchPrevious": "이전 일치 항목 (Shift+Enter)",
"searchRegex": "정규 표현식을 사용하세요",
"searchResultCount": "{{index}} {{count}}",
"searchWholeWord": "단어 전체를 일치시키세요",
"sessionExpiredNotice": "{{host}} 호스트에서 진행 중이던 이전 세션이 {{minutes}} 분 후 연결이 끊어지면서 만료되었습니다. 셸과 셸에서 실행 중이던 모든 항목이 종료되었습니다. 세션을 유지하려면 이 호스트에서 자동 Tmux를 활성화하십시오.",
"sessionExpiredNoticeNoMinutes": "{{host}} 에서 이전 세션이 연결이 끊긴 상태에서 만료되었습니다. 셸과 그 안에서 실행 중이던 모든 항목이 사라졌습니다. 세션을 유지하려면 이 호스트에서 자동 Tmux를 활성화하십시오.",
"sessionTakenOver": "다른 탭에서 세션이 열렸습니다. 다시 연결 중...",
"tmuxAttached": "연결된 클라이언트",
"tmuxAttachedCount": "{{count}}개 연결됨",
"tmuxCopyHint": "선택 영역을 조정하고 Enter 키를 누르면 클립보드에 복사됩니다.",
"tmuxCreateNew": "새 세션 시작",
"tmuxDetached": "tmux 세션에서 분리됨",
"tmuxLastActivity": "마지막 활동",
"tmuxSessionAttached": "tmux 세션 연결됨: {{name}}",
"tmuxSessionCreated": "tmux 세션 생성됨: {{name}}",
"tmuxSessionPickerDesc": "이 호스트에서 기존 tmux 세션을 찾았습니다. 다시 연결할 세션을 선택하거나 새 세션을 만드세요.",
"tmuxSessionPickerTitle": "tmux 세션",
"tmuxTimeDays": "{{count}}일 전",
"tmuxTimeHours": "{{count}}시간 전",
"tmuxTimeJustNow": "방금",
"tmuxTimeMinutes": "{{count}}분 전",
"tmuxUnavailable": "원격 호스트에 tmux가 설치되어 있지 않아 표준 셸로 대체합니다.",
"tmuxWindowCount": "{{count}}개 윈도우",
"tmuxWindows": "윈도우",
"unknownError": "알 수 없는 오류가 발생했습니다.",
"vaultAuthDescription": "HashiCorp Vault 로그인을 위한 창이 열렸습니다. 해당 창에서 로그인을 완료하면 연결이 자동으로 계속됩니다.",
"vaultAuthFailed": "Vault 인증에 실패했습니다. 다시 시도하세요.",
"vaultAuthTitle": "Vault 로그인 필요",
"vaultReopen": "로그인 창 다시 열기",
"websocketAbnormalClose": "연결이 예기치 않게 종료되었습니다. 리버스 프록시 또는 SSL 구성 문제일 수 있습니다. 서버 로그를 확인하세요.",
"websocketError": "WebSocket 연결 오류"
},
"terminalToolbar": {
"detachTmux": "tmux 분리",
"detachTmuxDescription": "tmux 세션 분리",
"dismissError": "이미지 오류를 닫습니다",
"hideToolbar": "툴바 숨기기",
"image": "이미지 관련 작업 (이미지에만 해당)",
"imageActionFailed": "이미지 작업이 실패했습니다.",
"layout": "툴바 표시 모드",
"layoutExpanded": "측정 지표를 추가하여 확장했습니다.",
"layoutIcon": "아이콘만",
"layoutLabeled": "아이콘 및 레이블",
"moveToolbar": "툴바 이동",
"moveToolbarHint": "드래그하여 툴바의 위치를 변경하세요.",
"paste": "이미지 붙여넣기",
"pasteImage": "클립보드에서 이미지 붙여넣기 (이미지만 해당)",
"retry": "다시 해 보다",
"showToolbar": "확장 도구 모음",
"upload": "이미지 업로드",
"uploadImage": "호스트에 이미지를 업로드하세요",
"uploadingImage": "업로드 중..."
},
"history": {
"noTerminalSelected": "선택된 터미널 없음",
"noTerminalSelectedHint": "SSH 터미널 탭을 열어 명령 기록을 확인하세요.",
"searchPlaceholder": "기록 검색...",
"clearAll": "모두 지우기",
"noHistoryEntries": "기록 항목 없음",
"trackingDisabled": "기록 추적이 비활성화됨"
},
"touchInput": {
"enabled": "터치 입력 활성화",
"enabledDesc": "단말기에서 한 손가락 터치 스크롤을 허용합니다.",
"momentumEnabled": "모멘텀을 활성화하세요",
"momentumEnabledDesc": "스와이프 후 잠시 스크롤을 더 내리세요.",
"advanced": "고급의",
"resetDefaults": "기본 설정으로 재설정",
"dragThreshold": "드래그 임계값",
"maxWheelDelta": "최대 휠 델타",
"momentumSampleWindow": "모멘텀 샘플 윈도우",
"releaseGracePeriod": "릴리스 유예 기간",
"minimumVelocity": "최소 속도",
"maximumVelocity": "최대 속도",
"maximumDuration": "최대 지속 시간",
"maximumTravel": "최대 이동",
"decayTime": "소멸 시간",
"pixelsPerTick": "틱당 픽셀",
"maximumFrameInterval": "최대 프레임 간격",
"maximumTicksPerFrame": "프레임당 최대 틱 수"
},
"imageStorage": {
"instanceId": "터미널 세션 인스턴스 ID",
"instanceIdDesc": "연결된 터미널 탭 중 하나의 인스턴스 ID입니다. 이는 가시성 테스트에 사용됩니다.",
"selectedModeUnavailable": "없는",
"test": "시험",
"testing": "테스트 중...",
"testFailed": "이미지 저장소 테스트에 실패했습니다.",
"testConnected": "터미널 세션이 연결되었습니다.",
"testNotConnected": "이 인스턴스 ID로 연결된 터미널 세션이 없습니다.",
"testSelectedMode": "업로드 시에는 {{mode}}를 사용합니다.",
"testLocalVisible": "로컬 디렉터리는 연결된 호스트에서 볼 수 있습니다.",
"testLocalNotVisible": "로컬 디렉터리는 연결된 호스트에서 볼 수 없습니다."
},
"settings": {
"admin": {
"group": {
"touch": "터치 입력",
"images": "이미지 저장"
},
"sessionTimeoutMinutes": {
"label": "터미널 세션 지속성",
"description": "연결이 끊긴 터미널이 서버에서 종료되기 전까지 유지되는 시간입니다. 최소 1분, 최대 1440분. 이 시간보다 오래 세션을 유지해야 하는 경우 호스트에서 자동 Tmux를 활성화하십시오."
},
"commandHistoryEnabled": {
"label": "명령어 히스토리",
"description": "명령어 히스토리 기록 허용. 비활성화 시 호스트별 설정과 무관하게 히스토리가 저장되지 않습니다."
},
"imageStorageMode": {
"label": "저장 모드",
"description": "붙여넣거나 업로드한 터미널 이미지가 표시되는 위치입니다. 연결 유형에 따라 로컬 및 원격(SFTP) 이미지가 자동으로 선택됩니다.",
"auto": "자동",
"local": "현지의",
"remoteSftp": "원격(SFTP)"
},
"imageLocalDir": {
"label": "로컬 디렉토리"
},
"imageHostPath": {
"label": "호스트 경로",
"description": "터미널 에이전트가 수신하는 로컬 디렉터리의 호스트 측 보기입니다."
},
"imageTtlMs": {
"label": "TTL(밀리초)"
},
"imageMaxCount": {
"label": "최대 이미지 개수"
},
"imageMaxBytes": {
"label": "최대 저장 용량(바이트)"
}
},
"host": {
"enableTerminalToolbar": {
"label": "터미널 툴바 활성화",
"description": "현재 활성화된 터미널에 도킹된 빠른 액세스 툴바를 표시하여 해당 호스트의 도구 및 실시간 통계를 볼 수 있도록 합니다."
},
"enableCommandHistory": {
"label": "명령어 히스토리",
"description": "히스토리 및 자동 완성을 위해 이 터미널에서 실행된 명령어를 기록합니다"
},
"enableTerminal": {
"label": "터미널 활성화"
}
}
},
"palette": {
"localTerminal": "로컬 터미널"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Toevoegen",
"arrowDown": "Pijl omlaag",
"arrowLeft": "Pijl naar links",
"arrowRight": "Pijl naar rechts",
"arrowUp": "Pijl omhoog",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Klaar",
"editQuickKeys": "Sneltoetsen bewerken",
"end": "Einde",
"esc": "Esc",
"home": "Thuis",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Plakken",
"quickKeyPlaceholder": "bijv. sudo ",
"quickKeysDesc": "Tik op × om te verwijderen. Ondersteunt maximaal 8 tekens.",
"quickKeysTitle": "Sneltoetsen",
"removeQuickKey": "Verwijderen",
"resetDefaults": "Terugzetten naar standaardinstellingen",
"shift": "Verschuiving",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Auto-Tmux inschakelen is mislukt.",
"autoTmuxEnabled": "Auto-Tmux ingeschakeld voor {{host}}. Dit geldt vanaf de volgende verbinding.",
"clipboardHttpWarning": "Kon het klembord niet lezen. Sommige browsers beperken de toegang tot het klembord buiten HTTPS - probeer in plaats daarvan Ctrl+V, of serveer Termix via HTTPS.",
"closeTab": "Dichtbij",
"connecting": "Verbinden...",
"connectionLost": "Verbinding verbroken",
"connectionRejected": "Verbinding geweigerd door de server. Controleer uw authenticatie- en netwerkconfiguratie.",
"connectionTimeout": "Verbindingstime-out",
"enableAutoTmuxAction": "Auto-Tmux inschakelen",
"hostKeyRejected": "SSH-hostsleutelverificatie geweigerd. Verbinding verbroken.",
"linkDialogCopy": "Kopiëren",
"linkDialogOpen": "Open",
"linkDialogTitle": "Open link",
"maxReconnectAttemptsReached": "Maximaal aantal herverbindingspogingen bereikt",
"noHostSelected": "Geen host geselecteerd",
"opksshAuthFailed": "Authenticatie mislukt. Controleer uw inloggegevens en probeer het opnieuw.",
"opksshTimeout": "De authenticatie is mislukt vanwege een time-out. Probeer het opnieuw.",
"passwordPromptFillTitle": "Voer het opgeslagen wachtwoord in op dit veld.",
"reconnect": "Opnieuw verbinding maken",
"reconnected": "De verbinding is succesvol hersteld.",
"reconnecting": "Opnieuw verbinding maken... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Lucifersdoosje",
"searchClose": "Sluiten (Escape)",
"searchNext": "Volgende wedstrijd (Invoeren)",
"searchNoResults": "Geen resultaten",
"searchPlaceholder": "Vinden",
"searchPrevious": "Vorige wedstrijd (Shift+Enter)",
"searchRegex": "Gebruik een reguliere expressie.",
"searchResultCount": "{{index}} van {{count}}",
"searchWholeWord": "Combineer het hele woord",
"sessionExpiredNotice": "Je vorige sessie op {{host}} is na {{minutes}} minuten verlopen; de shell en alles wat erin draaide, is verdwenen. Schakel Auto-Tmux op deze host in om sessies actief te houden.",
"sessionExpiredNoticeNoMinutes": "Je vorige sessie op {{host}} is verlopen terwijl de verbinding verbroken was; de shell en alles wat daarin draaide, is verdwenen. Schakel Auto-Tmux in op deze host om sessies actief te houden.",
"sessionTakenOver": "De sessie is in een ander tabblad geopend. Opnieuw verbinding maken...",
"tmuxAttached": "Aangesloten klanten",
"tmuxAttachedCount": "{{count}} bevestigd",
"tmuxCopyHint": "Pas je selectie aan en druk op Enter om naar het klembord te kopiëren.",
"tmuxCreateNew": "Start een nieuwe sessie",
"tmuxDetached": "Losgekoppeld van de tmux-sessie",
"tmuxLastActivity": "Laatste activiteit",
"tmuxSessionAttached": "tmux-sessie gekoppeld: {{name}}",
"tmuxSessionCreated": "tmux-sessie aangemaakt: {{name}}",
"tmuxSessionPickerDesc": "Bestaande tmux-sessies gevonden op deze host. Selecteer er een om opnieuw verbinding te maken of een nieuwe sessie te creëren.",
"tmuxSessionPickerTitle": "tmux-sessies",
"tmuxTimeDays": "{{count}}dagen geleden",
"tmuxTimeHours": "{{count}}uur geleden",
"tmuxTimeJustNow": "zojuist",
"tmuxTimeMinutes": "{{count}}m geleden",
"tmuxUnavailable": "Tmux is niet geïnstalleerd op de externe host, er wordt teruggevallen op de standaard shell.",
"tmuxWindowCount": "{{count}} venster",
"tmuxWindows": "Windows",
"unknownError": "Er is een onbekende fout opgetreden.",
"vaultAuthDescription": "Er is een venster geopend om in te loggen bij HashiCorp Vault. Voltooi de aanmelding daar; deze verbinding wordt automatisch tot stand gebracht.",
"vaultAuthFailed": "Authenticatie voor de kluis is mislukt. Probeer het opnieuw.",
"vaultAuthTitle": "Aanmelden bij Vault is vereist.",
"vaultReopen": "Heropen het aanmeldvenster",
"websocketAbnormalClose": "De verbinding is onverwacht verbroken. Dit kan te wijten zijn aan een probleem met de reverse proxy of de SSL-configuratie. Controleer de serverlogboeken.",
"websocketError": "WebSocket-verbindingsfout"
},
"terminalToolbar": {
"detachTmux": "Ontkoppel tmux",
"detachTmuxDescription": "Ontkoppel de tmux-sessie",
"dismissError": "Afbeeldingsfout negeren",
"hideToolbar": "Werkbalk verbergen",
"image": "Acties met afbeeldingen (alleen afbeeldingen)",
"imageActionFailed": "Afbeeldingsactie mislukt",
"layout": "Werkbalkweergavemodus",
"layoutExpanded": "Uitgebreid met meetgegevens",
"layoutIcon": "Alleen pictogram",
"layoutLabeled": "Pictogrammen en labels",
"moveToolbar": "Verplaats de werkbalk",
"moveToolbarHint": "Sleep om de werkbalk te verplaatsen.",
"paste": "Plak afbeelding",
"pasteImage": "Plak een afbeelding vanuit het klembord (alleen afbeeldingen)",
"retry": "Opnieuw proberen",
"showToolbar": "Werkbalk uitklappen",
"upload": "Afbeelding uploaden",
"uploadImage": "Upload een afbeelding naar de host.",
"uploadingImage": "Bezig met uploaden..."
},
"history": {
"noTerminalSelected": "Geen terminal geselecteerd",
"noTerminalSelectedHint": "Open een SSH-terminaltabblad om de opdrachtgeschiedenis te bekijken.",
"searchPlaceholder": "Zoekgeschiedenis...",
"clearAll": "Alles wissen",
"noHistoryEntries": "Geen geschiedenisvermeldingen",
"trackingDisabled": "Geschiedenisregistratie is uitgeschakeld"
},
"touchInput": {
"enabled": "Aanraakinvoer inschakelen",
"enabledDesc": "Schakel scrollen met één vinger in op terminals in.",
"momentumEnabled": "Activeer momentum",
"momentumEnabledDesc": "Scroll nog even kort verder na een veegbeweging.",
"advanced": "Geavanceerd",
"resetDefaults": "Terugzetten naar standaardinstellingen",
"dragThreshold": "Sleepdrempel",
"maxWheelDelta": "Maximale wieldelta",
"momentumSampleWindow": "Momentum-steekproefvenster",
"releaseGracePeriod": "Vrijlatingstermijn",
"minimumVelocity": "Minimale snelheid",
"maximumVelocity": "Maximale snelheid",
"maximumDuration": "Maximale duur",
"maximumTravel": "Maximale reis",
"decayTime": "Vervaltijd",
"pixelsPerTick": "Pixels per tik",
"maximumFrameInterval": "Maximale frame-interval",
"maximumTicksPerFrame": "Maximaal aantal ticks per frame"
},
"imageStorage": {
"instanceId": "Terminal sessie instantie-ID",
"instanceIdDesc": "Instantie-ID van een van uw verbonden terminaltabbladen, gebruikt om de zichtbaarheid te testen.",
"selectedModeUnavailable": "Niet beschikbaar",
"test": "Test",
"testing": "Testen...",
"testFailed": "Het testen van de beeldopslag is mislukt.",
"testConnected": "De terminalsessie is verbonden.",
"testNotConnected": "Er is geen verbonden terminalsessie met deze instantie-ID.",
"testSelectedMode": "Een upload zou het volgende gebruiken: {{mode}}",
"testLocalVisible": "De lokale map is zichtbaar vanaf de verbonden host.",
"testLocalNotVisible": "De lokale map is NIET zichtbaar vanaf de verbonden host."
},
"settings": {
"admin": {
"group": {
"touch": "Aanraakinvoer",
"images": "Afbeeldingsopslag"
},
"sessionTimeoutMinutes": {
"label": "Terminalsessiepersistentie",
"description": "Hoe lang een losgekoppelde terminal actief blijft op de server voordat deze wordt gesloten. Min. 1 · Max. 1440 minuten. Voor sessies die langer dan deze tijd actief moeten blijven, schakelt u Auto-Tmux in op de host."
},
"commandHistoryEnabled": {
"label": "Commandogeschiedenis",
"description": "Sta het vastleggen van de opdrachtgeschiedenis toe. Indien uitgeschakeld, wordt de geschiedenis niet opgeslagen, ongeacht de instellingen per host."
},
"imageStorageMode": {
"label": "Opslagmodus",
"description": "Hier worden geplakte en geüploade terminalafbeeldingen opgeslagen. Automatische selectie op basis van de verbinding, lokaal en extern (SFTP) zijn vastgelegd.",
"auto": "Auto",
"local": "Lokaal",
"remoteSftp": "Op afstand (SFTP)"
},
"imageLocalDir": {
"label": "Lokale map"
},
"imageHostPath": {
"label": "Hostpad",
"description": "De weergave aan de hostzijde van de lokale directory die terminalagents ontvangen."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Maximaal aantal afbeeldingen"
},
"imageMaxBytes": {
"label": "Maximale opslagcapaciteit in bytes"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Terminalwerkbalk inschakelen",
"description": "Toon een sneltoegangsbalk die is vastgezet aan de actieve terminal voor de tools en live statistieken van deze host."
},
"enableCommandHistory": {
"label": "Commandogeschiedenis",
"description": "Sla de commando's die in deze terminal worden uitgevoerd op voor de geschiedenis en automatisch aanvullen."
},
"enableTerminal": {
"label": "Terminal inschakelen"
}
}
},
"palette": {
"localTerminal": "Lokale terminal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Legge til",
"arrowDown": "Pil ned",
"arrowLeft": "Pil venstre",
"arrowRight": "Pil høyre",
"arrowUp": "Pil opp",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Ferdig",
"editQuickKeys": "Rediger hurtigtaster",
"end": "Slutt",
"esc": "Esc",
"home": "Hjem",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Lim inn",
"quickKeyPlaceholder": "f.eks. sudo ",
"quickKeysDesc": "Trykk på × for å fjerne. Støtter opptil 8 tegn.",
"quickKeysTitle": "Hurtigtaster",
"removeQuickKey": "Fjerne",
"resetDefaults": "Tilbakestill til standardinnstillinger",
"shift": "Skifte",
"tab": "Fane"
},
"terminal": {
"autoTmuxEnableFailed": "Kunne ikke aktivere Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux er aktivert for {{host}}. Det gjelder fra neste tilkobling.",
"clipboardHttpWarning": "Kunne ikke lese utklippstavlen. Noen nettlesere begrenser tilgang til utklippstavlen utenfor HTTPS – prøv Ctrl+V i stedet, eller server Termix over HTTPS.",
"closeTab": "Lukke",
"connecting": "Kobler til...",
"connectionLost": "Forbindelsen gikk tapt",
"connectionRejected": "Tilkoblingen ble avvist av serveren. Sjekk autentiseringen og nettverkskonfigurasjonen din.",
"connectionTimeout": "Tidsavbrudd for tilkobling",
"enableAutoTmuxAction": "Aktiver Auto-Tmux",
"hostKeyRejected": "Verifisering av SSH-vertsnøkkel avvist. Tilkoblingen ble avbrutt.",
"linkDialogCopy": "Kopiere",
"linkDialogOpen": "Åpne",
"linkDialogTitle": "Åpne lenke",
"maxReconnectAttemptsReached": "Maksimalt antall forsøk på å koble til igjen er nådd",
"noHostSelected": "Ingen vert valgt",
"opksshAuthFailed": "Autentiseringen mislyktes. Sjekk legitimasjonen din og prøv på nytt.",
"opksshTimeout": "Autentiseringen ble tidsavbrutt. Prøv på nytt.",
"passwordPromptFillTitle": "Skriv inn det lagrede passordet i denne meldingen?",
"reconnect": "Koble til igjen",
"reconnected": "Tilkoblet på nytt",
"reconnecting": "Kobler til på nytt... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Match-saken",
"searchClose": "Lukk (Escape)",
"searchNext": "Neste kamp (Enter)",
"searchNoResults": "Ingen resultater",
"searchPlaceholder": "Finne",
"searchPrevious": "Forrige treff (Shift+Enter)",
"searchRegex": "Bruk regulært uttrykk",
"searchResultCount": "{{index}} av {{count}}",
"searchWholeWord": "Finn hele ordet",
"sessionExpiredNotice": "Din forrige økt på {{host}} utløp etter {{minutes}} minutter frakobling; skallet og alt som kjører i det er borte. Aktiver Auto-Tmux på denne verten for å holde øktene aktive.",
"sessionExpiredNoticeNoMinutes": "Din forrige økt på {{host}} utløp mens den var frakoblet; skallet og alt som kjører i det er borte. Aktiver Auto-Tmux på denne verten for å holde øktene aktive.",
"sessionTakenOver": "Økten ble åpnet i en annen fane. Kobler til igjen...",
"tmuxAttached": "Tilknyttede klienter",
"tmuxAttachedCount": "{{count}} vedlagt",
"tmuxCopyHint": "Juster valget og trykk Enter for å kopiere til utklippstavlen",
"tmuxCreateNew": "Start ny økt",
"tmuxDetached": "Frakoblet fra tmux-økten",
"tmuxLastActivity": "Siste aktivitet",
"tmuxSessionAttached": "tmux-økt vedlagt: {{name}}",
"tmuxSessionCreated": "tmux-økt opprettet: {{name}}",
"tmuxSessionPickerDesc": "Eksisterende tmux-økter funnet på denne verten. Velg én for å koble til på nytt eller opprett en ny økt.",
"tmuxSessionPickerTitle": "tmux-økter",
"tmuxTimeDays": "for {{count}}dager siden",
"tmuxTimeHours": "for {{count}}t siden",
"tmuxTimeJustNow": "akkurat nå",
"tmuxTimeMinutes": "For {{count}}minutter siden",
"tmuxUnavailable": "tmux er ikke installert på den eksterne verten, og går tilbake til standard skall",
"tmuxWindowCount": "{{count}} vindu",
"tmuxWindows": "Vinduer",
"unknownError": "Ukjent feil oppstod",
"vaultAuthDescription": "Et vindu har åpnet seg for å logge på HashiCorp Vault. Fullfør påloggingen der; denne tilkoblingen vil fortsette automatisk.",
"vaultAuthFailed": "Hvelv-autentisering mislyktes. Prøv på nytt.",
"vaultAuthTitle": "Pålogging til arkivet kreves",
"vaultReopen": "Åpne påloggingsvinduet på nytt",
"websocketAbnormalClose": "Tilkoblingen ble avsluttet uventet. Dette kan skyldes et problem med omvendt proxy eller SSL-konfigurasjon. Sjekk serverloggene.",
"websocketError": "WebSocket-tilkoblingsfeil"
},
"terminalToolbar": {
"detachTmux": "Koble fra tmux",
"detachTmuxDescription": "Koble fra tmux-økten",
"dismissError": "Avvis bildefeil",
"hideToolbar": "Skjul verktøylinjen",
"image": "Bildehandlinger (kun bilder)",
"imageActionFailed": "Bildehandlingen mislyktes",
"layout": "Visningsmodus for verktøylinjen",
"layoutExpanded": "Utvidet med målinger",
"layoutIcon": "Kun ikon",
"layoutLabeled": "Ikoner og etiketter",
"moveToolbar": "Flytt verktøylinjen",
"moveToolbarHint": "Dra for å flytte verktøylinjen",
"paste": "Lim inn bilde",
"pasteImage": "Lim inn bilde fra utklippstavlen (kun bilder)",
"retry": "Prøv på nytt",
"showToolbar": "Utvid verktøylinjen",
"upload": "Last opp bilde",
"uploadImage": "Last opp et bilde til verten",
"uploadingImage": "Laster opp..."
},
"history": {
"noTerminalSelected": "Ingen terminal valgt",
"noTerminalSelectedHint": "Åpne en SSH-terminalfane for å se kommandohistorikken",
"searchPlaceholder": "Søkelogg...",
"clearAll": "Fjern alt",
"noHistoryEntries": "Ingen historikkoppføringer",
"trackingDisabled": "Historikksporing er deaktivert"
},
"touchInput": {
"enabled": "Aktiver berøringsinndata",
"enabledDesc": "Tillat énfinger-berøringsrulling i terminaler",
"momentumEnabled": "Aktiver momentum",
"momentumEnabledDesc": "Fortsett å bla kort etter et sveip",
"advanced": "Avansert",
"resetDefaults": "Tilbakestill til standardinnstillinger",
"dragThreshold": "Dragterskel",
"maxWheelDelta": "Maksimal hjuldelta",
"momentumSampleWindow": "Momentum-eksempelvindu",
"releaseGracePeriod": "Frigivelsesfrist",
"minimumVelocity": "Minimumshastighet",
"maximumVelocity": "Maksimal hastighet",
"maximumDuration": "Maksimal varighet",
"maximumTravel": "Maksimal reise",
"decayTime": "Forfallstid",
"pixelsPerTick": "Piksler per tikk",
"maximumFrameInterval": "Maksimalt bildeintervall",
"maximumTicksPerFrame": "Maksimalt antall ticks per ramme"
},
"imageStorage": {
"instanceId": "Terminaløktforekomst-ID",
"instanceIdDesc": "Forekomst-ID for en av dine tilkoblede terminalfaner, brukt til å teste synlighet.",
"selectedModeUnavailable": "Utilgjengelig",
"test": "Test",
"testing": "Testing...",
"testFailed": "Kunne ikke teste bildelagring",
"testConnected": "Terminaløkten er tilkoblet.",
"testNotConnected": "Ingen tilkoblet terminaløkt med denne forekomst-ID-en.",
"testSelectedMode": "En opplasting ville bruke: {{mode}}",
"testLocalVisible": "Den lokale katalogen er synlig fra den tilkoblede verten.",
"testLocalNotVisible": "Den lokale katalogen er IKKE synlig fra den tilkoblede verten."
},
"settings": {
"admin": {
"group": {
"touch": "Berøringsinndata",
"images": "Bildelagring"
},
"sessionTimeoutMinutes": {
"label": "Terminaløktpersistens",
"description": "Hvor lenge en frakoblet terminal holdes aktiv på serveren før den lukkes. Min 1 · Maks 1440 minutter. For økter som må overleve dette, aktiver Auto-Tmux på verten."
},
"commandHistoryEnabled": {
"label": "Kommandohistorikk",
"description": "Tillat opptak av kommandohistorikk. Når den er deaktivert, lagres ikke historikken uavhengig av innstillinger per vert."
},
"imageStorageMode": {
"label": "Lagringsmodus",
"description": "Der innlimte og opplastede terminalbilder skrives. Automatiske valg etter tilkobling, lokal og ekstern (SFTP) er fikset.",
"auto": "Bil",
"local": "Lokalt",
"remoteSftp": "Fjernstyrt (SFTP)"
},
"imageLocalDir": {
"label": "Lokal katalog"
},
"imageHostPath": {
"label": "Vertssti",
"description": "Vertsvisningen av den lokale katalogen som terminalagenter mottar."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Maksimalt antall bilder"
},
"imageMaxBytes": {
"label": "Maksimalt antall lagringsbyte"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Aktiver terminalverktøylinjen",
"description": "Vis en verktøylinje for hurtig tilgang som er festet til den aktive terminalen for denne vertens verktøy og livestatistikk"
},
"enableCommandHistory": {
"label": "Kommandohistorikk",
"description": "Record-kommandoer kjøres i denne terminalen for historikk og autofullføring"
},
"enableTerminal": {
"label": "Aktiver terminalen"
}
}
},
"palette": {
"localTerminal": "Lokal terminal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Dodaj",
"arrowDown": "Strzałka w dół",
"arrowLeft": "Strzałka w lewo",
"arrowRight": "Strzałka w prawo",
"arrowUp": "Strzałka w górę",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Gotowe",
"editQuickKeys": "Edytuj szybkie klawisze",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Wklej",
"quickKeyPlaceholder": "np. sudo ",
"quickKeysDesc": "Dotknij ×, aby usunąć. Maksymalnie 8 znaków.",
"quickKeysTitle": "Szybkie klawisze",
"removeQuickKey": "Usuń",
"resetDefaults": "Przywróć ustawienia domyślne",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Nie udało się włączyć funkcji Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux włączony dla {{host}}. Ma zastosowanie od następnego połączenia.",
"clipboardHttpWarning": "Nie udało się odczytać schowka. Niektóre przeglądarki ograniczają dostęp do schowka poza HTTPS. Użyj Ctrl+V albo udostępniaj Termix przez HTTPS.",
"closeTab": "Zamknij",
"connecting": "Łączenie...",
"connectionLost": "Utracono połączenie",
"connectionRejected": "Połączenie odrzucone przez serwer. Sprawdź uwierzytelnianie i konfigurację sieci.",
"connectionTimeout": "Przekroczono limit czasu połączenia",
"enableAutoTmuxAction": "Włącz Auto-Tmux",
"hostKeyRejected": "Weryfikacja klucza hosta SSH odrzucona. Połączenie anulowane.",
"linkDialogCopy": "Kopiuj",
"linkDialogOpen": "Otwórz",
"linkDialogTitle": "Otwórz link",
"maxReconnectAttemptsReached": "Osiągnięto maksymalną liczbę prób ponownego połączenia",
"noHostSelected": "Nie wybrano hosta",
"opksshAuthFailed": "Uwierzytelnienie nie powiodło się. Sprawdź poświadczenia i spróbuj ponownie.",
"opksshTimeout": "Upłynął limit czasu uwierzytelniania. Spróbuj ponownie.",
"passwordPromptFillTitle": "Czy wpisać zapisane hasło w tym monicie?",
"reconnect": "Połącz ponownie",
"reconnected": "Połączono ponownie pomyślnie",
"reconnecting": "Ponowne łączenie... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Uwzględnij wielkość liter",
"searchClose": "Zamknij (Esc)",
"searchNext": "Następne dopasowanie (Enter)",
"searchNoResults": "Brak wyników",
"searchPlaceholder": "Znajdź",
"searchPrevious": "Poprzednie dopasowanie (Shift+Enter)",
"searchRegex": "Użyj wyrażenia regularnego",
"searchResultCount": "{{index}} z {{count}}",
"searchWholeWord": "Dopasuj całe słowo",
"sessionExpiredNotice": "Twoja poprzednia sesja z {{host}} wygasła po {{minutes}} minutach odłączenia. Powłoka i wszystko, co w niej działa, zniknęło. Włącz Auto-Tmux na tym hoście, aby sesje pozostały aktywne.",
"sessionExpiredNoticeNoMinutes": "Twoja poprzednia sesja na {{host}} wygasła podczas rozłączenia; powłoka i wszystko, co w niej działa, zniknęło. Włącz Auto-Tmux na tym hoście, aby sesje pozostały aktywne.",
"sessionTakenOver": "Sesja została otwarta w innej karcie. Ponowne łączenie...",
"tmuxAttached": "Dołączeni klienci",
"tmuxAttachedCount": "{{count}} dołączonych",
"tmuxCopyHint": "Dostosuj zaznaczenie i naciśnij Enter, aby skopiować do schowka",
"tmuxCreateNew": "Rozpocznij nową sesję",
"tmuxDetached": "Odłączono od sesji tmux",
"tmuxLastActivity": "Ostatnia aktywność",
"tmuxSessionAttached": "Dołączono do sesji tmux: {{name}}",
"tmuxSessionCreated": "Utworzono sesję tmux: {{name}}",
"tmuxSessionPickerDesc": "Znaleziono istniejące sesje tmux na tym hoście. Wybierz jedną, aby ponownie dołączyć lub utworzyć nową sesję.",
"tmuxSessionPickerTitle": "Sesje tmux",
"tmuxTimeDays": "{{count}} dni temu",
"tmuxTimeHours": "{{count}} godz. temu",
"tmuxTimeJustNow": "przed chwilą",
"tmuxTimeMinutes": "{{count}} min temu",
"tmuxUnavailable": "tmux nie jest zainstalowany na zdalnym hoście, następuje powrót do standardowej powłoki",
"tmuxWindowCount": "{{count}} okno",
"tmuxWindows": "Okna",
"unknownError": "Wystąpił nieznany błąd",
"vaultAuthDescription": "Otworzyło się okno logowania do HashiCorp Vault. Dokończ logowanie, a połączenie zostanie nawiązane automatycznie.",
"vaultAuthFailed": "Uwierzytelnienie w Vault nie powiodło się. Spróbuj ponownie.",
"vaultAuthTitle": "Wymagane logowanie do Vault",
"vaultReopen": "Otwórz ponownie okno logowania",
"websocketAbnormalClose": "Połączenie zostało nieoczekiwanie zamknięte. Przyczyną może być reverse proxy lub problem z konfiguracją SSL. Sprawdź logi serwera.",
"websocketError": "Błąd połączenia WebSocket"
},
"terminalToolbar": {
"detachTmux": "Odłącz tmux",
"detachTmuxDescription": "Odłącz sesję tmux",
"dismissError": "Odrzuć błąd obrazu",
"hideToolbar": "Ukryj pasek narzędzi",
"image": "Akcje obrazu (tylko obrazy)",
"imageActionFailed": "Akcja obrazu nie powiodła się",
"layout": "Tryb wyświetlania paska narzędzi",
"layoutExpanded": "Rozszerzone o metryki",
"layoutIcon": "Tylko ikona",
"layoutLabeled": "Ikony i etykiety",
"moveToolbar": "Przenieś pasek narzędzi",
"moveToolbarHint": "Przeciągnij, aby zmienić położenie paska narzędzi",
"paste": "Wklej obraz",
"pasteImage": "Wklej obraz ze schowka (tylko obrazy)",
"retry": "Ponów",
"showToolbar": "Rozwiń pasek narzędzi",
"upload": "Prześlij obraz",
"uploadImage": "Prześlij obraz do hosta",
"uploadingImage": "Przesyłanie..."
},
"history": {
"noTerminalSelected": "Nie wybrano terminala",
"noTerminalSelectedHint": "Otwórz kartę terminala SSH, aby wyświetlić historię poleceń",
"searchPlaceholder": "Przeszukaj historię...",
"clearAll": "Wyczyść wszystko",
"noHistoryEntries": "Brak wpisów w historii",
"trackingDisabled": "Śledzenie historii jest wyłączone"
},
"touchInput": {
"enabled": "Włącz wprowadzanie dotykowe",
"enabledDesc": "Zezwól na przewijanie jednym palcem w terminalach",
"momentumEnabled": "Włącz Momentum",
"momentumEnabledDesc": "Kontynuuj przewijanie przez chwilę po przesunięciu",
"advanced": "Zaawansowane",
"resetDefaults": "Przywróć ustawienia domyślne",
"dragThreshold": "Próg przeciągania",
"maxWheelDelta": "Maksymalna delta koła",
"momentumSampleWindow": "Okno próbki pędu",
"releaseGracePeriod": "Okres karencji po puszczeniu",
"minimumVelocity": "Minimalna prędkość",
"maximumVelocity": "Maksymalna prędkość",
"maximumDuration": "Maksymalny czas trwania",
"maximumTravel": "Maksymalny dystans",
"decayTime": "Czas wygaszania",
"pixelsPerTick": "Pikseli na tick",
"maximumFrameInterval": "Maksymalny interwał klatek",
"maximumTicksPerFrame": "Maksymalna liczba tików na klatkę"
},
"imageStorage": {
"instanceId": "Identyfikator instancji sesji terminalowej",
"instanceIdDesc": "Identyfikator instancji jednej z podłączonych kart terminala, używany do testowania widoczności.",
"selectedModeUnavailable": "Niedostępny",
"test": "Test",
"testing": "Testowanie...",
"testFailed": "Nie udało się przetestować magazynu obrazów",
"testConnected": "Sesja terminalowa jest połączona.",
"testNotConnected": "Brak połączonej sesji terminala z tym identyfikatorem instancji.",
"testSelectedMode": "Przesyłanie użyłoby: {{mode}}",
"testLocalVisible": "Katalog lokalny jest widoczny z podłączonego hosta.",
"testLocalNotVisible": "Katalog lokalny NIE jest widoczny z podłączonego hosta."
},
"settings": {
"admin": {
"group": {
"touch": "Wprowadzanie dotykowe",
"images": "Przechowywanie obrazów"
},
"sessionTimeoutMinutes": {
"label": "Trwałość sesji terminalowej",
"description": "Jak długo odłączony terminal jest utrzymywany aktywny na serwerze przed jego zamknięciem. Min. 1 · Maks. 1440 minut. W przypadku sesji, które muszą przetrwać ten czas, włącz funkcję Auto-Tmux na hoście."
},
"commandHistoryEnabled": {
"label": "Historia poleceń",
"description": "Zezwól na rejestrowanie historii poleceń. Po wyłączeniu historia nie jest zapisywana, niezależnie od ustawień poszczególnych hostów."
},
"imageStorageMode": {
"label": "Tryb przechowywania",
"description": "Gdzie zapisywane są wklejone i przesłane obrazy z terminala. Auto wybiera według połączenia; Lokalny i Zdalny (SFTP) są stałe.",
"auto": "Auto",
"local": "Lokalny",
"remoteSftp": "Zdalny (SFTP)"
},
"imageLocalDir": {
"label": "Katalog lokalny"
},
"imageHostPath": {
"label": "Ścieżka hosta",
"description": "Widok katalogu lokalnego od strony hosta, który otrzymują agenci terminalowi."
},
"imageTtlMs": {
"label": "Czas życia (ms)"
},
"imageMaxCount": {
"label": "Maksymalna liczba obrazów"
},
"imageMaxBytes": {
"label": "Maksymalny rozmiar magazynu (bajty)"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Włącz pasek narzędzi terminala",
"description": "Pokaż pasek narzędzi szybkiego dostępu zadokowany do aktywnego terminala, umożliwiający dostęp do narzędzi i statystyk na żywo tego hosta"
},
"enableCommandHistory": {
"label": "Historia poleceń",
"description": "Zapisuj polecenia uruchamiane w tym terminalu na potrzeby historii i autouzupełniania"
},
"enableTerminal": {
"label": "Włącz terminal"
}
}
},
"palette": {
"localTerminal": "Terminal lokalny"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Adicionar",
"arrowDown": "Seta para baixo",
"arrowLeft": "Seta para a esquerda",
"arrowRight": "Seta para a direita",
"arrowUp": "Seta para cima",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Feito",
"editQuickKeys": "Editar teclas de atalho",
"end": "Fim",
"esc": "Esc",
"home": "Lar",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Colar",
"quickKeyPlaceholder": "por exemplo sudo ",
"quickKeysDesc": "Toque em × para remover. Suporta até 8 caracteres.",
"quickKeysTitle": "Teclas de atalho",
"removeQuickKey": "Remover",
"resetDefaults": "Restaurar padrões de fábrica",
"shift": "Mudança",
"tab": "Aba"
},
"terminal": {
"autoTmuxEnableFailed": "Falha ao ativar o Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux ativado para {{host}}. Aplica-se a partir da próxima conexão.",
"clipboardHttpWarning": "Não foi possível ler a área de transferência. Alguns navegadores restringem o acesso à área de transferência fora de HTTPS - tente Ctrl+V ou utilize HTTPS para acessar o Termix.",
"closeTab": "Fechar",
"connecting": "Conectando...",
"connectionLost": "Conexão perdida",
"connectionRejected": "Conexão rejeitada pelo servidor. Verifique sua autenticação e configuração de rede.",
"connectionTimeout": "Tempo limite de conexão",
"enableAutoTmuxAction": "Ativar Auto-Tmux",
"hostKeyRejected": "Verificação da chave SSH do host rejeitada. Conexão cancelada.",
"linkDialogCopy": "Cópia",
"linkDialogOpen": "Abrir",
"linkDialogTitle": "Abrir link",
"maxReconnectAttemptsReached": "Número máximo de tentativas de reconexão atingido",
"noHostSelected": "Nenhum host selecionado",
"opksshAuthFailed": "A autenticação falhou. Verifique suas credenciais e tente novamente.",
"opksshTimeout": "A autenticação expirou. Tente novamente.",
"passwordPromptFillTitle": "Digite a senha salva neste campo?",
"reconnect": "Reconectar",
"reconnected": "Reconectado com sucesso",
"reconnecting": "Reconectando... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Caixa de fósforos",
"searchClose": "Fechar (Esc)",
"searchNext": "Próxima partida (Entrar)",
"searchNoResults": "Nenhum resultado",
"searchPlaceholder": "Encontrar",
"searchPrevious": "Partida anterior (Shift+Enter)",
"searchRegex": "Utilizar expressão regular",
"searchResultCount": "{{index}} de {{count}}",
"searchWholeWord": "Combine a palavra inteira",
"sessionExpiredNotice": "Sua sessão anterior em {{host}} expirou após {{minutes}} minutos desconectado; o shell e tudo o que estava sendo executado nele foram perdidos. Habilite o Auto-Tmux neste host para manter as sessões ativas.",
"sessionExpiredNoticeNoMinutes": "Sua sessão anterior em {{host}} expirou enquanto estava desconectada; o shell e tudo o que estava sendo executado nele foram perdidos. Habilite o Auto-Tmux neste host para manter as sessões ativas.",
"sessionTakenOver": "A sessão foi aberta em outra aba. Reconectando...",
"tmuxAttached": "Clientes vinculados",
"tmuxAttachedCount": "{{count}} anexado",
"tmuxCopyHint": "Ajuste a seleção e pressione Enter para copiar para a área de transferência.",
"tmuxCreateNew": "Iniciar nova sessão",
"tmuxDetached": "Sessão desconectada do tmux",
"tmuxLastActivity": "Última atividade",
"tmuxSessionAttached": "Sessão tmux anexada: {{name}}",
"tmuxSessionCreated": "Sessão tmux criada: {{name}}",
"tmuxSessionPickerDesc": "Foram encontradas sessões tmux existentes neste host. Selecione uma para reconectar ou crie uma nova sessão.",
"tmuxSessionPickerTitle": "Sessões tmux",
"tmuxTimeDays": "{{count}}há alguns dias",
"tmuxTimeHours": "{{count}}h atrás",
"tmuxTimeJustNow": "agora mesmo",
"tmuxTimeMinutes": "{{count}}m atrás",
"tmuxUnavailable": "O tmux não está instalado no host remoto, utilizando o shell padrão como alternativa.",
"tmuxWindowCount": "{{count}} janela",
"tmuxWindows": "Windows",
"unknownError": "Ocorreu um erro desconhecido.",
"vaultAuthDescription": "Uma janela foi aberta para fazer login no HashiCorp Vault. Conclua o login; esta conexão será estabelecida automaticamente.",
"vaultAuthFailed": "A autenticação do cofre falhou. Tente novamente.",
"vaultAuthTitle": "É necessário fazer login no cofre.",
"vaultReopen": "Reabrir janela de login",
"websocketAbnormalClose": "A conexão foi encerrada inesperadamente. Isso pode ter ocorrido devido a um problema de configuração de proxy reverso ou SSL. Verifique os registros do servidor.",
"websocketError": "Erro de conexão WebSocket"
},
"terminalToolbar": {
"detachTmux": "Desanexar tmux",
"detachTmuxDescription": "Desanexar sessão tmux",
"dismissError": "Ignorar erro de imagem",
"hideToolbar": "Ocultar barra de ferramentas",
"image": "Ações de imagem (somente imagens)",
"imageActionFailed": "A ação de imagem falhou",
"layout": "Modo de exibição da barra de ferramentas",
"layoutExpanded": "Expandido com métricas",
"layoutIcon": "Apenas o ícone",
"layoutLabeled": "Ícones e rótulos",
"moveToolbar": "Mover barra de ferramentas",
"moveToolbarHint": "Arraste para reposicionar a barra de ferramentas.",
"paste": "Colar imagem",
"pasteImage": "Colar imagem da área de transferência (somente imagens)",
"retry": "Tentar novamente",
"showToolbar": "Expandir barra de ferramentas",
"upload": "Carregar imagem",
"uploadImage": "Faça o upload de uma imagem para o host.",
"uploadingImage": "Carregando..."
},
"history": {
"noTerminalSelected": "Nenhum terminal selecionado",
"noTerminalSelectedHint": "Abra uma aba do terminal SSH para visualizar seu histórico de comandos.",
"searchPlaceholder": "Histórico de pesquisa...",
"clearAll": "Limpar tudo",
"noHistoryEntries": "Nenhuma entrada no histórico.",
"trackingDisabled": "O rastreamento do histórico está desativado."
},
"touchInput": {
"enabled": "Ativar entrada por toque",
"enabledDesc": "Permitir rolagem com um único dedo em terminais.",
"momentumEnabled": "Ativar o impulso",
"momentumEnabledDesc": "Continue rolando brevemente após deslizar o dedo.",
"advanced": "Avançado",
"resetDefaults": "Restaurar padrões de fábrica",
"dragThreshold": "Limiar de arrasto",
"maxWheelDelta": "Delta máximo da roda",
"momentumSampleWindow": "Janela de amostra de momento",
"releaseGracePeriod": "Período de carência para lançamento",
"minimumVelocity": "Velocidade mínima",
"maximumVelocity": "Velocidade máxima",
"maximumDuration": "Duração máxima",
"maximumTravel": "Viagem máxima",
"decayTime": "Tempo de decaimento",
"pixelsPerTick": "Pixels por tick",
"maximumFrameInterval": "Intervalo máximo entre quadros",
"maximumTicksPerFrame": "Número máximo de ticks por quadro"
},
"imageStorage": {
"instanceId": "ID da instância da sessão do terminal",
"instanceIdDesc": "ID da instância de uma das suas abas de terminal conectadas, usada para testar a visibilidade.",
"selectedModeUnavailable": "Indisponível",
"test": "Teste",
"testing": "Testando...",
"testFailed": "Falha ao testar o armazenamento de imagens",
"testConnected": "A sessão do terminal está conectada.",
"testNotConnected": "Nenhuma sessão de terminal conectada com este ID de instância.",
"testSelectedMode": "Um upload usaria: {{mode}}",
"testLocalVisible": "O diretório local está visível a partir do host conectado.",
"testLocalNotVisible": "O diretório local NÃO está visível a partir do host conectado."
},
"settings": {
"admin": {
"group": {
"touch": "Entrada por toque",
"images": "Armazenamento de imagens"
},
"sessionTimeoutMinutes": {
"label": "Persistência de sessão do terminal",
"description": "Tempo que um terminal desconectado permanece ativo no servidor antes de ser fechado. Mínimo 1 minuto · Máximo 1440 minutos. Para sessões que precisam durar mais do que isso, habilite o Auto-Tmux no host."
},
"commandHistoryEnabled": {
"label": "Histórico de comandos",
"description": "Permitir o registro do histórico de comandos. Quando desativado, o histórico não é salvo, independentemente das configurações de cada host."
},
"imageStorageMode": {
"label": "Modo de armazenamento",
"description": "Local onde as imagens de terminal coladas e carregadas são exibidas. As seleções automáticas por conexão, Local e Remota (SFTP), foram corrigidas.",
"auto": "Auto",
"local": "Local",
"remoteSftp": "Remoto (SFTP)"
},
"imageLocalDir": {
"label": "Diretório local"
},
"imageHostPath": {
"label": "Caminho do host",
"description": "Visão do diretório local recebida pelos agentes de terminal, do lado do host."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Contagem máxima de imagens"
},
"imageMaxBytes": {
"label": "Bytes de armazenamento máximo"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Ativar a barra de ferramentas do terminal",
"description": "Exibir uma barra de ferramentas de acesso rápido acoplada ao terminal ativo com as ferramentas e estatísticas em tempo real deste host."
},
"enableCommandHistory": {
"label": "Histórico de comandos",
"description": "Registre os comandos executados neste terminal para histórico e preenchimento automático."
},
"enableTerminal": {
"label": "Habilitar terminal"
}
}
},
"palette": {
"localTerminal": "Terminal local"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Adicionar",
"arrowDown": "Seta para Baixo",
"arrowLeft": "Seta para a Esquerda",
"arrowRight": "Seta para a Direita",
"arrowUp": "Seta para Cima",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Concluir",
"editQuickKeys": "Editar teclas rápidas",
"end": "Fim",
"esc": "Esc",
"home": "Início",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Colar",
"quickKeyPlaceholder": "ex: sudo ",
"quickKeysDesc": "Toque no × para remover. Suporta até 8 caracteres.",
"quickKeysTitle": "Teclas Rápidas",
"removeQuickKey": "Remover",
"resetDefaults": "Restaurar predefinições",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Falha ao ativar o Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux ativado para {{host}}. Aplica-se a partir da ligação seguinte.",
"clipboardHttpWarning": "Não foi possível ler a área de transferência. Alguns browsers restringem o acesso à área de transferência fora de HTTPS - experimente Ctrl+V ou utilize HTTPS para aceder ao Termix.",
"closeTab": "Fechar",
"connecting": "A ligar...",
"connectionLost": "Ligação perdida",
"connectionRejected": "Ligação rejeitada pelo servidor. Verifique a sua autenticação e configuração de rede.",
"connectionTimeout": "Tempo limite da ligação",
"enableAutoTmuxAction": "Ativar o Auto-Tmux",
"hostKeyRejected": "Verificação da chave de host SSH rejeitada. Ligação cancelada.",
"linkDialogCopy": "Copiar",
"linkDialogOpen": "Abrir",
"linkDialogTitle": "Abrir Ligação",
"maxReconnectAttemptsReached": "Número máximo de tentativas de restabelecimento de ligação atingido",
"noHostSelected": "Nenhum host selecionado",
"opksshAuthFailed": "A autenticação falhou. Verifique as suas credenciais e tente novamente.",
"opksshTimeout": "A autenticação expirou. Tente novamente.",
"passwordPromptFillTitle": "Preencher a palavra-passe guardada neste pedido?",
"reconnect": "Reconectar",
"reconnected": "Ligação restabelecida com sucesso",
"reconnecting": "A restabelecer ligação... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Caixa de fósforos",
"searchClose": "Fechar (Esc)",
"searchNext": "Próxima partida (Entrar)",
"searchNoResults": "Nenhum resultado",
"searchPlaceholder": "Encontrar",
"searchPrevious": "Partida anterior (Shift+Enter)",
"searchRegex": "Utilizar expressão regular",
"searchResultCount": "{{index}} de {{count}}",
"searchWholeWord": "Combine a palavra inteira",
"sessionExpiredNotice": "A sua sessão anterior em {{host}} expirou após {{minutes}} minutos desligado; o shell e tudo o que estava a ser executado nele foram perdidos. Active o Auto-Tmux neste host para manter as sessões ativas.",
"sessionExpiredNoticeNoMinutes": "A sua sessão anterior em {{host}} expirou enquanto estava desligada; o shell e tudo o que estava a ser executado nele foram perdidos. Active o Auto-Tmux neste host para manter as sessões ativas.",
"sessionTakenOver": "A sessão foi aberta noutro separador. A restabelecer ligação...",
"tmuxAttached": "Clientes anexados",
"tmuxAttachedCount": "{{count}} anexado",
"tmuxCopyHint": "Ajuste a seleção e prima Enter para copiar para a área de transferência",
"tmuxCreateNew": "Iniciar nova sessão",
"tmuxDetached": "Desanexado da sessão tmux",
"tmuxLastActivity": "Última atividade",
"tmuxSessionAttached": "Sessão tmux anexada: {{name}}",
"tmuxSessionCreated": "Sessão tmux criada: {{name}}",
"tmuxSessionPickerDesc": "Foram encontradas sessões tmux neste host. Selecione uma para retomar ou crie uma nova sessão.",
"tmuxSessionPickerTitle": "Sessões tmux",
"tmuxTimeDays": "há {{count}} d",
"tmuxTimeHours": "há {{count}} h",
"tmuxTimeJustNow": "agora mesmo",
"tmuxTimeMinutes": "há {{count}} min",
"tmuxUnavailable": "O tmux não está instalado no host remoto, a reverter para shell padrão",
"tmuxWindowCount": "{{count}} janela",
"tmuxWindows": "Janelas",
"unknownError": "Ocorreu um erro desconhecido.",
"vaultAuthDescription": "Foi aberta uma janela para iniciar sessão no HashiCorp Vault. Conclua o início de sessão aí; esta ligação continuará automaticamente.",
"vaultAuthFailed": "A autenticação no Vault falhou. Tente novamente.",
"vaultAuthTitle": "Início de sessão no Vault necessário",
"vaultReopen": "Reabrir janela de início de sessão",
"websocketAbnormalClose": "A ligação foi fechada inesperadamente. Isto pode dever-se a um problema de configuração do proxy reverso ou SSL. Verifique os registos do servidor.",
"websocketError": "Erro de ligação WebSocket"
},
"terminalToolbar": {
"detachTmux": "Desanexar tmux",
"detachTmuxDescription": "Desanexar sessão tmux",
"dismissError": "Ignorar erro de imagem",
"hideToolbar": "Ocultar barra de ferramentas",
"image": "Ações de imagem (apenas imagens)",
"imageActionFailed": "A ação de imagem falhou",
"layout": "Modo de visualização da barra de ferramentas",
"layoutExpanded": "Expandido com métricas",
"layoutIcon": "Apenas o ícone",
"layoutLabeled": "Ícones e rótulos",
"moveToolbar": "Mover barra de ferramentas",
"moveToolbarHint": "Arraste para reposicionar a barra de ferramentas.",
"paste": "Colar imagem",
"pasteImage": "Colar imagem da área de transferência (apenas imagens)",
"retry": "Tentar novamente",
"showToolbar": "Expandir barra de ferramentas",
"upload": "Carregar imagem",
"uploadImage": "Faça o upload de uma imagem para o host.",
"uploadingImage": "Carregando..."
},
"history": {
"noTerminalSelected": "Nenhum terminal selecionado",
"noTerminalSelectedHint": "Abra um separador de terminal SSH para ver o histórico de comandos",
"searchPlaceholder": "Pesquisar histórico...",
"clearAll": "Limpar Tudo",
"noHistoryEntries": "Sem entradas no histórico",
"trackingDisabled": "O rastreio do histórico está desativado"
},
"touchInput": {
"enabled": "Ativar entrada por toque",
"enabledDesc": "Permitir a rolagem com um único dedo nos terminais.",
"momentumEnabled": "Ativar o impulso",
"momentumEnabledDesc": "Continue a rolar brevemente após deslizar o dedo.",
"advanced": "Avançado",
"resetDefaults": "Restaurar as predefinições de fábrica",
"dragThreshold": "Limiar de arrasto",
"maxWheelDelta": "Delta máximo da roda",
"momentumSampleWindow": "Janela de amostra de momento",
"releaseGracePeriod": "Período de carência para lançamento",
"minimumVelocity": "Velocidade mínima",
"maximumVelocity": "Velocidade máxima",
"maximumDuration": "Duração máxima",
"maximumTravel": "Viagem máxima",
"decayTime": "Tempo de decaimento",
"pixelsPerTick": "Pixels por tick",
"maximumFrameInterval": "Intervalo máximo entre fotogramas",
"maximumTicksPerFrame": "Número máximo de ticks por frame"
},
"imageStorage": {
"instanceId": "ID da instância da sessão do terminal",
"instanceIdDesc": "ID da instância de uma das suas abas de terminal ligadas, utilizada para testar a visibilidade.",
"selectedModeUnavailable": "Indisponível",
"test": "Teste",
"testing": "Testando...",
"testFailed": "Falha ao testar o armazenamento de imagens",
"testConnected": "A sessão do terminal está ligada.",
"testNotConnected": "Nenhuma sessão de terminal ligada com este ID de instância.",
"testSelectedMode": "Um upload utilizaria: {{mode}}",
"testLocalVisible": "O diretório local está visível a partir do host ligado.",
"testLocalNotVisible": "O diretório local NÃO está visível a partir do host ligado."
},
"settings": {
"admin": {
"group": {
"touch": "Entrada por toque",
"images": "Armazenamento de imagens"
},
"sessionTimeoutMinutes": {
"label": "Persistência de sessão do terminal",
"description": "Tempo que um terminal desligado permanece ativo no servidor antes de ser fechado. Mínimo 1 minuto · Máximo 1440 minutos. Para sessões que necessitem de durar mais do que isso, ative o Auto-Tmux no host."
},
"commandHistoryEnabled": {
"label": "Histórico de Comandos",
"description": "Permitir o registo do histórico de comandos. Quando desativado, o histórico não é guardado independentemente das definições por máquina."
},
"imageStorageMode": {
"label": "Modo de armazenamento",
"description": "Local onde são apresentadas as imagens de terminal coladas e carregadas. As seleções automáticas por ligação, Local e Remota (SFTP), foram corrigidas.",
"auto": "Auto",
"local": "Local",
"remoteSftp": "Remoto (SFTP)"
},
"imageLocalDir": {
"label": "Diretório local"
},
"imageHostPath": {
"label": "Caminho do host",
"description": "Vista do diretório local recebida pelos agentes de terminal, do lado do host."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Contagem máxima de imagens"
},
"imageMaxBytes": {
"label": "Bytes de armazenamento máximo"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Ativar a barra de ferramentas do terminal",
"description": "Visualizar uma barra de ferramentas de acesso rápido acoplada ao terminal ativo com as ferramentas e estatísticas em tempo real deste host."
},
"enableCommandHistory": {
"label": "Histórico de comandos",
"description": "Gravar comandos executados neste terminal para histórico e autocompletar"
},
"enableTerminal": {
"label": "Ativar Terminal"
}
}
},
"palette": {
"localTerminal": "Terminal local"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Adăuga",
"arrowDown": "Săgeată în jos",
"arrowLeft": "Săgeată stânga",
"arrowRight": "Săgeată dreapta",
"arrowUp": "Săgeată în sus",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Șterge",
"done": "Făcut",
"editQuickKeys": "Editați tastele rapide",
"end": "Sfârşit",
"esc": "Esc",
"home": "Acasă",
"pageDown": "Pagină următoare",
"pageUp": "PagUp",
"paste": "Pastă",
"quickKeyPlaceholder": "de exemplu sudo ",
"quickKeysDesc": "Atingeți × pentru a elimina. Acceptă până la 8 caractere.",
"quickKeysTitle": "Taste rapide",
"removeQuickKey": "Elimina",
"resetDefaults": "Resetare la setările implicite",
"shift": "Schimbare",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Nu s-a putut activa Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux activat pentru {{host}}. Se aplică de la următoarea conexiune.",
"clipboardHttpWarning": "Nu s-a putut citi clipboard-ul. Unele browsere restricționează accesul la clipboard în afara HTTPS - încercați Ctrl+V sau serviți Termix prin HTTPS.",
"closeTab": "Aproape",
"connecting": "Se conectează...",
"connectionLost": "Conexiune pierdută",
"connectionRejected": "Conexiune respinsă de server. Vă rugăm să verificați autentificarea și configurația rețelei.",
"connectionTimeout": "Expirare conexiune",
"enableAutoTmuxAction": "Activează Auto-Tmux",
"hostKeyRejected": "Verificarea cheii gazdei SSH a fost respinsă. Conexiunea a fost anulată.",
"linkDialogCopy": "Copie",
"linkDialogOpen": "Deschide",
"linkDialogTitle": "Deschideți linkul",
"maxReconnectAttemptsReached": "S-a atins numărul maxim de încercări de reconectare",
"noHostSelected": "Nicio gazdă selectată",
"opksshAuthFailed": "Autentificarea a eșuat. Verificați acreditările și încercați din nou.",
"opksshTimeout": "Autentificarea a expirat. Vă rugăm să încercați din nou.",
"passwordPromptFillTitle": "Introduceți parola salvată în această solicitare?",
"reconnect": "Reconectați-vă",
"reconnected": "Reconectare cu succes",
"reconnecting": "Se reconecta... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Potrivire caz",
"searchClose": "Închide (Escape)",
"searchNext": "Următorul meci (Introduce)",
"searchNoResults": "Niciun rezultat",
"searchPlaceholder": "Găsi",
"searchPrevious": "Potrivire anterioară (Shift+Enter)",
"searchRegex": "Folosește expresia regulată",
"searchResultCount": "{{index}} din {{count}}",
"searchWholeWord": "Potrivire cuvânt întreg",
"sessionExpiredNotice": "Sesiunea anterioară de pe {{host}} a expirat după {{minutes}} minute de deconectare; shell-ul și tot ce rulează în el au dispărut. Activați Auto-Tmux pe această gazdă pentru a menține sesiunile active.",
"sessionExpiredNoticeNoMinutes": "Sesiunea anterioară pe {{host}} a expirat în timp ce era deconectat; shell-ul și tot ce rulează în el au dispărut. Activați Auto-Tmux pe această gazdă pentru a menține sesiunile active.",
"sessionTakenOver": "Sesiunea a fost deschisă într-o altă filă. Se reconectare...",
"tmuxAttached": "Clienți atașați",
"tmuxAttachedCount": "{{count}} atașat",
"tmuxCopyHint": "Ajustați selecția și apăsați Enter pentru a copia în clipboard",
"tmuxCreateNew": "Începeți o sesiune nouă",
"tmuxDetached": "Detașat de sesiunea tmux",
"tmuxLastActivity": "Ultima activitate",
"tmuxSessionAttached": "sesiune tmux atașată: {{name}}",
"tmuxSessionCreated": "Sesiune tmux creată: {{name}}",
"tmuxSessionPickerDesc": "Au fost găsite sesiuni tmux existente pe această gazdă. Selectați una pentru a o reatașa sau pentru a crea o sesiune nouă.",
"tmuxSessionPickerTitle": "Sesiuni tmux",
"tmuxTimeDays": "{{count}}zile în urmă",
"tmuxTimeHours": "{{count}}acum o oră",
"tmuxTimeJustNow": "chiar acum",
"tmuxTimeMinutes": "{{count}}acum câteva minute",
"tmuxUnavailable": "tmux nu este instalat pe gazda la distanță, revenind la shell-ul standard",
"tmuxWindowCount": "fereastră {{count}}",
"tmuxWindows": "Ferestre",
"unknownError": "A apărut o eroare necunoscută",
"vaultAuthDescription": "S-a deschis o fereastră pentru a vă conecta la HashiCorp Vault. Finalizați conectarea acolo; această conexiune va continua automat.",
"vaultAuthFailed": "Autentificarea în seif a eșuat. Vă rugăm să încercați din nou.",
"vaultAuthTitle": "Este necesară conectarea la seif",
"vaultReopen": "Redeschideți fereastra de conectare",
"websocketAbnormalClose": "Conexiunea s-a închis pe neașteptate. Acest lucru se poate datora unei probleme de configurare a proxy-ului invers sau a SSL. Vă rugăm să verificați jurnalele serverului.",
"websocketError": "Eroare de conexiune WebSocket"
},
"terminalToolbar": {
"detachTmux": "Detașați tmux",
"detachTmuxDescription": "Detașați sesiunea tmux",
"dismissError": "Eroare de închidere a imaginii",
"hideToolbar": "Ascunde bara de instrumente",
"image": "Acțiuni de imagine (doar imagini)",
"imageActionFailed": "Acțiunea imaginii a eșuat",
"layout": "Mod de afișare a barei de instrumente",
"layoutExpanded": "Extins cu valori metrice",
"layoutIcon": "Numai pictogramă",
"layoutLabeled": "Pictograme și etichete",
"moveToolbar": "Mută bara de instrumente",
"moveToolbarHint": "Trageți pentru a repoziționa bara de instrumente",
"paste": "Lipiți imaginea",
"pasteImage": "Lipiți imaginea din clipboard (doar imagini)",
"retry": "Reîncercați",
"showToolbar": "Extindeți bara de instrumente",
"upload": "Încărcați imaginea",
"uploadImage": "Încărcați o imagine către gazdă",
"uploadingImage": "Se încarcă..."
},
"history": {
"noTerminalSelected": "Niciun terminal selectat",
"noTerminalSelectedHint": "Deschideți o filă de terminal SSH pentru a vizualiza istoricul comenzilor sale",
"searchPlaceholder": "Istoricul căutărilor...",
"clearAll": "Ștergeți tot",
"noHistoryEntries": "Nicio intrare în istoric",
"trackingDisabled": "Urmărirea istoricului este dezactivată"
},
"touchInput": {
"enabled": "Activează introducerea tactilă",
"enabledDesc": "Permiteți derularea tactilă cu un singur deget în terminale",
"momentumEnabled": "Activează impulsul",
"momentumEnabledDesc": "Continuați derularea scurtă după o glisare",
"advanced": "Avansat",
"resetDefaults": "Resetare la setările implicite",
"dragThreshold": "Pragul de rezistență",
"maxWheelDelta": "Delta maximă a roții",
"momentumSampleWindow": "Fereastra de eșantionare a impulsului",
"releaseGracePeriod": "Perioada de grație a lansării",
"minimumVelocity": "Viteză minimă",
"maximumVelocity": "Viteză maximă",
"maximumDuration": "Durată maximă",
"maximumTravel": "Cursă maximă",
"decayTime": "Timp de descreștere",
"pixelsPerTick": "Pixeli per bifă",
"maximumFrameInterval": "Interval maxim de cadre",
"maximumTicksPerFrame": "Număr maxim de ticuri per cadru"
},
"imageStorage": {
"instanceId": "ID-ul instanței sesiunii terminale",
"instanceIdDesc": "ID-ul instanței uneia dintre filele terminalului conectat, folosit pentru a testa vizibilitatea.",
"selectedModeUnavailable": "Indisponibil",
"test": "Test",
"testing": "Testare...",
"testFailed": "Nu s-a putut testa stocarea imaginilor",
"testConnected": "Sesiunea terminalului este conectată.",
"testNotConnected": "Nicio sesiune de terminal conectată cu acest ID de instanță.",
"testSelectedMode": "O încărcare ar folosi: {{mode}}",
"testLocalVisible": "Directorul local este vizibil de pe gazda conectată.",
"testLocalNotVisible": "Directorul local NU este vizibil de pe gazda conectată."
},
"settings": {
"admin": {
"group": {
"touch": "Intrare tactilă",
"images": "Stocarea imaginilor"
},
"sessionTimeoutMinutes": {
"label": "Persistența sesiunii terminale",
"description": "Cât timp este menținut activ pe server un terminal deconectat înainte de a fi închis. Min 1 · Max 1440 de minute. Pentru sesiunile care trebuie să dureze mai mult decât această durată, activați Auto-Tmux pe gazdă."
},
"commandHistoryEnabled": {
"label": "Istoricul comenzilor",
"description": "Permite înregistrarea istoricului comenzilor. Când este dezactivată, istoricul nu este salvat, indiferent de setările pentru fiecare gazdă."
},
"imageStorageMode": {
"label": "Mod de stocare",
"description": "Unde sunt scrise imaginile terminalului lipite și încărcate. Selecțiile automate prin conexiune, Local și Remote (SFTP) sunt fixe.",
"auto": "Auto",
"local": "Local",
"remoteSftp": "La distanță (SFTP)"
},
"imageLocalDir": {
"label": "Director local"
},
"imageHostPath": {
"label": "Calea gazdă",
"description": "Vizualizarea din partea gazdei a directorului local pe care îl primesc agenții terminalului."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Număr maxim de imagini"
},
"imageMaxBytes": {
"label": "Capacitate maximă de stocare în octeți"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Activează bara de instrumente Terminal",
"description": "Afișează o bară de instrumente cu acces rapid atașată la terminalul activ pentru instrumentele și statisticile live ale acestei gazde"
},
"enableCommandHistory": {
"label": "Istoricul comenzilor",
"description": "Înregistrați comenzile executate în acest terminal pentru istoric și completare automată"
},
"enableTerminal": {
"label": "Activează terminalul"
}
}
},
"palette": {
"localTerminal": "Terminal local"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Добавить",
"arrowDown": "Вниз",
"arrowLeft": "Влево",
"arrowRight": "Вправо",
"arrowUp": "Вверх",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Готово",
"editQuickKeys": "Редактировать",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Вставить",
"quickKeyPlaceholder": "напр. sudo ",
"quickKeysDesc": "Нажмите × для удаления. Поддерживается до 8 символов.",
"quickKeysTitle": "Быстрые клавиши",
"removeQuickKey": "Удалить",
"resetDefaults": "Сбросить по умолчанию",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Не удалось включить Auto-Tmux.",
"autoTmuxEnabled": "Автоматический Tmux включен для {{host}}. Он применяется со следующего соединения.",
"clipboardHttpWarning": "Не удалось прочитать содержимое буфера обмена. Некоторые браузеры ограничивают доступ к буферу обмена вне протокола HTTPS — попробуйте использовать Ctrl+V или используйте Termix через HTTPS.",
"closeTab": "Закрыть",
"connecting": "Подключение...",
"connectionLost": "Соединение потеряно",
"connectionRejected": "Подключение отклонено сервером. Проверьте аутентификацию и сетевые настройки.",
"connectionTimeout": "Тайм-аут подключения",
"enableAutoTmuxAction": "Включить Auto-Tmux",
"hostKeyRejected": "Проверка ключа хоста SSH отклонена. Подключение отменено.",
"linkDialogCopy": "Копировать",
"linkDialogOpen": "Открыть",
"linkDialogTitle": "Открыть ссылку",
"maxReconnectAttemptsReached": "Достигнуто максимальное количество попыток переподключения",
"noHostSelected": "Не выбран хост",
"opksshAuthFailed": "Аутентификация не удалась. Проверьте учётные данные и попробуйте снова.",
"opksshTimeout": "Время аутентификации истекло. Попробуйте снова.",
"passwordPromptFillTitle": "Вставить сохраненный пароль в этот запрос?",
"reconnect": "Переподключиться",
"reconnected": "Успешно переподключено",
"reconnecting": "Переподключение... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Спичечный коробок",
"searchClose": "Закрыть (Выход)",
"searchNext": "Следующий матч (Вход)",
"searchNoResults": "Результаты отсутствуют",
"searchPlaceholder": "Находить",
"searchPrevious": "Предыдущий матч (Shift+Enter)",
"searchRegex": "Используйте регулярные выражения",
"searchResultCount": "{{index}} из {{count}}",
"searchWholeWord": "Сопоставьте целое слово",
"sessionExpiredNotice": "Ваша предыдущая сессия на {{host}} завершилась через {{minutes}} минут после разрыва соединения; оболочка и все запущенные в ней программы удалены. Включите Auto-Tmux на этом хосте, чтобы поддерживать сессии активными.",
"sessionExpiredNoticeNoMinutes": "Ваша предыдущая сессия на {{host}} завершилась во время отключения; оболочка и все запущенные в ней приложения удалены. Включите Auto-Tmux на этом хосте, чтобы поддерживать сессии активными.",
"sessionTakenOver": "Сеанс был открыт в другой вкладке. Переподключение...",
"tmuxAttached": "Подключённые клиенты",
"tmuxAttachedCount": "{{count}} подключено",
"tmuxCopyHint": "Скорректируйте выделение и нажмите Enter, чтобы скопировать в буфер обмена",
"tmuxCreateNew": "Начать новую сессию",
"tmuxDetached": "Отключён от сессии tmux",
"tmuxLastActivity": "Последняя активность",
"tmuxSessionAttached": "Сессия tmux подключена: {{name}}",
"tmuxSessionCreated": "Сессия tmux создана: {{name}}",
"tmuxSessionPickerDesc": "Найдены существующие сессии tmux на этом хосте. Выберите одну для переподключения или создайте новую сессию.",
"tmuxSessionPickerTitle": "Сессии tmux",
"tmuxTimeDays": "{{count}}д назад",
"tmuxTimeHours": "{{count}}ч назад",
"tmuxTimeJustNow": "только что",
"tmuxTimeMinutes": "{{count}}м назад",
"tmuxUnavailable": "tmux не установлен на удалённом хосте, переход на стандартную оболочку",
"tmuxWindowCount": "{{count}} окно",
"tmuxWindows": "Окна",
"unknownError": "Произошла неизвестная ошибка",
"vaultAuthDescription": "Открыто окно для входа в HashiCorp Vault. Завершите вход там; подключение продолжится автоматически.",
"vaultAuthFailed": "Аутентификация Vault не удалась. Попробуйте снова.",
"vaultAuthTitle": "Требуется вход в Vault",
"vaultReopen": "Повторно открыть окно входа",
"websocketAbnormalClose": "Соединение неожиданно закрыто. Возможно, проблема с обратным прокси или конфигурацией SSL. Проверьте журналы сервера.",
"websocketError": "Ошибка подключения WebSocket"
},
"terminalToolbar": {
"detachTmux": "Отсоединить tmux",
"detachTmuxDescription": "Отключить сессию tmux",
"dismissError": "Отклонить ошибку изображения",
"hideToolbar": "Скрыть панель инструментов",
"image": "Действия с изображениями (только с изображениями)",
"imageActionFailed": "Операция с изображением не удалась.",
"layout": "режим отображения панели инструментов",
"layoutExpanded": "Дополнено метриками",
"layoutIcon": "Только иконка",
"layoutLabeled": "Иконки и подписи",
"moveToolbar": "Переместить панель инструментов",
"moveToolbarHint": "Перетащите панель инструментов, чтобы изменить ее положение.",
"paste": "Вставить изображение",
"pasteImage": "Вставить изображение из буфера обмена (только изображения)",
"retry": "Повторить попытку",
"showToolbar": "Развернуть панель инструментов",
"upload": "Загрузить изображение",
"uploadImage": "Загрузите изображение на хостинг.",
"uploadingImage": "Загрузка..."
},
"history": {
"noTerminalSelected": "Терминал не выбран",
"noTerminalSelectedHint": "Откройте вкладку SSH-терминала, чтобы просмотреть историю команд",
"searchPlaceholder": "Поиск по истории...",
"clearAll": "Очистить всё",
"noHistoryEntries": "Нет записей в истории",
"trackingDisabled": "Отслеживание истории отключено"
},
"touchInput": {
"enabled": "Включить сенсорный ввод",
"enabledDesc": "Разрешить прокрутку одним пальцем в терминалах",
"momentumEnabled": "Включить импульс",
"momentumEnabledDesc": "После свайпа продолжите прокрутку еще немного.",
"advanced": "Передовой",
"resetDefaults": "Сбросить до значений по умолчанию",
"dragThreshold": "Порог сопротивления",
"maxWheelDelta": "Максимальная разница колес",
"momentumSampleWindow": "Окно выборки импульса",
"releaseGracePeriod": "Льготный период освобождения",
"minimumVelocity": "Минимальная скорость",
"maximumVelocity": "Максимальная скорость",
"maximumDuration": "Максимальная продолжительность",
"maximumTravel": "Максимальный ход",
"decayTime": "Время распада",
"pixelsPerTick": "Пикселей на такт",
"maximumFrameInterval": "Максимальный интервал кадров",
"maximumTicksPerFrame": "Максимальное количество тактов на кадр"
},
"imageStorage": {
"instanceId": "Идентификатор экземпляра терминальной сессии",
"instanceIdDesc": "Идентификатор экземпляра одной из подключенных вкладок терминала, используемый для проверки видимости.",
"selectedModeUnavailable": "Недоступно",
"test": "Тест",
"testing": "Тестирование...",
"testFailed": "Не удалось проверить хранилище изображений.",
"testConnected": "Терминальная сессия подключена.",
"testNotConnected": "Нет подключенной терминальной сессии с этим идентификатором экземпляра.",
"testSelectedMode": "Для загрузки будет использоваться следующий синтаксис: {{mode}}",
"testLocalVisible": "Локальный каталог виден с подключенного хоста.",
"testLocalNotVisible": "Локальный каталог НЕ виден с подключенного хоста."
},
"settings": {
"admin": {
"group": {
"touch": "Сенсорный ввод",
"images": "Хранилище изображений"
},
"sessionTimeoutMinutes": {
"label": "Сохранение сессии в терминале",
"description": "Как долго отключенный терминал остается активным на сервере до его закрытия. Мин. 1 · Макс. 1440 минут. Для сессий, которые должны длиться дольше этого времени, включите Auto-Tmux на хосте."
},
"commandHistoryEnabled": {
"label": "История команд",
"description": "Включить запись истории команд. При отключении история не сохраняется независимо от настроек хостов."
},
"imageStorageMode": {
"label": "Режим хранения",
"description": "Где записываются и загружаются образы терминала. Автоматический выбор по типу подключения, локальному и удаленному (SFTP) — это фиксированная настройка.",
"auto": "Авто",
"local": "Местный",
"remoteSftp": "Удаленный (SFTP)"
},
"imageLocalDir": {
"label": "Локальный каталог"
},
"imageHostPath": {
"label": "Путь к хосту",
"description": "Представление локального каталога на стороне хоста, которое получают терминальные агенты."
},
"imageTtlMs": {
"label": "TTL (мс)"
},
"imageMaxCount": {
"label": "Максимальное количество изображений"
},
"imageMaxBytes": {
"label": "Максимальный объем хранилища в байтах"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Включить панель инструментов терминала",
"description": "Отобразить панель быстрого доступа, закрепленную на активном терминале, для отображения инструментов и статистики этого хоста в режиме реального времени."
},
"enableCommandHistory": {
"label": "История команд",
"description": "Сохранять команды, выполняемые в этом терминале, для истории и автодополнения"
},
"enableTerminal": {
"label": "Включить терминал"
}
}
},
"palette": {
"localTerminal": "Локальный терминал"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Додај",
"arrowDown": "Стрелица надоле",
"arrowLeft": "Стрелица лево",
"arrowRight": "Стрелица десно",
"arrowUp": "Стрелица нагоре",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Дел",
"done": "Готово",
"editQuickKeys": "Уреди брзе тастере",
"end": "Крај",
"esc": "Излаз",
"home": "Дом",
"pageDown": "ПгДн",
"pageUp": "PgUp",
"paste": "Залепи",
"quickKeyPlaceholder": "нпр. sudo ",
"quickKeysDesc": "Додирните × да бисте уклонили. Подржава до 8 знакова.",
"quickKeysTitle": "Брзи тастери",
"removeQuickKey": "Уклони",
"resetDefaults": "Ресетуј на подразумеване вредности",
"shift": "Померање",
"tab": "Таб"
},
"terminal": {
"autoTmuxEnableFailed": "Није успело омогућавање аутоматског Tmux-а",
"autoTmuxEnabled": "Аутоматски Tmux је омогућен за {{host}}. Примењује се од следећег повезивања.",
"clipboardHttpWarning": "Није могуће прочитати међуспремник. Неки прегледачи ограничавају приступ међуспремнику ван HTTPS-а - покушајте са Ctrl+V или покрените Termix преко HTTPS-а.",
"closeTab": "Затвори",
"connecting": "Повезивање...",
"connectionLost": "Веза је прекинута",
"connectionRejected": "Сервер је одбио везу. Проверите аутентификацију и конфигурацију мреже.",
"connectionTimeout": "Временско ограничење везе",
"enableAutoTmuxAction": "Омогући аутоматски Tmux",
"hostKeyRejected": "Верификација SSH кључа хоста је одбијена. Веза је отказана.",
"linkDialogCopy": "Копија",
"linkDialogOpen": "Отворено",
"linkDialogTitle": "Отвори линк",
"maxReconnectAttemptsReached": "Достигнут је максималан број покушаја поновног повезивања",
"noHostSelected": "Није изабран домаћин",
"opksshAuthFailed": "Аутентификација није успела. Проверите своје акредитиве и покушајте поново.",
"opksshTimeout": "Временско ограничење за аутентификацију је истекло. Молимо покушајте поново.",
"passwordPromptFillTitle": "Унесите сачувану лозинку у овај промпт?",
"reconnect": "Поново се повежите",
"reconnected": "Поново повезано успешно",
"reconnecting": "Поновно повезивање... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Упаривање великог и малог дела",
"searchClose": "Затвори (Escape)",
"searchNext": "Следећи меч (Унесите)",
"searchNoResults": "Нема резултата",
"searchPlaceholder": "Пронађи",
"searchPrevious": "Претходно подударање (Shift+Enter)",
"searchRegex": "Користите регуларни израз",
"searchResultCount": "{{index}} од {{count}}",
"searchWholeWord": "Подударање целе речи",
"sessionExpiredNotice": "Ваша претходна сесија на {{host}} је истекла након {{minutes}} минута од прекида везе; љуска и све што је у њој покренуто су нестали. Омогућите Auto-Tmux на овом хосту да би сесије остале активне.",
"sessionExpiredNoticeNoMinutes": "Ваша претходна сесија на {{host}} је истекла док сте били одвојени; љуска и све што је у њој покренуто су нестали. Омогућите Auto-Tmux на овом хосту да би сесије остале активне.",
"sessionTakenOver": "Сесија је отворена у другој картици. Поновно повезивање...",
"tmuxAttached": "Прикључени клијенти",
"tmuxAttachedCount": "{{count}} приложено",
"tmuxCopyHint": "Прилагодите избор и притисните Ентер да бисте копирали у међуспремник",
"tmuxCreateNew": "Започни нову сесију",
"tmuxDetached": "Одвојено од tmux сесије",
"tmuxLastActivity": "Последња активност",
"tmuxSessionAttached": "приложена tmux сесија: {{name}}",
"tmuxSessionCreated": "tmux сесија креирана: {{name}}",
"tmuxSessionPickerDesc": "Постојеће tmux сесије пронађене су на овом хосту. Изаберите једну да бисте је поново повезали или креирали нову сесију.",
"tmuxSessionPickerTitle": "tmux сесије",
"tmuxTimeDays": "{{count}}пре д",
"tmuxTimeHours": "{{count}}пре сати",
"tmuxTimeJustNow": "управо сада",
"tmuxTimeMinutes": "{{count}}пре мин",
"tmuxUnavailable": "tmux није инсталиран на удаљеном хосту, враћа се на стандардну љуску",
"tmuxWindowCount": "{{count}} прозор",
"tmuxWindows": "Прозори",
"unknownError": "Дошло је до непознате грешке",
"vaultAuthDescription": "Отворио се прозор за пријављивање на HashiCorp Vault. Завршите пријављивање тамо; ова веза ће се аутоматски наставити.",
"vaultAuthFailed": "Аутентификација трезора није успела. Молимо покушајте поново.",
"vaultAuthTitle": "Потребно је пријављивање у трезор",
"vaultReopen": "Поново отвори прозор за пријављивање",
"websocketAbnormalClose": "Веза је неочекивано прекинута. Ово може бити због проблема са обрнутим проксијем или SSL конфигурацијом. Молимо вас да проверите логове сервера.",
"websocketError": "Грешка у повезивању са WebSocket-ом"
},
"terminalToolbar": {
"detachTmux": "Одвојите tmux",
"detachTmuxDescription": "Одвојите tmux сесију",
"dismissError": "Грешка при одбацивању слике",
"hideToolbar": "Сакриј траку са алаткама",
"image": "Радње са сликама (само слике)",
"imageActionFailed": "Акција са сликом није успела",
"layout": "Режим приказа алатне траке",
"layoutExpanded": "Проширено са метрикама",
"layoutIcon": "Само икона",
"layoutLabeled": "Иконе и ознаке",
"moveToolbar": "Помери траку са алаткама.",
"moveToolbarHint": "Превуците да бисте променили положај траке са алаткама",
"paste": "Налепи слику",
"pasteImage": "Налепи слику из међуспремника (само слике)",
"retry": "Покушај поново",
"showToolbar": "Прошири траку са алаткама",
"upload": "Отпреми слику",
"uploadImage": "Отпремите слику на хост",
"uploadingImage": "Отпремање..."
},
"history": {
"noTerminalSelected": "Није изабран терминал",
"noTerminalSelectedHint": "Отворите картицу SSH терминала да бисте видели историју његових команди",
"searchPlaceholder": "Историја претраге...",
"clearAll": "Обриши све",
"noHistoryEntries": "Нема уноса у историји",
"trackingDisabled": "Праћење историје је онемогућено"
},
"touchInput": {
"enabled": "Омогући унос додиром",
"enabledDesc": "Дозволите скроловање једним прстом у терминалима",
"momentumEnabled": "Омогући Моментум",
"momentumEnabledDesc": "Наставите кратко да скролујете након превлачења прстом",
"advanced": "Напредно",
"resetDefaults": "Ресетуј на подразумеване вредности",
"dragThreshold": "Праг превлачења",
"maxWheelDelta": "Максимална делта точка",
"momentumSampleWindow": "Прозор узорка момента",
"releaseGracePeriod": "Грејс период за објављивање",
"minimumVelocity": "Минимална брзина",
"maximumVelocity": "Максимална брзина",
"maximumDuration": "Максимално трајање",
"maximumTravel": "Максимални ход",
"decayTime": "Време распада",
"pixelsPerTick": "Пиксела по тик-у",
"maximumFrameInterval": "Максимални интервал кадра",
"maximumTicksPerFrame": "Максималан број откуцаја по кадру"
},
"imageStorage": {
"instanceId": "ИД инстанце терминалне сесије",
"instanceIdDesc": "ИД инстанце једне од ваших повезаних картица терминала, која се користи за тестирање видљивости.",
"selectedModeUnavailable": "Недоступно",
"test": "Тест",
"testing": "Тестирање...",
"testFailed": "Тестирање складиштења слика није успело",
"testConnected": "Терминална сесија је повезана.",
"testNotConnected": "Нема повезане терминалне сесије са овим ID-ом инстанце.",
"testSelectedMode": "Отпремање би користило: {{mode}}",
"testLocalVisible": "Локални директоријум је видљив са повезаног хоста.",
"testLocalNotVisible": "Локални директоријум НИЈЕ видљив са повезаног хоста."
},
"settings": {
"admin": {
"group": {
"touch": "Унос додиром",
"images": "Складиштење слика"
},
"sessionTimeoutMinutes": {
"label": "Трајност терминалне сесије",
"description": "Колико дуго се искључени терминал одржава активним на серверу пре него што се затвори. Мин. 1 · Макс. 1440 минута. За сесије које морају да трају дуже од овог периода, омогућите Auto-Tmux на хосту."
},
"commandHistoryEnabled": {
"label": "Историја команди",
"description": "Дозволи снимање историје команди. Када је онемогућено, историја се не чува без обзира на подешавања за сваки хост."
},
"imageStorageMode": {
"label": "Режим складиштења",
"description": "Где се записују налепљене и отпремљене слике терминала. Аутоматски избори по конекцији, локални и удаљени (SFTP) су поправљени.",
"auto": "Аутоматски",
"local": "Локално",
"remoteSftp": "Удаљено (SFTP)"
},
"imageLocalDir": {
"label": "Локални директоријум"
},
"imageHostPath": {
"label": "Путања хоста",
"description": "Поглед са стране хоста на локални директоријум који терминални агенти примају."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Максималан број слика"
},
"imageMaxBytes": {
"label": "Максимални број бајтова за складиштење"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Омогући траку са алаткама терминала",
"description": "Прикажи траку са алаткама за брзи приступ причвршћену за активни терминал за алате и статистику уживо овог хоста"
},
"enableCommandHistory": {
"label": "Историја команди",
"description": "Снимање команди које се извршавају у овом терминалу за историју и аутоматско довршавање"
},
"enableTerminal": {
"label": "Омогући терминал"
}
}
},
"palette": {
"localTerminal": "Локални терминал"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Tillägga",
"arrowDown": "Pil nedåt",
"arrowLeft": "Pil vänster",
"arrowRight": "Pil höger",
"arrowUp": "Pil upp",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Gjort",
"editQuickKeys": "Redigera snabbknappar",
"end": "Avsluta",
"esc": "Esc",
"home": "Hem",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Klistra",
"quickKeyPlaceholder": "t.ex. sudo ",
"quickKeysDesc": "Tryck på × för att ta bort. Stöder upp till 8 tecken.",
"quickKeysTitle": "Snabbtangenter",
"removeQuickKey": "Ta bort",
"resetDefaults": "Återställ till standardinställningarna",
"shift": "Flytta",
"tab": "Flik"
},
"terminal": {
"autoTmuxEnableFailed": "Misslyckades med att aktivera Auto-Tmux",
"autoTmuxEnabled": "Auto-Tmux aktiverat för {{host}}. Det gäller från nästa anslutning.",
"clipboardHttpWarning": "Kunde inte läsa urklippet. Vissa webbläsare begränsar åtkomst till urklipp utanför HTTPS - prova Ctrl+V istället, eller servera Termix över HTTPS.",
"closeTab": "Nära",
"connecting": "Ansluter...",
"connectionLost": "Anslutningen förlorades",
"connectionRejected": "Anslutningen avvisades av servern. Kontrollera din autentisering och nätverkskonfiguration.",
"connectionTimeout": "Anslutningstimeout",
"enableAutoTmuxAction": "Aktivera Auto-Tmux",
"hostKeyRejected": "Verifiering av SSH-värdnyckel avvisad. Anslutningen avbröts.",
"linkDialogCopy": "Kopiera",
"linkDialogOpen": "Öppna",
"linkDialogTitle": "Öppna länk",
"maxReconnectAttemptsReached": "Maximalt antal återanslutningsförsök uppnådda",
"noHostSelected": "Ingen värd vald",
"opksshAuthFailed": "Autentiseringen misslyckades. Kontrollera dina inloggningsuppgifter och försök igen.",
"opksshTimeout": "Autentiseringen har gått ut. Försök igen.",
"passwordPromptFillTitle": "Fyll i det sparade lösenordet i den här frågan?",
"reconnect": "Återanslut",
"reconnected": "Återansluten lyckades",
"reconnecting": "Återansluter... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Matchfodral",
"searchClose": "Stäng (Escape)",
"searchNext": "Nästa match (Enter)",
"searchNoResults": "Inga resultat",
"searchPlaceholder": "Hitta",
"searchPrevious": "Föregående matchning (Shift+Enter)",
"searchRegex": "Använd reguljärt uttryck",
"searchResultCount": "{{index}} av {{count}}",
"searchWholeWord": "Matcha hela ordet",
"sessionExpiredNotice": "Din föregående session på {{host}} gick ut efter {{minutes}} minuters frånkoppling; skalet och allt som körs i det är borta. Aktivera Auto-Tmux på den här värden för att hålla sessionerna aktiva.",
"sessionExpiredNoticeNoMinutes": "Din föregående session på {{host}} upphörde medan den var frånkopplad; skalet och allt som körs i det är borta. Aktivera Auto-Tmux på den här värden för att hålla sessionerna aktiva.",
"sessionTakenOver": "Sessionen öppnades i en annan flik. Återansluter...",
"tmuxAttached": "Anslutna klienter",
"tmuxAttachedCount": "{{count}} bifogad",
"tmuxCopyHint": "Justera markeringen och tryck på Enter för att kopiera till urklipp",
"tmuxCreateNew": "Starta ny session",
"tmuxDetached": "Losskopplad från tmux-sessionen",
"tmuxLastActivity": "Senaste aktivitet",
"tmuxSessionAttached": "tmux-session bifogad: {{name}}",
"tmuxSessionCreated": "tmux-session skapad: {{name}}",
"tmuxSessionPickerDesc": "Befintliga tmux-sessioner hittades på den här värden. Välj en för att återansluta eller skapa en ny session.",
"tmuxSessionPickerTitle": "tmux-sessioner",
"tmuxTimeDays": "för {{count}}dagar sedan",
"tmuxTimeHours": "för {{count}}timmar sedan",
"tmuxTimeJustNow": "just nu",
"tmuxTimeMinutes": "för {{count}}minuter sedan",
"tmuxUnavailable": "tmux är inte installerat på fjärrdatorn, utan återgår till standardskal",
"tmuxWindowCount": "{{count}} fönster",
"tmuxWindows": "Fönster",
"unknownError": "Okänt fel inträffade",
"vaultAuthDescription": "Ett fönster har öppnats för att logga in på HashiCorp Vault. Slutför inloggningen där; anslutningen fortsätter automatiskt.",
"vaultAuthFailed": "Valvautentisering misslyckades. Försök igen.",
"vaultAuthTitle": "Inloggning till arkivet krävs",
"vaultReopen": "Öppna inloggningsfönstret igen",
"websocketAbnormalClose": "Anslutningen stängdes oväntat. Detta kan bero på ett problem med omvänd proxy eller SSL-konfiguration. Kontrollera serverloggarna.",
"websocketError": "WebSocket-anslutningsfel"
},
"terminalToolbar": {
"detachTmux": "Lossa tmux",
"detachTmuxDescription": "Koppla bort tmux-session",
"dismissError": "Stäng bildfel",
"hideToolbar": "Dölj verktygsfältet",
"image": "Bildåtgärder (endast bilder)",
"imageActionFailed": "Bildåtgärden misslyckades",
"layout": "Verktygsfältets visningsläge",
"layoutExpanded": "Utökad med mätvärden",
"layoutIcon": "Endast ikon",
"layoutLabeled": "Ikoner och etiketter",
"moveToolbar": "Flytta verktygsfältet",
"moveToolbarHint": "Dra för att flytta verktygsfältet",
"paste": "Klistra in bild",
"pasteImage": "Klistra in bild från urklipp (endast bilder)",
"retry": "Försöka igen",
"showToolbar": "Expandera verktygsfältet",
"upload": "Ladda upp bild",
"uploadImage": "Ladda upp en bild till värden",
"uploadingImage": "Laddar upp..."
},
"history": {
"noTerminalSelected": "Ingen terminal vald",
"noTerminalSelectedHint": "Öppna en SSH-terminalflik för att visa dess kommandohistorik",
"searchPlaceholder": "Sökhistorik...",
"clearAll": "Rensa alla",
"noHistoryEntries": "Inga historikposter",
"trackingDisabled": "Historikspårning är inaktiverad"
},
"touchInput": {
"enabled": "Aktivera pekinmatning",
"enabledDesc": "Tillåt scrollning med ett finger i terminaler",
"momentumEnabled": "Aktivera momentum",
"momentumEnabledDesc": "Fortsätt skrolla kort efter en svepning",
"advanced": "Avancerad",
"resetDefaults": "Återställ till standardinställningarna",
"dragThreshold": "Draggräns",
"maxWheelDelta": "Maximal hjuldelta",
"momentumSampleWindow": "Momentum-exempelfönster",
"releaseGracePeriod": "Frigivningsfrist",
"minimumVelocity": "Minsta hastighet",
"maximumVelocity": "Maximal hastighet",
"maximumDuration": "Maximal varaktighet",
"maximumTravel": "Maximal resa",
"decayTime": "Avklingningstid",
"pixelsPerTick": "Pixlar per tick",
"maximumFrameInterval": "Maximalt bildintervall",
"maximumTicksPerFrame": "Maximalt antal ticks per bildruta"
},
"imageStorage": {
"instanceId": "Terminalsessionsinstans-ID",
"instanceIdDesc": "Instans-ID för en av dina anslutna terminalflikar, används för att testa synligheten.",
"selectedModeUnavailable": "Inte tillgänglig",
"test": "Testa",
"testing": "Testning...",
"testFailed": "Misslyckades med att testa bildlagring",
"testConnected": "Terminalsessionen är ansluten.",
"testNotConnected": "Ingen ansluten terminalsession med detta instans-ID.",
"testSelectedMode": "En uppladdning skulle använda: {{mode}}",
"testLocalVisible": "Den lokala katalogen är synlig från den anslutna värden.",
"testLocalNotVisible": "Den lokala katalogen är INTE synlig från den anslutna värden."
},
"settings": {
"admin": {
"group": {
"touch": "Pekinmatning",
"images": "Bildlagring"
},
"sessionTimeoutMinutes": {
"label": "Terminalsessionens persistens",
"description": "Hur länge en frånkopplad terminal hålls aktiv på servern innan den stängs. Min 1 · Max 1440 minuter. För sessioner som måste överleva detta, aktivera Auto-Tmux på värden."
},
"commandHistoryEnabled": {
"label": "Kommandohistorik",
"description": "Tillåt inspelning av kommandohistorik. När det är inaktiverat sparas inte historiken oavsett inställningarna per värd."
},
"imageStorageMode": {
"label": "Lagringsläge",
"description": "Där inklistrade och uppladdade terminalbilder skrivs. Automatiska val via anslutning, lokal och fjärransluten (SFTP) är åtgärdade.",
"auto": "Bil",
"local": "Lokal",
"remoteSftp": "Fjärrstyrning (SFTP)"
},
"imageLocalDir": {
"label": "Lokal katalog"
},
"imageHostPath": {
"label": "Värdsökväg",
"description": "Värdsidans vy av den lokala katalogen som terminalagenter tar emot."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Max antal bilder"
},
"imageMaxBytes": {
"label": "Max antal lagringsbyte"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Aktivera terminalverktygsfältet",
"description": "Visa en snabbåtkomstverktygsfält dockad till den aktiva terminalen för den här värdens verktyg och livestatistik"
},
"enableCommandHistory": {
"label": "Kommandohistorik",
"description": "Record-kommandon körs i den här terminalen för historik och autofullständig ifyllning"
},
"enableTerminal": {
"label": "Aktivera terminalen"
}
}
},
"palette": {
"localTerminal": "Lokal terminal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "เพิ่ม",
"arrowDown": "ลูกศรลง",
"arrowLeft": "ลูกศรซ้าย",
"arrowRight": "ลูกศรขวา",
"arrowUp": "ลูกศรขึ้น",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "เดล",
"done": "เสร็จแล้ว",
"editQuickKeys": "แก้ไขปุ่มลัด",
"end": "จบ",
"esc": "เอส",
"home": "บ้าน",
"pageDown": "พีจีดีเอ็น",
"pageUp": "พีจีอัพ",
"paste": "แปะ",
"quickKeyPlaceholder": "เช่น sudo ",
"quickKeysDesc": "แตะ × เพื่อลบ รองรับได้สูงสุด 8 ตัวอักษร",
"quickKeysTitle": "ปุ่มลัด",
"removeQuickKey": "ลบ",
"resetDefaults": "รีเซ็ตเป็นการตั้งค่าเริ่มต้น",
"shift": "กะ",
"tab": "แท็บ"
},
"terminal": {
"autoTmuxEnableFailed": "ไม่สามารถเปิดใช้งาน Auto-Tmux ได้",
"autoTmuxEnabled": "เปิดใช้งาน Auto-Tmux สำหรับ {{host}}แล้ว จะมีผลตั้งแต่การเชื่อมต่อครั้งถัดไป",
"clipboardHttpWarning": "ไม่สามารถอ่านข้อมูลในคลิปบอร์ดได้ เบราว์เซอร์บางตัวจำกัดการเข้าถึงคลิปบอร์ดนอก HTTPS - ลองใช้ Ctrl+V แทน หรือใช้งาน Termix ผ่าน HTTPS",
"closeTab": "ปิด",
"connecting": "กำลังเชื่อมต่อ...",
"connectionLost": "การเชื่อมต่อขาดหาย",
"connectionRejected": "เซิร์ฟเวอร์ปฏิเสธการเชื่อมต่อ โปรดตรวจสอบการยืนยันตัวตนและการกำหนดค่าเครือข่ายของคุณ",
"connectionTimeout": "หมดเวลาการเชื่อมต่อ",
"enableAutoTmuxAction": "เปิดใช้งาน Auto-Tmux",
"hostKeyRejected": "การตรวจสอบคีย์โฮสต์ SSH ถูกปฏิเสธ การเชื่อมต่อถูกยกเลิก",
"linkDialogCopy": "สำเนา",
"linkDialogOpen": "เปิด",
"linkDialogTitle": "เปิดลิงก์",
"maxReconnectAttemptsReached": "จำนวนครั้งการเชื่อมต่อใหม่สูงสุดครบแล้ว",
"noHostSelected": "ไม่ได้เลือกโฮสต์",
"opksshAuthFailed": "การตรวจสอบสิทธิ์ล้มเหลว โปรดตรวจสอบข้อมูลประจำตัวของคุณและลองอีกครั้ง",
"opksshTimeout": "การตรวจสอบสิทธิ์หมดเวลา โปรดลองอีกครั้ง",
"passwordPromptFillTitle": "กรอกรหัสผ่านที่บันทึกไว้ลงในช่องนี้ใช่หรือไม่?",
"reconnect": "เชื่อมต่อใหม่",
"reconnected": "เชื่อมต่อสำเร็จแล้ว",
"reconnecting": "กำลังเชื่อมต่อใหม่... ({{attempt}}/{{max}})",
"searchCaseSensitive": "กล่องไม้ขีดไฟ",
"searchClose": "ปิด (หนี)",
"searchNext": "การแข่งขันถัดไป (กดเข้าร่วม)",
"searchNoResults": "ไม่พบผลลัพธ์",
"searchPlaceholder": "หา",
"searchPrevious": "การแข่งขันครั้งก่อนหน้า (Shift+Enter)",
"searchRegex": "ใช้ Regular Expression",
"searchResultCount": "{{index}} ของ {{count}}",
"searchWholeWord": "จับคู่คำทั้งหมด",
"sessionExpiredNotice": "เซสชันก่อนหน้าของคุณบน {{host}} หมดอายุหลังจาก {{minutes}} นาทีถูกตัดการเชื่อมต่อ เชลล์และทุกสิ่งที่กำลังทำงานอยู่ภายในนั้นหายไปแล้ว โปรดเปิดใช้งาน Auto-Tmux บนโฮสต์นี้เพื่อรักษาสถานะเซสชันให้คงอยู่",
"sessionExpiredNoticeNoMinutes": "เซสชันก่อนหน้าของคุณบน {{host}} หมดอายุขณะที่ไม่ได้เชื่อมต่อ เชลล์และทุกอย่างที่กำลังทำงานอยู่ภายในนั้นหายไปแล้ว โปรดเปิดใช้งาน Auto-Tmux บนโฮสต์นี้เพื่อรักษาสถานะเซสชันให้คงอยู่",
"sessionTakenOver": "เซสชั่นนี้เปิดอยู่ในแท็บอื่น กำลังเชื่อมต่อใหม่...",
"tmuxAttached": "ลูกค้าที่แนบมาด้วย",
"tmuxAttachedCount": "{{count}} แนบมาด้วย",
"tmuxCopyHint": "ปรับตำแหน่งที่เลือกแล้วกด Enter เพื่อคัดลอกไปยังคลิปบอร์ด",
"tmuxCreateNew": "เริ่มเซสชั่นใหม่",
"tmuxDetached": "ตัดการเชื่อมต่อจากเซสชัน tmux แล้ว",
"tmuxLastActivity": "กิจกรรมล่าสุด",
"tmuxSessionAttached": "การเชื่อมต่อเซสชัน tmux: {{name}}",
"tmuxSessionCreated": "สร้างเซสชัน tmux แล้ว: {{name}}",
"tmuxSessionPickerDesc": "พบเซสชัน tmux ที่มีอยู่แล้วในโฮสต์นี้ เลือกหนึ่งเซสชันเพื่อเชื่อมต่อใหม่หรือสร้างเซสชันใหม่",
"tmuxSessionPickerTitle": "เซสชัน tmux",
"tmuxTimeDays": "{{count}}วันก่อน",
"tmuxTimeHours": "{{count}}ชั่วโมงที่แล้ว",
"tmuxTimeJustNow": "เมื่อสักครู่นี้เอง",
"tmuxTimeMinutes": "{{count}}นาทีที่แล้ว",
"tmuxUnavailable": "tmux ไม่ได้ติดตั้งอยู่บนโฮสต์ระยะไกล จึงใช้เชลล์มาตรฐานแทน",
"tmuxWindowCount": "{{count}} หน้าต่าง",
"tmuxWindows": "วินโดวส์",
"unknownError": "เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ",
"vaultAuthDescription": "หน้าต่างสำหรับเข้าสู่ระบบ HashiCorp Vault ได้เปิดขึ้นแล้ว โปรดทำการเข้าสู่ระบบให้เสร็จสมบูรณ์ การเชื่อมต่อนี้จะดำเนินการต่อโดยอัตโนมัติ",
"vaultAuthFailed": "การตรวจสอบสิทธิ์ Vault ล้มเหลว โปรดลองอีกครั้ง",
"vaultAuthTitle": "ต้องลงชื่อเข้าใช้ Vault",
"vaultReopen": "เปิดหน้าต่างเข้าสู่ระบบอีกครั้ง",
"websocketAbnormalClose": "การเชื่อมต่อถูกตัดโดยไม่คาดคิด อาจเกิดจากปัญหาเกี่ยวกับรีเวิร์สพร็อกซีหรือการกำหนดค่า SSL โปรดตรวจสอบบันทึกของเซิร์ฟเวอร์",
"websocketError": "ข้อผิดพลาดในการเชื่อมต่อ WebSocket"
},
"terminalToolbar": {
"detachTmux": "ถอด tmux ออก",
"detachTmuxDescription": "ยกเลิกการเชื่อมต่อเซสชัน tmux",
"dismissError": "แก้ไขข้อผิดพลาดเกี่ยวกับรูปภาพ",
"hideToolbar": "ซ่อนแถบเครื่องมือ",
"image": "การดำเนินการกับรูปภาพ (เฉพาะรูปภาพ)",
"imageActionFailed": "การดำเนินการรูปภาพล้มเหลว",
"layout": "โหมดแสดงแถบเครื่องมือ",
"layoutExpanded": "ขยายเพิ่มเติมด้วยตัวชี้วัด",
"layoutIcon": "เฉพาะไอคอน",
"layoutLabeled": "ไอคอนและป้ายกำกับ",
"moveToolbar": "ย้ายแถบเครื่องมือ",
"moveToolbarHint": "ลากเพื่อจัดตำแหน่งแถบเครื่องมือใหม่",
"paste": "วางรูปภาพ",
"pasteImage": "วางภาพจากคลิปบอร์ด (เฉพาะภาพ)",
"retry": "ลองใหม่อีกครั้ง",
"showToolbar": "ขยายแถบเครื่องมือ",
"upload": "อัปโหลดรูปภาพ",
"uploadImage": "อัปโหลดรูปภาพไปยังโฮสต์",
"uploadingImage": "กำลังอัปโหลด..."
},
"history": {
"noTerminalSelected": "ไม่ได้เลือกเทอร์มินัล",
"noTerminalSelectedHint": "เปิดแท็บเทอร์มินัล SSH เพื่อดูประวัติคำสั่ง",
"searchPlaceholder": "ประวัติการค้นหา...",
"clearAll": "ล้างทั้งหมด",
"noHistoryEntries": "ไม่มีรายการประวัติ",
"trackingDisabled": "ปิดใช้งานการติดตามประวัติแล้ว"
},
"touchInput": {
"enabled": "เปิดใช้งานการป้อนข้อมูลแบบสัมผัส",
"enabledDesc": "อนุญาตให้เลื่อนหน้าจอด้วยนิ้วเดียวในเทอร์มินัล",
"momentumEnabled": "เปิดใช้งานโมเมนตัม",
"momentumEnabledDesc": "เลื่อนหน้าจอต่ออีกสักครู่หลังจากปัดนิ้ว",
"advanced": "ขั้นสูง",
"resetDefaults": "รีเซ็ตเป็นการตั้งค่าเริ่มต้น",
"dragThreshold": "เกณฑ์การลาก",
"maxWheelDelta": "เดลต้าล้อสูงสุด",
"momentumSampleWindow": "หน้าต่างตัวอย่างโมเมนตัม",
"releaseGracePeriod": "ระยะเวลาผ่อนผันการปล่อยตัว",
"minimumVelocity": "ความเร็วต่ำสุด",
"maximumVelocity": "ความเร็วสูงสุด",
"maximumDuration": "ระยะเวลาสูงสุด",
"maximumTravel": "การเดินทางสูงสุด",
"decayTime": "เวลาการเสื่อมสภาพ",
"pixelsPerTick": "พิกเซลต่อติ๊ก",
"maximumFrameInterval": "ช่วงเวลาเฟรมสูงสุด",
"maximumTicksPerFrame": "จำนวนขีดสูงสุดต่อเฟรม"
},
"imageStorage": {
"instanceId": "รหัสอินสแตนซ์เซสชันเทอร์มินัล",
"instanceIdDesc": "รหัสอินสแตนซ์ของแท็บเทอร์มินัลที่เชื่อมต่ออยู่แท็บหนึ่ง ใช้สำหรับทดสอบการมองเห็น",
"selectedModeUnavailable": "ไม่พร้อมใช้งาน",
"test": "ทดสอบ",
"testing": "กำลังทดสอบ...",
"testFailed": "การทดสอบการจัดเก็บรูปภาพล้มเหลว",
"testConnected": "การเชื่อมต่อเซสชันเทอร์มินัลเสร็จสมบูรณ์แล้ว",
"testNotConnected": "ไม่มีเซสชันเทอร์มินัลที่เชื่อมต่อกับรหัสอินสแตนซ์นี้",
"testSelectedMode": "การอัปโหลดจะใช้รูปแบบ: {{mode}}",
"testLocalVisible": "ไดเร็กทอรีภายในเครื่องสามารถมองเห็นได้จากโฮสต์ที่เชื่อมต่ออยู่",
"testLocalNotVisible": "ไดเร็กทอรีภายในเครื่องจะไม่สามารถมองเห็นได้จากโฮสต์ที่เชื่อมต่ออยู่"
},
"settings": {
"admin": {
"group": {
"touch": "การป้อนข้อมูลด้วยระบบสัมผัส",
"images": "การจัดเก็บรูปภาพ"
},
"sessionTimeoutMinutes": {
"label": "การคงอยู่ของเซสชันเทอร์มินัล",
"description": "ระยะเวลาที่เทอร์มินัลที่ไม่ได้เชื่อมต่อจะยังคงทำงานอยู่บนเซิร์ฟเวอร์ก่อนที่จะถูกปิด ต่ำสุด 1 นาที สูงสุด 1440 นาที สำหรับเซสชันที่ต้องใช้งานนานกว่านี้ ให้เปิดใช้งาน Auto-Tmux บนโฮสต์"
},
"commandHistoryEnabled": {
"label": "ประวัติคำสั่ง",
"description": "อนุญาตให้บันทึกประวัติคำสั่ง เมื่อปิดใช้งาน ประวัติจะไม่ถูกบันทึกไม่ว่าการตั้งค่าต่อโฮสต์จะเป็นอย่างไรก็ตาม"
},
"imageStorageMode": {
"label": "โหมดจัดเก็บข้อมูล",
"description": "ตำแหน่งที่จะบันทึกภาพเทอร์มินัลที่คัดลอกและอัปโหลด การเลือกอัตโนมัติตามการเชื่อมต่อ ทั้งแบบโลคอลและรีโมต (SFTP) จะถูกกำหนดไว้ตายตัว",
"auto": "อัตโนมัติ",
"local": "ท้องถิ่น",
"remoteSftp": "รีโมท (SFTP)"
},
"imageLocalDir": {
"label": "ไดเร็กทอรีท้องถิ่น"
},
"imageHostPath": {
"label": "เส้นทางโฮสต์",
"description": "มุมมองฝั่งโฮสต์ของไดเร็กทอรีท้องถิ่นที่เอเจนต์เทอร์มินัลได้รับ"
},
"imageTtlMs": {
"label": "ทีทีแอล (มิลลิวินาที)"
},
"imageMaxCount": {
"label": "จำนวนภาพสูงสุด"
},
"imageMaxBytes": {
"label": "พื้นที่จัดเก็บสูงสุด (ไบต์)"
}
},
"host": {
"enableTerminalToolbar": {
"label": "เปิดใช้งานแถบเครื่องมือเทอร์มินัล",
"description": "แสดงแถบเครื่องมือเข้าถึงด่วนที่ตรึงอยู่กับเทอร์มินัลที่ใช้งานอยู่ สำหรับเครื่องมือและสถิติแบบเรียลไทม์ของโฮสต์นี้"
},
"enableCommandHistory": {
"label": "ประวัติคำสั่ง",
"description": "บันทึกคำสั่งที่เรียกใช้ในเทอร์มินัลนี้เพื่อดูประวัติและฟังก์ชันเติมคำอัตโนมัติ"
},
"enableTerminal": {
"label": "เปิดใช้งานเทอร์มินัล"
}
}
},
"palette": {
"localTerminal": "สถานีปลายทางท้องถิ่น"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Ekle",
"arrowDown": "Aşağı Ok",
"arrowLeft": "Sol Ok",
"arrowRight": "Sağ Ok",
"arrowUp": "Yukarı Ok",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Tamam",
"editQuickKeys": "Hızlı tuşları düzenle",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Yapıştır",
"quickKeyPlaceholder": "ör. sudo ",
"quickKeysDesc": "Kaldırmak için × işaretine dokunun. En fazla 8 karakter desteklenir.",
"quickKeysTitle": "Hızlı Tuşlar",
"removeQuickKey": "Kaldır",
"resetDefaults": "Varsayılanlara sıfırla",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Otomatik Tmux etkinleştirme başarısız oldu.",
"autoTmuxEnabled": "{{host}}için otomatik Tmux etkinleştirildi. Bu, sonraki bağlantıdan itibaren geçerli olacak.",
"clipboardHttpWarning": "Panoyu okuyamadım. Bazı tarayıcılar HTTPS dışında panoya erişimi kısıtlar; bunun yerine Ctrl+V tuş kombinasyonunu deneyin veya Termix'i HTTPS üzerinden sunun.",
"closeTab": "Kapat",
"connecting": "Bağlanıyor...",
"connectionLost": "Bağlantı kesildi",
"connectionRejected": "Bağlantı sunucu tarafından reddedildi. Lütfen kimlik doğrulamanızı ve ağ yapılandırmanızı kontrol edin.",
"connectionTimeout": "Bağlantı zaman aşımı",
"enableAutoTmuxAction": "Otomatik Tmux'ı etkinleştirin",
"hostKeyRejected": "SSH sunucu anahtarı doğrulaması reddedildi. Bağlantı iptal edildi.",
"linkDialogCopy": "Kopyala",
"linkDialogOpen": "Aç",
"linkDialogTitle": "Bağlantıyı Aç",
"maxReconnectAttemptsReached": "Maksimum yeniden bağlanma denemesine ulaşıldı",
"noHostSelected": "Hiçbir sunucu seçilmedi",
"opksshAuthFailed": "Kimlik doğrulaması başarısız oldu. Lütfen kimlik bilgilerinizi kontrol edip tekrar deneyin.",
"opksshTimeout": "Kimlik doğrulaması zaman aşımına uğradı. Lütfen tekrar deneyin.",
"passwordPromptFillTitle": "Kayıtlı parola bu isteme doldurulsun mu?",
"reconnect": "Yeniden Bağlan",
"reconnected": "Başarıyla yeniden bağlandı",
"reconnecting": "Yeniden bağlanıyor... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Kibrit Kutusu",
"searchClose": "Kapat (Escape)",
"searchNext": "Sonraki Maç (Giriş)",
"searchNoResults": "Sonuç bulunamadı.",
"searchPlaceholder": "Bulmak",
"searchPrevious": "Önceki Maç (Shift+Enter)",
"searchRegex": "Düzenli İfade Kullanımı",
"searchResultCount": "{{index}} / {{count}}",
"searchWholeWord": "Kelimenin tamamını eşleştir",
"sessionExpiredNotice": "{{host}} üzerindeki önceki oturumunuz {{minutes}} dakika sonra sona erdi; kabuk ve içinde çalışan her şey silindi. Oturumların aktif kalması için bu sunucuda Auto-Tmux'u etkinleştirin.",
"sessionExpiredNoticeNoMinutes": "{{host}} üzerindeki önceki oturumunuz bağlantı kesikken sona erdi; kabuk ve içinde çalışan her şey kayboldu. Oturumların aktif kalması için bu sunucuda Auto-Tmux'u etkinleştirin.",
"sessionTakenOver": "Oturum başka bir sekmede açıldı. Yeniden bağlanılıyor...",
"tmuxAttached": "Bağlı istemciler",
"tmuxAttachedCount": "{{count}} bağlı",
"tmuxCopyHint": "Seçimi ayarlayın ve panoya kopyalamak için Enter tuşuna basın",
"tmuxCreateNew": "Yeni oturum başlat",
"tmuxDetached": "tmux oturumundan ayrıldı",
"tmuxLastActivity": "Son etkinlik",
"tmuxSessionAttached": "tmux oturumuna bağlanıldı: {{name}}",
"tmuxSessionCreated": "tmux oturumu oluşturuldu: {{name}}",
"tmuxSessionPickerDesc": "Bu sunucuda mevcut tmux oturumları bulundu. Yeniden bağlanmak için birini seçin veya yeni bir oturum oluşturun.",
"tmuxSessionPickerTitle": "tmux Oturumları",
"tmuxTimeDays": "{{count}}g önce",
"tmuxTimeHours": "{{count}}s önce",
"tmuxTimeJustNow": "az önce",
"tmuxTimeMinutes": "{{count}}dk önce",
"tmuxUnavailable": "Uzak sunucuda tmux yüklü değil, standart kabuğa dönülüyor",
"tmuxWindowCount": "{{count}} pencere",
"tmuxWindows": "Pencereler",
"unknownError": "Bilinmeyen bir hata oluştu",
"vaultAuthDescription": "HashiCorp Vault'ta oturum açmak için bir pencere açıldı. Orada oturum açmayı tamamlayın; bu bağlantı otomatik olarak devam edecek.",
"vaultAuthFailed": "Vault kimlik doğrulaması başarısız oldu. Lütfen tekrar deneyin.",
"vaultAuthTitle": "Vault Oturum Açması Gerekli",
"vaultReopen": "Oturum açma penceresini yeniden aç",
"websocketAbnormalClose": "Bağlantı beklenmedik şekilde kapandı. Bu, ters proxy veya SSL yapılandırma sorunundan kaynaklanabilir. Lütfen sunucu günlüklerini kontrol edin.",
"websocketError": "WebSocket bağlantı hatası"
},
"terminalToolbar": {
"detachTmux": "tmux'u ayır",
"detachTmuxDescription": "Tmux oturumunu ayır",
"dismissError": "Görüntü hatasını kapat",
"hideToolbar": "Araç çubuğunu gizle",
"image": "Görüntü işlemleri (yalnızca görüntüler)",
"imageActionFailed": "Görüntü işlemi başarısız oldu",
"layout": "Araç çubuğu görüntüleme modu",
"layoutExpanded": "Ölçütlerle genişletildi",
"layoutIcon": "Yalnızca simge",
"layoutLabeled": "Simgeler ve etiketler",
"moveToolbar": "Araç çubuğunu taşı",
"moveToolbarHint": "Araç çubuğunu yeniden konumlandırmak için sürükleyin.",
"paste": "Resmi yapıştır",
"pasteImage": "Panodan resim yapıştır (yalnızca resimler)",
"retry": "Tekrar dene",
"showToolbar": "Araç çubuğunu genişletin",
"upload": "Resim yükle",
"uploadImage": "Sunucuya bir resim yükleyin.",
"uploadingImage": "Yükleniyor..."
},
"history": {
"noTerminalSelected": "Terminal seçilmedi",
"noTerminalSelectedHint": "Komut geçmişini görüntülemek için bir SSH terminal sekmesi açın",
"searchPlaceholder": "Geçmişte ara...",
"clearAll": "Tümünü Temizle",
"noHistoryEntries": "Geçmiş girdisi yok",
"trackingDisabled": "Geçmiş takibi kapalı"
},
"touchInput": {
"enabled": "Dokunmatik Girişi Etkinleştir",
"enabledDesc": "Terminallerde tek parmakla dokunmatik kaydırmaya izin verin.",
"momentumEnabled": "İvme Kazandırın",
"momentumEnabledDesc": "Kaydırma işleminden sonra kısa bir süre daha aşağı kaydırmaya devam edin.",
"advanced": "Gelişmiş",
"resetDefaults": "Varsayılan ayarlara sıfırla",
"dragThreshold": "Sürükleme eşiği",
"maxWheelDelta": "Maksimum tekerlek deltası",
"momentumSampleWindow": "Momentum örnek penceresi",
"releaseGracePeriod": "Serbest bırakma erteleme süresi",
"minimumVelocity": "Minimum hız",
"maximumVelocity": "Maksimum hız",
"maximumDuration": "Maksimum süre",
"maximumTravel": "Maksimum seyahat",
"decayTime": "Bozunma süresi",
"pixelsPerTick": "Piksel başına tik",
"maximumFrameInterval": "Maksimum kare aralığı",
"maximumTicksPerFrame": "Kare başına maksimum tik sayısı"
},
"imageStorage": {
"instanceId": "Terminal oturum örneği kimliği",
"instanceIdDesc": "Görünürlüğü test etmek için kullanılan, bağlı terminal sekmelerinizden birinin örnek kimliği.",
"selectedModeUnavailable": "Mevcut değil",
"test": "Test",
"testing": "Test...",
"testFailed": "Görüntü depolama testi başarısız oldu.",
"testConnected": "Terminal oturumu bağlandı.",
"testNotConnected": "Bu örnek kimliğiyle bağlantılı bir terminal oturumu bulunmamaktadır.",
"testSelectedMode": "Yükleme işlemi şu şekilde yapılır: {{mode}}",
"testLocalVisible": "Yerel dizin, bağlı sunucudan görülebilir.",
"testLocalNotVisible": "Yerel dizin, bağlı sunucudan görünmez."
},
"settings": {
"admin": {
"group": {
"touch": "Dokunmatik Giriş",
"images": "Görüntü Depolama"
},
"sessionTimeoutMinutes": {
"label": "Terminal Oturum Kalıcılığı",
"description": "Bağlantısı kesilmiş bir terminalin sunucuda kapatılmadan önce ne kadar süreyle aktif tutulacağı. Minimum 1 dakika, maksimum 1440 dakika. Bu süreyi aşması gereken oturumlar için, sunucuda Auto-Tmux'u etkinleştirin."
},
"commandHistoryEnabled": {
"label": "Komut Geçmişi",
"description": "Komut geçmişi kaydına izin ver. Devre dışı bırakıldığında, host başına ayarlara bakılmaksızın geçmiş kaydedilmez."
},
"imageStorageMode": {
"label": "Depolama Modu",
"description": "Yapıştırılan ve yüklenen terminal görüntülerinin yazıldığı yer. Bağlantı türüne göre otomatik seçim, Yerel ve Uzak (SFTP) ayarları düzeltildi.",
"auto": "Otomatik",
"local": "Yerel",
"remoteSftp": "Uzaktan (SFTP)"
},
"imageLocalDir": {
"label": "Yerel dizin"
},
"imageHostPath": {
"label": "Ana bilgisayar yolu",
"description": "Terminal aracıların aldığı yerel dizinin sunucu tarafındaki görünümü."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Maksimum görüntü sayısı"
},
"imageMaxBytes": {
"label": "Maksimum depolama baytları"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Terminal Araç Çubuğunu Etkinleştir",
"description": "Etkin terminale sabitlenmiş, bu sunucunun araçlarına ve canlı istatistiklerine hızlı erişim sağlayan bir araç çubuğu gösterin."
},
"enableCommandHistory": {
"label": "Komut Geçmişi",
"description": "Geçmiş ve otomatik tamamlama için bu terminalde çalıştırılan komutları kaydet"
},
"enableTerminal": {
"label": "Terminali Etkinleştir"
}
}
},
"palette": {
"localTerminal": "Yerel Terminal"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Додати",
"arrowDown": "Стрілка вниз",
"arrowLeft": "Стрілка ліворуч",
"arrowRight": "Стрілка праворуч",
"arrowUp": "Стрілка вгору",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Дел",
"done": "Готово",
"editQuickKeys": "Редагувати швидкі клавіші",
"end": "Кінець",
"esc": "Esc",
"home": "Дім",
"pageDown": "ПгДн",
"pageUp": "PgUp",
"paste": "Вставити",
"quickKeyPlaceholder": "наприклад, sudo ",
"quickKeysDesc": "Натисніть ×, щоб видалити. Підтримує до 8 символів.",
"quickKeysTitle": "Швидкі клавіші",
"removeQuickKey": "Видалити",
"resetDefaults": "Скинути до налаштувань за замовчуванням",
"shift": "Зсув",
"tab": "Вкладка"
},
"terminal": {
"autoTmuxEnableFailed": "Не вдалося ввімкнути Auto-Tmux",
"autoTmuxEnabled": "Автоматичне Tmux-змінення увімкнено для {{host}}. Застосовується з наступного підключення.",
"clipboardHttpWarning": "Не вдалося прочитати буфер обміну. Деякі браузери обмежують доступ до буфера обміну поза HTTPS — спробуйте Ctrl+V або обслуговуйте Termix через HTTPS.",
"closeTab": "Закрити",
"connecting": "Підключення...",
"connectionLost": "З’єднання втрачено",
"connectionRejected": "З’єднання відхилено сервером. Перевірте автентифікацію та конфігурацію мережі.",
"connectionTimeout": "Тайм-аут з'єднання",
"enableAutoTmuxAction": "Увімкнути автоматичне Tmux",
"hostKeyRejected": "Перевірку ключа хоста SSH відхилено. З'єднання скасовано.",
"linkDialogCopy": "Копіювати",
"linkDialogOpen": "ВІДЧИНЕНО",
"linkDialogTitle": "Відкрити посилання",
"maxReconnectAttemptsReached": "Досягнуто максимальної кількості спроб повторного підключення",
"noHostSelected": "Хост не вибрано",
"opksshAuthFailed": "Помилка автентифікації. Перевірте свої облікові дані та спробуйте ще раз.",
"opksshTimeout": "Час очікування автентифікації минув. Спробуйте ще раз.",
"passwordPromptFillTitle": "Введіть збережений пароль у цей запит?",
"reconnect": "Знову підключитися",
"reconnected": "Успішно відновлено підключення",
"reconnecting": "Повторне підключення... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Зіставте регістр",
"searchClose": "Закрити (Escape)",
"searchNext": "Наступний матч (Enter)",
"searchNoResults": "Немає результатів",
"searchPlaceholder": "Знайти",
"searchPrevious": "Попередній збіг (Shift+Enter)",
"searchRegex": "Використовуйте регулярний вираз",
"searchResultCount": "{{index}} з {{count}}",
"searchWholeWord": "Зіставлення цілого слова",
"sessionExpiredNotice": "Ваш попередній сеанс на {{host}} завершився через {{minutes}} хвилин після відключення; оболонка та все, що в ній працює, зникли. Увімкніть Auto-Tmux на цьому хості, щоб підтримувати сеанси активними.",
"sessionExpiredNoticeNoMinutes": "Ваш попередній сеанс на {{host}} закінчився під час відключення; оболонка та все, що в ній працює, зникли. Увімкніть Auto-Tmux на цьому хості, щоб підтримувати сеанси активними.",
"sessionTakenOver": "Сеанс відкрито в іншій вкладці. Повторне підключення...",
"tmuxAttached": "Підключені клієнти",
"tmuxAttachedCount": "{{count}} додається",
"tmuxCopyHint": "Відрегулюйте виділення та натисніть Enter, щоб скопіювати його в буфер обміну",
"tmuxCreateNew": "Почати новий сеанс",
"tmuxDetached": "Від’єднано від сеансу tmux",
"tmuxLastActivity": "Остання активність",
"tmuxSessionAttached": "додано сеанс tmux: {{name}}",
"tmuxSessionCreated": "Створено сеанс tmux: {{name}}",
"tmuxSessionPickerDesc": "На цьому хості знайдено існуючі сеанси tmux. Виберіть один для повторного підключення або створення нового сеансу.",
"tmuxSessionPickerTitle": "Сесії tmux",
"tmuxTimeDays": "{{count}}днів тому",
"tmuxTimeHours": "{{count}}год тому",
"tmuxTimeJustNow": "щойно",
"tmuxTimeMinutes": "{{count}}хв тому",
"tmuxUnavailable": "tmux не встановлено на віддаленому хості, повертається до стандартної оболонки",
"tmuxWindowCount": "вікно {{count}}",
"tmuxWindows": "Вікна",
"unknownError": "Сталася невідома помилка",
"vaultAuthDescription": "Відкрилося вікно для входу в HashiCorp Vault. Завершіть вхід там; це з’єднання буде продовжено автоматично.",
"vaultAuthFailed": "Не вдалося виконати автентифікацію сховища. Спробуйте ще раз.",
"vaultAuthTitle": "Потрібен вхід до Сейфа",
"vaultReopen": "Знову відкрити вікно входу",
"websocketAbnormalClose": "З’єднання несподівано завершилося. Можливо, це пов’язано зі зворотним проксі-сервером або проблемою конфігурації SSL. Перевірте журнали сервера.",
"websocketError": "Помилка підключення WebSocket"
},
"terminalToolbar": {
"detachTmux": "Від’єднати tmux",
"detachTmuxDescription": "Від’єднати сеанс tmux",
"dismissError": "Помилка відхилення зображення",
"hideToolbar": "Приховати панель інструментів",
"image": "Дії із зображеннями (лише зображення)",
"imageActionFailed": "Не вдалося виконати дію із зображенням",
"layout": "Режим відображення панелі інструментів",
"layoutExpanded": "Розширено показниками",
"layoutIcon": "Тільки значок",
"layoutLabeled": "Піктограми та мітки",
"moveToolbar": "Переміщення панелі інструментів",
"moveToolbarHint": "Перетягніть, щоб змінити положення панелі інструментів",
"paste": "Вставити зображення",
"pasteImage": "Вставити зображення з буфера обміну (лише зображення)",
"retry": "Повторити спробу",
"showToolbar": "Розгорнути панель інструментів",
"upload": "Завантажити зображення",
"uploadImage": "Завантажте зображення на хостинг",
"uploadingImage": "Завантаження..."
},
"history": {
"noTerminalSelected": "Термінал не вибрано",
"noTerminalSelectedHint": "Відкрийте вкладку терміналу SSH, щоб переглянути історію його команд",
"searchPlaceholder": "Історія пошуку...",
"clearAll": "Очистити все",
"noHistoryEntries": "Немає записів в історії",
"trackingDisabled": "Відстеження історії вимкнено"
},
"touchInput": {
"enabled": "Увімкнути сенсорне введення",
"enabledDesc": "Дозволити прокручування одним пальцем у терміналах",
"momentumEnabled": "Увімкнути Momentum",
"momentumEnabledDesc": "Продовжуйте коротко гортати після проведення пальцем",
"advanced": "Розширений",
"resetDefaults": "Скинути до налаштувань за замовчуванням",
"dragThreshold": "Поріг перетягування",
"maxWheelDelta": "Максимальна дельта коліс",
"momentumSampleWindow": "Вікно вибірки імпульсу",
"releaseGracePeriod": "Пільговий період випуску",
"minimumVelocity": "Мінімальна швидкість",
"maximumVelocity": "Максимальна швидкість",
"maximumDuration": "Максимальна тривалість",
"maximumTravel": "Максимальний хід",
"decayTime": "Час розпаду",
"pixelsPerTick": "Пікселів на такт",
"maximumFrameInterval": "Максимальний інтервал кадру",
"maximumTicksPerFrame": "Максимальна кількість тиків на кадр"
},
"imageStorage": {
"instanceId": "Ідентифікатор екземпляра термінального сеансу",
"instanceIdDesc": "Ідентифікатор екземпляра однієї з ваших підключених вкладок терміналу, що використовується для перевірки видимості.",
"selectedModeUnavailable": "Недоступно",
"test": "Тест",
"testing": "Тестування...",
"testFailed": "Не вдалося перевірити сховище зображень",
"testConnected": "Термінальний сеанс підключено.",
"testNotConnected": "Немає підключеного термінального сеансу з цим ідентифікатором екземпляра.",
"testSelectedMode": "Для завантаження буде використано: {{mode}}",
"testLocalVisible": "Локальний каталог видно з підключеного хоста.",
"testLocalNotVisible": "Локальний каталог НЕ видно з підключеного хоста."
},
"settings": {
"admin": {
"group": {
"touch": "Сенсорний ввід",
"images": "Зберігання зображень"
},
"sessionTimeoutMinutes": {
"label": "Збереження термінального сеансу",
"description": "Як довго відключений термінал залишається активним на сервері, перш ніж його буде закрито. Мін. 1 · Макс. 1440 хвилин. Для сеансів, які мають тривати довше цього часу, увімкніть Auto-Tmux на хості."
},
"commandHistoryEnabled": {
"label": "Історія команд",
"description": "Дозволити запис історії команд. Якщо вимкнено, історія не зберігається незалежно від налаштувань для кожного хоста."
},
"imageStorageMode": {
"label": "Режим зберігання",
"description": "Куди записуються вставлені та завантажені образи терміналів. Виправлено автоматичний вибір за підключенням, локальним та віддаленим (SFTP).",
"auto": "Авто",
"local": "Місцевий",
"remoteSftp": "Віддалений (SFTP)"
},
"imageLocalDir": {
"label": "Локальний каталог"
},
"imageHostPath": {
"label": "Шлях хоста",
"description": "Вигляд локального каталогу, який отримують агенти терміналів, з боку хоста."
},
"imageTtlMs": {
"label": "TTL (мс)"
},
"imageMaxCount": {
"label": "Максимальна кількість зображень"
},
"imageMaxBytes": {
"label": "Максимальна кількість байтів пам'яті"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Увімкнути панель інструментів термінала",
"description": "Показати панель швидкого доступу, прикріплену до активного терміналу, для інструментів цього хоста та актуальної статистики"
},
"enableCommandHistory": {
"label": "Історія команд",
"description": "Запис команд, що виконуються в цьому терміналі, для історії та автозаповнення"
},
"enableTerminal": {
"label": "Увімкнути термінал"
}
}
},
"palette": {
"localTerminal": "Місцевий термінал"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "Thêm",
"arrowDown": "Mũi tên xuống",
"arrowLeft": "Mũi tên trái",
"arrowRight": "Mũi tên phải",
"arrowUp": "Mũi tên lên",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "Xong",
"editQuickKeys": "Chỉnh sửa phím tắt",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "Dán",
"quickKeyPlaceholder": "vd: sudo ",
"quickKeysDesc": "Nhấn × để xóa. Hỗ trợ tối đa 8 ký tự.",
"quickKeysTitle": "Phím tắt",
"removeQuickKey": "Xóa",
"resetDefaults": "Đặt lại mặc định",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "Không thể bật Auto-Tmux.",
"autoTmuxEnabled": "Tính năng Auto-Tmux đã được bật cho {{host}}. Tính năng này sẽ được áp dụng từ lần kết nối tiếp theo.",
"clipboardHttpWarning": "Không thể đọc nội dung clipboard. Một số trình duyệt hạn chế quyền truy cập clipboard khi không sử dụng HTTPS - hãy thử Ctrl+V hoặc sử dụng HTTPS để chạy Termix.",
"closeTab": "Đóng",
"connecting": "Đang kết nối...",
"connectionLost": "Mất kết nối",
"connectionRejected": "Kết nối bị máy chủ từ chối. Vui lòng kiểm tra xác thực và cấu hình mạng.",
"connectionTimeout": "Hết thời gian kết nối",
"enableAutoTmuxAction": "Bật Auto-Tmux",
"hostKeyRejected": "Xác minh khóa máy chủ SSH bị từ chối. Đã hủy kết nối.",
"linkDialogCopy": "Sao chép",
"linkDialogOpen": "Mở",
"linkDialogTitle": "Mở liên kết",
"maxReconnectAttemptsReached": "Đã đạt số lần kết nối lại tối đa",
"noHostSelected": "Chưa chọn máy chủ",
"opksshAuthFailed": "Xác thực thất bại. Vui lòng kiểm tra thông tin đăng nhập và thử lại.",
"opksshTimeout": "Xác thực đã hết thời gian chờ. Vui lòng thử lại.",
"passwordPromptFillTitle": "Điền mật khẩu đã lưu vào dòng nhắc này?",
"reconnect": "Kết nối lại",
"reconnected": "Đã kết nối lại thành công",
"reconnecting": "Đang kết nối lại... ({{attempt}}/{{max}})",
"searchCaseSensitive": "Hộp diêm",
"searchClose": "Đóng (Thoát)",
"searchNext": "Trận đấu tiếp theo (Nhập)",
"searchNoResults": "Không có kết quả",
"searchPlaceholder": "Tìm thấy",
"searchPrevious": "Kết quả khớp trước đó (Shift+Enter)",
"searchRegex": "Sử dụng biểu thức chính quy",
"searchResultCount": "{{index}} của {{count}}",
"searchWholeWord": "Ghép toàn bộ từ",
"sessionExpiredNotice": "Phiên làm việc trước của bạn trên {{host}} đã hết hạn sau {{minutes}} phút bị ngắt kết nối; shell và mọi thứ đang chạy trong đó đều đã biến mất. Hãy bật Auto-Tmux trên máy chủ này để duy trì các phiên làm việc.",
"sessionExpiredNoticeNoMinutes": "Phiên làm việc trước của bạn trên {{host}} đã hết hạn khi bị ngắt kết nối; shell và mọi thứ đang chạy trong đó đều đã biến mất. Hãy bật Auto-Tmux trên máy chủ này để duy trì các phiên làm việc.",
"sessionTakenOver": "Phiên đã được mở trong tab khác. Đang kết nối lại...",
"tmuxAttached": "Các máy khách đã gắn kết",
"tmuxAttachedCount": "{{count}} đã gắn kết",
"tmuxCopyHint": "Điều chỉnh lựa chọn và nhấn Enter để sao chép vào clipboard",
"tmuxCreateNew": "Tạo phiên mới",
"tmuxDetached": "Đã ngắt khỏi phiên tmux",
"tmuxLastActivity": "Hoạt động cuối",
"tmuxSessionAttached": "Đã gắn vào phiên tmux: {{name}}",
"tmuxSessionCreated": "Đã tạo phiên tmux: {{name}}",
"tmuxSessionPickerDesc": "Các phiên tmux hiện có trên máy chủ này. Chọn một phiên để gắn lại hoặc tạo phiên mới.",
"tmuxSessionPickerTitle": "Phiên tmux",
"tmuxTimeDays": "{{count}} ngày trước",
"tmuxTimeHours": "{{count}} giờ trước",
"tmuxTimeJustNow": "vừa xong",
"tmuxTimeMinutes": "{{count}} phút trước",
"tmuxUnavailable": "tmux chưa được cài đặt trên máy chủ từ xa, chuyển sang shell tiêu chuẩn",
"tmuxWindowCount": "{{count}} cửa sổ",
"tmuxWindows": "Cửa sổ",
"unknownError": "Lỗi không xác định đã xảy ra",
"vaultAuthDescription": "Một cửa sổ đã mở để đăng nhập vào HashiCorp Vault. Hoàn tất đăng nhập ở đó; kết nối này sẽ tự động tiếp tục.",
"vaultAuthFailed": "Xác thực Vault thất bại. Vui lòng thử lại.",
"vaultAuthTitle": "Yêu cầu đăng nhập Vault",
"vaultReopen": "Mở lại cửa sổ đăng nhập",
"websocketAbnormalClose": "Kết nối đóng đột ngột. Có thể do sự cố cấu hình reverse proxy hoặc SSL. Vui lòng kiểm tra nhật ký máy chủ.",
"websocketError": "Lỗi kết nối WebSocket"
},
"terminalToolbar": {
"detachTmux": "Tách tmux",
"detachTmuxDescription": "Tách phiên tmux",
"dismissError": "Bỏ qua lỗi hình ảnh",
"hideToolbar": "Ẩn thanh công cụ",
"image": "Thao tác với hình ảnh (chỉ áp dụng cho hình ảnh)",
"imageActionFailed": "Thao tác xử lý hình ảnh thất bại",
"layout": "Chế độ hiển thị thanh công cụ",
"layoutExpanded": "Đã mở rộng với các số liệu",
"layoutIcon": "Chỉ có biểu tượng",
"layoutLabeled": "Biểu tượng và nhãn",
"moveToolbar": "Di chuyển thanh công cụ",
"moveToolbarHint": "Kéo để định vị lại thanh công cụ",
"paste": "Dán hình ảnh",
"pasteImage": "Dán hình ảnh từ clipboard (chỉ áp dụng cho hình ảnh)",
"retry": "Thử lại",
"showToolbar": "Mở rộng thanh công cụ",
"upload": "Tải ảnh lên",
"uploadImage": "Tải ảnh lên máy chủ",
"uploadingImage": "Đang tải lên..."
},
"history": {
"noTerminalSelected": "Chưa chọn terminal",
"noTerminalSelectedHint": "Mở một tab terminal SSH để xem lịch sử lệnh",
"searchPlaceholder": "Tìm kiếm lịch sử...",
"clearAll": "Xóa tất cả",
"noHistoryEntries": "Không có mục lịch sử",
"trackingDisabled": "Tính năng ghi lịch sử đã bị tắt"
},
"touchInput": {
"enabled": "Cho phép nhập liệu cảm ứng",
"enabledDesc": "Cho phép cuộn bằng một ngón tay trong cửa sổ dòng lệnh.",
"momentumEnabled": "Tạo đà",
"momentumEnabledDesc": "Tiếp tục cuộn xuống một chút sau khi vuốt.",
"advanced": "Trình độ cao",
"resetDefaults": "Khôi phục về cài đặt mặc định",
"dragThreshold": "Ngưỡng kéo",
"maxWheelDelta": "Chênh lệch bánh xe tối đa",
"momentumSampleWindow": "Cửa sổ mẫu động lượng",
"releaseGracePeriod": "thời gian ân hạn thả",
"minimumVelocity": "Vận tốc tối thiểu",
"maximumVelocity": "Vận tốc tối đa",
"maximumDuration": "Thời gian tối đa",
"maximumTravel": "Hành trình tối đa",
"decayTime": "Thời gian phân rã",
"pixelsPerTick": "Số pixel trên mỗi tick",
"maximumFrameInterval": "Khoảng thời gian khung hình tối đa",
"maximumTicksPerFrame": "Số tick tối đa mỗi khung hình"
},
"imageStorage": {
"instanceId": "ID phiên bản phiên thiết bị đầu cuối",
"instanceIdDesc": "ID phiên bản của một trong các tab thiết bị đầu cuối được kết nối của bạn, được sử dụng để kiểm tra khả năng hiển thị.",
"selectedModeUnavailable": "Không khả dụng",
"test": "Bài kiểm tra",
"testing": "Đang thử nghiệm...",
"testFailed": "Không thể kiểm tra lưu trữ hình ảnh",
"testConnected": "Phiên kết nối terminal đã được thiết lập.",
"testNotConnected": "Không có phiên làm việc thiết bị đầu cuối nào được kết nối với ID phiên bản này.",
"testSelectedMode": "Quá trình tải lên sẽ sử dụng: {{mode}}",
"testLocalVisible": "Thư mục cục bộ có thể được nhìn thấy từ máy chủ được kết nối.",
"testLocalNotVisible": "Thư mục cục bộ KHÔNG hiển thị từ máy chủ được kết nối."
},
"settings": {
"admin": {
"group": {
"touch": "Nhập liệu cảm ứng",
"images": "Lưu trữ hình ảnh"
},
"sessionTimeoutMinutes": {
"label": "Duy trì phiên đầu cuối",
"description": "Thời gian duy trì kết nối của một thiết bị đầu cuối bị ngắt kết nối trên máy chủ trước khi nó bị đóng. Tối thiểu 1 phút · Tối đa 1440 phút. Đối với các phiên cần duy trì lâu hơn thời gian này, hãy bật Auto-Tmux trên máy chủ."
},
"commandHistoryEnabled": {
"label": "Lịch sử lệnh",
"description": "Cho phép ghi lịch sử lệnh. Khi tắt, lịch sử sẽ không được lưu bất kể cài đặt từng máy chủ."
},
"imageStorageMode": {
"label": "Chế độ lưu trữ",
"description": "Nơi hiển thị hình ảnh từ thiết bị đầu cuối được dán và tải lên. Chức năng tự động chọn theo kết nối, cục bộ và từ xa (SFTP) được cố định.",
"auto": "Tự động",
"local": "Địa phương",
"remoteSftp": "Từ xa (SFTP)"
},
"imageLocalDir": {
"label": "Thư mục cục bộ"
},
"imageHostPath": {
"label": "Đường dẫn máy chủ",
"description": "Đây là giao diện phía máy chủ của thư mục cục bộ mà các tác nhân đầu cuối nhận được."
},
"imageTtlMs": {
"label": "TTL (ms)"
},
"imageMaxCount": {
"label": "Số lượng ảnh tối đa"
},
"imageMaxBytes": {
"label": "Số byte lưu trữ tối đa"
}
},
"host": {
"enableTerminalToolbar": {
"label": "Kích hoạt thanh công cụ Terminal",
"description": "Hiển thị thanh công cụ truy cập nhanh được gắn vào cửa sổ terminal đang hoạt động để xem các công cụ và số liệu thống kê trực tiếp của máy chủ này."
},
"enableCommandHistory": {
"label": "Lịch sử lệnh",
"description": "Ghi lại các lệnh chạy trong terminal này để dùng cho lịch sử và tự động hoàn thành"
},
"enableTerminal": {
"label": "Bật Terminal"
}
}
},
"palette": {
"localTerminal": "Ga địa phương"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "添加",
"arrowDown": "Arrow Down",
"arrowLeft": "Arrow Left",
"arrowRight": "Arrow Right",
"arrowUp": "Arrow Up",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "完成",
"editQuickKeys": "编辑快速键",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "粘贴",
"quickKeyPlaceholder": "例如 sudo ",
"quickKeysDesc": "点击 × 删除。最多支持 8 个字符。",
"quickKeysTitle": "快速键",
"removeQuickKey": "删除",
"resetDefaults": "重置为默认值",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "启用自动 Tmux 失败",
"autoTmuxEnabled": "对 {{host}}启用自动 Tmux。它从下一个连接开始生效。",
"clipboardHttpWarning": "无法读取剪贴板内容。部分浏览器限制在 HTTPS 协议之外访问剪贴板——请尝试使用 Ctrl+V 粘贴,或通过 HTTPS 协议访问 Termix。",
"closeTab": "关闭",
"connecting": "正在连接…",
"connectionLost": "连接丢失",
"connectionRejected": "服务器拒绝了连接。请检查您的身份验证和网络配置。",
"connectionTimeout": "连接超时",
"enableAutoTmuxAction": "启用自动 Tmux",
"hostKeyRejected": "SSH主机密钥验证被拒绝。连接已取消。",
"linkDialogCopy": "复制",
"linkDialogOpen": "打开",
"linkDialogTitle": "打开链接",
"maxReconnectAttemptsReached": "已达到最大重连尝试次数",
"noHostSelected": "未选择主机",
"opksshAuthFailed": "认证失败,请检查凭据并重试。",
"opksshTimeout": "认证超时,请重试。",
"passwordPromptFillTitle": "将保存的密码填入此提示?",
"reconnect": "重新连接",
"reconnected": "重新连接成功",
"reconnecting": "正在重新连接…({{attempt}}/{{max}})",
"searchCaseSensitive": "区分大小写",
"searchClose": "关闭(Esc)",
"searchNext": "下一个匹配(Enter)",
"searchNoResults": "没有结果",
"searchPlaceholder": "搜索",
"searchPrevious": "上一个匹配(Shift+Enter)",
"searchRegex": "使用正则表达式",
"searchResultCount": "{{index}} 的 {{count}}",
"searchWholeWord": "全字匹配",
"sessionExpiredNotice": "您之前在 {{host}} 上的会话已在 {{minutes}} 分钟后断开连接并过期;shell 及其中运行的所有内容均已丢失。请在此主机上启用 Auto-Tmux 以保持会话连接。",
"sessionExpiredNoticeNoMinutes": "您之前在 {{host}} 上的会话已过期,且已断开连接;shell 及其中运行的所有内容均已丢失。请在此主机上启用 Auto-Tmux 以保持会话有效。",
"sessionTakenOver": "会话已在另一个标签页中打开。正在重新连接...",
"tmuxAttached": "已连接客户端",
"tmuxAttachedCount": "{{count}} 个已连接",
"tmuxCopyHint": "调整选择并按 Enter 复制到剪贴板",
"tmuxCreateNew": "启动新会话",
"tmuxDetached": "已从 tmux 会话分离",
"tmuxLastActivity": "最后活动",
"tmuxSessionAttached": "tmux 会话已附加:{{name}}",
"tmuxSessionCreated": "tmux 会话已创建:{{name}}",
"tmuxSessionPickerDesc": "在此主机上发现现有 tmux 会话。选择一个重新连接或创建新会话。",
"tmuxSessionPickerTitle": "tmux 会话",
"tmuxTimeDays": "{{count}} 天前",
"tmuxTimeHours": "{{count}} 小时前",
"tmuxTimeJustNow": "刚刚",
"tmuxTimeMinutes": "{{count}} 分钟前",
"tmuxUnavailable": "远程主机上未安装 tmux,回退到标准 shell",
"tmuxWindowCount": "{{count}} 个窗口",
"tmuxWindows": "窗口",
"unknownError": "发生未知错误",
"vaultAuthDescription": "已打开窗口以登录 HashiCorp Vault。在该处完成登录;此连接将自动继续。",
"vaultAuthFailed": "Vault 认证失败,请重试。",
"vaultAuthTitle": "需要 Vault 登录",
"vaultReopen": "重新打开登录窗口",
"websocketAbnormalClose": "连接意外关闭。这可能是因为反向代理或SSL配置问题。请检查服务器日志。",
"websocketError": "WebSocket 连接错误"
},
"terminalToolbar": {
"detachTmux": "分离 tmux",
"detachTmuxDescription": "分离 tmux 会话",
"dismissError": "忽略图像错误",
"hideToolbar": "隐藏工具栏",
"image": "图片操作(仅限图片)",
"imageActionFailed": "图片操作失败",
"layout": "工具栏显示模式",
"layoutExpanded": "扩展了指标",
"layoutIcon": "仅图标",
"layoutLabeled": "图标和标签",
"moveToolbar": "移动工具栏",
"moveToolbarHint": "拖动即可重新定位工具栏",
"paste": "粘贴图片",
"pasteImage": "从剪贴板粘贴图片(仅限图片)",
"retry": "重试",
"showToolbar": "展开工具栏",
"upload": "上传图片",
"uploadImage": "将图片上传到主机",
"uploadingImage": "正在上传..."
},
"history": {
"noTerminalSelected": "未选择终端",
"noTerminalSelectedHint": "打开一个 SSH 终端标签页以查看其命令历史",
"searchPlaceholder": "搜索历史...",
"clearAll": "全部清除",
"noHistoryEntries": "无历史记录",
"trackingDisabled": "历史跟踪已禁用"
},
"touchInput": {
"enabled": "启用触摸输入",
"enabledDesc": "允许终端单指触摸滚动",
"momentumEnabled": "启用动能",
"momentumEnabledDesc": "滑动后继续短暂滚动。",
"advanced": "高级",
"resetDefaults": "恢复默认设置",
"dragThreshold": "拖动阈值",
"maxWheelDelta": "最大滚轮增量",
"momentumSampleWindow": "动量样本窗口",
"releaseGracePeriod": "释放宽限期",
"minimumVelocity": "最小速度",
"maximumVelocity": "最大速度",
"maximumDuration": "最长持续时间",
"maximumTravel": "最大行程",
"decayTime": "衰减时间",
"pixelsPerTick": "每刻像素数",
"maximumFrameInterval": "最大帧间隔",
"maximumTicksPerFrame": "每帧最大刻度数"
},
"imageStorage": {
"instanceId": "终端会话实例 ID",
"instanceIdDesc": "用于测试可见性的已连接终端标签页的实例 ID。",
"selectedModeUnavailable": "不可用",
"test": "测试",
"testing": "测试中……",
"testFailed": "图像存储测试失败",
"testConnected": "终端会话已连接。",
"testNotConnected": "没有与此实例 ID 连接的终端会话。",
"testSelectedMode": "上传将使用:{{mode}}",
"testLocalVisible": "本地目录对连接的主机可见。",
"testLocalNotVisible": "从连接的主机上不可见本地目录。"
},
"settings": {
"admin": {
"group": {
"touch": "触摸输入",
"images": "图像存储"
},
"sessionTimeoutMinutes": {
"label": "终端会话持久性",
"description": "断开连接的终端在服务器上保持活动状态的最长时间。最短 1 分钟,最长 1440 分钟。对于必须保持活动时间超过此限制的会话,请在主机上启用自动终端复用 (Auto-Tmux)。"
},
"commandHistoryEnabled": {
"label": "命令历史",
"description": "允许记录命令历史。禁用后,无论各主机设置如何,均不保存历史记录。"
},
"imageStorageMode": {
"label": "存储模式",
"description": "粘贴和上传的终端图片的写入位置。自动模式根据连接自动选择,本地和远程(SFTP)为固定模式。",
"auto": "自动",
"local": "本地",
"remoteSftp": "远程(SFTP)"
},
"imageLocalDir": {
"label": "本地目录"
},
"imageHostPath": {
"label": "主机路径",
"description": "终端代理接收到的本地目录的主机端视图。"
},
"imageTtlMs": {
"label": "TTL(毫秒)"
},
"imageMaxCount": {
"label": "最大图像数量"
},
"imageMaxBytes": {
"label": "最大存储字节"
}
},
"host": {
"enableTerminalToolbar": {
"label": "启用终端工具栏",
"description": "在当前终端上显示该主机的工具和实时信息的 Dock栏"
},
"enableCommandHistory": {
"label": "命令历史",
"description": "记录此终端中运行的命令,用于历史记录和自动补全"
},
"enableTerminal": {
"label": "启用终端"
}
}
},
"palette": {
"localTerminal": "本地终端"
}
}
@@ -0,0 +1,203 @@
{
"mobileKeyboard": {
"addQuickKey": "新增",
"arrowDown": "下箭頭",
"arrowLeft": "左箭頭",
"arrowRight": "右箭頭",
"arrowUp": "上箭頭",
"backTab": "⇥",
"ctrl": "Ctrl",
"delete": "Del",
"done": "完成",
"editQuickKeys": "編輯快捷鍵",
"end": "End",
"esc": "Esc",
"home": "Home",
"pageDown": "PgDn",
"pageUp": "PgUp",
"paste": "貼上",
"quickKeyPlaceholder": "例如:sudo ",
"quickKeysDesc": "點選 × 來移除。最多支援 8 個字元。",
"quickKeysTitle": "快捷鍵",
"removeQuickKey": "移除",
"resetDefaults": "重設為預設值",
"shift": "Shift",
"tab": "Tab"
},
"terminal": {
"autoTmuxEnableFailed": "啟用自動 Tmux 失敗",
"autoTmuxEnabled": "對 {{host}}啟用自動 Tmux。它從下一個連接開始生效。",
"clipboardHttpWarning": "無法讀取剪貼簿內容。部分瀏覽器限制在 HTTPS 協定之外存取剪貼簿內容 - 請嘗試使用 Ctrl+V 貼上,或透過 HTTPS 協定存取 Termix。",
"closeTab": "關閉",
"connecting": "連線中...",
"connectionLost": "連線中斷",
"connectionRejected": "連線被伺服器拒絕。請檢查您的驗證與網路設定。",
"connectionTimeout": "連線逾時",
"enableAutoTmuxAction": "啟用自動 Tmux",
"hostKeyRejected": "SSH 主機金鑰驗證被拒絕。連線已取消。",
"linkDialogCopy": "複製",
"linkDialogOpen": "開啟",
"linkDialogTitle": "開啟連結",
"maxReconnectAttemptsReached": "已達最大重新連線嘗試次數",
"noHostSelected": "未選取主機",
"opksshAuthFailed": "驗證失敗。請檢查您的憑證並再試一次。",
"opksshTimeout": "驗證逾時。請再試一次。",
"passwordPromptFillTitle": "將已儲存的密碼填入此提示?",
"reconnect": "重新連線",
"reconnected": "已成功重新連線",
"reconnecting": "重新連線中... ({{attempt}}/{{max}})",
"searchCaseSensitive": "火柴盒",
"searchClose": "關閉(退出)",
"searchNext": "下一場比賽(輸入)",
"searchNoResults": "沒有結果",
"searchPlaceholder": "尋找",
"searchPrevious": "最後一場比賽(Shift+Enter)",
"searchRegex": "使用正規表示式",
"searchResultCount": "{{index}} 的 {{count}}",
"searchWholeWord": "整詞匹配",
"sessionExpiredNotice": "您之前在 {{host}} 上的會話已在 {{minutes}} 分鐘後斷開連接並過期;shell 及其中運行的所有內容均已丟失。請在此主機上啟用 Auto-Tmux 以保持會話連線。",
"sessionExpiredNoticeNoMinutes": "您先前在 {{host}} 上的會話已過期,且已斷線;shell 及其中執行的所有內容均已遺失。請在此主機上啟用 Auto-Tmux 以保持會話有效。",
"sessionTakenOver": "工作階段已在另一個分頁中開啟。正在重新連線…",
"tmuxAttached": "已附加的用戶端",
"tmuxAttachedCount": "{{count}} 個已附加",
"tmuxCopyHint": "調整選取範圍並按下 Enter 以複製到剪貼簿",
"tmuxCreateNew": "開始新的工作階段",
"tmuxDetached": "已從 tmux 工作階段中斷連線",
"tmuxLastActivity": "上次活動",
"tmuxSessionAttached": "tmux 工作階段已附加:{{name}}",
"tmuxSessionCreated": "tmux 工作階段已建立:{{name}}",
"tmuxSessionPickerDesc": "在此主機上找到現有的 tmux 工作階段。選擇一個以重新附加或建立新的工作階段。",
"tmuxSessionPickerTitle": "tmux 工作階段",
"tmuxTimeDays": "{{count}} 天前",
"tmuxTimeHours": "{{count}} 小時前",
"tmuxTimeJustNow": "剛剛",
"tmuxTimeMinutes": "{{count}} 分鐘前",
"tmuxUnavailable": "遠端主機未安裝 tmux,切換回標準 shell",
"tmuxWindowCount": "{{count}} 個視窗",
"tmuxWindows": "視窗",
"unknownError": "發生未知錯誤",
"vaultAuthDescription": "已開啟視窗以登入 HashiCorp Vault。在該處完成登入;此連線將自動繼續。",
"vaultAuthFailed": "Vault 驗證失敗。請再試一次。",
"vaultAuthTitle": "需要 Vault 登入",
"vaultReopen": "重新開啟登入視窗",
"websocketAbnormalClose": "連線意外關閉。這可能是由於反向代理或 SSL 設定問題所導致。請檢查伺服器記錄。",
"websocketError": "WebSocket 連線錯誤"
},
"terminalToolbar": {
"detachTmux": "分離 tmux",
"detachTmuxDescription": "分離 tmux 會話",
"dismissError": "忽略圖像錯誤",
"hideToolbar": "隱藏工具列",
"image": "圖片操作(僅限圖片)",
"imageActionFailed": "圖片操作失敗",
"layout": "工具列顯示模式",
"layoutExpanded": "擴展了指標",
"layoutIcon": "僅圖示",
"layoutLabeled": "圖標和標籤",
"moveToolbar": "移動工具列",
"moveToolbarHint": "拖曳即可重新定位工具列",
"paste": "貼上圖片",
"pasteImage": "從剪貼簿貼上圖片(僅限圖片)",
"retry": "重試",
"showToolbar": "展開工具列",
"upload": "上傳圖片",
"uploadImage": "將圖片上傳到主機",
"uploadingImage": "正在上傳..."
},
"history": {
"noTerminalSelected": "未選取終端機",
"noTerminalSelectedHint": "開啟 SSH 終端機分頁以檢視其命令歷史記錄",
"searchPlaceholder": "搜尋歷史記錄...",
"clearAll": "全部清除",
"noHistoryEntries": "無歷史記錄項目",
"trackingDisabled": "歷史記錄追蹤已停用"
},
"touchInput": {
"enabled": "啟用觸控輸入",
"enabledDesc": "允許終端單指觸摸滾動",
"momentumEnabled": "啟用動能",
"momentumEnabledDesc": "滑動後繼續短暫滾動。",
"advanced": "先進的",
"resetDefaults": "恢復預設設定",
"dragThreshold": "阻力閾值",
"maxWheelDelta": "最大車輪增量",
"momentumSampleWindow": "動量樣本窗口",
"releaseGracePeriod": "發布寬限期",
"minimumVelocity": "最低速度",
"maximumVelocity": "最大速度",
"maximumDuration": "最長持續時間",
"maximumTravel": "最大行程",
"decayTime": "衰減時間",
"pixelsPerTick": "每刻像素數",
"maximumFrameInterval": "最大訊框間隔",
"maximumTicksPerFrame": "每格最大幀數"
},
"imageStorage": {
"instanceId": "終端機會話實例 ID",
"instanceIdDesc": "用於測試可見性的已連接終端選項卡的實例 ID。",
"selectedModeUnavailable": "不可用",
"test": "測試",
"testing": "測試中…",
"testFailed": "影像儲存測試失敗",
"testConnected": "終端會話已連線。",
"testNotConnected": "沒有與此實例 ID 連線的終端會話。",
"testSelectedMode": "上傳時請使用: {{mode}}",
"testLocalVisible": "本機目錄對連接的主機可見。",
"testLocalNotVisible": "從連線的主機上不可見本機目錄。"
},
"settings": {
"admin": {
"group": {
"touch": "觸摸輸入",
"images": "影像儲存"
},
"sessionTimeoutMinutes": {
"label": "終端會話持久性",
"description": "斷開連線的終端機在伺服器上保持活動狀態的最長時間。最短 1 分鐘,最長 1440 分鐘。對於必須保持活動時間超過此限制的會話,請在主機上啟用自動終端機重複使用 (Auto-Tmux)。"
},
"commandHistoryEnabled": {
"label": "指令歷史記錄",
"description": "允許記錄指令歷史記錄。停用時,無論各主機設定如何,都不會儲存歷史記錄。"
},
"imageStorageMode": {
"label": "儲存模式",
"description": "終端鏡像貼上和上傳位置已設定。自動選擇連接、本機和遠端(SFTP)鏡像的功能已修復。",
"auto": "汽車",
"local": "當地的",
"remoteSftp": "遠端(SFTP)"
},
"imageLocalDir": {
"label": "本地目錄"
},
"imageHostPath": {
"label": "主機路徑",
"description": "終端代理接收到的本機目錄的主機端視圖。"
},
"imageTtlMs": {
"label": "TTL(毫秒)"
},
"imageMaxCount": {
"label": "最大影像數量"
},
"imageMaxBytes": {
"label": "最大儲存位元組"
}
},
"host": {
"enableTerminalToolbar": {
"label": "啟用終端機工具列",
"description": "在目前終端上顯示一個快速存取工具列,用於存取該主機的工具和即時統計資訊。"
},
"enableCommandHistory": {
"label": "命令歷史記錄",
"description": "記錄在此終端機中執行的命令,用於歷史記錄與自動完成"
},
"enableTerminal": {
"label": "啟用終端機"
}
}
},
"palette": {
"localTerminal": "本地端"
}
}
+193 -3
View File
@@ -2,7 +2,7 @@
"id": "ssh-terminal",
"name": "SSH Terminal",
"version": "1.0.0",
"description": "Interactive SSH terminal sessions, with jump hosts, host key verification and session sharing.",
"description": "Interactive SSH terminal sessions, with jump hosts, host key verification, command history and the desktop app's local terminal.",
"author": {
"name": "Termix"
},
@@ -19,8 +19,39 @@
"credentials:use",
"ssh:connect",
"network:serve",
"db:own",
"events:core",
"ui:surface"
],
"provides": [
{
"service": "sessions.live",
"version": "1.0.0",
"permission": "ssh-terminal.sessions"
},
{
"service": "terminal.history",
"version": "1.0.0",
"permission": "ssh-terminal.history"
}
],
"requires": [
{
"service": "tmux.sessions",
"versionRange": "^1.0.0",
"optional": true
},
{
"service": "sessions.sharing",
"versionRange": "^1.0.0",
"optional": true
},
{
"service": "recordings.writer",
"versionRange": "^1.0.0",
"optional": true
}
],
"contributes": {
"tabs": [
{
@@ -28,6 +59,19 @@
"titleKey": "nav.terminal",
"icon": "SquareTerminal",
"openFrom": ["host-context-menu", "palette"]
},
{
"id": "local-terminal",
"titleKey": "nav.localTerminal",
"icon": "Laptop",
"openFrom": ["rail", "palette"]
}
],
"panels": [
{
"id": "history",
"titleKey": "nav.history",
"icon": "History"
}
],
"actionSlots": [
@@ -36,14 +80,160 @@
"accepts": ["button"]
},
{
"id": "terminal.dock",
"id": "terminal.toolbarStatus",
"accepts": ["component"]
},
{
"id": "terminal.sidePanel",
"accepts": ["component"]
},
{
"id": "terminal.overlay",
"accepts": ["component"]
}
]
],
"permissions": [
{
"name": "sessions",
"titleKey": "permissions.sessions.title",
"descriptionKey": "permissions.sessions.description",
"defaultRoles": ["admin", "user"]
},
{
"name": "history",
"titleKey": "permissions.history.title",
"descriptionKey": "permissions.history.description",
"defaultRoles": ["admin", "user"]
}
],
"settings": {
"admin": [
{
"key": "sessionTimeoutMinutes",
"type": "number",
"labelKey": "settings.admin.sessionTimeoutMinutes.label",
"descriptionKey": "settings.admin.sessionTimeoutMinutes.description",
"group": "settings.admin.group.sessions",
"min": 1,
"max": 1440,
"default": 30
},
{
"key": "sessionPersistence",
"type": "boolean",
"labelKey": "settings.admin.sessionPersistence.label",
"descriptionKey": "settings.admin.sessionPersistence.description",
"group": "settings.admin.group.sessions",
"default": true
},
{
"key": "commandHistoryEnabled",
"type": "boolean",
"labelKey": "settings.admin.commandHistoryEnabled.label",
"descriptionKey": "settings.admin.commandHistoryEnabled.description",
"group": "settings.admin.group.sessions",
"default": true
},
{
"key": "touchInput",
"type": "custom",
"component": "touchInput",
"group": "settings.admin.group.touch"
},
{
"key": "imageStorageMode",
"type": "select",
"labelKey": "settings.admin.imageStorageMode.label",
"descriptionKey": "settings.admin.imageStorageMode.description",
"group": "settings.admin.group.images",
"options": [
{
"value": "auto",
"labelKey": "settings.admin.imageStorageMode.auto"
},
{
"value": "local",
"labelKey": "settings.admin.imageStorageMode.local"
},
{
"value": "remote-sftp",
"labelKey": "settings.admin.imageStorageMode.remoteSftp"
}
]
},
{
"key": "imageLocalDir",
"type": "secret",
"labelKey": "settings.admin.imageLocalDir.label",
"descriptionKey": "settings.admin.imageLocalDir.description",
"group": "settings.admin.group.images"
},
{
"key": "imageHostPath",
"type": "string",
"labelKey": "settings.admin.imageHostPath.label",
"descriptionKey": "settings.admin.imageHostPath.description",
"group": "settings.admin.group.images"
},
{
"key": "imageTtlMs",
"type": "number",
"labelKey": "settings.admin.imageTtlMs.label",
"descriptionKey": "settings.admin.imageTtlMs.description",
"group": "settings.admin.group.images",
"min": 0
},
{
"key": "imageMaxCount",
"type": "number",
"labelKey": "settings.admin.imageMaxCount.label",
"descriptionKey": "settings.admin.imageMaxCount.description",
"group": "settings.admin.group.images",
"min": 1
},
{
"key": "imageMaxBytes",
"type": "number",
"labelKey": "settings.admin.imageMaxBytes.label",
"descriptionKey": "settings.admin.imageMaxBytes.description",
"group": "settings.admin.group.images",
"min": 1048576
},
{
"key": "imageStorageTest",
"type": "custom",
"component": "imageStorageTest",
"group": "settings.admin.group.images"
}
],
"host": {
"fields": [
{
"key": "enableTerminal",
"type": "boolean",
"labelKey": "settings.host.enableTerminal.label",
"descriptionKey": "settings.host.enableTerminal.description",
"default": true
},
{
"key": "enableTerminalToolbar",
"type": "boolean",
"labelKey": "settings.host.enableTerminalToolbar.label",
"descriptionKey": "settings.host.enableTerminalToolbar.description",
"requires": "enableTerminal",
"default": true
},
{
"key": "enableCommandHistory",
"type": "boolean",
"labelKey": "settings.host.enableCommandHistory.label",
"descriptionKey": "settings.host.enableCommandHistory.description",
"requires": "enableTerminal",
"default": true
}
]
}
}
},
"locales": "locales",
"platforms": ["linux", "win32", "darwin"]
@@ -0,0 +1,13 @@
-- ssh-terminal 0001: adopt_command_history
-- Generated by termix-plugin migrations. Review before committing.
CREATE TABLE IF NOT EXISTS `command_history` (
`id` int AUTO_INCREMENT PRIMARY KEY,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`command` text NOT NULL,
`executed_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE
);
RENAME TABLE `command_history` TO `p_ssh_terminal_command_history`;
@@ -0,0 +1,14 @@
-- ssh-terminal 0001: adopt_command_history
-- Generated by termix-plugin migrations. Review before committing.
CREATE TABLE IF NOT EXISTS "command_history" (
"id" serial PRIMARY KEY,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"command" text NOT NULL,
"executed_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE,
FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS "idx_command_history_user_host" ON "command_history" ("user_id", "host_id");
ALTER TABLE "command_history" RENAME TO "p_ssh_terminal_command_history";
@@ -0,0 +1,39 @@
{
"version": 1,
"tables": {
"command_history": {
"columns": {
"id": {
"type": "id",
"primaryKey": true,
"notNull": true
},
"userId": {
"type": "refUser",
"length": 255,
"notNull": true
},
"hostId": {
"type": "refHost",
"notNull": true
},
"command": {
"type": "text",
"notNull": true
},
"executedAt": {
"type": "timestamp",
"notNull": true,
"defaultNow": true
}
},
"indexes": [
{
"name": "idx_command_history_user_host",
"columns": ["userId", "hostId"]
}
],
"adopts": "command_history"
}
}
}
@@ -0,0 +1,14 @@
-- ssh-terminal 0001: adopt_command_history
-- Generated by termix-plugin migrations. Review before committing.
CREATE TABLE IF NOT EXISTS "command_history" (
"id" integer PRIMARY KEY AUTOINCREMENT,
"user_id" text NOT NULL,
"host_id" integer NOT NULL,
"command" text NOT NULL,
"executed_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE,
FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS "idx_command_history_user_host" ON "command_history" ("user_id", "host_id");
ALTER TABLE "command_history" RENAME TO "p_ssh_terminal_command_history";
+284
View File
@@ -0,0 +1,284 @@
import dns from "dns/promises";
import net from "net";
import type { RawData } from "ws";
import ssh2Pkg, {
type GetStreamCallback,
type IdentityCallback,
type ParsedKey,
type SignCallback,
type SigningRequestOptions,
} from "ssh2";
import type { PluginLogger } from "@termix/plugin-sdk/backend";
const { AgentProtocol, BaseAgent } = ssh2Pkg;
export function getErrorMessage(
error: unknown,
fallback = "Unknown error",
): string {
return error instanceof Error ? error.message : fallback;
}
/** The terminal's structured logging, on top of ctx.log. */
export interface TerminalLogger {
info: (message: string, meta?: Record<string, unknown>) => void;
success: (message: string, meta?: Record<string, unknown>) => void;
warn: (message: string, meta?: Record<string, unknown>) => void;
error: (
message: string,
error?: unknown,
meta?: Record<string, unknown>,
) => void;
}
function withMeta(message: string, meta?: Record<string, unknown>): string {
if (!meta || Object.keys(meta).length === 0) return message;
try {
return `${message} ${JSON.stringify(meta)}`;
} catch {
return message;
}
}
export function createTerminalLogger(log: PluginLogger): TerminalLogger {
return {
info: (message, meta) => log.info(withMeta(message, meta)),
success: (message, meta) => log.info(withMeta(message, meta)),
warn: (message, meta) => log.warn(withMeta(message, meta)),
error: (message, error, meta) => {
// Some call sites pass the meta object where the error goes.
if (error && !(error instanceof Error) && typeof error === "object") {
log.error(withMeta(message, error as Record<string, unknown>));
return;
}
log.error(
withMeta(message, meta),
error instanceof Error
? error
: error
? new Error(String(error))
: undefined,
);
},
};
}
// Cap on a single decoded text frame. Legitimate control messages are tiny,
// and terminal input is bounded by what a user can type or paste.
export const MAX_WS_MESSAGE_BYTES = 1024 * 1024;
export class WsMessageError extends Error {}
function rawByteLength(raw: RawData): number {
if (Buffer.isBuffer(raw)) return raw.length;
if (Array.isArray(raw))
return raw.reduce((sum, part) => sum + part.length, 0);
if (raw instanceof ArrayBuffer) return raw.byteLength;
return 0;
}
/** Parses a frame into `{ type, data }`, throwing WsMessageError on anything else. */
export function parseWsMessage(raw: RawData): {
type: string;
data: unknown;
} {
if (rawByteLength(raw) > MAX_WS_MESSAGE_BYTES) {
throw new WsMessageError("Message too large");
}
let parsed: unknown;
try {
parsed = JSON.parse(raw.toString());
} catch {
throw new WsMessageError("Invalid JSON");
}
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new WsMessageError("Message must be a JSON object");
}
const { type, data } = parsed as { type?: unknown; data?: unknown };
if (typeof type !== "string") {
throw new WsMessageError("Message type must be a string");
}
return { type, data };
}
export function asObject(data: unknown): Record<string, unknown> {
return data !== null && typeof data === "object" && !Array.isArray(data)
? (data as Record<string, unknown>)
: {};
}
export function asString(value: unknown): string {
return typeof value === "string" ? value : "";
}
/** A sane integer width or height, or 0 when the value is unusable. */
export function toTerminalDimension(value: unknown): number {
const n = typeof value === "number" ? value : Number(value);
if (!Number.isFinite(n)) return 0;
const rounded = Math.floor(n);
if (rounded < 1) return 0;
return Math.min(rounded, 10000);
}
export function isWindowsSftpPath(path: string): boolean {
const normalized = path.replace(/\\/g, "/");
return /^[A-Za-z]:\//.test(normalized) || /^\/[A-Za-z]:\//.test(normalized);
}
export function sftpPathToLocalPath(sftpPath: string): string {
const normalized = sftpPath.replace(/\\/g, "/");
if (/^\/[A-Za-z]:\//.test(normalized)) {
return normalized.slice(1).replace(/\//g, "\\");
}
if (/^[A-Za-z]:\//.test(normalized)) {
return normalized.replace(/\//g, "\\");
}
return sftpPath;
}
export const SSH_DNS_RETRY_DELAYS_MS = [250, 750, 1500];
type Lookup = typeof dns.lookup;
type Sleep = (ms: number) => Promise<void>;
const sleep: Sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export function isRetriableDnsError(error: unknown): boolean {
const err = error as { code?: unknown; message?: unknown };
return (
err.code === "EAI_AGAIN" ||
(typeof err.message === "string" && err.message.includes("EAI_AGAIN"))
);
}
/** Resolves a hostname up front, retrying transient container DNS failures. */
export async function resolveHostForSshConnect(
host: string,
lookup: Lookup = dns.lookup,
retryDelaysMs = SSH_DNS_RETRY_DELAYS_MS,
wait: Sleep = sleep,
): Promise<{ host: string; resolvedAddress?: string; attempts: number }> {
const normalized = host.replace(/^\[|\]$/g, "").trim();
if (!normalized || net.isIP(normalized) !== 0) {
return { host: normalized || host, attempts: 0 };
}
for (let attempt = 0; ; attempt += 1) {
try {
const result = await lookup(normalized);
return {
host: result.address,
resolvedAddress: result.address,
attempts: attempt + 1,
};
} catch (error) {
if (!isRetriableDnsError(error) || attempt >= retryDelaysMs.length) {
throw error;
}
await wait(retryDelaysMs[attempt]);
}
}
}
function normalizeHostAddress(value: unknown): string {
if (typeof value !== "string") return "";
return value
.replace(/^\[|\]$/g, "")
.trim()
.toLowerCase();
}
/**
* Whether a numeric host id resolved to a different machine than the client
* meant. Ids from the desktop app and a sync server drift apart, and the
* resolved row supplies the credentials, so a mismatch must be refused.
*/
export function hostAddressMismatch(
clientAddress: unknown,
resolvedAddress: unknown,
): boolean {
const resolved = normalizeHostAddress(resolvedAddress);
if (!resolved) return false;
return resolved !== normalizeHostAddress(clientAddress);
}
export const HOST_ADDRESS_MISMATCH_MESSAGE =
"Host mismatch: this server resolved the selected host to a different machine, so the connection was refused. " +
"The host ids on this device and on the sync server have drifted apart. " +
'Set the connection origin to "This device" for this host, or re-run a full sync, then try again.';
export const HOST_NOT_ON_THIS_SERVER_MESSAGE =
"This host does not exist on the sync server, so the connection was refused. " +
'Run a sync so the server knows about it, or set the connection origin to "This device" for this host.';
export function resolveServerJumpHosts(
clientJumpHosts: Array<{ hostId: number }> | undefined,
serverJumpHosts: Array<{ hostId: number }> | undefined,
hostSyncId?: string | null,
): Array<{ hostId: number }> | undefined {
if (hostSyncId) return serverJumpHosts ?? [];
return clientJumpHosts?.length ? clientJumpHosts : serverJumpHosts;
}
export function resolveServerHostId(
clientHostId: number | null,
resolvedHost: { id?: unknown } | null,
): number | null {
return typeof resolvedHost?.id === "number" ? resolvedHost.id : clientHostId;
}
/** An in-memory SSH agent holding one key, for agent forwarding. */
export class MemoryAgent extends BaseAgent {
private key: ParsedKey;
constructor(key: ParsedKey) {
super();
this.key = key;
}
getIdentities(cb: IdentityCallback<ParsedKey>): void {
cb(null, [this.key]);
}
getStream(cb: GetStreamCallback): void {
const protocol = new AgentProtocol(false);
protocol.on("identities", (request) => {
protocol.getIdentitiesReply(request, [this.key]);
});
protocol.on("sign", (request, publicKey, data, options) => {
this.sign(publicKey, data, options, (error, signature) => {
if (error || !signature) return protocol.failureReply(request);
protocol.signReply(request, signature);
});
});
cb(null, protocol);
}
sign(
_pubKey: ParsedKey | Buffer | string,
data: Buffer,
optionsOrCb: SigningRequestOptions | SignCallback,
cb?: SignCallback,
): void {
const callback = typeof optionsOrCb === "function" ? optionsOrCb : cb!;
const options = typeof optionsOrCb === "function" ? {} : optionsOrCb;
try {
const algo =
options.hash === "sha256"
? "rsa-sha2-256"
: options.hash === "sha512"
? "rsa-sha2-512"
: undefined;
const signature = this.key.sign(data, algo);
callback(null, signature);
} catch (err) {
callback(err instanceof Error ? err : new Error(String(err)));
}
}
}
@@ -0,0 +1,91 @@
import { and, desc, eq, sql } from "drizzle-orm";
import type { PluginDatabase } from "@termix/plugin-sdk/backend";
/* eslint-disable @typescript-eslint/no-explicit-any */
// The table comes from ctx.db.define, which the SDK hands back untyped, and
// the drizzle handle is the server's own. Typed at this module's edge instead.
type Table = any;
type Drizzle = any;
/* eslint-enable @typescript-eslint/no-explicit-any */
export type HistoryRepository = ReturnType<typeof createHistoryRepository>;
/** Command history storage. Writes are followed by persist(), reads are not. */
export function createHistoryRepository(db: PluginDatabase, table: Table) {
const client = () => db.client<Drizzle>();
return {
async create(
userId: string,
hostId: number,
command: string,
executedAt = new Date().toISOString(),
): Promise<void> {
const drizzle = await client();
await drizzle
.insert(table)
.values({ userId, hostId, command, executedAt });
await db.persist();
},
async listUniqueCommandsForHost(
userId: string,
hostId: number,
limit = 500,
): Promise<string[]> {
const drizzle = await client();
const rows = await drizzle
.select({
command: table.command,
maxExecutedAt: sql<number>`MAX(${table.executedAt})`,
})
.from(table)
.where(and(eq(table.userId, userId), eq(table.hostId, hostId)))
.groupBy(table.command)
.orderBy(desc(sql`MAX(${table.executedAt})`))
.limit(limit);
return rows.map((row: { command: string }) => row.command);
},
async listCommandsForHost(
userId: string,
hostId: number,
limit = 200,
): Promise<Array<{ command: string; executedAt: string }>> {
const drizzle = await client();
const rows = await drizzle
.select({ command: table.command, executedAt: table.executedAt })
.from(table)
.where(and(eq(table.userId, userId), eq(table.hostId, hostId)))
.orderBy(desc(table.executedAt))
.limit(limit);
return rows as Array<{ command: string; executedAt: string }>;
},
async deleteCommandForHost(
userId: string,
hostId: number,
command: string,
): Promise<void> {
const drizzle = await client();
await drizzle
.delete(table)
.where(
and(
eq(table.userId, userId),
eq(table.hostId, hostId),
eq(table.command, command),
),
);
await db.persist();
},
async deleteByUserAndHost(userId: string, hostId: number): Promise<void> {
const drizzle = await client();
await drizzle
.delete(table)
.where(and(eq(table.userId, userId), eq(table.hostId, hostId)));
await db.persist();
},
};
}
@@ -0,0 +1,20 @@
/**
* Registered as ctx.registry.provide("ssh-terminal.hostImportNormalizer", ...)
* so a Termix-JSON host import keeps its terminal switches, which used to be
* host columns and are this plugin's host settings now.
*/
export function hostImportNormalizer(
raw: Record<string, unknown>,
): Record<string, unknown> | null {
const keys = [
"enableTerminal",
"enableTerminalToolbar",
"enableCommandHistory",
] as const;
if (keys.every((key) => raw[key] === undefined)) return null;
const values: Record<string, unknown> = {};
for (const key of keys) {
if (raw[key] !== undefined) values[key] = raw[key] !== false;
}
return values;
}
@@ -1,5 +1,4 @@
import path from "path";
import { databaseLogger } from "../../utils/logger.js";
/**
* Terminal image storage modes.
@@ -120,16 +119,10 @@ function parseClampedInt(value: unknown, min: number): number | null {
return Math.max(min, Math.trunc(parsed));
}
interface SettingSource {
export interface SettingSource {
get(key: string): Promise<string | null>;
}
function warnInvalid(key: string, source: string): void {
databaseLogger.warn("Ignoring invalid terminal image storage setting", {
operation: "terminal_image_storage_settings_invalid",
key,
source,
});
/** Told about a stored or environment value that was ignored. */
warn?: (message: string) => void;
}
function hasUnsafePathSyntax(value: string): boolean {
@@ -157,6 +150,11 @@ export async function resolveTerminalImageStorageSettings(
settings: SettingSource,
env: NodeJS.ProcessEnv = process.env,
): Promise<TerminalImageStorageSettings> {
const warnInvalid = (key: string, source: string) =>
settings.warn?.(
`Ignoring invalid terminal image storage setting ${key} from ${source}`,
);
async function pick<T>(
key: string,
envName: string,
@@ -5,8 +5,8 @@ import {
exceedsImageStorageLimit,
isExpiredImage,
isImageFilename,
} from "./terminal-image-utils.js";
import type { TerminalImageStorageSettings } from "./terminal-image-storage-settings.js";
} from "./image-utils.js";
import type { TerminalImageStorageSettings } from "./image-storage-settings.js";
/**
* Stable error codes for image storage failures. These are part of the upload
+166 -21
View File
@@ -1,32 +1,177 @@
import type { Router } from "express";
import type { PluginContext } from "@termix/plugin-sdk/backend";
import { createTerminalLogger } from "./helpers.js";
import { hostImportNormalizer } from "./host-import.js";
import { createHistoryRepository } from "./history-repository.js";
import { registerTerminalRoutes } from "./routes.js";
import {
handleTerminalUpgrade,
sessionManager,
stopTerminalServer,
} from "../../../../src/backend/hosts/terminal/index.js";
DEFAULT_TIMEOUT_MINUTES,
TerminalSessionManager,
type TerminalSession,
} from "./session-manager.js";
import {
SESSION_GUESTS_KEY,
type LiveSessionInfo,
type LiveSessionsV1,
type RecordingsWriterV1,
type SessionGuestsV1,
type SessionSharingV1,
type TerminalHistoryV1,
type TmuxSessionsV1,
} from "./services.js";
import { ADMIN_KEYS } from "./settings.js";
import { commandHistory } from "./tables.js";
import { createTerminalSocket } from "./terminal-socket.js";
function toInfo(session: TerminalSession): LiveSessionInfo {
return {
id: session.id,
userId: session.userId,
hostId: session.hostId,
hostName: session.hostName,
isConnected: session.isConnected,
createdAt: session.createdAt,
lastDetachedAt: session.lastDetachedAt,
tabInstanceId:
session.attachedTabInstanceId ?? session.tabInstanceId ?? null,
tmuxSessionName: session.tmuxSessionName,
cols: session.cols,
rows: session.rows,
};
}
/**
* A service another plugin may or may not provide. The handle always exists;
* whether its provider is up is asked through the proxy's `in` check.
*/
function optionalService<T extends object>(
ctx: PluginContext,
service: string,
probe: keyof T & string,
): T | null {
const handle = ctx.services.get<T>(service);
return probe in handle ? handle : null;
}
export async function activate(ctx: PluginContext) {
// Public because the terminal authenticates each socket itself: a share-link
// guest arrives with a share token instead of a session, so the check has to
// happen where that token is understood.
ctx.ws.upgrade(
"/terminal",
(request, socket, head) =>
handleTerminalUpgrade(
request as Parameters<typeof handleTerminalUpgrade>[0],
socket as Parameters<typeof handleTerminalUpgrade>[1],
head as Buffer,
),
{ public: true },
);
const log = createTerminalLogger(ctx.log);
// Published so collab and session-sharing can reach live sessions without
// importing a plugin. Revoked automatically when this plugin is disabled.
ctx.registry.provide("terminal.sessions", sessionManager ?? null);
const table = await ctx.db.define(commandHistory);
const history = createHistoryRepository(ctx.db, table);
// Read on every detach, so it is kept current rather than awaited there.
let timeoutMinutes = DEFAULT_TIMEOUT_MINUTES;
const applyTimeout = (value: unknown) => {
const minutes = Number(value);
timeoutMinutes =
Number.isFinite(minutes) && minutes > 0
? minutes
: DEFAULT_TIMEOUT_MINUTES;
};
const refreshTimeout = async () =>
applyTimeout(await ctx.settings.get(ADMIN_KEYS.sessionTimeoutMinutes));
await refreshTimeout();
ctx.settings.onChange(ADMIN_KEYS.sessionTimeoutMinutes, applyTimeout);
const sessionManager = new TerminalSessionManager({
log,
getTimeoutMinutes: () => timeoutMinutes,
getRecordings: () =>
optionalService<RecordingsWriterV1>(ctx, "recordings.writer", "open"),
});
ctx.disposables.add(() => sessionManager.destroyAll());
const socket = createTerminalSocket({
ctx,
log,
sessionManager,
getTmux: () =>
optionalService<TmuxSessionsV1>(ctx, "tmux.sessions", "detect"),
getSharing: () =>
optionalService<SessionSharingV1>(
ctx,
"sessions.sharing",
"authorizeJoin",
),
getGuests: () =>
ctx.registry.consume<SessionGuestsV1>(SESSION_GUESTS_KEY) ?? null,
});
// Public with optional auth: a share-link guest arrives with a share token
// instead of a session, so that check happens where the token is understood.
ctx.ws.route(
"/terminal",
(connection) => {
// Also picks up a value the boot migration wrote after activation.
void refreshTimeout().catch(() => {});
return socket.handleConnection(connection);
},
{
public: true,
optionalAuth: true,
},
);
ctx.disposables.add(() => socket.closeAll());
registerTerminalRoutes(ctx.http.router<Router>({ rawBody: true }), {
ctx,
log,
sessionManager,
history,
});
const liveSessions: LiveSessionsV1 = {
getSession: (sessionId) => {
const session = sessionManager.getSession(sessionId);
return session ? toInfo(session) : null;
},
listForUser: (userId) => {
// A plugin caller only ever sees its actor's sessions; core calls this
// outside any actor, for the user its own route authenticated.
const actor = ctx.currentActor();
if (actor && actor !== userId) return [];
return sessionManager.getUserSessions(userId).map(toInfo);
},
ownerEndSession: (sessionId, reason) =>
sessionManager.ownerEndSession(sessionId, reason),
disconnectParticipants: (sessionId, shareId, options) =>
sessionManager.disconnectShareParticipants(sessionId, shareId, options),
setRoomShareControl: (sessionId, shareId, controllerUserId) =>
sessionManager.setRoomShareControl(sessionId, shareId, controllerUserId),
subscribe: (sessionId, onData) => {
const session = sessionManager.getSession(sessionId);
if (!session) return () => {};
session.dataListeners.add(onData);
return () => session.dataListeners.delete(onData);
},
write: (sessionId, data) => {
const session = sessionManager.getSession(sessionId);
if (!session?.sshStream || session.sshStream.destroyed) return false;
sessionManager.bufferInput(sessionId, data);
session.sshStream.write(data);
return true;
},
idleTimeoutMinutes: () => timeoutMinutes,
};
ctx.services.provide("sessions.live", liveSessions);
const terminalHistory: TerminalHistoryV1 = {
list: async (hostId, limit = 200) => {
const userId = ctx.currentActor();
if (!userId) return [];
return history.listCommandsForHost(userId, hostId, limit);
},
};
ctx.services.provide("terminal.history", terminalHistory);
ctx.registry.provide(
"ssh-terminal.hostImportNormalizer",
hostImportNormalizer,
);
ctx.log.info("SSH terminal mounted at /plugin-ws/ssh-terminal/terminal");
}
export async function deactivate() {
await stopTerminalServer();
// Everything above was registered through ctx and is disposed by core.
}
@@ -1,19 +1,37 @@
/**
* Keyboard-interactive answering over the terminal WebSocket.
*
* The classifier decides what a round is; this keeps the terminal's message
* protocol (totp_required, totp_retry, password_required,
* warpgate_auth_required) and its timeouts exactly as the client expects.
* Core's classifier (ctx.ssh.classifyKeyboardInteractive) decides what a
* round is; this keeps the terminal's message protocol (totp_required,
* totp_retry, password_required, warpgate_auth_required) and its timeouts
* exactly as the client expects.
*/
import type { WebSocket } from "ws";
import { sshLogger, authLogger } from "../../utils/logger.js";
import { ensureCoreSshAuthProviders } from "./core-providers.js";
import {
classifyKeyboardInteractive,
responsesWithAnswer,
} from "./keyboard-interactive.js";
import type { KeyboardInteractivePrompt, SshConnectHost } from "./types.js";
import type {
PluginKeyboardInteractivePrompt,
PluginSsh,
PluginSshHost,
} from "@termix/plugin-sdk/backend";
import type { TerminalLogger } from "./helpers.js";
type KeyboardInteractivePrompt = PluginKeyboardInteractivePrompt;
const PASSWORD_PATTERN = /password/i;
/** The answer at one index, the stored password for password prompts, else empty. */
export function responsesWithAnswer(
prompts: KeyboardInteractivePrompt[],
answerIndex: number,
answer: string,
password: string | null | undefined,
): string[] {
return prompts.map((p, index) => {
if (index === answerIndex) return answer;
if (PASSWORD_PATTERN.test(p.prompt) && password) return password;
return "";
});
}
interface ResolvedCredentials {
username: string;
@@ -27,6 +45,8 @@ interface HostFlags {
}
interface AuthContext {
ssh: Pick<PluginSsh, "classifyKeyboardInteractive">;
log: TerminalLogger;
userId: string;
ws: WebSocket;
hostId: number;
@@ -60,7 +80,6 @@ export class SSHAuthManager {
resolvedCredentials: ResolvedCredentials,
hostConfig?: HostFlags,
): void {
ensureCoreSshAuthProviders();
this.context.isKeyboardInteractive = true;
const host = {
@@ -71,9 +90,9 @@ export class SSHAuthManager {
username: resolvedCredentials.username,
password: resolvedCredentials.password,
authType: resolvedCredentials.authType,
} as SshConnectHost;
} as PluginSshHost;
const decision = classifyKeyboardInteractive(
const decision = this.context.ssh.classifyKeyboardInteractive(
{ name, instructions, prompts },
host,
);
@@ -86,7 +105,12 @@ export class SSHAuthManager {
this.handleWarpgate(decision, finish);
return;
case "totp":
this.handleTotp(prompts, decision.promptIndex, finish, host.password);
this.handleTotp(
prompts,
decision.promptIndex,
finish,
host.password as string | undefined,
);
return;
case "input":
this.handleInput(
@@ -94,7 +118,7 @@ export class SSHAuthManager {
decision.promptIndex,
decision.isPush,
finish,
host.password,
host.password as string | undefined,
);
return;
}
@@ -122,7 +146,7 @@ export class SSHAuthManager {
if (this.context.keyboardInteractiveFinish) {
this.context.keyboardInteractiveFinish = null;
this.context.warpgateAuthPromptSent = false;
sshLogger.warn("Warpgate authentication timeout", {
this.context.log.warn("Warpgate authentication timeout", {
operation: "warpgate_timeout",
hostId: this.context.hostId,
});
@@ -143,11 +167,11 @@ export class SSHAuthManager {
password: string | undefined,
): void {
if (this.context.totpPromptSent) {
sshLogger.warn("TOTP prompt asked again - invalid code", {
this.context.log.warn("TOTP prompt asked again - invalid code", {
operation: "ssh_keyboard_interactive_totp_retry",
hostId: this.context.hostId,
});
authLogger.warn("TOTP verification failed for SSH session", {
this.context.log.warn("TOTP verification failed for SSH session", {
operation: "terminal_totp_failed",
userId: this.context.userId,
hostId: this.context.hostId,
@@ -172,7 +196,7 @@ export class SSHAuthManager {
this.armTimeout(TOTP_TIMEOUT_MS, "totp");
this.sendLog("auth", "info", "TOTP verification required");
authLogger.info("TOTP verification prompt sent to client", {
this.context.log.info("TOTP verification prompt sent to client", {
operation: "terminal_totp_prompt",
userId: this.context.userId,
hostId: this.context.hostId,
@@ -227,7 +251,7 @@ export class SSHAuthManager {
} else {
this.context.keyboardInteractiveResponded = false;
}
sshLogger.warn(
this.context.log.warn(
`${kind === "totp" ? "TOTP" : "Password"} prompt timeout`,
{
operation: `${kind}_timeout`,
+801
View File
@@ -0,0 +1,801 @@
import express, {
type NextFunction,
type Request,
type Response,
type Router,
} from "express";
import { randomUUID } from "crypto";
import multer from "multer";
import type { PluginContext } from "@termix/plugin-sdk/backend";
import { getErrorMessage, type TerminalLogger } from "./helpers.js";
import type { HistoryRepository } from "./history-repository.js";
import type { TerminalSessionManager } from "./session-manager.js";
import {
ADMIN_KEYS,
HOST_KEYS,
readClientSettings,
readImageStorageSettings,
} from "./settings.js";
import {
createConcurrencyLimiter,
exceedsNormalizedImageSize,
imageExtensionForFormat,
} from "./images/image-utils.js";
import {
selectImageStorageMode,
probeLocalImageVisibility,
storeImageLocally,
storeImageViaSftp,
TerminalImageStorageError,
type ImageSftpClient,
} from "./images/image-storage.js";
function isNonEmptyString(val: unknown): val is string {
return typeof val === "string" && val.trim().length > 0;
}
export const SENSITIVE_COMMAND_PATTERNS = [
/passw(or)?d/i,
/\bsecret\b/i,
/\btoken\b/i,
/\bapi.?key\b/i,
/PASS(WORD)?=/i,
/AWS_SECRET/i,
/mysql\b.*-p/i,
/sudo\s+-S\b/,
/htpasswd/i,
/sshpass/i,
/curl\b.*-u\s/i,
/export\b.*(?:PASSWORD|SECRET|TOKEN|KEY)=/i,
];
type SharpFactory = typeof import("sharp").default;
export interface TerminalRouteDeps {
ctx: PluginContext;
log: TerminalLogger;
sessionManager: TerminalSessionManager;
history: HistoryRepository;
}
/**
* The terminal's HTTP routes, on a router core mounts at
* /plugin-api/ssh-terminal/. It is created with rawBody so multer can read
* the upload stream; JSON routes parse their own body.
*/
export function registerTerminalRoutes(
router: Router,
{ ctx, log, sessionManager, history }: TerminalRouteDeps,
): void {
const json = express.json({ limit: "2mb" });
// Browser image handoff for local terminal-agent workflows.
const imageUpload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 50 * 1024 * 1024,
fields: 4,
fieldSize: 64 * 1024,
files: 1,
parts: 5,
headerPairs: 200,
},
});
const imageUploadMiddleware = imageUpload.single("image");
let sharpFactory: SharpFactory | null = null;
let sharpLoadFailure: string | null = null;
// A static import of sharp takes the plugin down with it when the native
// binary for the running architecture is missing. Resolve on first use so
// only image upload is lost.
async function loadSharp(): Promise<SharpFactory> {
if (sharpFactory) return sharpFactory;
if (sharpLoadFailure !== null) throw new Error(sharpLoadFailure);
try {
sharpFactory = (await import("sharp")).default;
return sharpFactory;
} catch (error) {
sharpLoadFailure = getErrorMessage(error, "unknown");
throw new Error(sharpLoadFailure);
}
}
const imageProcessingLimiter = createConcurrencyLimiter(4, 4);
const imageMultipartAdmissionLimiter = createConcurrencyLimiter(4, 4);
let imageUploadSequence = 0;
async function handleImageUploadMiddleware(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
let releaseAdmission: (() => void) | undefined;
try {
releaseAdmission = await imageMultipartAdmissionLimiter.acquire();
} catch {
res.status(503).json({
error: "Image upload capacity is temporarily unavailable",
code: "IMAGE_UPLOAD_CAPACITY_EXCEEDED",
});
return;
}
imageUploadMiddleware(req, res, (error: unknown) => {
try {
if (!error) {
next();
return;
}
if (error instanceof multer.MulterError) {
log.warn("Image upload multipart request rejected", {
operation: "terminal_image_upload_multipart_rejected",
code: error.code,
field: error.field,
contentType: req.headers["content-type"]?.split(";", 1)[0],
});
res.status(400).json({
error: "Image upload request rejected",
code: error.code,
field: error.field,
});
return;
}
log.warn("Image upload multipart request malformed", {
operation: "terminal_image_upload_multipart_invalid",
contentType: req.headers["content-type"]?.split(";", 1)[0],
});
res.status(400).json({
error: "Malformed image upload request",
code: "IMAGE_MULTIPART_INVALID",
});
} finally {
releaseAdmission?.();
}
});
}
function findTerminalSession(userId: string, instanceId: string) {
return sessionManager
.getUserSessions(userId)
.find(
(session) =>
(session.attachedTabInstanceId ?? session.tabInstanceId) ===
instanceId && session.isConnected,
);
}
/**
* @openapi
* /plugin-api/ssh-terminal/image-upload:
* post:
* summary: Upload an image into a terminal session
* description: Normalizes an uploaded or pasted image to PNG and stores it where the terminal's programs can read it, over SFTP on the connected host or in the backend's mapped local storage, depending on the image storage settings. Returns the path to hand to the program.
* tags:
* - Terminal
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* image:
* type: string
* format: binary
* instanceId:
* type: string
* source:
* type: string
* enum: [file, clipboard]
* clientUploadTimestamp:
* type: string
* responses:
* 200:
* description: The stored image's path.
* 400:
* description: Missing or invalid image or session.
* 409:
* description: The terminal is not connected.
* 413:
* description: The normalized image is too large.
* 502:
* description: Writing to the remote host failed.
* 503:
* description: Image storage or processing is unavailable.
* 507:
* description: The image storage limit was reached.
*/
router.post(
"/image-upload",
handleImageUploadMiddleware,
async (req: Request, res: Response) => {
const userId = ctx.currentActor();
const instanceId = req.body?.instanceId;
if (!req.file) {
return res.status(400).json({
error: "Image required",
code: "IMAGE_FILE_MISSING",
});
}
if (!isNonEmptyString(userId)) {
return res.status(400).json({
error: "Missing terminal session",
code: "IMAGE_SESSION_MISSING",
});
}
const requestId = randomUUID();
const sequence = ++imageUploadSequence;
const source =
req.body?.source === "file" || req.body?.source === "clipboard"
? req.body.source
: undefined;
const clientUploadTimestamp =
typeof req.body?.clientUploadTimestamp === "string" &&
!Number.isNaN(Date.parse(req.body.clientUploadTimestamp))
? req.body.clientUploadTimestamp
: undefined;
const serverReceivedAt = new Date().toISOString();
log.info("Terminal image upload received", {
operation: "terminal_image_upload_received",
requestId,
sequence,
source,
clientUploadTimestamp,
serverReceivedAt,
bytes: req.file.size,
});
const storageSettings = await readImageStorageSettings(ctx);
const session = isNonEmptyString(instanceId)
? findTerminalSession(userId, instanceId)
: undefined;
let localHostVisible = false;
if (storageSettings.localMappingConfigured && session?.sshConn) {
localHostVisible = await probeLocalImageVisibility(
session.sshConn,
storageSettings,
).catch(() => false);
}
const storageMode = selectImageStorageMode(storageSettings, {
remoteSftpAvailable: !!session?.sshConn,
localHostVisible,
});
if (storageMode === "unavailable") {
return res.status(503).json({
error: "Image storage is unavailable",
code: "IMAGE_STORAGE_UNAVAILABLE",
});
}
if (storageMode === "local" && !storageSettings.localMappingConfigured) {
return res.status(503).json({
error: "Local image storage is not configured",
code: "IMAGE_LOCAL_STORAGE_NOT_CONFIGURED",
});
}
if (storageMode === "remote-sftp") {
if (!isNonEmptyString(instanceId)) {
return res.status(400).json({
error: "Missing terminal session",
code: "IMAGE_SESSION_MISSING",
});
}
if (!session || !session.sshConn) {
return res.status(409).json({
error: "Terminal is not connected",
code: "IMAGE_TERMINAL_NOT_CONNECTED",
});
}
}
let sharp: SharpFactory;
try {
sharp = await loadSharp();
} catch (error) {
log.error("Image processing unavailable: sharp failed to load", error, {
operation: "terminal_image_upload_sharp_unavailable",
reason: getErrorMessage(error, "unknown"),
});
return res.status(503).json({
error: "Image processing is unavailable on this installation",
code: "IMAGE_PROCESSING_UNAVAILABLE",
});
}
let normalizedImage: Buffer;
let releaseImageProcessingSlot: (() => void) | undefined;
try {
releaseImageProcessingSlot = await imageProcessingLimiter.acquire();
} catch {
return res.status(503).json({
error: "Image upload capacity is temporarily exhausted",
code: "IMAGE_UPLOAD_CAPACITY_EXCEEDED",
});
}
try {
const image = sharp(req.file.buffer, {
failOn: "error",
limitInputPixels: 40_000_000,
});
const { format } = await image.metadata();
if (!imageExtensionForFormat(format)) {
return res.status(400).json({
error: "Unsupported image format",
code: "IMAGE_FORMAT_UNSUPPORTED",
});
}
normalizedImage = await image.rotate().png().toBuffer();
if (exceedsNormalizedImageSize(normalizedImage.length)) {
return res.status(413).json({
error: "Normalized image is too large",
code: "IMAGE_NORMALIZED_SIZE_LIMIT",
});
}
} catch (error) {
log.warn("Image upload failed image decoding", {
operation: "terminal_image_upload_decode",
mimeType: req.file.mimetype,
bytes: req.file.size,
reason: getErrorMessage(error, "unknown"),
});
return res.status(400).json({
error: "Invalid image data",
code: "IMAGE_DECODE_FAILED",
});
} finally {
releaseImageProcessingSlot?.();
}
let remoteSftp: ImageSftpClient | undefined;
try {
const stored =
storageMode === "remote-sftp"
? await (async () => {
remoteSftp = await new Promise<ImageSftpClient>(
(resolve, reject) => {
let settled = false;
const timer = setTimeout(() => {
settled = true;
reject(new Error("SFTP channel acquisition timed out"));
}, 3_000);
session!.sshConn!.sftp((err, sftp) => {
if (settled) {
sftp?.end?.();
return;
}
settled = true;
clearTimeout(timer);
if (err) return reject(err);
resolve(sftp);
});
},
);
return storeImageViaSftp(remoteSftp, normalizedImage, {
ttlMs: storageSettings.ttlMs,
maxCount: storageSettings.maxCount,
maxBytes: storageSettings.maxBytes,
});
})()
: await storeImageLocally(normalizedImage, storageSettings);
res.json(stored);
} catch (error) {
if (error instanceof TerminalImageStorageError) {
const status =
error.code === "IMAGE_STORAGE_LIMIT_REACHED"
? 507
: error.code === "IMAGE_REMOTE_WRITE_FAILED"
? 502
: error.code === "IMAGE_REMOTE_QUOTA_UNAVAILABLE"
? 503
: error.code === "IMAGE_LOCAL_INSPECTION_FAILED"
? 503
: 500;
log.warn("Image upload storage write failed", {
operation:
error.code === "IMAGE_REMOTE_WRITE_FAILED"
? "terminal_image_upload_sftp_failed"
: "terminal_image_upload_local_failed",
code: error.code,
userId,
instanceId,
reason: getErrorMessage(error.cause ?? error, "unknown"),
});
return res.status(status).json({
error: error.message,
code: error.code,
});
}
log.warn("Image upload failed to acquire remote channel", {
operation: "terminal_image_upload_sftp_failed",
code: "IMAGE_REMOTE_WRITE_FAILED",
userId,
instanceId,
reason: getErrorMessage(error, "unknown"),
});
return res.status(502).json({
error: "Failed to write image to the remote host",
code: "IMAGE_REMOTE_WRITE_FAILED",
});
} finally {
remoteSftp?.end?.();
}
},
);
/**
* @openapi
* /plugin-api/ssh-terminal/image-storage/test:
* post:
* summary: Test image storage visibility (admin only)
* description: Reports which storage mode an upload would take for one of the caller's already-connected terminal sessions. Uses the bounded local-mapping probe only; it never opens new connections.
* tags:
* - Terminal
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* instanceId:
* type: string
* responses:
* 200:
* description: Visibility test result.
* 400:
* description: Missing terminal session instanceId.
* 403:
* description: Admin access required.
* 500:
* description: Failed to run the test.
*/
router.post("/image-storage/test", json, async (req, res) => {
if (!(await ctx.rbac.has("admin.plugins.manage"))) {
return res.status(403).json({ error: "Admin access required" });
}
const userId = ctx.currentActor() ?? "";
const instanceId = req.body?.instanceId;
if (typeof instanceId !== "string" || instanceId.trim().length === 0) {
return res.status(400).json({
error: "Missing terminal session",
code: "IMAGE_SESSION_MISSING",
});
}
try {
const settings = await readImageStorageSettings(ctx);
const session = findTerminalSession(userId, instanceId);
const remoteSftpAvailable = !!session?.sshConn;
let localHostVisible: boolean | null = null;
if (settings.localMappingConfigured && session?.sshConn) {
localHostVisible = await probeLocalImageVisibility(
session.sshConn,
settings,
).catch(() => false);
}
const selectedMode = selectImageStorageMode(settings, {
remoteSftpAvailable,
...(localHostVisible !== null ? { localHostVisible } : {}),
});
res.json({
mode: settings.mode,
connected: !!session,
remoteSftpAvailable,
localHostVisible,
selectedMode,
localMappingConfigured: settings.localMappingConfigured,
});
} catch (err) {
log.error("Failed to test image storage visibility", err);
res
.status(500)
.json({ error: "Failed to test image storage visibility" });
}
});
/**
* @openapi
* /plugin-api/ssh-terminal/client-settings:
* get:
* summary: Terminal settings every user's terminal needs
* description: The admin-set session timeout and persistence flag, whether command history is on, and the touch input tuning. Readable by any signed-in user; changing them goes through the plugin's admin settings.
* tags:
* - Terminal
* responses:
* 200:
* description: The settings.
* 500:
* description: Failed to load settings.
*/
router.get("/client-settings", async (_req, res) => {
try {
res.json(await readClientSettings(ctx));
} catch (err) {
log.error("Failed to load terminal client settings", err);
res.status(500).json({ error: "Failed to load settings" });
}
});
/**
* @openapi
* /plugin-api/ssh-terminal/command-history:
* post:
* summary: Save command to history
* description: Saves a command to the caller's history for a host. Commands that look like they carry a secret, and anything while command history is off globally or for the host, are acknowledged but not stored.
* tags:
* - Terminal
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* hostId:
* type: integer
* command:
* type: string
* responses:
* 201:
* description: Command saved, or skipped on purpose.
* 400:
* description: Missing required parameters.
* 500:
* description: Failed to save command.
*/
router.post("/command-history", json, async (req, res) => {
const userId = ctx.currentActor();
const { hostId, command } = req.body ?? {};
if (!isNonEmptyString(userId) || !hostId || !isNonEmptyString(command)) {
log.warn("Invalid command history save request", {
operation: "command_history_save",
userId,
hasHostId: !!hostId,
hasCommand: !!command,
});
return res.status(400).json({ error: "Missing required parameters" });
}
const hostIdNum = parseInt(hostId, 10);
const trimmedCommand = command.trim();
const skipped = {
id: 0,
userId,
hostId: hostIdNum,
command: trimmedCommand,
executedAt: new Date().toISOString(),
};
if (SENSITIVE_COMMAND_PATTERNS.some((p) => p.test(trimmedCommand))) {
return res.status(201).json(skipped);
}
if ((await ctx.settings.get(ADMIN_KEYS.commandHistoryEnabled)) === false) {
return res.status(201).json(skipped);
}
if (
(await ctx.settings.getHost(
hostIdNum,
HOST_KEYS.enableCommandHistory,
)) === false
) {
return res.status(201).json(skipped);
}
try {
const executedAt = new Date().toISOString();
await history.create(userId, hostIdNum, trimmedCommand, executedAt);
res.status(201).json({
userId,
hostId: hostIdNum,
command: trimmedCommand,
executedAt,
});
} catch (err) {
log.error("Failed to save command to history", err);
res.status(500).json({
error: getErrorMessage(err, "Failed to save command"),
});
}
});
/**
* @openapi
* /plugin-api/ssh-terminal/command-history/{hostId}:
* get:
* summary: Get command history
* description: The caller's distinct commands on a host, most recently used first, for autocomplete.
* tags:
* - Terminal
* parameters:
* - in: path
* name: hostId
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: A list of commands.
* 400:
* description: Invalid request parameters.
* 500:
* description: Failed to fetch history.
*/
router.get("/command-history/:hostId", async (req, res) => {
const userId = ctx.currentActor();
const hostIdNum = parseInt(String(req.params.hostId), 10);
if (!isNonEmptyString(userId) || isNaN(hostIdNum)) {
log.warn("Invalid command history fetch request", {
userId,
hostId: hostIdNum,
});
return res.status(400).json({ error: "Invalid request parameters" });
}
try {
res.json(await history.listUniqueCommandsForHost(userId, hostIdNum));
} catch (err) {
log.error("Failed to fetch command history", err);
res.status(500).json({
error: getErrorMessage(err, "Failed to fetch history"),
});
}
});
/**
* @openapi
* /plugin-api/ssh-terminal/command-history/{hostId}/recent:
* get:
* summary: Get recent command history
* description: The caller's commands on a host, newest first, duplicates included. Needs hosts.view.
* tags:
* - Terminal
* parameters:
* - in: path
* name: hostId
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: A list of commands.
* 400:
* description: Invalid host id.
* 403:
* description: Missing the hosts.view permission.
* 500:
* description: Failed to fetch command history.
*/
router.get("/command-history/:hostId/recent", async (req, res) => {
if (!(await ctx.rbac.has("hosts.view"))) {
return res.status(403).json({ error: "Insufficient permissions" });
}
const userId = ctx.currentActor();
const hostIdNum = parseInt(String(req.params.hostId), 10);
if (!isNonEmptyString(userId) || !hostIdNum) {
return res.status(400).json({ error: "Invalid userId or hostId" });
}
try {
const rows = await history.listCommandsForHost(userId, hostIdNum);
res.json(rows.map((row) => row.command));
} catch (err) {
log.error("Failed to fetch command history from database", err, {
operation: "command_history_fetch",
hostId: hostIdNum,
});
res.status(500).json({ error: "Failed to fetch command history" });
}
});
/**
* @openapi
* /plugin-api/ssh-terminal/command-history/delete:
* post:
* summary: Delete a specific command from history
* description: Deletes one command from the caller's history for a host.
* tags:
* - Terminal
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* hostId:
* type: integer
* command:
* type: string
* responses:
* 200:
* description: Command deleted successfully.
* 400:
* description: Missing required parameters.
* 500:
* description: Failed to delete command.
*/
router.post("/command-history/delete", json, async (req, res) => {
const userId = ctx.currentActor();
const { hostId, command } = req.body ?? {};
if (!isNonEmptyString(userId) || !hostId || !isNonEmptyString(command)) {
log.warn("Invalid command delete request", {
operation: "command_history_delete",
userId,
hasHostId: !!hostId,
hasCommand: !!command,
});
return res.status(400).json({ error: "Missing required parameters" });
}
try {
await history.deleteCommandForHost(
userId,
parseInt(hostId, 10),
command.trim(),
);
res.json({ success: true });
} catch (err) {
log.error("Failed to delete command from history", err);
res.status(500).json({
error: getErrorMessage(err, "Failed to delete command"),
});
}
});
/**
* @openapi
* /plugin-api/ssh-terminal/command-history/{hostId}:
* delete:
* summary: Clear command history
* description: Clears the caller's whole command history for a host.
* tags:
* - Terminal
* parameters:
* - in: path
* name: hostId
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Command history cleared successfully.
* 400:
* description: Invalid request.
* 500:
* description: Failed to clear history.
*/
router.delete("/command-history/:hostId", async (req, res) => {
const userId = ctx.currentActor();
const hostIdNum = parseInt(String(req.params.hostId), 10);
if (!isNonEmptyString(userId) || isNaN(hostIdNum)) {
log.warn("Invalid command history clear request");
return res.status(400).json({ error: "Invalid request" });
}
try {
await history.deleteByUserAndHost(userId, hostIdNum);
log.info("Terminal history cleared", {
operation: "terminal_history_clear",
userId,
hostId: hostIdNum,
});
res.json({ success: true });
} catch (err) {
log.error("Failed to clear command history", err);
res.status(500).json({
error: getErrorMessage(err, "Failed to clear history"),
});
}
});
}
@@ -0,0 +1,143 @@
/**
* The services this plugin provides and consumes.
*
* Provided: "sessions.live" (live SSH sessions, for session sharing, collab
* and recording) and "terminal.history" (command history, for the AI
* assistant). Consumed, all optional: "tmux.sessions" (B10),
* "sessions.sharing" (B12) and "recordings.writer" (B13). The terminal keeps
* working without any of them: no tmux attach, no shared joins, no recording.
*/
export interface LiveSessionInfo {
id: string;
userId: string;
hostId: number;
hostName: string;
isConnected: boolean;
createdAt: number;
lastDetachedAt: number | null;
tabInstanceId: string | null;
tmuxSessionName: string | null;
cols: number;
rows: number;
}
export interface LiveSessionsV1 {
getSession: (sessionId: string) => LiveSessionInfo | null;
/** The caller's own sessions. A plugin caller only ever gets its actor's. */
listForUser: (userId: string) => LiveSessionInfo[];
/** Tells every guest the owner ended it, then closes the session. */
ownerEndSession: (sessionId: string, reason: string) => void;
/** Removes the participants that joined through one share. */
disconnectParticipants: (
sessionId: string,
shareId: string,
options: { reason: string; userId?: string | null },
) => number;
/** Hands input control of a room share to one member, or back to the owner. */
setRoomShareControl: (
sessionId: string,
shareId: string,
controllerUserId: string | null,
) => void;
/** Output as it arrives. Returns the unsubscribe. */
subscribe: (sessionId: string, onData: (data: string) => void) => () => void;
/** Types into the session. False when it is not live. */
write: (sessionId: string, data: string) => boolean;
/** How long a detached session is kept, in minutes. */
idleTimeoutMinutes: () => number;
}
export interface CommandHistoryEntry {
command: string;
executedAt: string;
}
export interface TerminalHistoryV1 {
/** The acting user's most recent commands on one host, newest first. */
list: (hostId: number, limit?: number) => Promise<CommandHistoryEntry[]>;
}
export interface TmuxDetection {
available: boolean;
sessions: string[];
}
/**
* What the terminal asks of the tmux plugin. Clients are ssh2 objects. Every
* method is async to a consumer: a service call goes through the permission
* check and the audit line first.
*/
export interface TmuxSessionsV1 {
detect: (client: unknown) => Promise<TmuxDetection>;
/** Writes the attach (or new-session) command into a shell stream. */
attachOrCreate: (
stream: unknown,
name?: string,
newName?: string,
) => Promise<void>;
/** Waits for a new session to exist and returns its confirmed name. */
waitForSession: (client: unknown, name: string) => Promise<string>;
}
export interface SharedSessionRef {
id: string;
sessionId: string;
permissionLevel: "read-write" | "read-only";
}
/** For signed-in members; every call runs as the acting user. */
export interface SessionSharingV1 {
/** A user share targeting the actor, or a room stage they are a member of. */
authorizeJoin: (
shareId: string,
) => Promise<{ share: SharedSessionRef; displayName: string } | null>;
recordJoin: (shareId: string) => Promise<void>;
/** Joins the socket to a collab room's event fan-out. False when not a member. */
subscribeRoom: (roomId: string, socket: unknown) => Promise<boolean>;
unsubscribeRoom: (socket: unknown, roomId?: string) => Promise<void>;
}
/**
* Guest joins through a share link or a room guest link. A guest has no user,
* so the per-call RBAC check a service runs cannot apply: the token is the
* authority. Published on ctx.registry as SESSION_GUESTS_KEY instead.
*/
export interface SessionGuestsV1 {
resolve: (request: {
shareToken?: string;
roomGuestToken?: string;
clientIp: string;
}) => Promise<
{ ok: true; share: SharedSessionRef } | { ok: false; reason: string }
>;
recordJoin: (shareId: string) => Promise<void>;
}
export const SESSION_GUESTS_KEY = "sessions.sharing.guests";
export interface RecordingSink {
/** Appends one batch; the first batch starts with the asciicast header. */
append: (chunk: string) => Promise<void>;
/** Writes or updates the recording row. */
persist: (summary: {
endedAt: number;
durationSeconds: number;
terminatedByOwner: boolean;
terminationReason: string | null;
}) => Promise<void>;
/** Nothing was recorded; drop whatever was set up. */
discard: () => void;
}
export interface RecordingsWriterV1 {
/** Null when recording is off for this user or host. */
open: (meta: {
sessionId: string;
hostId: number;
userId: string;
protocol: "ssh";
format: "asciicast";
startedAt: number;
}) => Promise<RecordingSink | null>;
}
@@ -1,17 +1,11 @@
import { randomUUID } from "crypto";
import { type Client, type ClientChannel } from "ssh2";
import { WebSocket } from "ws";
import fs from "fs";
import path from "path";
import { sshLogger } from "../../utils/logger.js";
import {
getCurrentSettingValue,
createCurrentSessionRecordingRepository,
} from "../../database/repositories/factory.js";
import type { TerminalLogger } from "./helpers.js";
import type { RecordingSink, RecordingsWriterV1 } from "./services.js";
const MAX_BUFFER_BYTES = 512 * 1024;
const DATA_DIR = process.env.DATA_DIR ?? "./db/data";
const SESSION_LOGS_DIR = path.join(DATA_DIR, "session_logs");
const DEFAULT_TIMEOUT_MINUTES = 30;
export const DEFAULT_TIMEOUT_MINUTES = 30;
const HEALTH_CHECK_INTERVAL_MS = 60_000;
const MAX_SESSIONS_PER_USER = 10;
// Coalesces recording writes: a chatty SSH stream can emit dozens of "data"
@@ -55,10 +49,12 @@ export interface TerminalSession {
outputBuffer: string[];
outputBufferBytes: number;
recordingPath: string | null;
/** Output listeners from the sessions.live service. */
dataListeners: Set<(data: string) => void>;
/** Resolves once the recordings service answered; null when off. */
recordingSink: Promise<RecordingSink | null> | null;
recordingHeader: string | null;
recordingBytes: number;
recordingId: number | null;
recordingWriteChain: Promise<void>;
recordingPersistChain: Promise<void>;
pendingRecordingData: string;
@@ -96,25 +92,27 @@ export function isMessageAllowedForParticipant(
return true;
}
class TerminalSessionManager {
private static instance: TerminalSessionManager;
export interface SessionManagerDeps {
log: TerminalLogger;
/** Minutes a detached session is kept; read on every detach. */
getTimeoutMinutes: () => number;
/** The recordings.writer service, when a plugin provides it. */
getRecordings: () => RecordingsWriterV1 | null;
}
export class TerminalSessionManager {
private sessions = new Map<string, TerminalSession>();
private healthCheckTimer: NodeJS.Timeout | null = null;
private readonly log: TerminalLogger;
private constructor() {
constructor(private readonly deps: SessionManagerDeps) {
this.log = deps.log;
this.healthCheckTimer = setInterval(
() => this.healthCheck(),
HEALTH_CHECK_INTERVAL_MS,
);
}
static getInstance(): TerminalSessionManager {
if (!TerminalSessionManager.instance) {
TerminalSessionManager.instance = new TerminalSessionManager();
}
return TerminalSessionManager.instance;
}
createSession(
userId: string,
hostId: number,
@@ -149,7 +147,7 @@ class TerminalSessionManager {
!existing.sshStream.destroyed;
if (isLiveSession) {
// Don't destroy a live session (even if detached) — the caller should attach instead
sshLogger.warn(
this.log.warn(
"Tab instance has live session, skipping duplicate create",
{
operation: "session_tab_duplicate_skip",
@@ -160,7 +158,7 @@ class TerminalSessionManager {
);
return existing.id;
}
sshLogger.warn("Tab instance already has session, destroying old", {
this.log.warn("Tab instance already has session, destroying old", {
operation: "session_tab_duplicate_cleanup",
existingSessionId: existing.id,
tabInstanceId,
@@ -169,13 +167,34 @@ class TerminalSessionManager {
}
}
const id = crypto.randomUUID();
const id = randomUUID();
const now = Date.now();
let recordingPath: string | null = null;
let recordingSink: Promise<RecordingSink | null> | null = null;
let recordingHeader: string | null = null;
if (sessionLoggingEnabled) {
const userLogDir = path.join(SESSION_LOGS_DIR, userId);
recordingPath = path.join(userLogDir, `${id}.cast`);
const recordings = sessionLoggingEnabled ? this.deps.getRecordings() : null;
if (recordings) {
// Events recorded before this resolves wait in pendingRecordingData.
recordingSink = Promise.resolve()
.then(() =>
recordings.open({
sessionId: id,
hostId,
userId,
protocol: "ssh",
format: "asciicast",
startedAt: now,
}),
)
.catch((err) => {
this.log.warn("Could not start a session recording", {
operation: "session_recording_open_error",
sessionId: id,
error: err instanceof Error ? err.message : String(err),
});
return null;
});
}
if (recordingSink) {
recordingHeader = `${JSON.stringify({
version: 2,
width: cols,
@@ -202,16 +221,16 @@ class TerminalSessionManager {
detachTimeout: null,
outputBuffer: [],
outputBufferBytes: 0,
recordingPath,
dataListeners: new Set(),
recordingSink,
recordingHeader,
recordingBytes: 0,
recordingId: null,
recordingWriteChain: Promise.resolve(),
recordingPersistChain: Promise.resolve(),
pendingRecordingData: "",
recordingFlushTimer: null,
tmuxSessionName: null,
sessionLoggingEnabled,
sessionLoggingEnabled: !!recordingSink,
sessionStartedAt: now,
lastPersistedBytes: 0,
terminatedByOwner: false,
@@ -219,7 +238,7 @@ class TerminalSessionManager {
};
this.sessions.set(id, session);
sshLogger.info("Terminal session created", {
this.log.info("Terminal session created", {
operation: "session_created",
sessionId: id,
userId,
@@ -276,7 +295,7 @@ class TerminalSessionManager {
): TerminalSession | null {
const session = this.sessions.get(sessionId);
if (!session) {
sshLogger.warn("Session not found for attachment", {
this.log.warn("Session not found for attachment", {
operation: "session_attach_not_found",
sessionId,
userId,
@@ -284,7 +303,7 @@ class TerminalSessionManager {
return null;
}
if (session.userId !== userId) {
sshLogger.warn("Session userId mismatch", {
this.log.warn("Session userId mismatch", {
operation: "session_attach_user_mismatch",
sessionId,
expectedUserId: session.userId,
@@ -293,7 +312,7 @@ class TerminalSessionManager {
return null;
}
if (!session.isConnected) {
sshLogger.warn("Session not connected", {
this.log.warn("Session not connected", {
operation: "session_attach_not_connected",
sessionId,
userId,
@@ -316,7 +335,7 @@ class TerminalSessionManager {
session.tabInstanceId &&
tabInstanceId
) {
sshLogger.warn("Session actively attached to different tab instance", {
this.log.warn("Session actively attached to different tab instance", {
operation: "session_attach_instance_conflict",
sessionId,
sessionInstanceId: session.tabInstanceId,
@@ -341,7 +360,7 @@ class TerminalSessionManager {
tabInstanceId &&
session.tabInstanceId !== tabInstanceId
) {
sshLogger.info(
this.log.info(
"Session attached to different tab instance (split-screen)",
{
operation: "session_attach_split_screen",
@@ -373,7 +392,7 @@ class TerminalSessionManager {
session.detachTimeout = null;
}
const participantId = crypto.randomUUID();
const participantId = randomUUID();
session.participants.set(participantId, {
ws,
userId,
@@ -385,7 +404,7 @@ class TerminalSessionManager {
session.lastDetachedAt = null;
this.broadcastParticipants(sessionId);
sshLogger.info("WebSocket attached to session", {
this.log.info("WebSocket attached to session", {
operation: "session_attach",
sessionId,
userId,
@@ -414,7 +433,7 @@ class TerminalSessionManager {
const session = this.sessions.get(sessionId);
if (!session || !session.isConnected) return null;
const participantId = crypto.randomUUID();
const participantId = randomUUID();
session.participants.set(participantId, {
ws,
userId: opts.userId,
@@ -427,7 +446,7 @@ class TerminalSessionManager {
});
this.broadcastParticipants(sessionId);
sshLogger.info("Participant joined shared session", {
this.log.info("Participant joined shared session", {
operation: "session_join_participant",
sessionId,
userId: opts.userId,
@@ -558,7 +577,7 @@ class TerminalSessionManager {
if (participant.ws === ws && !participant.isOwner) {
session.participants.delete(id);
this.broadcastParticipants(sessionId);
sshLogger.info("Participant left shared session", {
this.log.info("Participant left shared session", {
operation: "session_leave_participant",
sessionId,
userId: participant.userId,
@@ -577,7 +596,7 @@ class TerminalSessionManager {
session.terminatedByOwner = true;
session.terminationReason = reason;
sshLogger.info("Owner ended shared session", {
this.log.info("Owner ended shared session", {
operation: "session_owner_end",
sessionId,
reason,
@@ -608,7 +627,7 @@ class TerminalSessionManager {
const timeoutMs = this.getTimeoutMs();
session.detachTimeout = setTimeout(() => {
sshLogger.info("Session idle timeout expired", {
this.log.info("Session idle timeout expired", {
operation: "session_idle_timeout",
sessionId,
userId: session.userId,
@@ -616,7 +635,7 @@ class TerminalSessionManager {
this.destroySession(sessionId);
}, timeoutMs);
sshLogger.info("WebSocket detached from session", {
this.log.info("WebSocket detached from session", {
operation: "session_detach",
sessionId,
userId: session.userId,
@@ -634,9 +653,10 @@ class TerminalSessionManager {
}
this.maybePersistLog(session, true);
if (session.recordingPath && session.recordingBytes === 0) {
fs.promises.unlink(session.recordingPath).catch(() => {});
if (session.recordingSink && session.recordingBytes === 0) {
void session.recordingSink.then((sink) => sink?.discard());
}
session.dataListeners.clear();
for (const participant of session.participants.values()) {
if (participant.isOwner) continue;
@@ -688,7 +708,7 @@ class TerminalSessionManager {
this.sessions.delete(sessionId);
sshLogger.info("Terminal session destroyed", {
this.log.info("Terminal session destroyed", {
operation: "session_destroyed",
sessionId,
userId: session.userId,
@@ -709,7 +729,7 @@ class TerminalSessionManager {
session.recordingPersistChain = session.recordingPersistChain
.then(() => this.persistSessionLog(session))
.catch((err) => {
sshLogger.warn("Failed to persist session log", {
this.log.warn("Failed to persist session log", {
operation: "session_log_persist_error",
sessionId: session.id,
error: err instanceof Error ? err.message : String(err),
@@ -718,44 +738,29 @@ class TerminalSessionManager {
}
private async persistSessionLog(session: TerminalSession): Promise<void> {
if (!session.recordingPath) return;
if (!session.recordingSink) return;
await session.recordingWriteChain;
const sink = await session.recordingSink;
if (!sink) return;
const endedAt = Date.now();
const duration = Math.floor((endedAt - session.sessionStartedAt) / 1000);
try {
const repo = createCurrentSessionRecordingRepository();
if (session.recordingId == null) {
const created = await repo.create({
hostId: session.hostId,
userId: session.userId,
startedAt: new Date(session.sessionStartedAt).toISOString(),
endedAt: new Date(endedAt).toISOString(),
duration,
recordingPath: session.recordingPath,
protocol: "ssh",
format: "asciicast",
terminatedByOwner: session.terminatedByOwner || undefined,
terminationReason: session.terminationReason ?? undefined,
});
session.recordingId = created.id;
} else {
await repo.updateEnded(session.recordingId, {
endedAt: new Date(endedAt).toISOString(),
duration,
terminatedByOwner: session.terminatedByOwner || undefined,
terminationReason: session.terminationReason ?? undefined,
});
}
await sink.persist({
endedAt,
durationSeconds: duration,
terminatedByOwner: session.terminatedByOwner,
terminationReason: session.terminationReason,
});
} catch (err) {
sshLogger.warn("Failed to insert session recording row", {
this.log.warn("Failed to insert session recording row", {
operation: "session_recording_insert_error",
sessionId: session.id,
error: err instanceof Error ? err.message : String(err),
});
}
sshLogger.info("Session log persisted", {
this.log.info("Session log persisted", {
operation: "session_log_persisted",
sessionId: session.id,
userId: session.userId,
@@ -790,6 +795,14 @@ class TerminalSessionManager {
if (removed) session.outputBufferBytes -= removed.length;
}
for (const listener of session.dataListeners) {
try {
listener(data);
} catch {
// A listener that throws must not break the session.
}
}
this.recordSessionEvent(session, "o", data);
}
@@ -810,7 +823,7 @@ class TerminalSessionManager {
type: "i" | "o" | "r",
data: string,
): void {
if (!session.sessionLoggingEnabled || !session.recordingPath || !data)
if (!session.sessionLoggingEnabled || !session.recordingSink || !data)
return;
const elapsed = (Date.now() - session.sessionStartedAt) / 1000;
const line = `${JSON.stringify([elapsed, type, data])}\n`;
@@ -825,27 +838,33 @@ class TerminalSessionManager {
}
}
/** Coalesces buffered recording lines into a single disk write. */
/**
* Coalesces buffered recording lines into one write, chained so they land
* in order. Never one write per chunk: that starved the libuv threadpool
* (issue #1049).
*/
private flushRecording(session: TerminalSession): void {
if (!session.recordingPath || !session.pendingRecordingData) return;
const pendingSink = session.recordingSink;
if (!pendingSink || !session.pendingRecordingData) return;
const chunk = session.pendingRecordingData;
session.pendingRecordingData = "";
const firstWrite = session.recordingBytes === Buffer.byteLength(chunk);
session.recordingWriteChain = session.recordingWriteChain.then(async () => {
if (firstWrite) {
await fs.promises.mkdir(path.dirname(session.recordingPath!), {
recursive: true,
});
await fs.promises.writeFile(
session.recordingPath!,
`${session.recordingHeader}${chunk}`,
"utf8",
session.recordingWriteChain = session.recordingWriteChain
.then(async () => {
const sink = await pendingSink;
if (!sink) return;
await sink.append(
firstWrite ? `${session.recordingHeader}${chunk}` : chunk,
);
return;
}
await fs.promises.appendFile(session.recordingPath!, chunk, "utf8");
});
})
.catch((err) => {
this.log.warn("Failed to write session recording", {
operation: "session_recording_write_error",
sessionId: session.id,
error: err instanceof Error ? err.message : String(err),
});
});
}
flushBuffer(session: TerminalSession): string | null {
@@ -862,18 +881,12 @@ class TerminalSessionManager {
}
private getTimeoutMs(): number {
try {
const value = getCurrentSettingValue("terminal_session_timeout_minutes");
if (value) {
const minutes = parseInt(value, 10);
if (!isNaN(minutes) && minutes > 0) {
return minutes * 60_000;
}
}
} catch {
// DB not available, use default
}
return DEFAULT_TIMEOUT_MINUTES * 60_000;
const minutes = this.deps.getTimeoutMinutes();
return (
(Number.isFinite(minutes) && minutes > 0
? minutes
: DEFAULT_TIMEOUT_MINUTES) * 60_000
);
}
private healthCheck(): void {
@@ -897,7 +910,7 @@ class TerminalSessionManager {
: 0;
if (detachedDuration > GRACE_PERIOD_MS) {
sshLogger.info(
this.log.info(
"SSH stream destroyed during detach window, cleaning up",
{
operation: "session_health_check_stream_destroyed",
@@ -930,5 +943,3 @@ class TerminalSessionManager {
}
}
}
export const sessionManager = TerminalSessionManager.getInstance();
@@ -0,0 +1,91 @@
import type { PluginContext } from "@termix/plugin-sdk/backend";
import {
normalizeTouchInputSettings,
type TouchInputSettings,
} from "../shared/touch-input-settings.js";
import {
resolveTerminalImageStorageSettings,
TERMINAL_IMAGE_STORAGE_KEYS,
type TerminalImageStorageSettings,
} from "./images/image-storage-settings.js";
import { DEFAULT_TIMEOUT_MINUTES } from "./session-manager.js";
/**
* Admin setting keys, as declared in manifest.json. The image storage fields
* have no default on purpose: an unset field falls through to the old
* TERMIX_IMAGE_* environment variables, the way the settings table did.
*/
export const ADMIN_KEYS = {
sessionTimeoutMinutes: "sessionTimeoutMinutes",
sessionPersistence: "sessionPersistence",
commandHistoryEnabled: "commandHistoryEnabled",
touchInput: "touchInput",
imageStorageMode: "imageStorageMode",
imageLocalDir: "imageLocalDir",
imageHostPath: "imageHostPath",
imageTtlMs: "imageTtlMs",
imageMaxCount: "imageMaxCount",
imageMaxBytes: "imageMaxBytes",
} as const;
export const HOST_KEYS = {
enableTerminal: "enableTerminal",
enableTerminalToolbar: "enableTerminalToolbar",
enableCommandHistory: "enableCommandHistory",
} as const;
const IMAGE_FIELD_BY_LEGACY_KEY: Record<string, string> = {
[TERMINAL_IMAGE_STORAGE_KEYS.mode]: ADMIN_KEYS.imageStorageMode,
[TERMINAL_IMAGE_STORAGE_KEYS.localDir]: ADMIN_KEYS.imageLocalDir,
[TERMINAL_IMAGE_STORAGE_KEYS.hostPath]: ADMIN_KEYS.imageHostPath,
[TERMINAL_IMAGE_STORAGE_KEYS.ttlMs]: ADMIN_KEYS.imageTtlMs,
[TERMINAL_IMAGE_STORAGE_KEYS.maxCount]: ADMIN_KEYS.imageMaxCount,
[TERMINAL_IMAGE_STORAGE_KEYS.maxBytes]: ADMIN_KEYS.imageMaxBytes,
};
export async function readImageStorageSettings(
ctx: PluginContext,
): Promise<TerminalImageStorageSettings> {
return resolveTerminalImageStorageSettings({
get: async (legacyKey) => {
const field = IMAGE_FIELD_BY_LEGACY_KEY[legacyKey];
if (!field) return null;
const value = await ctx.settings.get(field);
if (value === undefined || value === null || value === "") return null;
return String(value);
},
warn: (message) => ctx.log.warn(message),
});
}
export interface ClientSettings {
sessionTimeoutMinutes: number;
sessionPersistence: boolean;
commandHistoryEnabled: boolean;
touchInput: TouchInputSettings;
}
/** What every signed-in user's terminal needs, whether or not they are an admin. */
export async function readClientSettings(
ctx: PluginContext,
): Promise<ClientSettings> {
const all = await ctx.settings.getAll("admin");
const timeout = Number(all[ADMIN_KEYS.sessionTimeoutMinutes]);
let touch: unknown = all[ADMIN_KEYS.touchInput];
if (typeof touch === "string") {
try {
touch = JSON.parse(touch);
} catch {
touch = null;
}
}
return {
sessionTimeoutMinutes:
Number.isFinite(timeout) && timeout > 0
? timeout
: DEFAULT_TIMEOUT_MINUTES,
sessionPersistence: all[ADMIN_KEYS.sessionPersistence] !== false,
commandHistoryEnabled: all[ADMIN_KEYS.commandHistoryEnabled] !== false,
touchInput: normalizeTouchInputSettings(touch),
};
}
@@ -0,0 +1,39 @@
import {
adoptLegacyTable,
defineTable,
id,
refHost,
refUser,
text,
timestamp,
} from "@termix/plugin-sdk/db";
/**
* Commands typed into terminal sessions, per user and host.
*
* Adopted from core's command_history, so the column and index names are the
* legacy ones: the rename carries the existing rows and index across.
*/
export const commandHistory = adoptLegacyTable(
"command_history",
defineTable(
"command_history",
{
id: id(),
userId: refUser(),
hostId: refHost(),
command: text().notNull(),
executedAt: timestamp().notNull().defaultNow(),
},
{
indexes: [
{
name: "idx_command_history_user_host",
columns: ["userId", "hostId"],
},
],
},
),
);
export const tables = [commandHistory];
File diff suppressed because it is too large. Load diff
@@ -1,21 +1,17 @@
import { useEffect, useRef } from "react";
import type { TabProps } from "@termix/plugin-sdk/frontend";
import { TerminalTabContent } from "@/features/terminal/TerminalTabContent";
import type { TabRenderProps } from "@/shell/tab-registry";
import type { TerminalHandle } from "@/features/terminal/terminal-types";
import { TerminalTabContent } from "./terminal/TerminalTabContent";
import type { TerminalHandle } from "./terminal/terminal-types";
import { registerSession, setActiveSession } from "./session-registry";
/**
* Wraps the core terminal tab component to register its ref in the session
* registry, so terminal.sendToActive/sendToSession (used by the snippets
* plugin, among others) can reach a live session without core exposing
* terminalRef through the public tab props.
* Registers the tab's terminal handle in the session registry, so
* terminal.sendToActive/sendToSession (used by the snippets plugin, among
* others) can reach a live session.
*/
export function TerminalTabWithRegistry(props: TabProps) {
const tabRecord = props.tab as unknown as {
id: string;
terminalRef?: { current: TerminalHandle | null };
};
const tabRecord = props.tab as { id: string };
const handle = props.handleRef as { current: TerminalHandle | null } | null;
const host = props.host as
| {
id: string;
@@ -33,7 +29,7 @@ export function TerminalTabWithRegistry(props: TabProps) {
const tryRegister = () => {
if (cancelled || registeredRef.current) return;
const ref = tabRecord.terminalRef?.current;
const ref = handle?.current;
if (!ref) {
if (attempts++ < 40) setTimeout(tryRegister, 250);
return;
@@ -64,5 +60,5 @@ export function TerminalTabWithRegistry(props: TabProps) {
if (props.isFocusedPane) setActiveSession(tabRecord.id);
}, [props.isFocusedPane, tabRecord.id]);
return <TerminalTabContent {...(props as unknown as TabRenderProps)} />;
return <TerminalTabContent {...props} />;
}
@@ -0,0 +1,38 @@
import { Suspense, lazy, type Ref } from "react";
import type { TerminalHandle } from "./terminal/terminal-types";
const CommandHistoryProvider = lazy(() =>
import("./terminal/command-history/CommandHistoryContext").then((m) => ({
default: m.CommandHistoryProvider,
})),
);
const Terminal = lazy(() =>
import("./terminal/Terminal").then((m) => ({ default: m.Terminal })),
);
/**
* The terminal as other code embeds it, registered as "terminal.view".
*
* Props are the terminal's own (hostConfig, isVisible, title, splitScreen,
* executeCommand and the rest), plus `handleRef` for the TerminalHandle
* (connect, disconnect, sendInput, fit), since a registered component cannot
* take a React ref across the plugin boundary.
*/
export function TerminalView({
handleRef,
...props
}: {
handleRef?: Ref<TerminalHandle>;
[prop: string]: unknown;
}) {
return (
<Suspense fallback={null}>
<CommandHistoryProvider>
<Terminal
ref={handleRef}
{...(props as unknown as React.ComponentProps<typeof Terminal>)}
/>
</CommandHistoryProvider>
</Suspense>
);
}
@@ -1,41 +1,43 @@
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { copyToClipboard } from "@/lib/clipboard";
import { Button } from "@/components/button";
import { Input } from "@/components/input";
import { Copy, Search, Terminal, Trash2 } from "lucide-react";
import { copyToClipboard, Button, Input } from "@termix/plugin-sdk/ui";
import {
usePluginApi,
useTranslation,
type PanelProps,
} from "@termix/plugin-sdk/frontend";
import {
getCommandHistory,
deleteCommandFromHistory,
clearCommandHistory,
} from "@/main-axios";
import type { Tab } from "@/types/ui-types";
deleteCommandFromHistory,
getCommandHistory,
hostSetting,
} from "../terminal-api";
export function HistoryPanel({
activeTabId,
terminalTabs,
}: {
activeTabId: string;
terminalTabs: Tab[];
}) {
/** Command history for the terminal the user is working in. */
export function HistoryPanel({ targetTab }: PanelProps) {
const { t } = useTranslation();
const api = usePluginApi();
const [search, setSearch] = useState("");
const [commands, setCommands] = useState<string[]>([]);
const activeTab = terminalTabs.find((t) => t.id === activeTabId);
const activeTab = targetTab;
const activeIsTerminal = !!activeTab;
const hostId = activeTab?.host?.id ? parseInt(activeTab.host.id, 10) : null;
const trackingEnabled = activeTab?.host?.enableCommandHistory !== false;
const trackingEnabled = hostSetting(
activeTab?.host,
"enableCommandHistory",
true,
);
useEffect(() => {
if (!hostId || !trackingEnabled) {
setCommands([]);
return;
}
getCommandHistory(hostId)
getCommandHistory(api, hostId)
.then(setCommands)
.catch(() => setCommands([]));
}, [hostId, trackingEnabled]);
}, [api, hostId, trackingEnabled]);
if (activeIsTerminal && !trackingEnabled) {
return (
@@ -45,10 +47,10 @@ export function HistoryPanel({
</div>
<div className="flex flex-col gap-1">
<span className="text-sm font-semibold text-muted-foreground/60">
{t("newUi.sidebar.history.trackingDisabled")}
{t("history.trackingDisabled")}
</span>
<span className="text-xs text-muted-foreground/40">
{t("newUi.sidebar.history.trackingDisabledHint")}
{t("history.trackingDisabledHint")}
</span>
</div>
</div>
@@ -63,10 +65,10 @@ export function HistoryPanel({
</div>
<div className="flex flex-col gap-1">
<span className="text-sm font-semibold text-muted-foreground/60">
{t("newUi.sidebar.history.noTerminalSelected")}
{t("history.noTerminalSelected")}
</span>
<span className="text-xs text-muted-foreground/40">
{t("newUi.sidebar.history.noTerminalSelectedHint")}
{t("history.noTerminalSelectedHint")}
</span>
</div>
</div>
@@ -80,7 +82,7 @@ export function HistoryPanel({
async function handleDelete(cmd: string) {
if (!hostId) return;
try {
await deleteCommandFromHistory(hostId, cmd);
await deleteCommandFromHistory(api, hostId, cmd);
setCommands((prev) => prev.filter((c) => c !== cmd));
} catch {
/* ignore */
@@ -98,7 +100,7 @@ export function HistoryPanel({
<div className="relative">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
<Input
placeholder={t("newUi.sidebar.history.searchPlaceholder")}
placeholder={t("history.searchPlaceholder")}
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-8"
@@ -106,13 +108,13 @@ export function HistoryPanel({
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">
{filtered.length} command{filtered.length !== 1 ? "s" : ""}
{t("history.commandCount", { count: filtered.length })}
</span>
<button
onClick={async () => {
if (!hostId) return;
try {
await clearCommandHistory(hostId);
await clearCommandHistory(api, hostId);
} catch {
/* ignore */
}
@@ -120,13 +122,13 @@ export function HistoryPanel({
}}
className="text-xs text-accent-brand hover:text-accent-brand/70"
>
{t("newUi.sidebar.history.clearAll")}
{t("history.clearAll")}
</button>
</div>
<div className="flex flex-col gap-1">
{filtered.length === 0 && (
<span className="text-xs text-muted-foreground/60 text-center py-8">
{t("newUi.sidebar.history.noHistoryEntries")}
{t("history.noHistoryEntries")}
</span>
)}
{filtered.map((cmd, i) => (
+131 -13
View File
@@ -1,19 +1,41 @@
import type { ComponentType } from "react";
import { Terminal } from "lucide-react";
import { lazy, Suspense, type ComponentType } from "react";
import { History, Laptop, Terminal } from "lucide-react";
import type {
PanelProps,
StandaloneViewProps,
TabProps,
TermixApp,
} from "@termix/plugin-sdk/frontend";
import { loadTerminal } from "@/features/terminal/TerminalTabContent";
import TerminalApp from "@/features/terminal/TerminalApp";
import { isElectron } from "@termix/plugin-sdk/ui";
import { loadTerminal } from "./terminal/TerminalTabContent";
import TerminalApp from "./terminal/TerminalApp";
import {
TERMINAL_DOCK_SLOT,
TERMINAL_OVERLAY_SLOT,
TERMINAL_SIDE_PANEL_SLOT,
TERMINAL_TOOLBAR_SLOT,
} from "@/features/terminal/terminal-slots";
TERMINAL_TOOLBAR_STATUS_SLOT,
} from "./terminal/terminal-slots";
import { TerminalTabWithRegistry } from "./TerminalTabWithRegistry";
import { listSessions, sendToActive, sendToSession } from "./session-registry";
import { TerminalView } from "./TerminalView";
import { HistoryPanel } from "./history/HistoryPanel";
import { TouchInputSettings } from "./settings/TouchInputSettings";
import { ImageStorageTest } from "./settings/ImageStorageTest";
import { resetTouchInputSettingsCache } from "./terminal/touch-input-settings-store";
import { hostSetting } from "./terminal-api";
const LocalTerminal = lazy(() =>
import("./local-terminal/LocalTerminal").then((m) => ({
default: m.LocalTerminal,
})),
);
interface TerminalOpenOptions {
path?: string;
joinSharedSessionId?: string;
joinShareId?: string;
label?: string;
}
/** `?view=terminal` full-screen links. */
function TerminalStandalone({ hostId, params }: StandaloneViewProps) {
@@ -25,9 +47,21 @@ function TerminalStandalone({ hostId, params }: StandaloneViewProps) {
);
}
/** The desktop app's own shell, one tab per open. */
function LocalTerminalTab({ tab, isVisible }: TabProps) {
return (
<Suspense fallback={null}>
<LocalTerminal
instanceId={(tab.instanceId as string | undefined) ?? tab.id}
isVisible={isVisible}
/>
</Suspense>
);
}
/**
* The SSH terminal. Its component still lives in core until the terminal's
* own Phase B step; this plugin decides whether the shell offers it.
* The SSH terminal: its tab, the desktop app's local terminal, the command
* history panel and the places other plugins can plug into a session.
*/
export function activate(app: TermixApp): void {
app.registerTab(
@@ -38,6 +72,8 @@ export function activate(app: TermixApp): void {
titleKey: "nav.terminal",
persistent: true,
session: true,
commandTarget: true,
ownBackground: true,
restore: (host) => !!host.enableSsh,
activityTypes: ["terminal"],
standalone: TerminalStandalone,
@@ -54,17 +90,75 @@ export function activate(app: TermixApp): void {
order: 10,
tabType: "terminal",
copyUrlView: "terminal",
when: (host) => !!host.enableSsh && host.enableTerminal !== false,
when: (host) =>
!!host.enableSsh && hostSetting(host, "enableTerminal", true),
});
// Places other plugins can fill: toolbar buttons, a docked side panel and
// overlays that follow the session's connection flow.
if (isElectron()) {
app.registerTab("local-terminal", LocalTerminalTab, {
icon: Laptop,
titleKey: "nav.localTerminal",
hostless: true,
session: true,
ownBackground: true,
multiInstance: true,
inLayouts: false,
});
app.registerRailItem({
id: "local-terminal",
icon: Laptop,
titleKey: "nav.localTerminal",
kind: "tab",
electronOnly: true,
hideable: true,
separatorAfter: true,
});
app.registerPaletteEntry({
id: "local-terminal",
titleKey: "palette.localTerminal",
icon: Laptop,
keywords: ["local", "shell", "terminal"],
scope: "global",
run: (shell) => shell.openSingletonTab("local-terminal"),
});
}
app.registerPanel(
"history",
HistoryPanel as unknown as ComponentType<PanelProps>,
);
app.registerRailItem({
id: "history",
icon: History,
titleKey: "nav.history",
hideable: true,
promotable: true,
rightDockable: true,
separatorAfter: true,
});
// Places other plugins can fill: toolbar buttons and readouts, a side
// panel and overlays that follow the session's connection flow.
app.declareActionSlot({ id: TERMINAL_TOOLBAR_SLOT, accepts: ["button"] });
app.declareActionSlot({ id: TERMINAL_DOCK_SLOT, accepts: ["component"] });
app.declareActionSlot({
id: TERMINAL_TOOLBAR_STATUS_SLOT,
accepts: ["component"],
});
app.declareActionSlot({
id: TERMINAL_SIDE_PANEL_SLOT,
accepts: ["component"],
});
app.declareActionSlot({ id: TERMINAL_OVERLAY_SLOT, accepts: ["component"] });
// A terminal other code can embed (collab rooms, the homepage widget, the
// tmux monitor, the file manager's window) without importing this plugin.
app.registerComponent(
"terminal.view",
TerminalView as unknown as ComponentType<Record<string, unknown>>,
);
// Lets another plugin (snippets) push text into a live terminal session
// without reaching into core's tab state itself.
// without reaching into the shell's tab state itself.
app.registerAction("terminal.listSessions", () => listSessions());
app.registerAction("terminal.sendToActive", ((
text: string,
@@ -75,4 +169,28 @@ export function activate(app: TermixApp): void {
text: string,
opts?: { run?: boolean },
) => sendToSession(sessionId, text, opts)) as never);
// Opens a new terminal tab on a host: at a path (the file manager), or
// joining someone else's shared session (the connections panel).
app.registerAction("terminal.open", ((
host: Parameters<TermixApp["tabs"]["openTab"]>[0],
options: TerminalOpenOptions = {},
) =>
app.tabs.openTab(host, "terminal", {
forceNewTab: true,
label: options.label,
data: {
...(options.path ? { initialPath: options.path } : {}),
...(options.joinSharedSessionId
? {
joinSharedSessionId: options.joinSharedSessionId,
joinShareId: options.joinShareId ?? null,
}
: {}),
},
})) as never);
app.registerSettingsComponent("touchInput", TouchInputSettings);
app.registerSettingsComponent("imageStorageTest", ImageStorageTest);
app.onDispose(resetTouchInputSettingsCache);
}
@@ -1,6 +1,6 @@
import type { Terminal } from "@xterm/xterm";
import type { KeybindingAction } from "@/types/keybindings";
import { invokeAction } from "@/shell/action-registry";
import { type KeybindingAction } from "@termix/plugin-sdk/ui";
import { invokeAction } from "@termix/plugin-sdk/frontend";
export interface SnippetHostContext {
ip?: string;
@@ -1,4 +1,4 @@
import { isMacPlatform } from "@/lib/tab-jump-hotkey";
import { isMacPlatform } from "@termix/plugin-sdk/ui";
export function getMacLineNavigationSequence(e: KeyboardEvent): string | null {
if (e.type !== "keydown" || !isMacPlatform()) return null;
@@ -3,18 +3,22 @@ import { FitAddon } from "@xterm/addon-fit";
import { ClipboardAddon } from "@xterm/addon-clipboard";
import { useXTerm } from "react-xtermjs";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import { useTheme } from "@/components/theme-provider";
import { resolveTermixThemeColors } from "@/features/terminal/terminal-theme";
import { DEFAULT_TERMINAL_CONFIG, TERMINAL_FONTS } from "@/lib/terminal-themes";
import { getMacLineNavigationSequence } from "@/lib/mac-line-navigation";
import { ensureTerminalFontsLoaded } from "@/features/terminal/terminal-global-styles";
import { getMacLineNavigationSequence } from "../lib/mac-line-navigation";
import {
handleTerminalClipboardKeyEvent,
createTerminalContextMenuHandler,
} from "@/features/terminal/terminal-clipboard";
import { RobustClipboardProvider } from "@/lib/clipboard-provider";
import { copyToClipboard, readFromClipboard } from "@/lib/clipboard";
} from "../terminal/terminal-clipboard";
import {
useAppTheme as useTheme,
resolveTermixThemeColors,
DEFAULT_TERMINAL_CONFIG,
TERMINAL_FONTS,
ensureTerminalFontsLoaded,
RobustClipboardProvider,
copyToClipboard,
readFromClipboard,
} from "@termix/plugin-sdk/ui";
import { useTranslation } from "@termix/plugin-sdk/frontend";
export function LocalTerminal({
instanceId,
@@ -1,4 +1,4 @@
import type { TerminalHandle } from "@/features/terminal/terminal-types";
import type { TerminalHandle } from "./terminal/terminal-types";
export interface TerminalSessionInfo {
id: string;
@@ -0,0 +1,100 @@
import { useState } from "react";
import { toast } from "sonner";
import { Button, Input } from "@termix/plugin-sdk/ui";
import {
usePluginApi,
useTranslation,
type SettingsComponentProps,
} from "@termix/plugin-sdk/frontend";
interface TestResult {
mode: string;
connected: boolean;
remoteSftpAvailable: boolean;
localHostVisible: boolean | null;
selectedMode: "local" | "remote-sftp" | "unavailable";
localMappingConfigured: boolean;
}
const SELECTED_MODE_LABEL_KEYS: Record<TestResult["selectedMode"], string> = {
local: "settings.admin.imageStorageMode.local",
"remote-sftp": "settings.admin.imageStorageMode.remoteSftp",
unavailable: "imageStorage.selectedModeUnavailable",
};
/**
* Checks which storage an image upload would use for one of the admin's own
* connected terminals, against the saved settings.
*/
export function ImageStorageTest(_props: SettingsComponentProps) {
const { t } = useTranslation();
const api = usePluginApi();
const [instanceId, setInstanceId] = useState("");
const [testing, setTesting] = useState(false);
const [result, setResult] = useState<TestResult | null>(null);
const runTest = async () => {
setTesting(true);
setResult(null);
try {
const { data } = await api.post<TestResult>("/image-storage/test", {
instanceId: instanceId.trim(),
});
setResult(data);
} catch {
toast.error(t("imageStorage.testFailed"));
} finally {
setTesting(false);
}
};
return (
<div className="flex flex-col gap-2 py-3">
<div className="flex flex-col gap-0.5">
<span className="text-sm font-medium leading-snug">
{t("imageStorage.instanceId")}
</span>
<span className="text-xs text-muted-foreground leading-snug">
{t("imageStorage.instanceIdDesc")}
</span>
</div>
<Input
aria-label={t("imageStorage.instanceId")}
value={instanceId}
onChange={(event) => setInstanceId(event.target.value)}
className="h-8 w-full @md:w-64 text-xs"
/>
{result && (
<div className="text-xs text-muted-foreground">
<div>
{result.connected
? t("imageStorage.testConnected")
: t("imageStorage.testNotConnected")}
</div>
<div>
{t("imageStorage.testSelectedMode", {
mode: t(SELECTED_MODE_LABEL_KEYS[result.selectedMode]),
})}
</div>
{result.localHostVisible !== null && (
<div>
{result.localHostVisible
? t("imageStorage.testLocalVisible")
: t("imageStorage.testLocalNotVisible")}
</div>
)}
</div>
)}
<div>
<Button
size="sm"
variant="outline"
disabled={testing || !instanceId.trim()}
onClick={() => void runTest()}
>
{testing ? t("imageStorage.testing") : t("imageStorage.test")}
</Button>
</div>
</div>
);
}
Loaded 100 of 309 files, more files were not shown because too many files have changed in this diff. Show more