mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
release-2.9.0 (#1532)
* fix(macos): skip single-instance lock in Mac App Store builds (#1454)
fix(macos): skip single-instance lock in Mac App Store builds
* Fix/backend optional sharp (#1455)
Fix/backend optional sharp
* feat: issue #1218 (#1218)
https://github.com/Termix-SSH/Support/issues/1218
* feat: Add option to use saved global custom theme under Connection Defaults (#1209)
https://github.com/Termix-SSH/Support/issues/1209
* feat: Add Fleet Sharing functionality to the UI (#1228)
https://github.com/Termix-SSH/Support/issues/1228
* feat: Add a list view to the Docker management page (#1237)
https://github.com/Termix-SSH/Support/issues/1237
* feat: issue #1264 (#1264)
https://github.com/Termix-SSH/Support/issues/1264
* feat: 2FA With FortiToken (#1288)
https://github.com/Termix-SSH/Support/issues/1288
* feat: Allow to disable showing paths to folders (#1274)
https://github.com/Termix-SSH/Support/issues/1274
* feat: Sync Network Graph between Desktop and Remote Server (#1245)
https://github.com/Termix-SSH/Support/issues/1245
* feat: Version Number on offline servers (#1291)
https://github.com/Termix-SSH/Support/issues/1291
* feat: Support OrbStack Docker socket path on macOS / Fix "Docker is not installed" on mac... (#1302)
https://github.com/Termix-SSH/Support/issues/1302
* feat: Sidebar host click should focus existing tab instead of opening a new connection (#1289)
https://github.com/Termix-SSH/Support/issues/1289
* fix: Search in side bar shows overlapping hosts when grouped by tags (#1303)
https://github.com/Termix-SSH/Support/issues/1303
* fix: I can't send a file on its own. (#1304)
https://github.com/Termix-SSH/Support/issues/1304
* fix: sudo password not autofilling (#1248)
https://github.com/Termix-SSH/Support/issues/1248
* fix: node-MainThread (#1300)
https://github.com/Termix-SSH/Support/issues/1300
* fix: Tunnel authentication with shared credentials (#1295)
https://github.com/Termix-SSH/Support/issues/1295
* fix: host key updates not syncing, metrics 404 race on first connect
Host key writes never bumped updatedAt so sync never picked them up.
Also retry the first metrics fetch briefly instead of failing right away.
* chore: increment ver
* feat: add category and icon fields to plugin manifest schema
* ci(deps): bump the github-actions group with 2 updates (#1458)
Bumps the github-actions group with 2 updates: [crowdin/github-action](https://github.com/crowdin/github-action) and [actions/github-script](https://github.com/actions/github-script).
Updates `crowdin/github-action` from 2 to 3
- [Release notes](https://github.com/crowdin/github-action/releases)
- [Commits](https://github.com/crowdin/github-action/compare/v2...v3)
Updates `actions/github-script` from 7 to 9
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v9)
---
updated-dependencies:
- dependency-name: crowdin/github-action
dependency-version: '3'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: actions/github-script
dependency-version: '9'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(deps-dev): bump the dev-patch-updates group with 13 updates (#1459)
Bumps the dev-patch-updates group with 13 updates:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.11.0` | `6.11.1` |
| [@codemirror/search](https://github.com/codemirror/search) | `6.7.1` | `6.7.2` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.9` | `6.43.12` |
| [@testing-library/dom](https://github.com/testing-library/dom-testing-library) | `10.4.1` | `10.4.2` |
| [@testing-library/react](https://github.com/testing-library/react-testing-library) | `16.3.2` | `16.3.3` |
| [@testing-library/user-event](https://github.com/testing-library/user-event) | `14.6.5` | `14.6.7` |
| [@types/ssh2](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ssh2) | `1.15.5` | `1.15.6` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.1.0` | `6.1.1` |
| [cytoscape](https://github.com/cytoscape/cytoscape.js) | `3.34.1` | `3.34.3` |
| [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.5.4` | `0.5.7` |
| [i18next](https://github.com/i18next/i18next) | `26.4.0` | `26.4.2` |
| [prettier](https://github.com/prettier/prettier) | `3.9.6` | `3.9.8` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.12` | `17.0.14` |
Updates `@codemirror/commands` from 6.11.0 to 6.11.1
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)
Updates `@codemirror/search` from 6.7.1 to 6.7.2
- [Changelog](https://github.com/codemirror/search/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/search/commits)
Updates `@codemirror/view` from 6.43.9 to 6.43.12
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)
Updates `@testing-library/dom` from 10.4.1 to 10.4.2
- [Release notes](https://github.com/testing-library/dom-testing-library/releases)
- [Changelog](https://github.com/testing-library/dom-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/dom-testing-library/compare/v10.4.1...v10.4.2)
Updates `@testing-library/react` from 16.3.2 to 16.3.3
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v16.3.2...v16.3.3)
Updates `@testing-library/user-event` from 14.6.5 to 14.6.7
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.6.5...v14.6.7)
Updates `@types/ssh2` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ssh2)
Updates `@vitejs/plugin-react` from 6.1.0 to 6.1.1
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.1/packages/plugin-react)
Updates `cytoscape` from 3.34.1 to 3.34.3
- [Release notes](https://github.com/cytoscape/cytoscape.js/releases)
- [Commits](https://github.com/cytoscape/cytoscape.js/compare/v3.34.1...v3.34.3)
Updates `eslint-plugin-react-refresh` from 0.5.4 to 0.5.7
- [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases)
- [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/compare/v0.5.4...v0.5.7)
Updates `i18next` from 26.4.0 to 26.4.2
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.4.0...v26.4.2)
Updates `prettier` from 3.9.6 to 3.9.8
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.9.6...3.9.8)
Updates `react-i18next` from 17.0.12 to 17.0.14
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.12...v17.0.14)
---
updated-dependencies:
- dependency-name: "@codemirror/commands"
dependency-version: 6.11.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@codemirror/search"
dependency-version: 6.7.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@codemirror/view"
dependency-version: 6.43.12
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/dom"
dependency-version: 10.4.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/react"
dependency-version: 16.3.3
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/user-event"
dependency-version: 14.6.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@types/ssh2"
dependency-version: 1.15.6
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.1.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: cytoscape
dependency-version: 3.34.3
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: eslint-plugin-react-refresh
dependency-version: 0.5.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: i18next
dependency-version: 26.4.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: prettier
dependency-version: 3.9.8
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: react-i18next
dependency-version: 17.0.14
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(deps): bump the prod-patch-updates group with 6 updates (#1461)
Bumps the prod-patch-updates group with 6 updates:
| Package | From | To |
| --- | --- | --- |
| [@tanstack/react-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual) | `3.14.10` | `3.14.13` |
| [compression](https://github.com/expressjs/compression) | `1.8.1` | `1.8.2` |
| [jose](https://github.com/panva/jose) | `6.2.9` | `6.2.12` |
| [jszip](https://github.com/Stuk/jszip) | `3.10.1` | `3.10.2` |
| [socks](https://github.com/JoshGlazebrook/socks) | `2.8.9` | `2.8.10` |
| [undici](https://github.com/nodejs/undici) | `8.10.0` | `8.10.2` |
Updates `@tanstack/react-virtual` from 3.14.10 to 3.14.13
- [Release notes](https://github.com/TanStack/virtual/releases)
- [Changelog](https://github.com/TanStack/virtual/blob/main/packages/react-virtual/CHANGELOG.md)
- [Commits](https://github.com/TanStack/virtual/commits/@tanstack/react-virtual@3.14.13/packages/react-virtual)
Updates `compression` from 1.8.1 to 1.8.2
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/v1.8.1...v1.8.2)
Updates `jose` from 6.2.9 to 6.2.12
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.9...v6.2.12)
Updates `jszip` from 3.10.1 to 3.10.2
- [Changelog](https://github.com/Stuk/jszip/blob/main/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.10.1...v3.10.2)
Updates `socks` from 2.8.9 to 2.8.10
- [Release notes](https://github.com/JoshGlazebrook/socks/releases)
- [Commits](https://github.com/JoshGlazebrook/socks/compare/2.8.9...2.8.10)
Updates `undici` from 8.10.0 to 8.10.2
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.10.0...v8.10.2)
---
updated-dependencies:
- dependency-name: "@tanstack/react-virtual"
dependency-version: 3.14.13
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: compression
dependency-version: 1.8.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: jose
dependency-version: 6.2.12
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: jszip
dependency-version: 3.10.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: socks
dependency-version: 2.8.10
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: undici
dependency-version: 8.10.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat: add plugin worker protocol, bootstrap and plugin_storage table
Worker-side ctx proxy plus the message envelope it talks over, and the
per-plugin key/value table behind ctx.storage with migrations for all
three dialects. Rows cascade with the plugin so uninstalling leaves
nothing behind.
* feat: add allowlisted host view for plugins
An allowlist rather than a list of fields to strip. A blocklist rots:
the moment a new secret-bearing column lands on SSHHost it starts
leaking silently. With an allowlist a new field stays invisible to
plugins until someone adds it here on purpose.
* feat: add plugin loader with manifest validation and crash restart
Validates an unpacked plugin, spawns a worker_threads worker and manages
activate/deactivate/crash-restart with backoff, disabling after three
tries.
The crash counter clears only after a plugin stays up past a stability
window, not on activation. Clearing it on activation let a plugin that
activates cleanly and then dies restart forever, because every attempt
looked like the first.
loader.ts documents what the worker boundary does and does not buy you.
Node's permission model is process-wide, not per-worker, so it cannot be
enabled for plugin workers alone and is not claimed here.
* feat: route internal events through a shared plugin event bus
Formalises three ad-hoc publish paths that already existed:
automation-events.ts, metrics/automation-bridge.ts and the
hostSessionStatus singleton, now the host.session.status topic.
Both invariants the old code relied on are preserved. Publishing stays
fire-and-forget, so a failing subscriber cannot disturb a metrics poll or
a host delete. The automations engine subscribes to the bus at scheduler
start instead of being imported ad hoc, which keeps the edge
one-directional: automations may import repositories, hosts modules must
not import automations.
* feat: add plugin ctx broker with permission gate and audit
Every ctx call a plugin makes is checked against its granted
capabilities, performed by the main thread, then audited. A capability
must be both declared in the manifest and granted in the database, so
widening a plugin's reach always needs a new manifest the user can see,
never just a database row.
Audit attribution is set by the broker, not the plugin. The plugin
supplies only the details, so it cannot forge the actor.
fix: stop plugin workers seeing plaintext credentials in error messages
A failing ctx.ssh.connect handed the worker the raw error from deep in
the SSH stack, which embeds the connect config. The worker received, in
full:
Authentication failed for root using password <SECRET>
where <SECRET> was the host's real plaintext password. Any plugin with
ssh.exec could read it by catching the error, defeating the whole point
of the handle-based ssh API.
Errors crossing the boundary are now sanitised. Only PluginFacingError
and PluginPermissionError, both authored here, reach a plugin verbatim;
anything else becomes a generic message and the real one goes to the
log. This uses a marker class rather than matching on message text,
because matching would start leaking again the moment an upstream error
happened to contain a familiar phrase.
Covered by credential-isolation.test.ts, which plants a sentinel in
every secret-bearing field and asserts it appears in no message the
worker ever receives.
* feat: dispatch plugin HTTP routes and wire the runtime into startup
ctx.http.route now registers a real router on the /plugin-api/:pluginId
dispatcher, replacing its stub 404. A plugin never touches the Express
req/res: it gets a plain summary and returns a plain object, so sockets
and session cookies stay on this side of the boundary. Authorization and
cookie headers are not forwarded.
Plugins load last in the start-up sequence, after the server is fully
wired, and are terminated before the database on shutdown so none can
outlive it. A plugin that fails to load cannot stop the backend.
fix: correct the nginx plugin-api location regex
The pattern was ^/plugin-api/(/.*)?$, which needs a double slash and so
never matched /plugin-api/<id>/<route>. Every other block in the file
uses ^/prefix(/.*)?$. Left alone, plugin routes would work in dev and
404 only behind Docker.
* fix: update stale SFTP transfer test for single file destination paths
The test still asserted transferToHost was called with the destination
directory "/srv". Commit 7727fa09 (#1304) deliberately changed a single
file transfer to send the full destination path including the filename,
so the correct expectation is "/srv/remote-1.txt". The code was right
and the test was left behind.
* feat: ship the ssh terminal as a first-party in-process plugin
Adds a hardcoded process:transport-owner tier so the terminal can own its
WebSocket server and ssh2 clients, which cannot cross the worker boundary.
* feat: ship docker as a first-party in-process plugin
Moves docker backend and frontend into plugins/docker/, alongside
ssh-terminal. Adds a host editor tab seam, a dashboard card registry,
and a build step to compile plugin backends into dist.
* feat: ship host metrics as a first-party in-process plugin
* fix: harden plugin auth and add per-request identity for ctx calls
Require auth on /plugin-api, gate plugin enable/grant/revoke routes
behind admin.plugins.manage, and thread the calling user through
worker ctx.hosts/ctx.ssh calls instead of always using the install
owner. Also adds the plugin permissions grant/revoke UI.
* feat: ship the AI assistant as a first-party in-process plugin
Moves src/backend/ai and its routes into plugins/ai, mounted through a
small /ai dispatcher instead of its own port. Moves the ai-enabled and
ai-private-endpoints settings routes out of user-settings-routes.ts
into the plugin too, so it has no leftover core dependents.
* fix: remove compiled plugin JS left behind in plugins/ after build
copy-bundled-plugins.cjs compiled and rewrote plugin backend TS in
place, copied it to dist/plugins, but never deleted the .js it had
just emitted next to the .ts sources. Those files shadowed the real
source for vitest/tsx and had import paths rewritten for the dist
layout, so tests failed with "Cannot find module" once a local build
had run. Affects docker, host-metrics and ai, not just this one.
* feat: add a permission-gated backend service registry for plugins
* feat: add a permission-gated UI action registry and action slots for plugins
Moves the terminal toolbar's hardwired AI button into a slot the ssh-terminal
plugin offers and the ai plugin fills, gated on ai.services.use.
* feat: add cross-plugin secret references gated by the provider's RBAC permission
* feat: move proxmox discovery/import/sync into a first-party plugin
* feat: move remote desktop (RDP/VNC/Telnet) into a first-party plugin
Relocates guacamole backend/frontend source and tests into
plugins/remote-desktop, following the ssh-terminal/docker pattern.
* feat: wire remote-desktop plugin into core and rename i18n keys
Repoints core consumers (dispatcher, collab, session-sharing, tab
utils) at the plugin, adds isTabTypeAvailable gating for rdp/vnc/
telnet, widens hostCapability to accept an array, and renames the
guacamole i18n namespace to remoteDesktop.
* feat: move fleets into a first-party plugin
* feat: move automations into a first-party plugin
* feat: remove legacy alert rules, superseded by automations
Alert rules already auto-migrate into automations on boot and the old
engine stands down after. Deletes the rule/firing tables, routes, UI,
and AI tools; keeps notification channels since automations still use
them. Also fixes automations' notify step, which still called the
deleted repository.
* feat: move network topology into a first-party plugin
* feat: move tailscale device discovery and settings into a plugin
Adds plugins/tailscale/ with a devices sidebar tab, admin settings
section, and re-auth dialog. Host Metrics tailscale manager card and
SSH re-auth banner parsing stay in core.
* feat: move workspaces into a first-party plugin
* feat: move web endpoint into a first-party plugin
* feat: collapse plugin runtime to one tier behind a plugin SDK
* feat: make each plugin an npm workspace built by a termix-plugin CLI
* feat: give plugins their own tables, migrations and sync entities
* feat: serve plugin http and websockets from one mount instead of ports
* feat: namespace plugin permissions and keep them valid when a plugin is off
* feat: give plugins their own settings and one settings screen
* feat: load plugin frontends at runtime and remove plugin ids from the shell
* fix: mock the ai status fetch in its activate test
* feat: add auth extension points and one ssh connect pipeline
* feat: rebuild workspaces as the reference plugin
* fix: gate second factors after SSO/LDAP behind an admin setting
* feat: convert network-topology to a full plugin (B1)
* feat: move snippets backend and tables into a plugin (B2)
* feat: point core frontend snippets pickers at the plugin (B2)
* feat: add the snippets plugin (B2)
* feat: wire ai, automations, fleets and ssh-terminal to snippets (B2)
* feat: finish the tailscale plugin, including SSH auth (B3)
* feat: finish the fleets plugin, add ctx.hosts and host-commands SDK (B4)
* chore: update proxmox and host-metrics locale files for B5
* chore: regenerate drizzle migration journals for B5
* feat: add ctx.hosts create/update to the SDK, host-metrics drops proxmox
* feat: move proxmox node stats and collectors into the proxmox plugin
* feat: drop proxmox host columns, use plugin settings instead
* feat: finish the fleets plugin, add ctx.hosts and host-commands SDK (B4)
* feat: convert file manager into a plugin
* feat: convert ssh tunnels into a plugin
* feat: finish the web-endpoint plugin, add ctx.desktop (B8)
* feat: move the terminal fully into the ssh-terminal plugin (B9)
* feat: convert tmux monitoring into a plugin (B10)
* feat: convert serial console into the serial plugin
* feat: move session sharing and collab rooms into a plugin (B12)
* feat: turn session recording into a plugin (B13)
* feat: finish the remote-desktop plugin (B14)
* feat: finish the host-metrics plugin, move host status into core (B16)
* feat: finish the docker plugin (B15)
* feat: finish the automations plugin (B17)
* feat: finish the ai plugin (B18)
* feat: finish the homepage plugin, move service links and canvas out of core (B19)
* feat: convert wake-on-lan into a plugin (B20)
* feat: convert secret-sources into a plugin (B20)
* feat: move totp and passkeys into plugins (C1)
* feat: move sso and ldap logins into plugins (C2)
* feat: move opkssh and warpgate into plugins (C3)
* feat: move step-ca into a plugin (C4)
* feat: move vault ssh signing into a plugin (C5)
* feat: move termix identity into a plugin (C6)
* feat: move acme certificate automation into a plugin (C7)
* fix: finish 2.9.0 plugin moves and get every check green
* chore: lock the plugin boundary and remove 2.9.0 leftovers
* fix: harden the plugin runtime after the 2.9.0 security review
* feat: add plugin packing, signing, pinned trust and tmxplug bundling
* test: prove 2.8 to 2.9.0 upgrade is lossless and fix what it caught
* fix: give bundled plugin backends a real require for CJS deps
* feat: improve UI loading time and jitter
* feat: show credential sharing inline in the sidebar like host sharing
* feat: tailscale add host and copy ip, fix outbound fetch hang and plugin ESM warning
* feat: unify alerts into one plugin, channels, rules and inbox
* feat: rebuild desktop sync as linked server accounts, fix packaged plugin bundles and UI scaling
* fix: set blacksmith builder cache-key to stop broken gha cache fallback
* fix: link plugin-sdk workspace before npm ci in docker deps stage
* fix: link every plugin workspace before npm ci in docker deps stage
* fix(host-metrics): exclude image and firmware filesystems (#1469)
* fix(host-metrics): exclude image and firmware filesystems
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(hosts): normalize imported jump host references (#1470)
* fix(hosts): normalize imported jump host references
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(ci): build plugin SDK and repair MySQL settings migration (#1471)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): keep inline rename consistent (#1476)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): keep inline rename consistent
* fix(dashboard): reuse translated host status labels (#1475)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(dashboard): reuse translated host status labels
* docs(api): clarify local and remote version fields (#1474)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* docs(api): clarify local and remote version fields
* fix(file-manager): preserve selected backgrounds on hover (#1473)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): preserve selection feedback on hover
* test(ui): await dialog focus cleanup before teardown
* fix(file-manager): restore Ctrl/Cmd+F file search (#1472)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): focus file search with Ctrl or Cmd F
* fix: keep rounded-full circular in production builds
* fix(file-manager): cancel uploads and stop remote writes (#1477)
* fix(file-manager): cancel uploads and stop remote writes
* test(css): exercise the configured PostCSS plugin chain
* fix(file-manager): apply compact density to directory tree (#1478)
* fix(file-manager): apply compact density to directory tree
* test(css): exercise the configured PostCSS plugin chain
* fix(file-manager): preserve font sizes in compact mode (#1480)
* fix(file-manager): preserve font sizes in compact mode
* test(css): exercise the configured PostCSS plugin chain
* fix(file-manager): prioritize file names in narrow lists (#1483)
* fix(file-manager): prioritize file names in narrow lists
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* test(remote-desktop): always mutate ciphertext in tamper test
* fix(remote-desktop): probe the saved host guacd endpoint (#1481)
* fix(remote-desktop): probe the saved host guacd endpoint
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* fix(homepage): check allowlisted private service reachability (#1479)
* fix(homepage): check allowlisted private service reachability
* test(css): exercise the configured PostCSS plugin chain
* fix(remote-desktop): stop unsolicited clipboard read prompts (#1482)
* fix(remote-desktop): read clipboard only on explicit paste
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* test(remote-desktop): always mutate ciphertext in tamper test
* chore: simplify plugin readmes, changelogs and manifest descriptions
* fix: keep plugins loaded after signing in from the login screen
* fix(file-manager): accept OS file drops over existing rows (#1484)
* fix(file-manager): accept OS file drops over existing rows
* test(file-manager): target the file label in drop tests
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* test(remote-desktop): always mutate ciphertext in tamper test
* fix(file-manager): pipeline SFTP download reads (#1485)
* feat: add plugin sdk build to beta release
* fix: make link-store secret check immune to random base64 matches
* fix(ldap): preserve the directory-provided bind DN (#1486)
* fix: compute beta changes across dev branches and move the beta tag
* chore: untrack plugin sdk ARCHITECTURE.md
* chore: remove references to plugin sdk ARCHITECTURE.md
* feat: merge host editor terminal tabs into one owned by ssh-terminal
* feat: add descriptions to plugin host enable toggles
* fix(file-manager): show download progress in file viewers (#1487)
* fix(electron): signal backend readiness over IPC (#1488)
* fix(split-screen): allow layout creation without randomUUID (#1490)
* fix(split-screen): allow layout creation without randomUUID
* test(credentials): avoid flaky Ed25519 key generation
* fix(sidebar): remeasure visible rows after density changes (#1489)
* fix(sidebar): remeasure visible rows after density changes
* test(credentials): avoid flaky Ed25519 key generation
* feat(split-screen): redesign split screen with free-form panes and pane picker
* feat(sidebar): add host click behavior setting, default to always opening a new tab
* feat(snippets): redesign panel to match the host list
* fix(session-recording): stop session logs panel from refetching in a loop
* feat(snippets): bring back sharing, target hosts and drag reorder
* feat(ssh-terminal): add toolbar position, start state, display and fade host settings
* feat(ssh-terminal): add host setting to turn off password prompt auto-fill
* fix(ai): stop streams getting cut off and keep tool steps across messages
* feat: improve quick connect system using plugin api
* fix: proxmox guest sync, startup snippet sync, drop dead cloudflare ssh transport
* feat: move terminal settings, look and ssh tools into ssh-terminal plugin
* feat: move snippets, keybinding actions and macros out of core into plugins
* feat: store rdp, vnc and telnet logins in a generic per-protocol table
* feat: type the plugin host record, move plugin strings out of core and add lint checks
* feat: replace core homepage widget registry with generic extension points
* feat: plugins only see their own host settings in the browser
* feat: harden plugin api for separate repos, signed updates and generic external login
* fix: keep plugin css below core utilities and version plugin assets by their css
* fix: load and save plugin host settings in the editor and tighten settings row spacing
* feat: redesign user host defaults system
* fix(remote-desktop): release held keys on focus loss (#1498)
* fix(session-sharing): replace local presentation after takeover (#1500)
* fix(file-manager): refresh disk usage with directory listing (#1503)
* fix(ssh-terminal): preserve input order during local echo reconciliation (#1505)
* feat(remote-desktop): expose display zoom controls for RDP (#1514)
* fix(file-manager): restore text selection and copying in Markdown previews (#1518)
* fix(sidebar): keep compact host addresses visible (#1524)
* fix(hosts): preserve plugin settings when reopening the editor (#1499)
* fix(ssh-terminal): close owner sockets when presentations unmount (#1501)
* fix(session-sharing): synchronize shared SSH terminal dimensions (#1502)
* fix(session-sharing): show participant presence in member terminals (#1504)
* fix(ai): sync provider configuration and keys across linked devices (#1506)
* feat(ssh-terminal): add a duplicate tab action (#1510)
* feat(file-manager): allow disabling move-to-trash confirmation (#1511)
* feat(file-manager): default to the remote login directory (#1512)
* feat(homepage): synchronize canvas layouts across devices (#1515)
* fix(shortcuts): prevent accidental double-Shift palette activation (#1519)
* feat(tmux-monitor): add a unified multi-host view (#1520)
* fix(electron): allow explicit quit with active connections (#1522)
* feat: reconnect all disconnected terminal tabs (#1523)
* fix(hosts): preserve recording and sharing settings in the dev editor (#1509)
* fix(database): preserve RDP domain during startup backfill (#1497)
* feat(sidebar): add keyboard type-ahead search for hosts (#1516)
* feat(automations): add per-host maintenance mode and recurring schedules (#1521)
* feat(tmux): add a per-host mouse toggle and preserve remote configuration (#1513)
* fix(session-sharing): use the linked server for desktop meetings and links (#1507)
* fix(tunnels): support TOTP for client tunnels and jump hosts (#1508)
* fix: type errors and lint failures from merged plugin prs
* feat(installer): make the Windows desktop shortcut optional (#1517)
* fix: ship the plugin sdk and serialport in desktop builds
* fix: prefer ipv4 when resolving ssh host names (#1365)
* fix: readable terminal selection with the light theme (#1319)
* fix: build desktop session share links on the linked server (#1350)
* fix: show meeting guests to members and hide the roster from guests (#1353)
* fix: reliable file manager saves with sudo prompt and no base64 guessing (#1335)
* fix: show disk usage for the mount being browsed (#1347)
* fix: load the linux tray icon reliably and log tray failures (#1332)
* fix: cover the gnome close path in the desktop quit test (#1332)
* fix: prompt for jump host TOTP in terminal, file manager and metrics (#1312)
* feat: optional auto reconnect for dropped ssh sessions (#1321)
* feat: tighter compact host rows with inline tags (#1325)
* feat: bring back the alert feed homepage widget
* feat: pick target terminals for snippets again
* feat: share button on connection tabs again
* feat: recent servers list when linking the desktop app
* feat: list the fixed app shortcuts in keyboard shortcuts
* fix: translate hardcoded toasts and drop em dashes from ui text
* fix: translate leftover hardcoded ui text
* fix: toolbar files button opens at the shell directory again
* fix: show the remote desktop note under connection origin again
* chore: openapi docs for host metrics manager routes
* chore: openapi docs for undocumented routes and fix two broken doc blocks
* fix: declare terminal.listSessions use for snippet targets
* revert: drop the alert feed widget, the notification center replaces it
* feat: restore the alert feed homepage widget
* fix: quick connect fills the host address from plugin auth editors like tailscale
* fix: docker container logs use the base background color
* chore: fix dependabot alerts and apply safe npm upgrades
* fix: proxmox stats tab reads its switch from plugin host settings
* fix: count plugin ssh logins toward host status and stop showing reachable as a warning
* fix: hide the built-in source pill in the host editor
* fix: host status shows online when the port answers and only warns on a failed login
* fix: drop the top border on the split layout menu
* fix: host defaults tabs sit where the host editor puts them and skip plugins with nothing to default
* fix: remote desktop auth method labels and notes for vnc and telnet
* fix: add description to the web endpoint enable switch
* fix: crowdin sync and pretranslate for plugin locales, run plugin tests on release
* feat: move telemetry out of core into a usage statistics plugin
* fix: recent activity card no longer cuts off host names early
* fix: remove duplicate top border on split screen menu
* fix: redesign session log player controls, fix tooltip overlap and drop dot separators
* fix: file manager and sftp text showing raw i18n keys
* fix: onboarding rerunning for existing users after upgrading
* fix: fleet detail buttons overflowing the sidebar
* fix: session log player scrolling off screen horizontally
* fix: network graph lag when adding hosts and on every render
* fix: maintenance index name, shared host read only view and badge polling
* fix: meeting tabs, workspace tab merging, rail panels as tabs and general ui lag
* chore: update release notes
* fix: remote desktop tabs reconnecting on every tab switch
* fix: build plugin sdk before linting in release workflow
* chore: lint, format, and bump version to 2.9.0
* chore: skip crowdin pre-translate in release for now
* chore: sync Crowdin translations for 2.9.0
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Koi <38512047+datlt4@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
This commit is contained in:
4405 files changed
+1341026
-289722
No files matched your search
@@ -0,0 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0
|
||||
|
||||
First release, bundled with Termix 2.9.0.
|
||||
|
||||
Groups hosts into fleets, either by hand or by tag. Run commands, package actions and file transfers on every host in a fleet at once, and see an inventory of its hosts.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Fleets
|
||||
|
||||
Group hosts into fleets and run commands, package actions and file transfers on all of them at once.
|
||||
|
||||
## Features
|
||||
|
||||
- Group hosts into fleets by hand or by tag. Hosts with a matching tag join on their own.
|
||||
- Run a command on every host in a fleet at once and see each result.
|
||||
- Run package actions, like updates, across a fleet.
|
||||
- Transfer files to and from every host in a fleet.
|
||||
- See an inventory of the hosts in a fleet.
|
||||
- Share fleets with other users or roles.
|
||||
|
||||
## Permissions
|
||||
|
||||
- `fleets.view`: See fleets, their hosts and inventory. Admins and users have it by default.
|
||||
- `fleets.manage`: Create, edit, delete and share fleets. Admins and users have it by default.
|
||||
- `fleets.execute`: Run commands, package actions and file transfers on a fleet. Admins and users have it by default.
|
||||
|
||||
## Services
|
||||
|
||||
Provides to other plugins:
|
||||
|
||||
- `fleets.access`: list fleets and run actions on them.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm run build # build into dist/
|
||||
npm run test # run this plugin's tests
|
||||
npm run typecheck # type-check this plugin
|
||||
```
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remove",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connect",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Bekyk vlote",
|
||||
"description": "Sien vlote, hul lede en inventaris."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Bestuur vlote",
|
||||
"description": "Skep, wysig en verwyder vlote, verander lidmaatskap en deel vlote met ander gebruikers of rolle."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Voer vlootaksies uit",
|
||||
"description": "Voer bevele en pakketaksies oor 'n vloot uit, en dra lêers na en van sy lidgashere oor."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Vlote",
|
||||
"createFleet": "Skep Vloot",
|
||||
"createFleetTitle": "Skep Vloot",
|
||||
"editFleetTitle": "Wysig vloot",
|
||||
"createFleetDescription": "'n Vloot is 'n benoemde groep gashere teen wie jy as 'n eenheid aksies kan uitvoer.",
|
||||
"nameLabel": "Naam",
|
||||
"namePlaceholder": "bv. webbedieners",
|
||||
"nameRequired": "Vlootnaam word vereis",
|
||||
"descriptionLabel": "Beskrywing",
|
||||
"descriptionPlaceholder": "Opsionele beskrywing",
|
||||
"colorLabel": "Kleur",
|
||||
"tagRulesLabel": "Etiketreëls",
|
||||
"tagRulesPlaceholder": "prod-web, rand",
|
||||
"tagRulesHint": "Gashere met enige van hierdie etikette word outomaties ingesluit, benewens gashere wat handmatig bygevoeg word.",
|
||||
"fleetCreated": "Vloot geskep",
|
||||
"fleetUpdated": "Vloot opgedateer",
|
||||
"fleetDeleted": "Vloot uitgevee",
|
||||
"saveFailed": "Kon nie vloot stoor nie",
|
||||
"deleteFailed": "Kon nie vloot uitvee nie",
|
||||
"loadFailed": "Kon nie vlote laai nie",
|
||||
"deleteFleetTitle": "Vee Vloot uit",
|
||||
"deleteFleetDescription": "Is jy seker jy wil \"{{name}}\" verwyder? Dit verwyder nie die lidgashere self nie.",
|
||||
"searchFleets": "Soek vlote...",
|
||||
"noFleets": "Geen vlote nog nie. Skep een om te begin.",
|
||||
"memberCount": "{{count}} gasheer",
|
||||
"memberCount_other": "{{count}} gashere",
|
||||
"manageMembers": "Bestuur Lede",
|
||||
"manageMembersTitle": "Bestuur Vlootlede",
|
||||
"manageMembersDescription": "Wissel gashere in of uit hierdie vloot se statiese lidmaatskap. Gashere wat ooreenstem met etiketreëls word altyd ingesluit.",
|
||||
"searchHosts": "Soek gashere...",
|
||||
"noHostsFound": "Geen gashere gevind nie",
|
||||
"noMembers": "Hierdie vloot het nog geen lede nie. Voeg gashere by of stel 'n etiketreël.",
|
||||
"membersLoadFailed": "Kon nie vlootlede laai nie",
|
||||
"memberUpdateFailed": "Kon nie vlootlidmaatskap opdateer nie",
|
||||
"tabRun": "Voer die opdrag uit",
|
||||
"tabTransfer": "Dra lêers oor",
|
||||
"tabInventory": "Voorraad",
|
||||
"openInventoryTab": "Maak oop in volle oortjie",
|
||||
"tabPackages": "Pakkette",
|
||||
"runOnFleet": "Hardloop op Vloot",
|
||||
"commandPlaceholder": "optyd && df -h",
|
||||
"commandRequired": "'n Opdrag word vereis",
|
||||
"commandInputsLabel": "Opdraginvoere",
|
||||
"commandSucceeded": "Opdrag het op alle gashere geslaag",
|
||||
"commandPartialFailure": "{{failed}} van {{total}} gashere het misluk",
|
||||
"commandFailed": "Kon nie die opdrag uitvoer nie",
|
||||
"resultSuccess": "Sukses",
|
||||
"resultFailed": "Misluk",
|
||||
"push": "Stoot",
|
||||
"pull": "Trek",
|
||||
"remotePathLabel": "Afgeleë pad",
|
||||
"remotePathRequired": "'n Afgeleë pad word benodig",
|
||||
"fileLabel": "Lêer",
|
||||
"fileRequired": "'n Lêer word vereis",
|
||||
"pushHint": "Die gekose lêer word na hierdie presiese pad op elke gasheer in die vloot geskryf.",
|
||||
"pullHint": "Die lêer op hierdie pad word van elke gasheer gelees en as 'n enkele zip-lêer afgelaai, een lêergids per gasheer.",
|
||||
"transferComplete": "Oordrag voltooi",
|
||||
"transferFailed": "Oordrag het misluk",
|
||||
"refreshInventory": "Verfris voorraad",
|
||||
"inventoryLoadFailed": "Kon nie voorraad laai nie",
|
||||
"inventoryRefreshed": "Voorraad verfris",
|
||||
"inventoryRefreshFailed": "Kon nie voorraad verfris nie",
|
||||
"inventoryNoData": "Geen voorraaddata nog nie - verfris om dit te versamel.",
|
||||
"inventoryHost": "Gasheer",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} versamel",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kern",
|
||||
"inventoryArch": "Boog",
|
||||
"inventoryUptime": "Optyd",
|
||||
"inventoryPackageManager": "Pakketbestuurder",
|
||||
"inventoryCollectedAt": "Versamel",
|
||||
"packageActionLabel": "Aksie",
|
||||
"packageInstall": "Installeer",
|
||||
"packageRemove": "Verwyder",
|
||||
"packageUpgradeAll": "Gradeer alle pakkette op",
|
||||
"packageNameLabel": "Pakketnaam",
|
||||
"packageNameRequired": "'n Pakketnaam word vereis",
|
||||
"packageHint": "Vereis bestuursvlaktoegang per gasheer en verhoog met behulp van die gasheer se gestoorde sudo-wagwoord.",
|
||||
"shareFleet": "Deel Vloot",
|
||||
"shareFleetTitle": "Deel \"{{name}}\"",
|
||||
"shareFleetDescription": "Deel hierdie vloot se huidige lid-gashere met die gekose gebruikers of rolle. Gashere wat later by die vloot gevoeg word, word nie outomaties gedeel nie - deel weer om hulle in te sluit.",
|
||||
"shareSelectionRequired": "Kies ten minste een gebruiker of rol",
|
||||
"fleetSharedSuccessfully": "Gedeelde {{shared}} van {{total}} gasheer(s) in hierdie vloot",
|
||||
"failedToShareFleet": "Kon nie vloot deel nie",
|
||||
"noMembersToShare": "Hierdie vloot het nog geen lidgashere om te deel nie."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Vlote"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Bekyk dokumente",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Ure totdat toegang verval",
|
||||
"expiry": {
|
||||
"never": "Nooit",
|
||||
"oneHour": "1 uur",
|
||||
"oneDay": "24 uur",
|
||||
"sevenDays": "7 dae",
|
||||
"thirtyDays": "30 dae",
|
||||
"custom": "Pasgemaak"
|
||||
},
|
||||
"expiryLabel": "Toegang verval",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Verbind",
|
||||
"description": "Slegs oop sessies: terminaal, afstandwerkskerm, lêerbestuurder, tonnels en Docker. Geen toegang tot die gasheerkonfigurasie nie."
|
||||
},
|
||||
"view": {
|
||||
"label": "Bekyk",
|
||||
"description": "Verbind, plus sien die gasheerkonfigurasie. Geheime word nooit gewys nie."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Wysig",
|
||||
"description": "Bekyk, plus wysig nie-verifikasie gasheerinstellings. Die eienaar se SSH-verifikasie bly privaat en slegs vir die eienaar."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Bestuur",
|
||||
"description": "Wysig, plus deel die gasheer met ander, verander toestemmingsvlakke en herroep toegang."
|
||||
}
|
||||
},
|
||||
"noMatches": "Geen ooreenstemmings gevind nie",
|
||||
"permissionLevelLabel": "Toestemmingsvlak",
|
||||
"rolesTab": "Rolle",
|
||||
"searchPlaceholder": "Soek gebruikers of rolle...",
|
||||
"shareButton": "Deel",
|
||||
"shareWithCount": "Deel ({{count}})",
|
||||
"usersTab": "Gebruikers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "عرض الأساطيل",
|
||||
"description": "اطلع على الأساطيل وأعضائها ومخزونها."
|
||||
},
|
||||
"manage": {
|
||||
"title": "إدارة الأساطيل",
|
||||
"description": "إنشاء الأساطيل وتعديلها وحذفها، وتغيير العضوية، ومشاركة الأساطيل مع مستخدمين أو أدوار أخرى."
|
||||
},
|
||||
"execute": {
|
||||
"title": "نفّذ عمليات الأسطول",
|
||||
"description": "قم بتشغيل الأوامر وإجراءات الحزم عبر مجموعة من الأجهزة، ونقل الملفات من وإلى مضيفيها الأعضاء."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "الأساطيل",
|
||||
"createFleet": "إنشاء أسطول",
|
||||
"createFleetTitle": "إنشاء أسطول",
|
||||
"editFleetTitle": "تعديل الأسطول",
|
||||
"createFleetDescription": "الأسطول عبارة عن مجموعة محددة من المضيفين يمكنك شنّ عمليات ضدهم كوحدة واحدة.",
|
||||
"nameLabel": "الاسم",
|
||||
"namePlaceholder": "مثال: خوادم الويب",
|
||||
"nameRequired": "اسم الأسطول مطلوب",
|
||||
"descriptionLabel": "الوصف",
|
||||
"descriptionPlaceholder": "وصف اختياري",
|
||||
"colorLabel": "اللون",
|
||||
"tagRulesLabel": "قواعد الوسوم",
|
||||
"tagRulesPlaceholder": "موقع الإنتاج، الحافة",
|
||||
"tagRulesHint": "يتم تضمين المضيفين الذين يحملون أيًا من هذه العلامات تلقائيًا، بالإضافة إلى المضيفين الذين تمت إضافتهم يدويًا.",
|
||||
"fleetCreated": "تم إنشاء الأسطول",
|
||||
"fleetUpdated": "تحديث الأسطول",
|
||||
"fleetDeleted": "تم حذف الأسطول",
|
||||
"saveFailed": "فشل في إنقاذ الأسطول",
|
||||
"deleteFailed": "فشل حذف الأسطول",
|
||||
"loadFailed": "فشل تحميل الأساطيل",
|
||||
"deleteFleetTitle": "حذف الأسطول",
|
||||
"deleteFleetDescription": "هل أنت متأكد من رغبتك في حذف \"{{name}}\"؟ هذا لا يحذف المضيفين الأعضاء أنفسهم.",
|
||||
"searchFleets": "أساطيل البحث...",
|
||||
"noFleets": "لا توجد أساطيل حتى الآن. أنشئ أسطولاً للبدء.",
|
||||
"memberCount": "{{count}} المضيف",
|
||||
"memberCount_other": "{{count}} مضيفات",
|
||||
"manageMembers": "إدارة الأعضاء",
|
||||
"manageMembersTitle": "إدارة أعضاء الأسطول",
|
||||
"manageMembersDescription": "قم بتفعيل أو تعطيل عضوية المضيفين في هذه المجموعة الثابتة. يتم تضمين المضيفين الذين تتطابق قواعد الوسوم معهم دائمًا.",
|
||||
"searchHosts": "ابحث عن مضيفين...",
|
||||
"noHostsFound": "لم يتم العثور على مضيفين",
|
||||
"noMembers": "لا يوجد أعضاء في هذا الأسطول حتى الآن. أضف مضيفين أو حدد قاعدة للوسوم.",
|
||||
"membersLoadFailed": "فشل تحميل أعضاء الأسطول",
|
||||
"memberUpdateFailed": "فشل تحديث عضوية الأسطول",
|
||||
"tabRun": "تشغيل الأمر",
|
||||
"tabTransfer": "نقل الملفات",
|
||||
"tabInventory": "جرد",
|
||||
"openInventoryTab": "افتح في علامة تبويب كاملة",
|
||||
"tabPackages": "الحزم",
|
||||
"runOnFleet": "انطلق على الأسطول",
|
||||
"commandPlaceholder": "وقت التشغيل && df -h",
|
||||
"commandRequired": "يلزم إصدار أمر",
|
||||
"commandInputsLabel": "إدخالات الأوامر",
|
||||
"commandSucceeded": "تم تنفيذ الأمر بنجاح على جميع الأجهزة المضيفة",
|
||||
"commandPartialFailure": "فشل {{failed}} من أصل {{total}} مضيف",
|
||||
"commandFailed": "فشل تنفيذ الأمر",
|
||||
"resultSuccess": "نجاح",
|
||||
"resultFailed": "فشل",
|
||||
"push": "يدفع",
|
||||
"pull": "يحذب",
|
||||
"remotePathLabel": "المسار البعيد",
|
||||
"remotePathRequired": "يلزم وجود مسار بعيد",
|
||||
"fileLabel": "ملف",
|
||||
"fileRequired": "الملف مطلوب",
|
||||
"pushHint": "يتم كتابة الملف المحدد في هذا المسار المحدد على كل مضيف في الأسطول.",
|
||||
"pullHint": "يتم قراءة الملف الموجود في هذا المسار من كل مضيف وتنزيله كملف مضغوط واحد، مجلد واحد لكل مضيف.",
|
||||
"transferComplete": "اكتمل النقل",
|
||||
"transferFailed": "فشل النقل",
|
||||
"refreshInventory": "تحديث المخزون",
|
||||
"inventoryLoadFailed": "فشل تحميل المخزون",
|
||||
"inventoryRefreshed": "تم تحديث المخزون",
|
||||
"inventoryRefreshFailed": "فشل تحديث المخزون",
|
||||
"inventoryNoData": "لا توجد بيانات جرد حتى الآن - قم بتحديث الصفحة لجمعها.",
|
||||
"inventoryHost": "المضيف",
|
||||
"inventoryCollectedCount": "تم جمع {{count}}/{{total}}",
|
||||
"inventoryOs": "نظام التشغيل",
|
||||
"inventoryKernel": "النواة",
|
||||
"inventoryArch": "قوس",
|
||||
"inventoryUptime": "مدة التشغيل",
|
||||
"inventoryPackageManager": "مدير الحزم",
|
||||
"inventoryCollectedAt": "تم جمعها",
|
||||
"packageActionLabel": "الإجراء",
|
||||
"packageInstall": "ثَبَّتَ",
|
||||
"packageRemove": "إزالة",
|
||||
"packageUpgradeAll": "قم بترقية جميع الحزم",
|
||||
"packageNameLabel": "اسم الحزمة",
|
||||
"packageNameRequired": "اسم الحزمة مطلوب",
|
||||
"packageHint": "يتطلب الوصول على مستوى الإدارة لكل مضيف ويتم رفعه باستخدام كلمة مرور sudo المخزنة للمضيف.",
|
||||
"shareFleet": "أسطول مشترك",
|
||||
"shareFleetTitle": "شارك \"{{name}}\"",
|
||||
"shareFleetDescription": "يُشارك هذا الأمر أجهزة الأعضاء الحالية في هذه المجموعة مع المستخدمين أو الأدوار المُحددة. لا تتم مشاركة الأجهزة التي تُضاف إلى المجموعة لاحقًا تلقائيًا - يُرجى إعادة المشاركة لإضافتها.",
|
||||
"shareSelectionRequired": "اختر مستخدمًا واحدًا أو دورًا واحدًا على الأقل",
|
||||
"fleetSharedSuccessfully": "مشترك {{shared}} من {{total}} مضيف (مضيفين) في هذه المجموعة",
|
||||
"failedToShareFleet": "فشل في مشاركة الأسطول",
|
||||
"noMembersToShare": "لا يوجد لدى هذا الأسطول مضيفون أعضاء للمشاركة حتى الآن."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "الأساطيل"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "عرض المستندات",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "عدد الساعات حتى انتهاء صلاحية الوصول",
|
||||
"expiry": {
|
||||
"never": "أبدًا",
|
||||
"oneHour": "ساعة واحدة",
|
||||
"oneDay": "24 ساعة",
|
||||
"sevenDays": "7 أيام",
|
||||
"thirtyDays": "30 يومًا",
|
||||
"custom": "مخصص"
|
||||
},
|
||||
"expiryLabel": "انتهاء صلاحية الوصول",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "اتصال",
|
||||
"description": "فتح الجلسات فقط: الطرفية، سطح المكتب البعيد، مدير الملفات، الأنفاق وDocker. لا يمكن الوصول إلى إعدادات المضيف."
|
||||
},
|
||||
"view": {
|
||||
"label": "عرض",
|
||||
"description": "الاتصال، بالإضافة إلى عرض إعدادات المضيف. لا تظهر الأسرار أبدًا."
|
||||
},
|
||||
"edit": {
|
||||
"label": "تعديل",
|
||||
"description": "يمكنك عرض وتعديل إعدادات المضيف غير المتعلقة بالمصادقة. تبقى مصادقة SSH الخاصة بالمالك سرية ومخصصة له فقط."
|
||||
},
|
||||
"manage": {
|
||||
"label": "إدارة",
|
||||
"description": "التعديل، بالإضافة إلى مشاركة المضيف مع الآخرين، وتغيير مستويات الصلاحية وإلغاء الوصول."
|
||||
}
|
||||
},
|
||||
"noMatches": "لا توجد نتائج مطابقة",
|
||||
"permissionLevelLabel": "مستوى الصلاحية",
|
||||
"rolesTab": "الأدوار",
|
||||
"searchPlaceholder": "البحث عن مستخدمين أو أدوار...",
|
||||
"shareButton": "مشاركة",
|
||||
"shareWithCount": "مشاركة ({{count}})",
|
||||
"usersTab": "المستخدمون"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Преглед на автопаркове",
|
||||
"description": "Вижте автопаркове, техните членове и инвентар."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Управление на автопаркове",
|
||||
"description": "Създавайте, редактирайте и изтривайте автопаркове, променяйте членството и споделяйте автопаркове с други потребители или роли."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Изпълняване на действия за флотилия",
|
||||
"description": "Изпълнявайте команди и действия за пакетиране в рамките на флотилия и прехвърляйте файлове към и от нейните хостове-членки."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Флотили",
|
||||
"createFleet": "Създаване на флот",
|
||||
"createFleetTitle": "Създаване на флот",
|
||||
"editFleetTitle": "Редактиране на флота",
|
||||
"createFleetDescription": "Флотът е именувана група от хостове, срещу които можете да изпълнявате действия като единица.",
|
||||
"nameLabel": "Име",
|
||||
"namePlaceholder": "напр. уеб сървъри",
|
||||
"nameRequired": "Името на флота е задължително",
|
||||
"descriptionLabel": "Описание",
|
||||
"descriptionPlaceholder": "Незадължително описание",
|
||||
"colorLabel": "Цвят",
|
||||
"tagRulesLabel": "Правила за етикети",
|
||||
"tagRulesPlaceholder": "prod-web, ръб",
|
||||
"tagRulesHint": "Хостовете с някой от тези етикети се включват автоматично, в допълнение към хостовете, добавени ръчно.",
|
||||
"fleetCreated": "Създаден флот",
|
||||
"fleetUpdated": "Актуализиран автопарк",
|
||||
"fleetDeleted": "Флотът е изтрит",
|
||||
"saveFailed": "Запазването на флота не бе успешно",
|
||||
"deleteFailed": "Изтриването на автопарка не бе успешно",
|
||||
"loadFailed": "Зареждането на автопарковете не бе успешно",
|
||||
"deleteFleetTitle": "Изтриване на флота",
|
||||
"deleteFleetDescription": "Сигурни ли сте, че искате да изтриете „{{name}}“? Това не изтрива самите хостове-членове.",
|
||||
"searchFleets": "Търсене на автопаркове...",
|
||||
"noFleets": "Все още няма автопаркове. Създайте такъв, за да започнете.",
|
||||
"memberCount": "{{count}} хост",
|
||||
"memberCount_other": "{{count}} хостове",
|
||||
"manageMembers": "Управление на членовете",
|
||||
"manageMembersTitle": "Управление на членовете на автопарка",
|
||||
"manageMembersDescription": "Включване/изключване на хостове от статичното членство на този флот. Хостовете, съответстващи на правилата за тагове, винаги са включени.",
|
||||
"searchHosts": "Търсене на хостове...",
|
||||
"noHostsFound": "Не са намерени хостове",
|
||||
"noMembers": "Тази флотилия все още няма членове. Добавете хостове или задайте правило за етикетиране.",
|
||||
"membersLoadFailed": "Зареждането на членовете на автопарка не бе успешно",
|
||||
"memberUpdateFailed": "Актуализирането на членството в автопарка не бе успешно",
|
||||
"tabRun": "Изпълнете командата",
|
||||
"tabTransfer": "Прехвърляне на файлове",
|
||||
"tabInventory": "Инвентар",
|
||||
"openInventoryTab": "Отвори в цял раздел",
|
||||
"tabPackages": "Пакети",
|
||||
"runOnFleet": "Работи на флота",
|
||||
"commandPlaceholder": "време на работа && df -h",
|
||||
"commandRequired": "Изисква се команда",
|
||||
"commandInputsLabel": "Входни команди",
|
||||
"commandSucceeded": "Командата е успешна на всички хостове",
|
||||
"commandPartialFailure": "{{failed}} от {{total}} хостове неуспешно",
|
||||
"commandFailed": "Неуспешно изпълнение на командата",
|
||||
"resultSuccess": "Успех",
|
||||
"resultFailed": "Неуспешно",
|
||||
"push": "Натиснете",
|
||||
"pull": "Дръпнете",
|
||||
"remotePathLabel": "Отдалечен път",
|
||||
"remotePathRequired": "Изисква се отдалечен път",
|
||||
"fileLabel": "Файл",
|
||||
"fileRequired": "Изисква се файл",
|
||||
"pushHint": "Избраният файл се записва точно по този път на всеки хост във флота.",
|
||||
"pullHint": "Файлът по този път се чете от всеки хост и се изтегля като един zip архив, по една папка на хост.",
|
||||
"transferComplete": "Прехвърлянето е завършено",
|
||||
"transferFailed": "Прехвърлянето не бе успешно",
|
||||
"refreshInventory": "Обновяване на инвентара",
|
||||
"inventoryLoadFailed": "Зареждането на инвентара не бе успешно",
|
||||
"inventoryRefreshed": "Инвентаризацията е обновена",
|
||||
"inventoryRefreshFailed": "Неуспешно обновяване на инвентара",
|
||||
"inventoryNoData": "Все още няма данни за инвентара - обновете страницата, за да ги съберете.",
|
||||
"inventoryHost": "Домакин",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} събрани",
|
||||
"inventoryOs": "ОС",
|
||||
"inventoryKernel": "Ядро",
|
||||
"inventoryArch": "Арка",
|
||||
"inventoryUptime": "Време на работа",
|
||||
"inventoryPackageManager": "Мениджър на пакети",
|
||||
"inventoryCollectedAt": "Събрани",
|
||||
"packageActionLabel": "Действие",
|
||||
"packageInstall": "Инсталиране",
|
||||
"packageRemove": "Премахване",
|
||||
"packageUpgradeAll": "Надстройте всички пакети",
|
||||
"packageNameLabel": "Име на пакета",
|
||||
"packageNameRequired": "Изисква се име на пакета",
|
||||
"packageHint": "Изисква достъп на ниво управление за всеки хост и повишава правата, използвайки съхранената sudo парола на хоста.",
|
||||
"shareFleet": "Споделяне на флота",
|
||||
"shareFleetTitle": "Сподели „{{name}}“",
|
||||
"shareFleetDescription": "Споделя текущите хостове-членове на този флот с избраните потребители или роли. Хостовете, добавени към флота по-късно, не се споделят автоматично - споделете отново, за да ги включите.",
|
||||
"shareSelectionRequired": "Изберете поне един потребител или роля",
|
||||
"fleetSharedSuccessfully": "Споделени {{shared}} от {{total}} хост(а) в този флот",
|
||||
"failedToShareFleet": "Споделянето на автопарка не бе успешно",
|
||||
"noMembersToShare": "Този флот все още няма хостове-членове за споделяне."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Флотили"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Преглед на документи",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Часове до изтичане на срока на достъп",
|
||||
"expiry": {
|
||||
"never": "Никога",
|
||||
"oneHour": "1 час",
|
||||
"oneDay": "24 часа",
|
||||
"sevenDays": "7 дни",
|
||||
"thirtyDays": "30 дни",
|
||||
"custom": "Персонализирано"
|
||||
},
|
||||
"expiryLabel": "Изтичане на достъпа",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Свържете се",
|
||||
"description": "Само отворени сесии: терминал, отдалечен работен плот, файлов мениджър, тунели и Docker. Няма достъп до конфигурацията на хоста."
|
||||
},
|
||||
"view": {
|
||||
"label": "Преглед",
|
||||
"description": "Свържете се, плюс вижте конфигурацията на хоста. Тайните никога не се показват."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Редактиране",
|
||||
"description": "Преглеждайте и променяйте настройките на хоста без удостоверяване. SSH удостоверяването на собственика остава частно и само за собственика."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Управление",
|
||||
"description": "Редактирайте, плюс споделяйте хоста с други, променяйте нивата на разрешения и отменяйте достъпа."
|
||||
}
|
||||
},
|
||||
"noMatches": "Няма намерени съвпадения",
|
||||
"permissionLevelLabel": "Ниво на разрешение",
|
||||
"rolesTab": "Роли",
|
||||
"searchPlaceholder": "Търсене на потребители или роли...",
|
||||
"shareButton": "Споделяне",
|
||||
"shareWithCount": "Споделяне ({{count}})",
|
||||
"usersTab": "Потребители"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remove",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connect",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Veure flotes",
|
||||
"description": "Veure flotes, els seus membres i l'inventari."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Gestionar flotes",
|
||||
"description": "Crea, edita i elimina flotes, canvia la pertinença i comparteix flotes amb altres usuaris o rols."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Executar accions de flota",
|
||||
"description": "Executa ordres i crea accions de paquets a través d'una flota i transfereix fitxers des de i cap als seus amfitrions membres."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flotes",
|
||||
"createFleet": "Crear flota",
|
||||
"createFleetTitle": "Crear flota",
|
||||
"editFleetTitle": "Edita la flota",
|
||||
"createFleetDescription": "Una flota és un grup d'amfitrions amb nom contra els quals podeu executar accions com una unitat.",
|
||||
"nameLabel": "Nom",
|
||||
"namePlaceholder": "p. ex., servidors web",
|
||||
"nameRequired": "El nom de la flota és obligatori",
|
||||
"descriptionLabel": "Descripció",
|
||||
"descriptionPlaceholder": "Descripció opcional",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Regles d'etiquetes",
|
||||
"tagRulesPlaceholder": "prod-web, vora",
|
||||
"tagRulesHint": "Els amfitrions amb qualsevol d'aquestes etiquetes s'inclouen automàticament, a més dels amfitrions afegits manualment.",
|
||||
"fleetCreated": "Flota creada",
|
||||
"fleetUpdated": "Flota actualitzada",
|
||||
"fleetDeleted": "Flota suprimida",
|
||||
"saveFailed": "No s'ha pogut desar la flota",
|
||||
"deleteFailed": "No s'ha pogut suprimir la flota",
|
||||
"loadFailed": "No s'han pogut carregar les flotes",
|
||||
"deleteFleetTitle": "Suprimeix la flota",
|
||||
"deleteFleetDescription": "Esteu segur que voleu suprimir \"{{name}}\"? Això no suprimeix els amfitrions membres en si.",
|
||||
"searchFleets": "Cerca flotes...",
|
||||
"noFleets": "Encara no hi ha cap flota. Crea'n una per començar.",
|
||||
"memberCount": "amfitrió {{count}}",
|
||||
"memberCount_other": "amfitrions {{count}}",
|
||||
"manageMembers": "Gestiona els membres",
|
||||
"manageMembersTitle": "Gestionar els membres de la flota",
|
||||
"manageMembersDescription": "Activa o desactiva la pertinença estàtica dels hosts a aquesta flota. Els hosts que coincideixen amb les regles d'etiqueta sempre s'hi inclouen.",
|
||||
"searchHosts": "Cerca amfitrions...",
|
||||
"noHostsFound": "No s'han trobat amfitrions",
|
||||
"noMembers": "Aquesta flota encara no té membres. Afegeix amfitrions o defineix una regla d'etiqueta.",
|
||||
"membersLoadFailed": "No s'han pogut carregar els membres de la flota",
|
||||
"memberUpdateFailed": "No s'ha pogut actualitzar la pertinença a la flota",
|
||||
"tabRun": "Executa l'ordre",
|
||||
"tabTransfer": "Transferir fitxers",
|
||||
"tabInventory": "Inventari",
|
||||
"openInventoryTab": "Obre en una pestanya completa",
|
||||
"tabPackages": "Paquets",
|
||||
"runOnFleet": "Funciona amb Flota",
|
||||
"commandPlaceholder": "temps de funcionament && df -h",
|
||||
"commandRequired": "Cal una comanda",
|
||||
"commandInputsLabel": "Entrades de comandament",
|
||||
"commandSucceeded": "L'ordre ha tingut èxit en tots els hosts",
|
||||
"commandPartialFailure": "Els hosts han fallat {{failed}} de {{total}}",
|
||||
"commandFailed": "No s'ha pogut executar l'ordre",
|
||||
"resultSuccess": "Èxit",
|
||||
"resultFailed": "Fallat",
|
||||
"push": "Empènyer",
|
||||
"pull": "Estirar",
|
||||
"remotePathLabel": "Camí remot",
|
||||
"remotePathRequired": "Cal una ruta remota",
|
||||
"fileLabel": "Fitxer",
|
||||
"fileRequired": "Es requereix un fitxer",
|
||||
"pushHint": "El fitxer seleccionat s'escriu a aquesta ruta exacta a tots els hosts de la flota.",
|
||||
"pullHint": "El fitxer d'aquesta ruta es llegeix des de tots els hosts i es descarrega com un únic zip, una carpeta per host.",
|
||||
"transferComplete": "Transferència completada",
|
||||
"transferFailed": "La transferència ha fallat",
|
||||
"refreshInventory": "Actualitza l'inventari",
|
||||
"inventoryLoadFailed": "No s'ha pogut carregar l'inventari",
|
||||
"inventoryRefreshed": "Inventari actualitzat",
|
||||
"inventoryRefreshFailed": "No s'ha pogut actualitzar l'inventari",
|
||||
"inventoryNoData": "Encara no hi ha dades d'inventari; actualitza la pàgina per recopilar-les.",
|
||||
"inventoryHost": "Amfitrió",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} recollit",
|
||||
"inventoryOs": "Sistema operatiu",
|
||||
"inventoryKernel": "Nucli",
|
||||
"inventoryArch": "Arc",
|
||||
"inventoryUptime": "Temps de funcionament",
|
||||
"inventoryPackageManager": "Gestor de paquets",
|
||||
"inventoryCollectedAt": "Recollit",
|
||||
"packageActionLabel": "Acció",
|
||||
"packageInstall": "Instal·la",
|
||||
"packageRemove": "Elimina",
|
||||
"packageUpgradeAll": "Actualitza tots els paquets",
|
||||
"packageNameLabel": "Nom del paquet",
|
||||
"packageNameRequired": "Cal un nom de paquet",
|
||||
"packageHint": "Requereix accés de nivell d'administració per host i eleva els privilegis mitjançant la contrasenya sudo emmagatzemada per l'host.",
|
||||
"shareFleet": "Comparteix flota",
|
||||
"shareFleetTitle": "Comparteix \"{{name}}\"",
|
||||
"shareFleetDescription": "Comparteix els amfitrions membres actuals d'aquesta flota amb els usuaris o rols seleccionats. Els amfitrions afegits a la flota més tard no es comparteixen automàticament; torneu a compartir per incloure'ls.",
|
||||
"shareSelectionRequired": "Seleccioneu com a mínim un usuari o rol",
|
||||
"fleetSharedSuccessfully": "S'han compartit {{shared}} de {{total}} hosts en aquesta flota",
|
||||
"failedToShareFleet": "No s'ha pogut compartir la flota",
|
||||
"noMembersToShare": "Aquesta flota encara no té amfitrions membres per compartir."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flotes"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Veure documents",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hores fins que caduca l'accés",
|
||||
"expiry": {
|
||||
"never": "Mai",
|
||||
"oneHour": "1 hora",
|
||||
"oneDay": "24 hores",
|
||||
"sevenDays": "7 dies",
|
||||
"thirtyDays": "30 dies",
|
||||
"custom": "Personalitzat"
|
||||
},
|
||||
"expiryLabel": "Caducitat de l'accés",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connecta",
|
||||
"description": "Només sessions obertes: terminal, escriptori remot, gestor de fitxers, túnels i Docker. No hi ha accés a la configuració de l'amfitrió."
|
||||
},
|
||||
"view": {
|
||||
"label": "Vista",
|
||||
"description": "Connecta't i consulta la configuració de l'amfitrió. Els secrets no es mostren mai."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edita",
|
||||
"description": "Visualitza i modifica la configuració de l'amfitrió que no és d'autenticació. L'autenticació SSH del propietari es manté privada i només del propietari."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Gestiona",
|
||||
"description": "Edita i comparteix l'amfitrió amb altres persones, canvia els nivells de permisos i revoca l'accés."
|
||||
}
|
||||
},
|
||||
"noMatches": "No s'han trobat coincidències",
|
||||
"permissionLevelLabel": "Nivell de permís",
|
||||
"rolesTab": "Rols",
|
||||
"searchPlaceholder": "Cerca usuaris o rols...",
|
||||
"shareButton": "Comparteix",
|
||||
"shareWithCount": "Comparteix ({{count}})",
|
||||
"usersTab": "Usuaris"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Zobrazit flotily",
|
||||
"description": "Prohlédněte si flotily, jejich členy a inventář."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Správa vozových parků",
|
||||
"description": "Vytvářejte, upravujte a mazejte flotily, měňte členství a sdílejte flotily s ostatními uživateli nebo rolemi."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Spouštění akcí flotily",
|
||||
"description": "Spouštět příkazy a akce balíčků napříč flotilou a přenášet soubory do a z jejích členských hostitelů."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flotily",
|
||||
"createFleet": "Vytvořit flotilu",
|
||||
"createFleetTitle": "Vytvořit flotilu",
|
||||
"editFleetTitle": "Upravit flotilu",
|
||||
"createFleetDescription": "Flotila je pojmenovaná skupina hostitelů, proti kterým můžete jako jednotce spouštět akce.",
|
||||
"nameLabel": "Jméno",
|
||||
"namePlaceholder": "např. webové servery",
|
||||
"nameRequired": "Název flotily je povinný",
|
||||
"descriptionLabel": "Popis",
|
||||
"descriptionPlaceholder": "Volitelný popis",
|
||||
"colorLabel": "Barva",
|
||||
"tagRulesLabel": "Pravidla pro tagy",
|
||||
"tagRulesPlaceholder": "prod-web, okraj",
|
||||
"tagRulesHint": "Hostitelé s některým z těchto tagů jsou zahrnuti automaticky, kromě hostitelů přidaných ručně.",
|
||||
"fleetCreated": "Vytvořená flotila",
|
||||
"fleetUpdated": "Aktualizace vozového parku",
|
||||
"fleetDeleted": "Flotila smazána",
|
||||
"saveFailed": "Uložení flotily se nezdařilo",
|
||||
"deleteFailed": "Nepodařilo se smazat flotilu",
|
||||
"loadFailed": "Načtení flotil se nezdařilo",
|
||||
"deleteFleetTitle": "Smazat flotilu",
|
||||
"deleteFleetDescription": "Opravdu chcete smazat „{{name}}“? Tímto se neodstraní samotní hostitelé členů.",
|
||||
"searchFleets": "Hledat flotily...",
|
||||
"noFleets": "Zatím žádné flotily. Vytvořte si jednu a začněte.",
|
||||
"memberCount": "{{count}} hostitel",
|
||||
"memberCount_other": "{{count}} hostitelé",
|
||||
"manageMembers": "Spravovat členy",
|
||||
"manageMembersTitle": "Správa členů vozového parku",
|
||||
"manageMembersDescription": "Přepíná hostitele do statického členství této flotily nebo z něj. Hostitelé odpovídající pravidlům tagů jsou vždy zahrnuti.",
|
||||
"searchHosts": "Hledat hostitele…",
|
||||
"noHostsFound": "Nenalezeny žádné hostitele",
|
||||
"noMembers": "Tato flotila zatím nemá žádné členy. Přidejte hostitele nebo nastavte pravidlo pro tagy.",
|
||||
"membersLoadFailed": "Nepodařilo se načíst členy flotily",
|
||||
"memberUpdateFailed": "Nepodařilo se aktualizovat členství ve flotile",
|
||||
"tabRun": "Spustit příkaz",
|
||||
"tabTransfer": "Přenos souborů",
|
||||
"tabInventory": "Inventář",
|
||||
"openInventoryTab": "Otevřít na celou kartu",
|
||||
"tabPackages": "Balíčky",
|
||||
"runOnFleet": "Běh na flotile",
|
||||
"commandPlaceholder": "doba provozuschopnosti && df -h",
|
||||
"commandRequired": "Je vyžadován příkaz",
|
||||
"commandInputsLabel": "Vstupy příkazů",
|
||||
"commandSucceeded": "Příkaz byl úspěšný na všech hostitelích",
|
||||
"commandPartialFailure": "{{failed}} z {{total}} hostitelů selhalo",
|
||||
"commandFailed": "Nepodařilo se spustit příkaz",
|
||||
"resultSuccess": "Úspěch",
|
||||
"resultFailed": "Selhalo",
|
||||
"push": "TAM",
|
||||
"pull": "SEM",
|
||||
"remotePathLabel": "Vzdálená cesta",
|
||||
"remotePathRequired": "Je vyžadována vzdálená cesta.",
|
||||
"fileLabel": "Soubor",
|
||||
"fileRequired": "Je vyžadován soubor",
|
||||
"pushHint": "Vybraný soubor se zapíše do této přesné cesty na každém hostiteli ve flotile.",
|
||||
"pullHint": "Soubor v této cestě je načten z každého hostitele a stažen jako jeden zip soubor, jedna složka na hostitele.",
|
||||
"transferComplete": "Převod dokončen",
|
||||
"transferFailed": "Přenos se nezdařil",
|
||||
"refreshInventory": "Obnovit inventář",
|
||||
"inventoryLoadFailed": "Nepodařilo se načíst inventář",
|
||||
"inventoryRefreshed": "Obnovení zásob",
|
||||
"inventoryRefreshFailed": "Nepodařilo se aktualizovat inventář",
|
||||
"inventoryNoData": "Zatím nejsou k dispozici žádná data o zásobách – obnovte stránku pro jejich shromažďování.",
|
||||
"inventoryHost": "Hostitel",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} shromážděno",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Jádro",
|
||||
"inventoryArch": "Oblouk",
|
||||
"inventoryUptime": "Doba provozuschopnosti",
|
||||
"inventoryPackageManager": "Správce balíčků",
|
||||
"inventoryCollectedAt": "Sebrané",
|
||||
"packageActionLabel": "Akce",
|
||||
"packageInstall": "Instalovat",
|
||||
"packageRemove": "Odstranit",
|
||||
"packageUpgradeAll": "Aktualizujte všechny balíčky",
|
||||
"packageNameLabel": "Název balíčku",
|
||||
"packageNameRequired": "Je vyžadován název balíčku.",
|
||||
"packageHint": "Vyžaduje přístup na úrovni správy pro každého hostitele a zvyšuje oprávnění pomocí uloženého hesla sudo hostitele.",
|
||||
"shareFleet": "Sdílení flotily",
|
||||
"shareFleetTitle": "Sdílet „{{name}}“",
|
||||
"shareFleetDescription": "Sdílí aktuální členské hostitele této flotily s vybranými uživateli nebo rolemi. Hostitelé přidaní do flotily později se nesdílejí automaticky – chcete-li je zahrnout, sdílejte je znovu.",
|
||||
"shareSelectionRequired": "Vyberte alespoň jednoho uživatele nebo roli",
|
||||
"fleetSharedSuccessfully": "Sdílený {{shared}} z {{total}} hostitel(ů) v této flotile",
|
||||
"failedToShareFleet": "Sdílení flotily se nezdařilo",
|
||||
"noMembersToShare": "Tato flotila zatím nemá žádné členské hostitele, o které by se dalo sdílet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flotily"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Zobrazit dokumenty",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hodiny do vypršení přístupu",
|
||||
"expiry": {
|
||||
"never": "Nikdy",
|
||||
"oneHour": "1 hodina",
|
||||
"oneDay": "24 hodin",
|
||||
"sevenDays": "7 dní",
|
||||
"thirtyDays": "30 dní",
|
||||
"custom": "Zvyk"
|
||||
},
|
||||
"expiryLabel": "Vypršení přístupu",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Připojit",
|
||||
"description": "Pouze otevřené relace: terminál, vzdálená plocha, správce souborů, tunely a Docker. Bez přístupu ke konfiguraci hostitele."
|
||||
},
|
||||
"view": {
|
||||
"label": "Pohled",
|
||||
"description": "Připojení a zobrazení konfigurace hostitele. Tajné kódy se nikdy nezobrazují."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Upravit",
|
||||
"description": "Zobrazení a úprava nastavení hostitele bez ověřování. Ověřování vlastníka přes SSH zůstává soukromé a pouze pro vlastníka."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Spravovat",
|
||||
"description": "Upravovat a sdílet hostitele s ostatními, měnit úrovně oprávnění a odebírat přístup."
|
||||
}
|
||||
},
|
||||
"noMatches": "Nenalezeny žádné shody",
|
||||
"permissionLevelLabel": "Úroveň oprávnění",
|
||||
"rolesTab": "Role",
|
||||
"searchPlaceholder": "Hledat uživatele nebo role...",
|
||||
"shareButton": "Podíl",
|
||||
"shareWithCount": "Sdílet ({{count}})",
|
||||
"usersTab": "Uživatelé"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Se flåder",
|
||||
"description": "Se flåder, deres medlemmer og inventar."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Administrer flåder",
|
||||
"description": "Opret, rediger og slet flåder, skift medlemskab og del flåder med andre brugere eller roller."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Kør flådehandlinger",
|
||||
"description": "Kør kommandoer og pakkehandlinger på tværs af en flåde, og overfør filer til og fra dens medlemsværter."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flåder",
|
||||
"createFleet": "Opret flåde",
|
||||
"createFleetTitle": "Opret flåde",
|
||||
"editFleetTitle": "Rediger flåde",
|
||||
"createFleetDescription": "En flåde er en navngiven gruppe af værter, som du kan udføre handlinger mod som en enhed.",
|
||||
"nameLabel": "Navn",
|
||||
"namePlaceholder": "f.eks. webservere",
|
||||
"nameRequired": "Flådenavn er påkrævet",
|
||||
"descriptionLabel": "Beskrivelse",
|
||||
"descriptionPlaceholder": "Valgfri beskrivelse",
|
||||
"colorLabel": "Farve",
|
||||
"tagRulesLabel": "Tagregler",
|
||||
"tagRulesPlaceholder": "prod-web, kant",
|
||||
"tagRulesHint": "Værter med et af disse tags inkluderes automatisk, udover værter, der tilføjes manuelt.",
|
||||
"fleetCreated": "Flåde oprettet",
|
||||
"fleetUpdated": "Flåden er opdateret",
|
||||
"fleetDeleted": "Flåden slettet",
|
||||
"saveFailed": "Kunne ikke redde flåden",
|
||||
"deleteFailed": "Kunne ikke slette flåden",
|
||||
"loadFailed": "Kunne ikke indlæse flåder",
|
||||
"deleteFleetTitle": "Slet flåde",
|
||||
"deleteFleetDescription": "Er du sikker på, at du vil slette \"{{name}}\"? Dette sletter ikke selve medlemsværterne.",
|
||||
"searchFleets": "Søg efter flåder...",
|
||||
"noFleets": "Ingen flåder endnu. Opret en for at komme i gang.",
|
||||
"memberCount": "{{count}} vært",
|
||||
"memberCount_other": "{{count}} værter",
|
||||
"manageMembers": "Administrer medlemmer",
|
||||
"manageMembersTitle": "Administrer flådemedlemmer",
|
||||
"manageMembersDescription": "Slå værter til eller fra af denne flådes statiske medlemskab. Værter, der matches af tagregler, er altid inkluderet.",
|
||||
"searchHosts": "Søg efter værter...",
|
||||
"noHostsFound": "Ingen værter fundet",
|
||||
"noMembers": "Denne flåde har endnu ingen medlemmer. Tilføj værter eller angiv en tagregel.",
|
||||
"membersLoadFailed": "Kunne ikke indlæse flådemedlemmer",
|
||||
"memberUpdateFailed": "Kunne ikke opdatere flådemedlemskab",
|
||||
"tabRun": "Kør kommando",
|
||||
"tabTransfer": "Overfør filer",
|
||||
"tabInventory": "Inventar",
|
||||
"openInventoryTab": "Åbn i fuld fane",
|
||||
"tabPackages": "Pakker",
|
||||
"runOnFleet": "Kør på flåden",
|
||||
"commandPlaceholder": "oppetid && df -h",
|
||||
"commandRequired": "En kommando er påkrævet",
|
||||
"commandInputsLabel": "Kommandoindgange",
|
||||
"commandSucceeded": "Kommandoen lykkedes på alle værter",
|
||||
"commandPartialFailure": "{{failed}} af {{total}} værter mislykkedes",
|
||||
"commandFailed": "Kunne ikke køre kommandoen",
|
||||
"resultSuccess": "Succes",
|
||||
"resultFailed": "Mislykkedes",
|
||||
"push": "Skubbe",
|
||||
"pull": "Træk",
|
||||
"remotePathLabel": "Fjernsti",
|
||||
"remotePathRequired": "En fjernsti er påkrævet",
|
||||
"fileLabel": "Fil",
|
||||
"fileRequired": "En fil er påkrævet",
|
||||
"pushHint": "Den valgte fil skrives til præcis denne sti på alle værter i flåden.",
|
||||
"pullHint": "Filen på denne sti læses fra hver vært og downloades som en enkelt zip-fil, én mappe pr. vært.",
|
||||
"transferComplete": "Overførsel fuldført",
|
||||
"transferFailed": "Overførslen mislykkedes",
|
||||
"refreshInventory": "Opdater lagerbeholdning",
|
||||
"inventoryLoadFailed": "Kunne ikke indlæse lagerbeholdningen",
|
||||
"inventoryRefreshed": "Opdateret lagerbeholdning",
|
||||
"inventoryRefreshFailed": "Kunne ikke opdatere lagerbeholdningen",
|
||||
"inventoryNoData": "Ingen lagerdata endnu - opdater for at indsamle dem.",
|
||||
"inventoryHost": "Vært",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} indsamlet",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernen",
|
||||
"inventoryArch": "Buen",
|
||||
"inventoryUptime": "Oppetid",
|
||||
"inventoryPackageManager": "Pakkehåndtering",
|
||||
"inventoryCollectedAt": "Indsamlet",
|
||||
"packageActionLabel": "Handling",
|
||||
"packageInstall": "Installere",
|
||||
"packageRemove": "Fjerne",
|
||||
"packageUpgradeAll": "Opgrader alle pakker",
|
||||
"packageNameLabel": "Pakkenavn",
|
||||
"packageNameRequired": "Et pakkenavn er påkrævet",
|
||||
"packageHint": "Kræver adgang på administratorniveau pr. vært og hæver ved hjælp af værtens gemte sudo-adgangskode.",
|
||||
"shareFleet": "Del flåde",
|
||||
"shareFleetTitle": "Del \"{{name}}\"",
|
||||
"shareFleetDescription": "Deler denne flådes nuværende medlemsværter med de valgte brugere eller roller. Værter, der tilføjes til flåden senere, deles ikke automatisk - del igen for at inkludere dem.",
|
||||
"shareSelectionRequired": "Vælg mindst én bruger eller rolle",
|
||||
"fleetSharedSuccessfully": "Delt {{shared}} af {{total}} vært(er) i denne flåde",
|
||||
"failedToShareFleet": "Kunne ikke dele flåden",
|
||||
"noMembersToShare": "Denne flåde har endnu ingen medlemsværter at dele."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flåder"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Se dokumenter",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Timer indtil adgangen udløber",
|
||||
"expiry": {
|
||||
"never": "Aldrig",
|
||||
"oneHour": "1 time",
|
||||
"oneDay": "24 timer",
|
||||
"sevenDays": "7 dage",
|
||||
"thirtyDays": "30 dage",
|
||||
"custom": "Skik"
|
||||
},
|
||||
"expiryLabel": "Udløb af adgang",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Forbinde",
|
||||
"description": "Kun åbne sessioner: terminal, fjernskrivebord, filhåndtering, tunneler og Docker. Ingen adgang til værtkonfigurationen."
|
||||
},
|
||||
"view": {
|
||||
"label": "Udsigt",
|
||||
"description": "Opret forbindelse, plus se værtkonfigurationen. Hemmeligheder vises aldrig."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Redigere",
|
||||
"description": "Se og rediger ikke-godkendelseshostindstillinger. Ejerens SSH-godkendelse forbliver privat og kun for ejeren."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Styre",
|
||||
"description": "Rediger, plus del værten med andre, skift tilladelsesniveauer og tilbagekald adgang."
|
||||
}
|
||||
},
|
||||
"noMatches": "Ingen matches fundet",
|
||||
"permissionLevelLabel": "Tilladelsesniveau",
|
||||
"rolesTab": "Roller",
|
||||
"searchPlaceholder": "Søg efter brugere eller roller...",
|
||||
"shareButton": "Dele",
|
||||
"shareWithCount": "Del ({{count}})",
|
||||
"usersTab": "Brugere"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Flotten ansehen",
|
||||
"description": "Flotten, ihre Mitglieder und ihren Bestand ansehen."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Flottenmanagement",
|
||||
"description": "Flotten erstellen, bearbeiten und löschen, Mitgliedschaften ändern und Flotten mit anderen Benutzern oder Rollen teilen."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Flottenaktionen durchführen",
|
||||
"description": "Führe Befehle und Paketaktionen über eine ganze Flotte hinweg aus und übertrage Dateien zu und von den Mitgliedshosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flotten",
|
||||
"createFleet": "Flotte erstellen",
|
||||
"createFleetTitle": "Flotte erstellen",
|
||||
"editFleetTitle": "Flotte bearbeiten",
|
||||
"createFleetDescription": "Eine Flotte ist eine benannte Gruppe von Hosts, gegen die Sie als Einheit Aktionen ausführen können.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "z.B. Webserver",
|
||||
"nameRequired": "Flottenname erforderlich",
|
||||
"descriptionLabel": "Beschreibung",
|
||||
"descriptionPlaceholder": "Optionale Beschreibung",
|
||||
"colorLabel": "Farbe",
|
||||
"tagRulesLabel": "Tag-Regeln",
|
||||
"tagRulesPlaceholder": "Produktweb, Rand",
|
||||
"tagRulesHint": "Hosts, die über eines dieser Tags verfügen, werden automatisch mit einbezogen, ebenso wie manuell hinzugefügte Hosts.",
|
||||
"fleetCreated": "Flotte erstellt",
|
||||
"fleetUpdated": "Flotte aktualisiert",
|
||||
"fleetDeleted": "Flotte gelöscht",
|
||||
"saveFailed": "Es gelang nicht, die Flotte zu retten.",
|
||||
"deleteFailed": "Flotte konnte nicht gelöscht werden",
|
||||
"loadFailed": "Flotten konnten nicht geladen werden",
|
||||
"deleteFleetTitle": "Flotte löschen",
|
||||
"deleteFleetDescription": "Möchten Sie wirklich \"{{name}}\" löschen? Dadurch werden die Mitgliedshosts selbst nicht gelöscht.",
|
||||
"searchFleets": "Flotten durchsuchen...",
|
||||
"noFleets": "Noch keine Flotten vorhanden. Erstellen Sie eine, um loszulegen.",
|
||||
"memberCount": "{{count}} Host",
|
||||
"memberCount_other": "{{count}} Hosts",
|
||||
"manageMembers": "Mitglieder verwalten",
|
||||
"manageMembersTitle": "Flottenmitglieder verwalten",
|
||||
"manageMembersDescription": "Hosts können der statischen Mitgliedschaft dieser Flotte hinzugefügt oder daraus entfernt werden. Hosts, die den Tag-Regeln entsprechen, sind immer enthalten.",
|
||||
"searchHosts": "Hosts suchen...",
|
||||
"noHostsFound": "Keine Hosts gefunden",
|
||||
"noMembers": "Diese Flotte hat noch keine Mitglieder. Füge Hosts hinzu oder lege eine Tag-Regel fest.",
|
||||
"membersLoadFailed": "Flottenmitglieder konnten nicht geladen werden",
|
||||
"memberUpdateFailed": "Aktualisierung der Flottenmitgliedschaft fehlgeschlagen",
|
||||
"tabRun": "Befehl ausführen",
|
||||
"tabTransfer": "Dateien übertragen",
|
||||
"tabInventory": "Inventar",
|
||||
"openInventoryTab": "Im vollständigen Tab öffnen",
|
||||
"tabPackages": "Pakete",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "Ein Befehl ist erforderlich",
|
||||
"commandInputsLabel": "Befehlseingaben",
|
||||
"commandSucceeded": "Der Befehl wurde auf allen Hosts erfolgreich ausgeführt.",
|
||||
"commandPartialFailure": "{{failed}} von {{total}} Hosts fehlgeschlagen",
|
||||
"commandFailed": "Befehl konnte nicht ausgeführt werden",
|
||||
"resultSuccess": "Erfolgreich",
|
||||
"resultFailed": "Fehlgeschlagen",
|
||||
"push": "Drücken",
|
||||
"pull": "Ziehen",
|
||||
"remotePathLabel": "Remote-Pfad",
|
||||
"remotePathRequired": "Ein Remote-Pfad ist erforderlich",
|
||||
"fileLabel": "Datei",
|
||||
"fileRequired": "Eine Datei wird benötigt",
|
||||
"pushHint": "Die ausgewählte Datei wird auf jedem Host der Flotte unter genau diesem Pfad gespeichert.",
|
||||
"pullHint": "Die Datei unter diesem Pfad wird von jedem Host gelesen und als einzelne ZIP-Datei heruntergeladen, ein Ordner pro Host.",
|
||||
"transferComplete": "Übertragung abgeschlossen",
|
||||
"transferFailed": "Übertragung fehlgeschlagen",
|
||||
"refreshInventory": "Inventar aktualisieren",
|
||||
"inventoryLoadFailed": "Fehler beim Laden des Inventars",
|
||||
"inventoryRefreshed": "Bestand aktualisiert",
|
||||
"inventoryRefreshFailed": "Aktualisierung des Inventars fehlgeschlagen",
|
||||
"inventoryNoData": "Noch keine Bestandsdaten vorhanden – aktualisieren Sie die Seite, um sie abzurufen.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} gesammelt",
|
||||
"inventoryOs": "Betriebssystem",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Bogen",
|
||||
"inventoryUptime": "Betriebszeit",
|
||||
"inventoryPackageManager": "Paketmanager",
|
||||
"inventoryCollectedAt": "Gesammelt",
|
||||
"packageActionLabel": "Aktion",
|
||||
"packageInstall": "Installieren",
|
||||
"packageRemove": "Entfernen",
|
||||
"packageUpgradeAll": "Alle Pakete aktualisieren",
|
||||
"packageNameLabel": "Paketname",
|
||||
"packageNameRequired": "Ein Paketname ist erforderlich.",
|
||||
"packageHint": "Erfordert Administratorrechte pro Host und verwendet das gespeicherte sudo-Passwort des Hosts für die Rechteerweiterung.",
|
||||
"shareFleet": "Flotte teilen",
|
||||
"shareFleetTitle": "Teilen \"{{name}}\"",
|
||||
"shareFleetDescription": "Teilt die aktuellen Hosts dieser Flotte mit den ausgewählten Benutzern oder Rollen. Hosts, die später zur Flotte hinzugefügt werden, werden nicht automatisch geteilt – teilen Sie die Flotte erneut, um sie einzubeziehen.",
|
||||
"shareSelectionRequired": "Wählen Sie mindestens einen Benutzer oder eine Rolle aus.",
|
||||
"fleetSharedSuccessfully": "Gemeinsam genutzte {{shared}} von {{total}} Hosts in dieser Flotte",
|
||||
"failedToShareFleet": "Flottenteilung gescheitert",
|
||||
"noMembersToShare": "Diese Flotte hat noch keine Mitgliedshosts, die geteilt werden können."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flotten"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Dokumentation anzeigen",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Stunden bis zum Zugriffsablauf",
|
||||
"expiry": {
|
||||
"never": "Nie",
|
||||
"oneHour": "1 Stunde",
|
||||
"oneDay": "24 Stunden",
|
||||
"sevenDays": "7 Tage",
|
||||
"thirtyDays": "30 Tage",
|
||||
"custom": "Benutzerdefiniert"
|
||||
},
|
||||
"expiryLabel": "Zugriffsablauf",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Verbinden",
|
||||
"description": "Nur Sitzungen öffnen: Terminal, Remotedesktop, Dateimanager, Tunnel und Docker. Kein Zugriff auf die Hostkonfiguration."
|
||||
},
|
||||
"view": {
|
||||
"label": "Anzeigen",
|
||||
"description": "Verbinden sowie Hostkonfiguration anzeigen. Geheimnisse werden nie angezeigt."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Bearbeiten",
|
||||
"description": "Sie können die Einstellungen für Hosts ohne Authentifizierung anzeigen und bearbeiten. Die SSH-Authentifizierung des Besitzers bleibt privat und ist nur für den Besitzer zugänglich."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Verwalten",
|
||||
"description": "Bearbeiten sowie Host für andere freigeben, Berechtigungsstufen ändern und Zugriff entziehen."
|
||||
}
|
||||
},
|
||||
"noMatches": "Keine Treffer gefunden",
|
||||
"permissionLevelLabel": "Berechtigungsstufe",
|
||||
"rolesTab": "Rollen",
|
||||
"searchPlaceholder": "Benutzer oder Rollen suchen...",
|
||||
"shareButton": "Teilen",
|
||||
"shareWithCount": "Teilen ({{count}})",
|
||||
"usersTab": "Benutzer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Δείτε στόλους",
|
||||
"description": "Δείτε τους στόλους, τα μέλη τους και το απόθεμά τους."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Διαχείριση στόλων",
|
||||
"description": "Δημιουργήστε, επεξεργαστείτε και διαγράψτε στόλους, αλλάξτε μέλη και μοιραστείτε στόλους με άλλους χρήστες ή ρόλους."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Εκτέλεση ενεργειών στόλου",
|
||||
"description": "Εκτελέστε εντολές και κάντε πακέτα ενεργειών σε ένα στόλο και μεταφέρετε αρχεία από και προς τους κεντρικούς υπολογιστές-μέλη του."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Στόλοι",
|
||||
"createFleet": "Δημιουργία στόλου",
|
||||
"createFleetTitle": "Δημιουργία στόλου",
|
||||
"editFleetTitle": "Επεξεργασία στόλου",
|
||||
"createFleetDescription": "Ένας στόλος είναι μια ονομαστική ομάδα οικοδεσποτών εναντίον των οποίων μπορείτε να εκτελέσετε ενέργειες ως μονάδα.",
|
||||
"nameLabel": "Ονομα",
|
||||
"namePlaceholder": "π.χ., Διακομιστές Ιστού",
|
||||
"nameRequired": "Απαιτείται το όνομα του στόλου",
|
||||
"descriptionLabel": "Περιγραφή",
|
||||
"descriptionPlaceholder": "Προαιρετική περιγραφή",
|
||||
"colorLabel": "Χρώμα",
|
||||
"tagRulesLabel": "Κανόνες ετικετών",
|
||||
"tagRulesPlaceholder": "product-web, edge",
|
||||
"tagRulesHint": "Οι κεντρικοί υπολογιστές με οποιαδήποτε από αυτές τις ετικέτες περιλαμβάνονται αυτόματα, εκτός από τους κεντρικούς υπολογιστές που προστίθενται χειροκίνητα.",
|
||||
"fleetCreated": "Στόλος δημιουργήθηκε",
|
||||
"fleetUpdated": "Ενημερώθηκε ο στόλος",
|
||||
"fleetDeleted": "Ο στόλος διαγράφηκε",
|
||||
"saveFailed": "Αποτυχία αποθήκευσης στόλου",
|
||||
"deleteFailed": "Η διαγραφή του στόλου απέτυχε",
|
||||
"loadFailed": "Αποτυχία φόρτωσης στόλων",
|
||||
"deleteFleetTitle": "Διαγραφή στόλου",
|
||||
"deleteFleetDescription": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το \"{{name}}\"; Αυτό δεν διαγράφει τους ίδιους τους κεντρικούς υπολογιστές-μέλη.",
|
||||
"searchFleets": "Αναζήτηση στόλων...",
|
||||
"noFleets": "Δεν υπάρχουν ακόμη στόλοι. Δημιουργήστε έναν για να ξεκινήσετε.",
|
||||
"memberCount": "{{count}} κεντρικός υπολογιστής",
|
||||
"memberCount_other": "{{count}} κεντρικοί υπολογιστές",
|
||||
"manageMembers": "Διαχείριση μελών",
|
||||
"manageMembersTitle": "Διαχείριση Μελών Στόλου",
|
||||
"manageMembersDescription": "Εναλλαγή ή διακοπή της στατικής ιδιότητας μέλους αυτού του στόλου. Οι κεντρικοί υπολογιστές που αντιστοιχούν σε κανόνες ετικέτας περιλαμβάνονται πάντα.",
|
||||
"searchHosts": "Αναζήτηση κεντρικών υπολογιστών...",
|
||||
"noHostsFound": "Δεν βρέθηκαν κεντρικοί υπολογιστές",
|
||||
"noMembers": "Αυτός ο στόλος δεν έχει ακόμη μέλη. Προσθέστε κεντρικούς υπολογιστές ή ορίστε έναν κανόνα ετικέτας.",
|
||||
"membersLoadFailed": "Αποτυχία φόρτωσης μελών στόλου",
|
||||
"memberUpdateFailed": "Η ενημέρωση της ιδιότητας μέλους του στόλου απέτυχε",
|
||||
"tabRun": "Εκτέλεση εντολής",
|
||||
"tabTransfer": "Μεταφορά αρχείων",
|
||||
"tabInventory": "Καταγραφή εμπορευμάτων",
|
||||
"openInventoryTab": "Άνοιγμα σε πλήρη καρτέλα",
|
||||
"tabPackages": "Πακέτα",
|
||||
"runOnFleet": "Εκτέλεση σε στόλο",
|
||||
"commandPlaceholder": "χρόνος λειτουργίας && df -h",
|
||||
"commandRequired": "Απαιτείται εντολή",
|
||||
"commandInputsLabel": "Είσοδοι εντολών",
|
||||
"commandSucceeded": "Η εντολή ήταν επιτυχής σε όλους τους κεντρικούς υπολογιστές",
|
||||
"commandPartialFailure": "{{failed}} από {{total}} κεντρικούς υπολογιστές απέτυχαν",
|
||||
"commandFailed": "Αποτυχία εκτέλεσης εντολής",
|
||||
"resultSuccess": "Επιτυχία",
|
||||
"resultFailed": "Αποτυχημένος",
|
||||
"push": "Σπρώξτε",
|
||||
"pull": "Τραβήξτε",
|
||||
"remotePathLabel": "Απομακρυσμένη διαδρομή",
|
||||
"remotePathRequired": "Απαιτείται μια απομακρυσμένη διαδρομή",
|
||||
"fileLabel": "Αρχείο",
|
||||
"fileRequired": "Απαιτείται ένα αρχείο",
|
||||
"pushHint": "Το επιλεγμένο αρχείο εγγράφεται σε αυτήν ακριβώς τη διαδρομή σε κάθε κεντρικό υπολογιστή του στόλου.",
|
||||
"pullHint": "Το αρχείο σε αυτήν τη διαδρομή διαβάζεται από κάθε κεντρικό υπολογιστή και λαμβάνεται ως ενιαίο αρχείο zip, ένας φάκελος ανά κεντρικό υπολογιστή.",
|
||||
"transferComplete": "Η μεταφορά ολοκληρώθηκε",
|
||||
"transferFailed": "Η μεταφορά απέτυχε",
|
||||
"refreshInventory": "Ανανέωση Αποθέματος",
|
||||
"inventoryLoadFailed": "Αποτυχία φόρτωσης αποθέματος",
|
||||
"inventoryRefreshed": "Το απόθεμα ανανεώθηκε",
|
||||
"inventoryRefreshFailed": "Η ανανέωση του αποθέματος απέτυχε",
|
||||
"inventoryNoData": "Δεν υπάρχουν ακόμη δεδομένα αποθέματος - κάντε ανανέωση για να τα συλλέξετε.",
|
||||
"inventoryHost": "Πλήθος",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} συλλέχθηκαν",
|
||||
"inventoryOs": "Λειτουργικό σύστημα",
|
||||
"inventoryKernel": "Πυρήνας",
|
||||
"inventoryArch": "Αψίδα",
|
||||
"inventoryUptime": "Χρόνος λειτουργίας",
|
||||
"inventoryPackageManager": "Διαχειριστής πακέτων",
|
||||
"inventoryCollectedAt": "Συγκεντρωμένος",
|
||||
"packageActionLabel": "Δράση",
|
||||
"packageInstall": "Εγκαθιστώ",
|
||||
"packageRemove": "Αφαιρώ",
|
||||
"packageUpgradeAll": "Αναβάθμιση όλων των πακέτων",
|
||||
"packageNameLabel": "Όνομα πακέτου",
|
||||
"packageNameRequired": "Απαιτείται όνομα πακέτου",
|
||||
"packageHint": "Απαιτείται πρόσβαση σε επίπεδο διαχείρισης ανά κεντρικό υπολογιστή και αναβαθμίζεται χρησιμοποιώντας τον αποθηκευμένο κωδικό πρόσβασης sudo του κεντρικού υπολογιστή.",
|
||||
"shareFleet": "Κοινόχρηστος στόλος",
|
||||
"shareFleetTitle": "Κοινή χρήση \"{{name}}\"",
|
||||
"shareFleetDescription": "Κοινοποιεί τους τρέχοντες κεντρικούς υπολογιστές-μέλη αυτού του στόλου με τους επιλεγμένους χρήστες ή ρόλους. Οι κεντρικοί υπολογιστές που προστίθενται στον στόλο αργότερα δεν κοινοποιούνται αυτόματα - κοινοποιήστε τους ξανά για να τους συμπεριλάβετε.",
|
||||
"shareSelectionRequired": "Επιλέξτε τουλάχιστον έναν χρήστη ή ρόλο",
|
||||
"fleetSharedSuccessfully": "Κοινόχρηστο {{shared}} από {{total}} κεντρικούς υπολογιστές σε αυτόν τον στόλο",
|
||||
"failedToShareFleet": "Αποτυχία κοινής χρήσης στόλου",
|
||||
"noMembersToShare": "Αυτός ο στόλος δεν έχει ακόμη μέλη-οικοδεσπότες για κοινή χρήση."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Στόλοι"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Προβολή εγγράφων",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Ώρες μέχρι τη λήξη της πρόσβασης",
|
||||
"expiry": {
|
||||
"never": "Ποτέ",
|
||||
"oneHour": "1 ώρα",
|
||||
"oneDay": "24 ώρες",
|
||||
"sevenDays": "7 ημέρες",
|
||||
"thirtyDays": "30 ημέρες",
|
||||
"custom": "Εθιμο"
|
||||
},
|
||||
"expiryLabel": "Λήξη πρόσβασης",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Συνδέω",
|
||||
"description": "Μόνο ανοιχτές συνεδρίες: τερματικό, απομακρυσμένη επιφάνεια εργασίας, διαχειριστής αρχείων, σήραγγες και Docker. Δεν υπάρχει πρόσβαση στη διαμόρφωση του κεντρικού υπολογιστή."
|
||||
},
|
||||
"view": {
|
||||
"label": "Θέα",
|
||||
"description": "Συνδεθείτε και δείτε τη διαμόρφωση του κεντρικού υπολογιστή. Τα μυστικά δεν εμφανίζονται ποτέ."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Εκδίδω",
|
||||
"description": "Προβολή και τροποποίηση ρυθμίσεων κεντρικού υπολογιστή που δεν απαιτούν έλεγχο ταυτότητας. Ο έλεγχος ταυτότητας SSH του κατόχου παραμένει ιδιωτικός και μόνο για τον κάτοχο."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Διαχειρίζομαι",
|
||||
"description": "Επεξεργαστείτε και μοιραστείτε τον κεντρικό υπολογιστή με άλλους, αλλάξτε τα επίπεδα δικαιωμάτων και ανακαλέστε την πρόσβαση."
|
||||
}
|
||||
},
|
||||
"noMatches": "Δεν βρέθηκαν αντιστοιχίσεις",
|
||||
"permissionLevelLabel": "Επίπεδο δικαιωμάτων",
|
||||
"rolesTab": "Ρόλοι",
|
||||
"searchPlaceholder": "Αναζήτηση χρηστών ή ρόλων...",
|
||||
"shareButton": "Μερίδιο",
|
||||
"shareWithCount": "Κοινή χρήση ({{count}})",
|
||||
"usersTab": "Χρήστες"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Ver flotas",
|
||||
"description": "Consulta las flotas, sus miembros y su inventario."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Gestionar flotas",
|
||||
"description": "Crea, edita y elimina flotas, cambia la membresía y comparte flotas con otros usuarios o roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Ejecutar acciones de flota",
|
||||
"description": "Ejecuta comandos y acciones de paquetes en toda una flota, y transfiere archivos hacia y desde sus hosts miembros."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flotas",
|
||||
"createFleet": "Crear flota",
|
||||
"createFleetTitle": "Crear flota",
|
||||
"editFleetTitle": "Editar flota",
|
||||
"createFleetDescription": "Una flota es un grupo con nombre de anfitriones contra los que puedes ejecutar acciones como una unidad.",
|
||||
"nameLabel": "Nombre",
|
||||
"namePlaceholder": "p. ej., servidores web",
|
||||
"nameRequired": "Se requiere el nombre de la flota.",
|
||||
"descriptionLabel": "Descripción",
|
||||
"descriptionPlaceholder": "Descripción opcional",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Reglas de etiquetas",
|
||||
"tagRulesPlaceholder": "web de producción, borde",
|
||||
"tagRulesHint": "Los hosts que tengan cualquiera de estas etiquetas se incluyen automáticamente, además de los hosts añadidos manualmente.",
|
||||
"fleetCreated": "Flota creada",
|
||||
"fleetUpdated": "Flota actualizada",
|
||||
"fleetDeleted": "Flota eliminada",
|
||||
"saveFailed": "No se logró salvar la flota.",
|
||||
"deleteFailed": "No se pudo eliminar la flota",
|
||||
"loadFailed": "No se pudieron cargar las flotas.",
|
||||
"deleteFleetTitle": "Eliminar flota",
|
||||
"deleteFleetDescription": "¿Estás seguro de que quieres eliminar \"{{name}}\"? Esto no elimina los hosts miembros en sí.",
|
||||
"searchFleets": "Buscar flotas...",
|
||||
"noFleets": "Aún no hay flotas. Crea una para empezar.",
|
||||
"memberCount": "{{count}} anfitrión",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Gestionar miembros",
|
||||
"manageMembersTitle": "Gestionar los miembros de la flota",
|
||||
"manageMembersDescription": "Activa o desactiva la pertenencia estática de esta flota a hosts. Los hosts que coincidan con las reglas de etiquetas siempre se incluirán.",
|
||||
"searchHosts": "Buscar hosts...",
|
||||
"noHostsFound": "No se encontraron equipos",
|
||||
"noMembers": "Esta flota aún no tiene miembros. Añade anfitriones o establece una regla de etiquetas.",
|
||||
"membersLoadFailed": "No se pudo cargar a los miembros de la flota.",
|
||||
"memberUpdateFailed": "No se pudo actualizar la membresía de la flota.",
|
||||
"tabRun": "Ejecutar comando",
|
||||
"tabTransfer": "Transferir archivos",
|
||||
"tabInventory": "Inventario",
|
||||
"openInventoryTab": "Abrir en pestaña completa",
|
||||
"tabPackages": "Paquetes",
|
||||
"runOnFleet": "Operar en Fleet",
|
||||
"commandPlaceholder": "tiempo de actividad && df -h",
|
||||
"commandRequired": "Se requiere un comando",
|
||||
"commandInputsLabel": "Entradas de comandos",
|
||||
"commandSucceeded": "El comando se ejecutó correctamente en todos los hosts.",
|
||||
"commandPartialFailure": "{{failed}} de {{total}} hosts fallaron",
|
||||
"commandFailed": "Error al ejecutar el comando",
|
||||
"resultSuccess": "Éxito",
|
||||
"resultFailed": "Fallido",
|
||||
"push": "Empujar",
|
||||
"pull": "Jalar",
|
||||
"remotePathLabel": "Ruta remota",
|
||||
"remotePathRequired": "Se requiere una ruta remota",
|
||||
"fileLabel": "Archivo",
|
||||
"fileRequired": "Se requiere un archivo",
|
||||
"pushHint": "El archivo seleccionado se escribe en esta ruta exacta en cada host de la flota.",
|
||||
"pullHint": "El archivo ubicado en esta ruta se lee desde cada host y se descarga como un único archivo zip, una carpeta por host.",
|
||||
"transferComplete": "Transferencia completada",
|
||||
"transferFailed": "Transferencia fallida",
|
||||
"refreshInventory": "Actualizar inventario",
|
||||
"inventoryLoadFailed": "No se pudo cargar el inventario.",
|
||||
"inventoryRefreshed": "Inventario actualizado",
|
||||
"inventoryRefreshFailed": "No se pudo actualizar el inventario.",
|
||||
"inventoryNoData": "Aún no hay datos de inventario; actualice la página para recopilarlos.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} recopilado",
|
||||
"inventoryOs": "SO",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arco",
|
||||
"inventoryUptime": "Tiempo de actividad",
|
||||
"inventoryPackageManager": "gestor de paquetes",
|
||||
"inventoryCollectedAt": "Coleccionado",
|
||||
"packageActionLabel": "Acción",
|
||||
"packageInstall": "Instalar",
|
||||
"packageRemove": "Eliminar",
|
||||
"packageUpgradeAll": "Actualizar todos los paquetes",
|
||||
"packageNameLabel": "Nombre del paquete",
|
||||
"packageNameRequired": "Se requiere un nombre de paquete.",
|
||||
"packageHint": "Requiere acceso de nivel de administrador por host y eleva los privilegios utilizando la contraseña sudo almacenada en el host.",
|
||||
"shareFleet": "Compartir flota",
|
||||
"shareFleetTitle": "Compartir \"{{name}}\"",
|
||||
"shareFleetDescription": "Comparte los hosts miembros actuales de esta flota con los usuarios o roles seleccionados. Los hosts que se agreguen a la flota posteriormente no se comparten automáticamente; vuelva a compartir para incluirlos.",
|
||||
"shareSelectionRequired": "Seleccione al menos un usuario o rol.",
|
||||
"fleetSharedSuccessfully": "Host(s) compartido(s) en esta flota {{shared}} de {{total}} host(s) en esta flota",
|
||||
"failedToShareFleet": "No se compartió la flota",
|
||||
"noMembersToShare": "Esta flota aún no tiene anfitriones miembros para compartir."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flotas"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Ver documentación",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Horas hasta que caduque el acceso",
|
||||
"expiry": {
|
||||
"never": "Nunca",
|
||||
"oneHour": "1 hora",
|
||||
"oneDay": "24 horas",
|
||||
"sevenDays": "7 días",
|
||||
"thirtyDays": "30 días",
|
||||
"custom": "Personalizado"
|
||||
},
|
||||
"expiryLabel": "Caducidad del acceso",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Conectar",
|
||||
"description": "Abrir solo sesiones: terminal, escritorio remoto, gestor de archivos, túneles y Docker. Sin acceso a la configuración del host."
|
||||
},
|
||||
"view": {
|
||||
"label": "Ver",
|
||||
"description": "Conectar, además de ver la configuración del host. Los secretos nunca se muestran."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Editar",
|
||||
"description": "Visualice y modifique la configuración del host que no requiere autenticación. La autenticación SSH del propietario permanece privada y solo accesible para él."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Gestionar",
|
||||
"description": "Editar, además de compartir el host con otros, cambiar niveles de permiso y revocar el acceso."
|
||||
}
|
||||
},
|
||||
"noMatches": "No se han encontrado coincidencias",
|
||||
"permissionLevelLabel": "Nivel de permiso",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Buscar usuarios o roles...",
|
||||
"shareButton": "Compartir",
|
||||
"shareWithCount": "Compartir ({{count}})",
|
||||
"usersTab": "Usuarios"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Näytä laivastot",
|
||||
"description": "Katso laivastot, niiden jäsenet ja varastot."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Hallitse ajoneuvokalustoja",
|
||||
"description": "Luo, muokkaa ja poista ajoneuvokantoja, muuta jäsenyyksiä ja jaa ajoneuvokantoja muiden käyttäjien tai roolien kanssa."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Suorita laivaston toiminnot",
|
||||
"description": "Suorita komentoja ja paketoi toimintoja koko laivastossa ja siirrä tiedostoja sen jäsenpalvelimiin ja niistä pois."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Laivastot",
|
||||
"createFleet": "Luo laivasto",
|
||||
"createFleetTitle": "Luo laivasto",
|
||||
"editFleetTitle": "Muokkaa kalustoa",
|
||||
"createFleetDescription": "Laivasto on nimetty joukko isäntäjoukkoja, joita vastaan voit suorittaa toimintoja yksikkönä.",
|
||||
"nameLabel": "Nimi",
|
||||
"namePlaceholder": "esim. web-palvelimet",
|
||||
"nameRequired": "Laivaston nimi on pakollinen",
|
||||
"descriptionLabel": "Kuvaus",
|
||||
"descriptionPlaceholder": "Valinnainen kuvaus",
|
||||
"colorLabel": "Väri",
|
||||
"tagRulesLabel": "Tagisäännöt",
|
||||
"tagRulesPlaceholder": "prod-web, reuna",
|
||||
"tagRulesHint": "Näillä tunnisteilla varustetut isännät sisällytetään automaattisesti manuaalisesti lisättyjen isäntien lisäksi.",
|
||||
"fleetCreated": "Laivasto luotu",
|
||||
"fleetUpdated": "Kalustoa päivitetty",
|
||||
"fleetDeleted": "Kalusto poistettu",
|
||||
"saveFailed": "Laivaston tallentaminen epäonnistui",
|
||||
"deleteFailed": "Kaluston poistaminen epäonnistui",
|
||||
"loadFailed": "Kalustojen lataus epäonnistui",
|
||||
"deleteFleetTitle": "Poista kalusto",
|
||||
"deleteFleetDescription": "Oletko varma, että haluat poistaa \"{{name}}\"? Tämä ei poista itse jäsenpalvelimia.",
|
||||
"searchFleets": "Hae laivastoja...",
|
||||
"noFleets": "Ei vielä laivastoja. Luo yksi aloittaaksesi.",
|
||||
"memberCount": "{{count}} isäntä",
|
||||
"memberCount_other": "{{count}} isännät",
|
||||
"manageMembers": "Hallinnoi jäseniä",
|
||||
"manageMembersTitle": "Hallinnoi laivaston jäseniä",
|
||||
"manageMembersDescription": "Lisää isännät tämän laivaston staattiseen jäsenyyteen tai poista ne siitä. Tunnistesääntöjen mukaiset isännät sisällytetään aina.",
|
||||
"searchHosts": "Hae isäntiä...",
|
||||
"noHostsFound": "Ei isäntiä",
|
||||
"noMembers": "Tässä laivastossa ei ole vielä jäseniä. Lisää isäntiä tai aseta tunnistesääntö.",
|
||||
"membersLoadFailed": "Laivaston jäsenten lataaminen epäonnistui",
|
||||
"memberUpdateFailed": "Laivaston jäsenyyden päivittäminen epäonnistui",
|
||||
"tabRun": "Suorita komento",
|
||||
"tabTransfer": "Tiedostojen siirto",
|
||||
"tabInventory": "Varasto",
|
||||
"openInventoryTab": "Avaa koko välilehdessä",
|
||||
"tabPackages": "Paketit",
|
||||
"runOnFleet": "Suorita laivastolla",
|
||||
"commandPlaceholder": "käyttöaika && df -h",
|
||||
"commandRequired": "Komento vaaditaan",
|
||||
"commandInputsLabel": "Komentotulot",
|
||||
"commandSucceeded": "Komento onnistui kaikissa isännissä",
|
||||
"commandPartialFailure": "{{failed}} / {{total}} isäntäkoneesta epäonnistui",
|
||||
"commandFailed": "Komennon suorittaminen epäonnistui",
|
||||
"resultSuccess": "Menestys",
|
||||
"resultFailed": "Epäonnistui",
|
||||
"push": "Työnnä",
|
||||
"pull": "Vedä",
|
||||
"remotePathLabel": "Etäpolku",
|
||||
"remotePathRequired": "Etäpolku vaaditaan",
|
||||
"fileLabel": "Tiedosto",
|
||||
"fileRequired": "Tiedosto vaaditaan",
|
||||
"pushHint": "Valittu tiedosto kirjoitetaan täsmälleen tälle polulle jokaisella laivaston isännällä.",
|
||||
"pullHint": "Tämän polun tiedosto luetaan jokaiselta isännältä ja ladataan yhtenä zip-tiedostona, yksi kansio isäntää kohden.",
|
||||
"transferComplete": "Siirto valmis",
|
||||
"transferFailed": "Siirto epäonnistui",
|
||||
"refreshInventory": "Päivitä varasto",
|
||||
"inventoryLoadFailed": "Varaston lataaminen epäonnistui",
|
||||
"inventoryRefreshed": "Varasto päivitetty",
|
||||
"inventoryRefreshFailed": "Varaston päivittäminen epäonnistui",
|
||||
"inventoryNoData": "Ei vielä varastotietoja – päivitä kerätäksesi ne.",
|
||||
"inventoryHost": "Isäntä",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} kerätty",
|
||||
"inventoryOs": "Käyttöjärjestelmä",
|
||||
"inventoryKernel": "Ydin",
|
||||
"inventoryArch": "Kaari",
|
||||
"inventoryUptime": "Käyttöaika",
|
||||
"inventoryPackageManager": "Pakettien hallinta",
|
||||
"inventoryCollectedAt": "Kerätty",
|
||||
"packageActionLabel": "Toiminta",
|
||||
"packageInstall": "Asentaa",
|
||||
"packageRemove": "Poistaa",
|
||||
"packageUpgradeAll": "Päivitä kaikki paketit",
|
||||
"packageNameLabel": "Paketin nimi",
|
||||
"packageNameRequired": "Paketin nimi on pakollinen",
|
||||
"packageHint": "Vaatii hallintatason käyttöoikeudet isäntäkohtaisesti ja nostaa käyttöoikeuksia isännän tallennetulla sudo-salasanalla.",
|
||||
"shareFleet": "Jaa laivasto",
|
||||
"shareFleetTitle": "Jaa \"{{name}}\"",
|
||||
"shareFleetDescription": "Jakaa tämän laivaston nykyiset jäsenisännät valittujen käyttäjien tai roolien kanssa. Myöhemmin laivastoon lisättyjä isäntiä ei jaeta automaattisesti – jaa uudelleen sisällyttääksesi ne.",
|
||||
"shareSelectionRequired": "Valitse vähintään yksi käyttäjä tai rooli",
|
||||
"fleetSharedSuccessfully": "Jaettu {{shared}} / {{total}} isäntäkoneesta tässä laivastossa",
|
||||
"failedToShareFleet": "Kaluston jakaminen epäonnistui",
|
||||
"noMembersToShare": "Tällä laivueella ei ole vielä jaettavia jäsenisäntiä."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Laivastot"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Näytä dokumentit",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Tuntia käyttöoikeuden vanhenemiseen",
|
||||
"expiry": {
|
||||
"never": "Ei koskaan",
|
||||
"oneHour": "1 tunti",
|
||||
"oneDay": "24 tuntia",
|
||||
"sevenDays": "7 päivää",
|
||||
"thirtyDays": "30 päivää",
|
||||
"custom": "Mukautettu"
|
||||
},
|
||||
"expiryLabel": "Käyttöoikeuden päättyminen",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Yhdistä",
|
||||
"description": "Vain avoimet istunnot: pääte, etätyöpöytä, tiedostonhallinta, tunnelit ja Docker. Ei pääsyä isäntäkokoonpanoon."
|
||||
},
|
||||
"view": {
|
||||
"label": "Näytä",
|
||||
"description": "Yhdistä ja katso isäntäkokoonpano. Salaisuuksia ei koskaan näytetä."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Muokata",
|
||||
"description": "Näytä ja muokkaa ei-todennukseen perustuvia isäntäasetuksia. Omistajan SSH-todennus pysyy yksityisenä ja vain omistajalle tarkoitettuna."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Hallitse",
|
||||
"description": "Muokkaa ja jaa isäntä muiden kanssa, muuta käyttöoikeustasoja ja peruuta käyttöoikeuksia."
|
||||
}
|
||||
},
|
||||
"noMatches": "Ei osumia",
|
||||
"permissionLevelLabel": "Käyttöoikeustaso",
|
||||
"rolesTab": "Roolit",
|
||||
"searchPlaceholder": "Hae käyttäjiä tai rooleja...",
|
||||
"shareButton": "Jakaa",
|
||||
"shareWithCount": "Jaa ({{count}})",
|
||||
"usersTab": "Käyttäjät"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Consulter les flottes",
|
||||
"description": "Consultez les flottes, leurs membres et leur inventaire."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Gérer les flottes",
|
||||
"description": "Créer, modifier et supprimer des flottes, changer les membres et partager des flottes avec d'autres utilisateurs ou rôles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Actions de la flotte",
|
||||
"description": "Exécutez des commandes et des actions de paquetage sur l'ensemble d'une flotte, et transférez des fichiers vers et depuis ses hôtes membres."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flottes",
|
||||
"createFleet": "Créer une flotte",
|
||||
"createFleetTitle": "Créer une flotte",
|
||||
"editFleetTitle": "Modifier la flotte",
|
||||
"createFleetDescription": "Une flotte est un groupe nommé d'hôtes contre lesquels vous pouvez mener des actions en tant qu'unité.",
|
||||
"nameLabel": "Nom",
|
||||
"namePlaceholder": "par exemple, les serveurs Web",
|
||||
"nameRequired": "Le nom de la flotte est requis.",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Description facultative",
|
||||
"colorLabel": "Couleur",
|
||||
"tagRulesLabel": "Règles de balisage",
|
||||
"tagRulesPlaceholder": "prod-web, bord",
|
||||
"tagRulesHint": "Les hôtes possédant l'une de ces étiquettes sont automatiquement inclus, en plus des hôtes ajoutés manuellement.",
|
||||
"fleetCreated": "Flotte créée",
|
||||
"fleetUpdated": "Flotte mise à jour",
|
||||
"fleetDeleted": "Flotte supprimée",
|
||||
"saveFailed": "Échec de la sauvegarde de la flotte",
|
||||
"deleteFailed": "Échec de la suppression de la flotte",
|
||||
"loadFailed": "Échec du chargement des flottes",
|
||||
"deleteFleetTitle": "Supprimer la flotte",
|
||||
"deleteFleetDescription": "Êtes-vous sûr de vouloir supprimer «{{name}}» ? Cela ne supprime pas les hôtes membres eux-mêmes.",
|
||||
"searchFleets": "Rechercher des flottes...",
|
||||
"noFleets": "Aucune flotte pour le moment. Créez-en une pour commencer.",
|
||||
"memberCount": "{{count}} hôte",
|
||||
"memberCount_other": "{{count}} hôtes",
|
||||
"manageMembers": "Gérer les membres",
|
||||
"manageMembersTitle": "Gérer les membres de la flotte",
|
||||
"manageMembersDescription": "Activez ou désactivez l'appartenance statique des hôtes à cette flotte. Les hôtes correspondant aux règles de balisage sont toujours inclus.",
|
||||
"searchHosts": "Rechercher des hôtes...",
|
||||
"noHostsFound": "Aucun hôte trouvé",
|
||||
"noMembers": "Cette flotte est vide pour le moment. Ajoutez des hôtes ou définissez une règle de balisage.",
|
||||
"membersLoadFailed": "Échec du chargement des membres de la flotte",
|
||||
"memberUpdateFailed": "Échec de la mise à jour de l'adhésion à la flotte",
|
||||
"tabRun": "Exécuter la commande",
|
||||
"tabTransfer": "Transférer des fichiers",
|
||||
"tabInventory": "Inventaire",
|
||||
"openInventoryTab": "Ouvrir dans l'onglet complet",
|
||||
"tabPackages": "Paquets",
|
||||
"runOnFleet": "Exécuter sur la flotte",
|
||||
"commandPlaceholder": "disponibilité && df -h",
|
||||
"commandRequired": "Une commande est requise",
|
||||
"commandInputsLabel": "Entrées de commande",
|
||||
"commandSucceeded": "La commande a réussi sur tous les hôtes.",
|
||||
"commandPartialFailure": "{{failed}} des hôtes {{total}} ont échoué",
|
||||
"commandFailed": "Échec de l'exécution de la commande",
|
||||
"resultSuccess": "Succès",
|
||||
"resultFailed": "Échec",
|
||||
"push": "Pousser",
|
||||
"pull": "Tirer",
|
||||
"remotePathLabel": "Chemin distant",
|
||||
"remotePathRequired": "Un chemin distant est nécessaire",
|
||||
"fileLabel": "Fichier",
|
||||
"fileRequired": "Un fichier est requis",
|
||||
"pushHint": "Le fichier sélectionné est enregistré à cet emplacement précis sur chaque hôte du parc.",
|
||||
"pullHint": "Le fichier situé à cet emplacement est lu depuis chaque hôte et téléchargé sous forme d'un seul fichier zip, un dossier par hôte.",
|
||||
"transferComplete": "Transfert terminé",
|
||||
"transferFailed": "Échec du transfert",
|
||||
"refreshInventory": "Actualiser l'inventaire",
|
||||
"inventoryLoadFailed": "Échec du chargement de l'inventaire",
|
||||
"inventoryRefreshed": "Inventaire mis à jour",
|
||||
"inventoryRefreshFailed": "Échec de la mise à jour de l'inventaire",
|
||||
"inventoryNoData": "Aucune donnée d'inventaire pour le moment - actualisez la page pour la récupérer.",
|
||||
"inventoryHost": "Hôte",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collecté",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Noyau",
|
||||
"inventoryArch": "Cambre",
|
||||
"inventoryUptime": "Disponibilité",
|
||||
"inventoryPackageManager": "Gestionnaire de paquets",
|
||||
"inventoryCollectedAt": "Collecté",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Installer",
|
||||
"packageRemove": "Supprimer",
|
||||
"packageUpgradeAll": "Mettez à niveau tous les forfaits",
|
||||
"packageNameLabel": "Nom du paquet",
|
||||
"packageNameRequired": "Un nom de paquet est requis.",
|
||||
"packageHint": "Nécessite un accès de niveau administrateur par hôte et élève les privilèges à l'aide du mot de passe sudo stocké sur l'hôte.",
|
||||
"shareFleet": "Flotte partagée",
|
||||
"shareFleetTitle": "Partager \"{{name}}\"",
|
||||
"shareFleetDescription": "Permet de partager les hôtes membres actuels de cette flotte avec les utilisateurs ou rôles sélectionnés. Les hôtes ajoutés ultérieurement à la flotte ne sont pas partagés automatiquement ; veuillez renouveler le partage pour les inclure.",
|
||||
"shareSelectionRequired": "Sélectionnez au moins un utilisateur ou un rôle",
|
||||
"fleetSharedSuccessfully": "Partagé {{shared}} de {{total}} hôtes dans cette flotte",
|
||||
"failedToShareFleet": "Échec du partage de la flotte",
|
||||
"noMembersToShare": "Cette flotte ne compte pas encore d'hôtes membres à partager."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flottes"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Voir la documentation",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Heures jusqu'à l'expiration de l'accès",
|
||||
"expiry": {
|
||||
"never": "Jamais",
|
||||
"oneHour": "1 heure",
|
||||
"oneDay": "24 heures",
|
||||
"sevenDays": "7 jours",
|
||||
"thirtyDays": "30 jours",
|
||||
"custom": "Personnalisé"
|
||||
},
|
||||
"expiryLabel": "Expiration de l’accès",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connexion",
|
||||
"description": "Ouverture de sessions uniquement : terminal, bureau à distance, gestionnaire de fichiers, tunnels et Docker. Aucun accès à la configuration de l'hôte."
|
||||
},
|
||||
"view": {
|
||||
"label": "Consultation",
|
||||
"description": "Connexion, plus consultation de la configuration de l’hôte. Les secrets ne sont jamais affichés."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Modification",
|
||||
"description": "Consultez et modifiez les paramètres de l'hôte sans authentification. L'authentification SSH du propriétaire reste privée et accessible uniquement à ce dernier."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Gestion",
|
||||
"description": "Modification, plus partager l'hôte avec d'autres utilisateurs, modifier les niveaux de permission et révoquer l'accès."
|
||||
}
|
||||
},
|
||||
"noMatches": "Aucun résultat trouvé",
|
||||
"permissionLevelLabel": "Niveau de permission",
|
||||
"rolesTab": "Rôles",
|
||||
"searchPlaceholder": "Rechercher des utilisateurs ou des rôles…",
|
||||
"shareButton": "Partager",
|
||||
"shareWithCount": "Partager ({{count}})",
|
||||
"usersTab": "Utilisateurs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "צפה בציי רכב",
|
||||
"description": "ראה ציי רכב, חבריהם ומלאי."
|
||||
},
|
||||
"manage": {
|
||||
"title": "ניהול ציי רכב",
|
||||
"description": "צור, ערוך ומחק ציי רכב, שנה חברות ושתף ציי רכב עם משתמשים או תפקידים אחרים."
|
||||
},
|
||||
"execute": {
|
||||
"title": "הפעל פעולות צי",
|
||||
"description": "הפעל פקודות ופעולות חבילה ברחבי צי, והעבר קבצים אל המארחים החברים בו וממנה."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "ציי רכב",
|
||||
"createFleet": "צור צי",
|
||||
"createFleetTitle": "צור צי",
|
||||
"editFleetTitle": "עריכת צי",
|
||||
"createFleetDescription": "צי הוא קבוצה בעלת שם של מארחים שניתן להפעיל פעולות נגדם כיחידה.",
|
||||
"nameLabel": "שֵׁם",
|
||||
"namePlaceholder": "לדוגמה, שרתי אינטרנט",
|
||||
"nameRequired": "נדרש שם צי",
|
||||
"descriptionLabel": "תֵאוּר",
|
||||
"descriptionPlaceholder": "תיאור אופציונלי",
|
||||
"colorLabel": "צֶבַע",
|
||||
"tagRulesLabel": "כללי תגיות",
|
||||
"tagRulesPlaceholder": "רשת דחיפה, קצה",
|
||||
"tagRulesHint": "מארחים עם אחד מהתגים הללו נכללים אוטומטית, בנוסף למארחים שנוספו ידנית.",
|
||||
"fleetCreated": "צי נוצר",
|
||||
"fleetUpdated": "הצי עודכן",
|
||||
"fleetDeleted": "הצי נמחק",
|
||||
"saveFailed": "נכשלה הצלת הצי",
|
||||
"deleteFailed": "מחיקת הצי נכשלה",
|
||||
"loadFailed": "טעינת ציי רכב נכשלה",
|
||||
"deleteFleetTitle": "מחיקת צי",
|
||||
"deleteFleetDescription": "האם אתה בטוח שברצונך למחוק את \"{{name}}\"? פעולה זו אינה מוחקת את המארחים החברים עצמם.",
|
||||
"searchFleets": "חיפוש ציי רכב...",
|
||||
"noFleets": "אין עדיין ציי רכב. צור אחד כדי להתחיל.",
|
||||
"memberCount": "מארח {{count}}",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "ניהול חברים",
|
||||
"manageMembersTitle": "ניהול חברי צי",
|
||||
"manageMembersDescription": "פתח או הסר מארחים מהחברות הסטטית של צי זה. מארחים שתואמים לפי כללי תג תמיד כלולים.",
|
||||
"searchHosts": "חיפוש מארחים...",
|
||||
"noHostsFound": "לא נמצאו מארחים",
|
||||
"noMembers": "לצי הזה עדיין אין חברים. הוסף מארחים או הגדר כלל תגיות.",
|
||||
"membersLoadFailed": "טעינת חברי הצי נכשלה",
|
||||
"memberUpdateFailed": "נכשל עדכון חברות הצי",
|
||||
"tabRun": "הפעל את הפקודה",
|
||||
"tabTransfer": "העברת קבצים",
|
||||
"tabInventory": "מְלַאי",
|
||||
"openInventoryTab": "פתח בכרטיסייה המלאה",
|
||||
"tabPackages": "חבילות",
|
||||
"runOnFleet": "רוץ על הצי",
|
||||
"commandPlaceholder": "זמן פעולה ו-df -h",
|
||||
"commandRequired": "נדרשת פקודה",
|
||||
"commandInputsLabel": "קלטי פקודה",
|
||||
"commandSucceeded": "הפקודה הצליחה בכל המארחים",
|
||||
"commandPartialFailure": "{{failed}} מתוך {{total}} מארחים נכשלו",
|
||||
"commandFailed": "נכשל בהפעלת הפקודה",
|
||||
"resultSuccess": "הַצלָחָה",
|
||||
"resultFailed": "נִכשָׁל",
|
||||
"push": "לִדחוֹף",
|
||||
"pull": "מְשׁוֹך",
|
||||
"remotePathLabel": "נתיב מרוחק",
|
||||
"remotePathRequired": "נדרש נתיב מרוחק",
|
||||
"fileLabel": "קוֹבֶץ",
|
||||
"fileRequired": "נדרש קובץ",
|
||||
"pushHint": "הקובץ שנבחר נכתב בדיוק לנתיב הזה בכל מחשב מארח בצי.",
|
||||
"pullHint": "הקובץ בנתיב זה נקרא מכל מארח ומורד כקובץ zip יחיד, תיקייה אחת לכל מארח.",
|
||||
"transferComplete": "ההעברה הושלמה",
|
||||
"transferFailed": "ההעברה נכשלה",
|
||||
"refreshInventory": "רענון המלאי",
|
||||
"inventoryLoadFailed": "טעינת המלאי נכשלה",
|
||||
"inventoryRefreshed": "רענון המלאי",
|
||||
"inventoryRefreshFailed": "רענון המלאי נכשל",
|
||||
"inventoryNoData": "אין עדיין נתוני מלאי - רענן כדי לאסוף אותם.",
|
||||
"inventoryHost": "מְאָרֵחַ",
|
||||
"inventoryCollectedCount": "נאסף {{count}}/{{total}}",
|
||||
"inventoryOs": "מערכת הפעלה",
|
||||
"inventoryKernel": "גַרעִין",
|
||||
"inventoryArch": "קֶשֶׁת",
|
||||
"inventoryUptime": "זמן פעולה",
|
||||
"inventoryPackageManager": "מנהל החבילות",
|
||||
"inventoryCollectedAt": "נאסף",
|
||||
"packageActionLabel": "פְּעוּלָה",
|
||||
"packageInstall": "לְהַתְקִין",
|
||||
"packageRemove": "לְהַסִיר",
|
||||
"packageUpgradeAll": "שדרגו את כל החבילות",
|
||||
"packageNameLabel": "שם החבילה",
|
||||
"packageNameRequired": "נדרש שם חבילה",
|
||||
"packageHint": "דורש גישת ניהול לכל מחשב מארח ועולה באמצעות סיסמת ה-sudo המאוחסנת של המארח.",
|
||||
"shareFleet": "שתף צי",
|
||||
"shareFleetTitle": "שתף \"{{name}}\"",
|
||||
"shareFleetDescription": "משתף את המארחים החברים הנוכחיים של צי זה עם המשתמשים או התפקידים שנבחרו. מארחים שנוספו לצי מאוחר יותר לא ישותפו אוטומטית - שתף שוב כדי לכלול אותם.",
|
||||
"shareSelectionRequired": "בחר לפחות משתמש או תפקיד אחד",
|
||||
"fleetSharedSuccessfully": "משותף {{shared}} של {{total}} מארח(ים) בצי זה",
|
||||
"failedToShareFleet": "שיתוף הצי נכשל",
|
||||
"noMembersToShare": "לצי הזה עדיין אין מארחים חברים לשיתוף."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "ציי רכב"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "הצג מסמכים",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "שעות עד לפקיעת הגישה",
|
||||
"expiry": {
|
||||
"never": "לְעוֹלָם לֹא",
|
||||
"oneHour": "שעה אחת",
|
||||
"oneDay": "24 שעות",
|
||||
"sevenDays": "7 ימים",
|
||||
"thirtyDays": "30 ימים",
|
||||
"custom": "מִנְהָג"
|
||||
},
|
||||
"expiryLabel": "פקיעת גישה",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "לְחַבֵּר",
|
||||
"description": "הפעלות פתוחות בלבד: טרמינל, שולחן עבודה מרוחק, מנהל קבצים, מנהרות ו-Docker. אין גישה לתצורת המארח."
|
||||
},
|
||||
"view": {
|
||||
"label": "נוֹף",
|
||||
"description": "התחבר, בנוסף ראה את תצורת המארח. סודות לעולם אינם מוצגים."
|
||||
},
|
||||
"edit": {
|
||||
"label": "לַעֲרוֹך",
|
||||
"description": "הצג ושנה הגדרות של מארח שאינו קשור לאימות. אימות ה-SSH של הבעלים נשאר פרטי ולבעלים בלבד."
|
||||
},
|
||||
"manage": {
|
||||
"label": "לְנַהֵל",
|
||||
"description": "עריכה, בנוסף לשיתוף המארח עם אחרים, שינוי רמות הרשאה וביטול גישה."
|
||||
}
|
||||
},
|
||||
"noMatches": "לא נמצאו התאמות",
|
||||
"permissionLevelLabel": "רמת הרשאה",
|
||||
"rolesTab": "תפקידים",
|
||||
"searchPlaceholder": "חיפוש משתמשים או תפקידים...",
|
||||
"shareButton": "לַחֲלוֹק",
|
||||
"shareWithCount": "שתף ({{count}})",
|
||||
"usersTab": "משתמשים"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "नाम",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "विवरण",
|
||||
"descriptionPlaceholder": "वैकल्पिक विवरण",
|
||||
"colorLabel": "रंग",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} होस्ट",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "होस्ट खोजें...",
|
||||
"noHostsFound": "कोई होस्ट नहीं मिला",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "पैकेजेस",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "सफल",
|
||||
"resultFailed": "असफल",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "फ़ाइल",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "स्थानांतरण पूर्ण",
|
||||
"transferFailed": "स्थानांतरण विफल",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "होस्ट",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "ओएस",
|
||||
"inventoryKernel": "कर्नेल",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "अपटाइम",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "क्रिया",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "हटाएँ",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "दस्तावेज़ देखें",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "पहुँच समाप्त होने तक के घंटे",
|
||||
"expiry": {
|
||||
"never": "कभी नहीं",
|
||||
"oneHour": "1 घंटा",
|
||||
"oneDay": "24 घंटे",
|
||||
"sevenDays": "7 दिन",
|
||||
"thirtyDays": "30 दिन",
|
||||
"custom": "कस्टम"
|
||||
},
|
||||
"expiryLabel": "पहुँच समाप्ति",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "कनेक्ट",
|
||||
"description": "केवल सत्र खोलें: टर्मिनल, रिमोट डेस्कटॉप, फ़ाइल प्रबंधक, टनल और Docker। होस्ट कॉन्फ़िगरेशन तक पहुँच नहीं।"
|
||||
},
|
||||
"view": {
|
||||
"label": "देखें",
|
||||
"description": "कनेक्ट करें, साथ ही होस्ट कॉन्फ़िगरेशन देखें। सीक्रेट कभी नहीं दिखाए जाते।"
|
||||
},
|
||||
"edit": {
|
||||
"label": "संपादित करें",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "प्रबंधित करें",
|
||||
"description": "संपादित करें, और दूसरों के साथ होस्ट साझा करें, अनुमति स्तर बदलें और पहुँच रद्द करें।"
|
||||
}
|
||||
},
|
||||
"noMatches": "कोई मिलान नहीं मिला",
|
||||
"permissionLevelLabel": "अनुमति स्तर",
|
||||
"rolesTab": "भूमिकाएँ",
|
||||
"searchPlaceholder": "उपयोगकर्ता या भूमिकाएँ खोजें...",
|
||||
"shareButton": "साझा करें",
|
||||
"shareWithCount": "साझा करें ({{count}})",
|
||||
"usersTab": "उपयोगकर्ता"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Flották megtekintése",
|
||||
"description": "Tekintse meg a flottákat, azok tagjait és a készletet."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Flották kezelése",
|
||||
"description": "Flották létrehozása, szerkesztése és törlése, tagság módosítása, valamint flották megosztása más felhasználókkal vagy szerepkörökkel."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Flotta műveletek futtatása",
|
||||
"description": "Parancsok és csomagműveletek futtatása egy flottán belül, fájlok átvitele a taggépekre és a taggépekről."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flották",
|
||||
"createFleet": "Flotta létrehozása",
|
||||
"createFleetTitle": "Flotta létrehozása",
|
||||
"editFleetTitle": "Flotta szerkesztése",
|
||||
"createFleetDescription": "A flotta egy elnevezett hosztcsoport, amely ellen egységként műveleteket lehet végrehajtani.",
|
||||
"nameLabel": "Név",
|
||||
"namePlaceholder": "pl. webszerverek",
|
||||
"nameRequired": "A flotta nevének megadása kötelező",
|
||||
"descriptionLabel": "Leírás",
|
||||
"descriptionPlaceholder": "Opcionális leírás",
|
||||
"colorLabel": "Szín",
|
||||
"tagRulesLabel": "Címkeszabályok",
|
||||
"tagRulesPlaceholder": "prod-web, él",
|
||||
"tagRulesHint": "Az ezen címkék bármelyikével rendelkező hosztok automatikusan bele lesznek foglalva, a manuálisan hozzáadott hosztok mellett.",
|
||||
"fleetCreated": "Flotta létrehozva",
|
||||
"fleetUpdated": "Flotta frissítve",
|
||||
"fleetDeleted": "Flotta törölve",
|
||||
"saveFailed": "Nem sikerült menteni a flottát",
|
||||
"deleteFailed": "Nem sikerült törölni a flottát",
|
||||
"loadFailed": "Nem sikerült betölteni a flottákat",
|
||||
"deleteFleetTitle": "Flotta törlése",
|
||||
"deleteFleetDescription": "Biztosan törölni szeretnéd a(z) \"{{name}}\" fájlt? Ez nem törli a taghosztokat.",
|
||||
"searchFleets": "Flották keresése...",
|
||||
"noFleets": "Még nincsenek flották. Hozz létre egyet a kezdéshez.",
|
||||
"memberCount": "{{count}} gazdagép",
|
||||
"memberCount_other": "{{count}} gazdagépek",
|
||||
"manageMembers": "Tagok kezelése",
|
||||
"manageMembersTitle": "Flottatagok kezelése",
|
||||
"manageMembersDescription": "Hosztok be- és kikapcsolása a flotta statikus tagságába. A címkeszabályoknak megfelelő hosztok mindig szerepelnek.",
|
||||
"searchHosts": "Gazdagépek keresése...",
|
||||
"noHostsFound": "Nem találhatók hostok",
|
||||
"noMembers": "Ennek a flottának még nincsenek tagjai. Adjon hozzá hosztokat, vagy állítson be címkézési szabályt.",
|
||||
"membersLoadFailed": "Nem sikerült betölteni a flotta tagjait",
|
||||
"memberUpdateFailed": "Nem sikerült frissíteni a flottatagságot",
|
||||
"tabRun": "Parancs futtatása",
|
||||
"tabTransfer": "Fájlok átvitele",
|
||||
"tabInventory": "Leltár",
|
||||
"openInventoryTab": "Megnyitás teljes lapon",
|
||||
"tabPackages": "Csomagok",
|
||||
"runOnFleet": "Fleet Run",
|
||||
"commandPlaceholder": "üzemidő && df -h",
|
||||
"commandRequired": "Parancsra van szükség",
|
||||
"commandInputsLabel": "Parancsbemenetek",
|
||||
"commandSucceeded": "A parancs minden gépen sikeresen lefutott",
|
||||
"commandPartialFailure": "{{failed}} / {{total}} gazdagépből {{total}} hibát jelzett",
|
||||
"commandFailed": "Nem sikerült futtatni a parancsot",
|
||||
"resultSuccess": "Siker",
|
||||
"resultFailed": "Sikertelen",
|
||||
"push": "Nyomás",
|
||||
"pull": "Húzni",
|
||||
"remotePathLabel": "Távoli útvonal",
|
||||
"remotePathRequired": "Távoli elérési út szükséges",
|
||||
"fileLabel": "Fájl",
|
||||
"fileRequired": "Fájl megadása szükséges",
|
||||
"pushHint": "A kiválasztott fájl pontosan erre az elérési útra kerül a flotta minden egyes gépén.",
|
||||
"pullHint": "Az ezen az elérési úton található fájlt minden hosztról beolvassa a rendszer, és egyetlen zip fájlként tölti le, hosztonként egy mappában.",
|
||||
"transferComplete": "Átvitel befejezve",
|
||||
"transferFailed": "Átvitel sikertelen",
|
||||
"refreshInventory": "Készlet frissítése",
|
||||
"inventoryLoadFailed": "Nem sikerült betölteni a készletet",
|
||||
"inventoryRefreshed": "Készlet frissítve",
|
||||
"inventoryRefreshFailed": "Nem sikerült frissíteni a készletet",
|
||||
"inventoryNoData": "Még nincsenek készletadatok – frissítse az oldalt az összegyűjtéséhez.",
|
||||
"inventoryHost": "Házigazda",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} begyűjtött",
|
||||
"inventoryOs": "operációs rendszer",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Boltív",
|
||||
"inventoryUptime": "Üzemidő",
|
||||
"inventoryPackageManager": "Csomagkezelő",
|
||||
"inventoryCollectedAt": "Összegyűjtött",
|
||||
"packageActionLabel": "Akció",
|
||||
"packageInstall": "Telepítés",
|
||||
"packageRemove": "Eltávolítás",
|
||||
"packageUpgradeAll": "Az összes csomag frissítése",
|
||||
"packageNameLabel": "Csomag neve",
|
||||
"packageNameRequired": "Csomagnév megadása kötelező",
|
||||
"packageHint": "Gazdagépenként felügyeleti szintű hozzáférést igényel, és a gazdagép tárolt sudo jelszavával emeli ki a jogosultságokat.",
|
||||
"shareFleet": "Megosztási flotta",
|
||||
"shareFleetTitle": "Megosztás \"{{name}}\"",
|
||||
"shareFleetDescription": "Megosztja a flotta jelenlegi tag hosztjait a kiválasztott felhasználókkal vagy szerepkörökkel. A flottához később hozzáadott hosztok nem kerülnek automatikusan megosztásra – ossza meg újra, hogy belefoglalja őket.",
|
||||
"shareSelectionRequired": "Válasszon ki legalább egy felhasználót vagy szerepkört",
|
||||
"fleetSharedSuccessfully": "Megosztott {{shared}} a {{total}} hosztból ebben a flottában",
|
||||
"failedToShareFleet": "Nem sikerült megosztani a flottát",
|
||||
"noMembersToShare": "Ennek a flottának még nincsenek megosztható tag hosztjai."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flották"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Dokumentumok megtekintése",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Órák a hozzáférés lejártáig",
|
||||
"expiry": {
|
||||
"never": "Soha",
|
||||
"oneHour": "1 óra",
|
||||
"oneDay": "24 óra",
|
||||
"sevenDays": "7 nap",
|
||||
"thirtyDays": "30 nap",
|
||||
"custom": "Szokás"
|
||||
},
|
||||
"expiryLabel": "Hozzáférés lejárata",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Csatlakozás",
|
||||
"description": "Csak nyitott munkamenetek: terminál, távoli asztal, fájlkezelő, alagutak és Docker. Nincs hozzáférés a gazdagép konfigurációjához."
|
||||
},
|
||||
"view": {
|
||||
"label": "Kilátás",
|
||||
"description": "Csatlakozás, valamint a gazdagép konfigurációjának megtekintése. A titkos kódok soha nem jelennek meg."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Szerkesztés",
|
||||
"description": "A nem hitelesítési hosztbeállítások megtekintése és módosítása. A tulajdonos SSH-hitelesítése privát és csak a tulajdonos számára elérhető marad."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Kezelés",
|
||||
"description": "Szerkeszd, és oszd meg a gazdagépet másokkal, módosítsd a jogosultsági szinteket és vond vissza a hozzáférést."
|
||||
}
|
||||
},
|
||||
"noMatches": "Nincs találat",
|
||||
"permissionLevelLabel": "Jogosultsági szint",
|
||||
"rolesTab": "Szerepkörök",
|
||||
"searchPlaceholder": "Felhasználók vagy szerepkörök keresése...",
|
||||
"shareButton": "Részesedés",
|
||||
"shareWithCount": "Megosztás ({{count}})",
|
||||
"usersTab": "Felhasználók"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remove",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connect",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Visualizza le flotte",
|
||||
"description": "Visualizza le flotte, i loro membri e l'inventario."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Gestire le flotte",
|
||||
"description": "Crea, modifica ed elimina flotte, cambia i membri e condividi le flotte con altri utenti o ruoli."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Esegui azioni della flotta",
|
||||
"description": "Esegui comandi e azioni sui pacchetti su un'intera flotta e trasferisci file da e verso gli host membri."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flotte",
|
||||
"createFleet": "Crea flotta",
|
||||
"createFleetTitle": "Crea flotta",
|
||||
"editFleetTitle": "Modifica flotta",
|
||||
"createFleetDescription": "Una flotta è un gruppo denominato di host su cui è possibile eseguire azioni come un'unica unità.",
|
||||
"nameLabel": "Nome",
|
||||
"namePlaceholder": "ad esempio, server web",
|
||||
"nameRequired": "Il nome della flotta è obbligatorio",
|
||||
"descriptionLabel": "Descrizione",
|
||||
"descriptionPlaceholder": "Descrizione opzionale",
|
||||
"colorLabel": "Colore",
|
||||
"tagRulesLabel": "Regole dei tag",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Oltre agli host aggiunti manualmente, vengono inclusi automaticamente anche quelli che presentano uno qualsiasi di questi tag.",
|
||||
"fleetCreated": "Flotta creata",
|
||||
"fleetUpdated": "Flotta aggiornata",
|
||||
"fleetDeleted": "Flotta eliminata",
|
||||
"saveFailed": "Impossibile salvare la flotta",
|
||||
"deleteFailed": "Impossibile eliminare la flotta",
|
||||
"loadFailed": "Impossibile caricare le flotte",
|
||||
"deleteFleetTitle": "Elimina flotta",
|
||||
"deleteFleetDescription": "Sei sicuro di voler eliminare \"{{name}}\"? Questa operazione non elimina gli host membri stessi.",
|
||||
"searchFleets": "Cerca flotte...",
|
||||
"noFleets": "Nessuna flotta ancora. Creane una per iniziare.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} host",
|
||||
"manageMembers": "Gestisci i membri",
|
||||
"manageMembersTitle": "Gestisci i membri della flotta",
|
||||
"manageMembersDescription": "Attiva o disattiva l'inclusione degli host nell'elenco statico di questa flotta. Gli host corrispondenti alle regole dei tag sono sempre inclusi.",
|
||||
"searchHosts": "Cerca host...",
|
||||
"noHostsFound": "Nessun host trovato",
|
||||
"noMembers": "Questa flotta non ha ancora membri. Aggiungi host o imposta una regola di tag.",
|
||||
"membersLoadFailed": "Impossibile caricare i membri della flotta",
|
||||
"memberUpdateFailed": "Impossibile aggiornare l'appartenenza alla flotta",
|
||||
"tabRun": "Comando di esecuzione",
|
||||
"tabTransfer": "Trasferimento file",
|
||||
"tabInventory": "Inventario",
|
||||
"openInventoryTab": "Apri in una scheda completa",
|
||||
"tabPackages": "Pacchetti",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "È richiesto un comando",
|
||||
"commandInputsLabel": "Input di comando",
|
||||
"commandSucceeded": "Comando eseguito correttamente su tutti gli host",
|
||||
"commandPartialFailure": "{{failed}} di {{total}} host non sono riusciti",
|
||||
"commandFailed": "Impossibile eseguire il comando",
|
||||
"resultSuccess": "Successo",
|
||||
"resultFailed": "Fallito",
|
||||
"push": "Spingere",
|
||||
"pull": "Tiro",
|
||||
"remotePathLabel": "Percorso remoto",
|
||||
"remotePathRequired": "È necessario un percorso remoto",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "È necessario un file",
|
||||
"pushHint": "Il file selezionato viene scritto esattamente in questo percorso su ogni host della flotta.",
|
||||
"pullHint": "Il file presente in questo percorso viene letto da ogni host e scaricato come un singolo file zip, una cartella per ogni host.",
|
||||
"transferComplete": "Trasferimento completato",
|
||||
"transferFailed": "Trasferimento non riuscito",
|
||||
"refreshInventory": "Aggiorna l'inventario",
|
||||
"inventoryLoadFailed": "Impossibile caricare l'inventario",
|
||||
"inventoryRefreshed": "Inventario aggiornato",
|
||||
"inventoryRefreshFailed": "Impossibile aggiornare l'inventario",
|
||||
"inventoryNoData": "Nessun dato di inventario disponibile al momento: aggiorna la pagina per visualizzarlo.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} raccolto",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arco",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Responsabile del pacchetto",
|
||||
"inventoryCollectedAt": "Raccolto",
|
||||
"packageActionLabel": "Azione",
|
||||
"packageInstall": "Installare",
|
||||
"packageRemove": "Rimuovi",
|
||||
"packageUpgradeAll": "Aggiorna tutti i pacchetti",
|
||||
"packageNameLabel": "Nome del pacchetto",
|
||||
"packageNameRequired": "È richiesto un nome per il pacchetto",
|
||||
"packageHint": "Richiede l'accesso a livello di gestione per host ed eleva i privilegi utilizzando la password sudo memorizzata sull'host.",
|
||||
"shareFleet": "Condividi la flotta",
|
||||
"shareFleetTitle": "Condividi \"{{name}}\"",
|
||||
"shareFleetDescription": "Condivide gli host attualmente membri di questa flotta con gli utenti o i ruoli selezionati. Gli host aggiunti alla flotta in un secondo momento non vengono condivisi automaticamente: è necessario ripetere la condivisione per includerli.",
|
||||
"shareSelectionRequired": "Seleziona almeno un utente o un ruolo",
|
||||
"fleetSharedSuccessfully": "Condiviso {{shared}} di {{total}} host(i) in questa flotta",
|
||||
"failedToShareFleet": "Impossibile condividere la flotta",
|
||||
"noMembersToShare": "Questa flotta non ha ancora host membri da condividere."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flotte"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Visualizza documentazione",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Ore fino alla scadenza dell'accesso",
|
||||
"expiry": {
|
||||
"never": "Mai",
|
||||
"oneHour": "1 ora",
|
||||
"oneDay": "24 ore",
|
||||
"sevenDays": "7 giorni",
|
||||
"thirtyDays": "30 giorni",
|
||||
"custom": "Personalizzato"
|
||||
},
|
||||
"expiryLabel": "Scadenza accesso",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connetti",
|
||||
"description": "Apri solo sessioni: terminale, desktop remoto, gestione file, tunnel e Docker. Nessun accesso alla configurazione dell'host."
|
||||
},
|
||||
"view": {
|
||||
"label": "Visualizza",
|
||||
"description": "Connetti, più visualizza la configurazione dell'host. I segreti non vengono mai mostrati."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Modifica",
|
||||
"description": "Visualizza e modifica le impostazioni host senza autenticazione. L'autenticazione SSH del proprietario rimane privata e accessibile solo al proprietario."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Gestisci",
|
||||
"description": "Modifica, più condividi l'host con altri, modifica i livelli di autorizzazione e revoca l'accesso."
|
||||
}
|
||||
},
|
||||
"noMatches": "Nessuna corrispondenza trovata",
|
||||
"permissionLevelLabel": "Livello di autorizzazione",
|
||||
"rolesTab": "Ruoli",
|
||||
"searchPlaceholder": "Cerca utenti o ruoli...",
|
||||
"shareButton": "Condividi",
|
||||
"shareWithCount": "Condividi ({{count}})",
|
||||
"usersTab": "Utenti"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "車両一覧を見る",
|
||||
"description": "艦隊、その構成員、および保有資産をご覧ください。"
|
||||
},
|
||||
"manage": {
|
||||
"title": "車両群を管理する",
|
||||
"description": "艦隊の作成、編集、削除、メンバーシップの変更、および他のユーザーや役割との艦隊の共有が可能です。"
|
||||
},
|
||||
"execute": {
|
||||
"title": "艦隊作戦を実行する",
|
||||
"description": "フリート全体でコマンドやパッケージアクションを実行し、メンバーホストとの間でファイルを転送します。"
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "艦隊",
|
||||
"createFleet": "艦隊を作成する",
|
||||
"createFleetTitle": "艦隊を作成する",
|
||||
"editFleetTitle": "艦隊を編集",
|
||||
"createFleetDescription": "フリートとは、複数のホストをまとめてグループ化し、それらを一つの単位としてアクションを実行できるグループです。",
|
||||
"nameLabel": "名前",
|
||||
"namePlaceholder": "例:Webサーバー",
|
||||
"nameRequired": "艦隊名は必須です",
|
||||
"descriptionLabel": "説明",
|
||||
"descriptionPlaceholder": "任意の説明",
|
||||
"colorLabel": "色",
|
||||
"tagRulesLabel": "タグルール",
|
||||
"tagRulesPlaceholder": "プロダクションウェブ、エッジ",
|
||||
"tagRulesHint": "これらのタグのいずれかが付いているホストは、手動で追加されたホストに加えて、自動的に含まれます。",
|
||||
"fleetCreated": "艦隊が編成されました",
|
||||
"fleetUpdated": "艦隊情報更新",
|
||||
"fleetDeleted": "艦隊削除",
|
||||
"saveFailed": "艦隊の救出に失敗しました",
|
||||
"deleteFailed": "艦隊の削除に失敗しました",
|
||||
"loadFailed": "艦隊の読み込みに失敗しました",
|
||||
"deleteFleetTitle": "艦隊を削除",
|
||||
"deleteFleetDescription": "「{{name}}」を削除してもよろしいですか?これはメンバーホスト自体を削除するものではありません。",
|
||||
"searchFleets": "艦隊を検索...",
|
||||
"noFleets": "まだ艦隊がありません。まずは艦隊を作成してください。",
|
||||
"memberCount": "{{count}} ホスト",
|
||||
"memberCount_other": "{{count}} ホスト",
|
||||
"manageMembers": "メンバーの管理",
|
||||
"manageMembersTitle": "フリートメンバーの管理",
|
||||
"manageMembersDescription": "このフリートの静的メンバーシップにホストを追加または削除する切り替えを行います。タグルールに一致するホストは常に含まれます。",
|
||||
"searchHosts": "ホストを検索...",
|
||||
"noHostsFound": "ホストが見つかりません",
|
||||
"noMembers": "この艦隊にはまだメンバーがいません。ホストを追加するか、タグルールを設定してください。",
|
||||
"membersLoadFailed": "艦隊メンバーの読み込みに失敗しました",
|
||||
"memberUpdateFailed": "艦隊メンバーシップの更新に失敗しました",
|
||||
"tabRun": "コマンドを実行する",
|
||||
"tabTransfer": "ファイルの転送",
|
||||
"tabInventory": "在庫",
|
||||
"openInventoryTab": "全画面表示",
|
||||
"tabPackages": "パッケージ",
|
||||
"runOnFleet": "ラン・オン・フリート",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "コマンドが必要です",
|
||||
"commandInputsLabel": "コマンド入力",
|
||||
"commandSucceeded": "コマンドはすべてのホストで正常に実行されました。",
|
||||
"commandPartialFailure": "{{failed}} の {{total}} ホストが失敗しました",
|
||||
"commandFailed": "コマンドの実行に失敗しました",
|
||||
"resultSuccess": "成功",
|
||||
"resultFailed": "失敗",
|
||||
"push": "押す",
|
||||
"pull": "引く",
|
||||
"remotePathLabel": "リモートパス",
|
||||
"remotePathRequired": "遠隔経路が必要です",
|
||||
"fileLabel": "ファイル",
|
||||
"fileRequired": "ファイルが必要です",
|
||||
"pushHint": "選択されたファイルは、艦隊内のすべてのホスト上のこの正確なパスに書き込まれます。",
|
||||
"pullHint": "このパスにあるファイルは、すべてのホストから読み込まれ、ホストごとに1つのフォルダ、つまり単一のzipファイルとしてダウンロードされます。",
|
||||
"transferComplete": "転送が完了しました",
|
||||
"transferFailed": "転送に失敗しました",
|
||||
"refreshInventory": "在庫を更新",
|
||||
"inventoryLoadFailed": "在庫の読み込みに失敗しました",
|
||||
"inventoryRefreshed": "在庫が更新されました",
|
||||
"inventoryRefreshFailed": "在庫の更新に失敗しました",
|
||||
"inventoryNoData": "在庫データがまだありません。更新して取得してください。",
|
||||
"inventoryHost": "ホスト",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} が収集されました",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "カーネル",
|
||||
"inventoryArch": "アーチ",
|
||||
"inventoryUptime": "アップタイム",
|
||||
"inventoryPackageManager": "パッケージマネージャー",
|
||||
"inventoryCollectedAt": "収集済み",
|
||||
"packageActionLabel": "アクション",
|
||||
"packageInstall": "インストール",
|
||||
"packageRemove": "削除",
|
||||
"packageUpgradeAll": "すべてのパッケージをアップグレードする",
|
||||
"packageNameLabel": "パッケージ名",
|
||||
"packageNameRequired": "パッケージ名は必須です。",
|
||||
"packageHint": "ホストごとに管理者レベルのアクセス権限が必要で、ホストに保存されているsudoパスワードを使用して権限を昇格します。",
|
||||
"shareFleet": "シェアフリート",
|
||||
"shareFleetTitle": "「{{name}} を共有する」",
|
||||
"shareFleetDescription": "このフリートの現在のメンバーホストを、選択したユーザーまたはロールと共有します。後からフリートに追加されたホストは自動的に共有されません。それらを含めるには、再度共有を実行してください。",
|
||||
"shareSelectionRequired": "少なくとも1人のユーザーまたは役割を選択してください。",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "車両共有に失敗しました",
|
||||
"noMembersToShare": "この艦隊には、まだ共有できるメンバーホストがいません。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "艦隊"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "ドキュメントを表示",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "アクセス期限までの時間数",
|
||||
"expiry": {
|
||||
"never": "無期限",
|
||||
"oneHour": "1時間",
|
||||
"oneDay": "24時間",
|
||||
"sevenDays": "7日間",
|
||||
"thirtyDays": "30日間",
|
||||
"custom": "カスタム"
|
||||
},
|
||||
"expiryLabel": "アクセス期限",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "接続",
|
||||
"description": "セッションのみ開く: ターミナル、リモートデスクトップ、ファイルマネージャー、トンネル、Docker。ホスト設定へのアクセスはありません。"
|
||||
},
|
||||
"view": {
|
||||
"label": "表示",
|
||||
"description": "接続に加え、ホスト設定を表示できます。シークレットは表示されません。"
|
||||
},
|
||||
"edit": {
|
||||
"label": "編集",
|
||||
"description": "認証不要のホスト設定を表示および変更できます。所有者のSSH認証情報は非公開で、所有者のみがアクセスできます。"
|
||||
},
|
||||
"manage": {
|
||||
"label": "管理",
|
||||
"description": "編集に加え、ホストを他者と共有し、権限レベルを変更、アクセスを取り消すことができます。"
|
||||
}
|
||||
},
|
||||
"noMatches": "一致する結果がありません",
|
||||
"permissionLevelLabel": "権限レベル",
|
||||
"rolesTab": "ロール",
|
||||
"searchPlaceholder": "ユーザーまたはロールを検索...",
|
||||
"shareButton": "共有",
|
||||
"shareWithCount": "共有 ({{count}})",
|
||||
"usersTab": "ユーザー"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "함대 보기",
|
||||
"description": "함대, 구성원 및 보유 자산을 확인하세요."
|
||||
},
|
||||
"manage": {
|
||||
"title": "차량 관리",
|
||||
"description": "함대를 생성, 편집 및 삭제하고, 구성원을 변경하고, 다른 사용자 또는 역할과 함대를 공유할 수 있습니다."
|
||||
},
|
||||
"execute": {
|
||||
"title": "함대 작업 실행",
|
||||
"description": "여러 호스트로 구성된 네트워크에서 명령을 실행하고 패키지 작업을 수행하며, 구성원 호스트 간에 파일을 전송합니다."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "함대",
|
||||
"createFleet": "함대 생성",
|
||||
"createFleetTitle": "함대 생성",
|
||||
"editFleetTitle": "함대 편집",
|
||||
"createFleetDescription": "함대는 여러 호스트를 하나의 그룹으로 묶어 작업을 수행할 수 있도록 명명된 그룹입니다.",
|
||||
"nameLabel": "이름",
|
||||
"namePlaceholder": "예: 웹 서버",
|
||||
"nameRequired": "함대 이름은 필수 입력 사항입니다.",
|
||||
"descriptionLabel": "설명",
|
||||
"descriptionPlaceholder": "선택 사항 설명",
|
||||
"colorLabel": "색상",
|
||||
"tagRulesLabel": "태그 규칙",
|
||||
"tagRulesPlaceholder": "프로드웹, 엣지",
|
||||
"tagRulesHint": "이러한 태그 중 하나라도 있는 호스트는 수동으로 추가된 호스트 외에도 자동으로 포함됩니다.",
|
||||
"fleetCreated": "함대 생성됨",
|
||||
"fleetUpdated": "함대 업데이트됨",
|
||||
"fleetDeleted": "함대 삭제됨",
|
||||
"saveFailed": "함대를 구하는 데 실패했습니다",
|
||||
"deleteFailed": "함대 삭제에 실패했습니다",
|
||||
"loadFailed": "함대를 로드하는 데 실패했습니다.",
|
||||
"deleteFleetTitle": "함대 삭제",
|
||||
"deleteFleetDescription": "\"{{name}}\"을 삭제하시겠습니까? 삭제해도 멤버 호스트 자체는 삭제되지 않습니다.",
|
||||
"searchFleets": "함대를 검색하세요...",
|
||||
"noFleets": "아직 함대가 없습니다. 시작하려면 함대를 생성하세요.",
|
||||
"memberCount": "{{count}} 호스트",
|
||||
"memberCount_other": "호스트 {{count}}개",
|
||||
"manageMembers": "회원 관리",
|
||||
"manageMembersTitle": "함대 구성원 관리",
|
||||
"manageMembersDescription": "호스트를 이 함대의 고정 멤버십에 추가하거나 제외할 수 있습니다. 태그 규칙에 따라 일치하는 호스트는 항상 포함됩니다.",
|
||||
"searchHosts": "호스트 검색...",
|
||||
"noHostsFound": "호스트를 찾을 수 없습니다",
|
||||
"noMembers": "이 함대에는 아직 멤버가 없습니다. 호스트를 추가하거나 태그 규칙을 설정하세요.",
|
||||
"membersLoadFailed": "함대 구성원을 불러오는 데 실패했습니다.",
|
||||
"memberUpdateFailed": "함대 구성원 정보 업데이트에 실패했습니다.",
|
||||
"tabRun": "명령 실행",
|
||||
"tabTransfer": "파일 전송",
|
||||
"tabInventory": "목록",
|
||||
"openInventoryTab": "전체 화면에서 열기",
|
||||
"tabPackages": "패키지",
|
||||
"runOnFleet": "Fleet에서 실행",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "명령어가 필요합니다",
|
||||
"commandInputsLabel": "명령 입력",
|
||||
"commandSucceeded": "모든 호스트에서 명령이 성공했습니다.",
|
||||
"commandPartialFailure": "{{failed}} 의 {{total}} 호스트가 실패했습니다.",
|
||||
"commandFailed": "명령 실행에 실패했습니다.",
|
||||
"resultSuccess": "성공",
|
||||
"resultFailed": "실패",
|
||||
"push": "푸시",
|
||||
"pull": "당기다",
|
||||
"remotePathLabel": "원격 경로",
|
||||
"remotePathRequired": "원격 경로가 필요합니다",
|
||||
"fileLabel": "파일",
|
||||
"fileRequired": "파일이 필요합니다",
|
||||
"pushHint": "선택한 파일은 전체 호스트의 정확히 이 경로에 기록됩니다.",
|
||||
"pullHint": "이 경로에 있는 파일은 모든 호스트에서 읽어와 호스트당 하나의 폴더로 구성된 단일 zip 파일로 다운로드됩니다.",
|
||||
"transferComplete": "전송 완료",
|
||||
"transferFailed": "전송 실패",
|
||||
"refreshInventory": "재고 새로 고침",
|
||||
"inventoryLoadFailed": "재고 불러오기에 실패했습니다",
|
||||
"inventoryRefreshed": "재고가 갱신되었습니다",
|
||||
"inventoryRefreshFailed": "재고 새로 고침에 실패했습니다.",
|
||||
"inventoryNoData": "아직 재고 데이터가 없습니다. 새로 고침하여 데이터를 수집하세요.",
|
||||
"inventoryHost": "호스트",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} 수집됨",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "커널",
|
||||
"inventoryArch": "아치",
|
||||
"inventoryUptime": "업타임",
|
||||
"inventoryPackageManager": "패키지 관리자",
|
||||
"inventoryCollectedAt": "모은",
|
||||
"packageActionLabel": "작업",
|
||||
"packageInstall": "설치하다",
|
||||
"packageRemove": "제거",
|
||||
"packageUpgradeAll": "모든 패키지 업그레이드",
|
||||
"packageNameLabel": "패키지 이름",
|
||||
"packageNameRequired": "패키지 이름은 필수 입력 사항입니다.",
|
||||
"packageHint": "호스트별 관리자 권한 접근이 필요하며, 호스트에 저장된 sudo 암호를 사용하여 권한을 상승시킵니다.",
|
||||
"shareFleet": "공유 차량",
|
||||
"shareFleetTitle": "\"{{name}} \" 공유",
|
||||
"shareFleetDescription": "선택한 사용자 또는 역할과 이 플릿의 현재 멤버 호스트를 공유합니다. 나중에 플릿에 추가된 호스트는 자동으로 공유되지 않습니다. 포함시키려면 다시 공유해야 합니다.",
|
||||
"shareSelectionRequired": "사용자 또는 역할을 하나 이상 선택하세요.",
|
||||
"fleetSharedSuccessfully": "이 함대에서 {{shared}} 개의 {{total}} 호스트가 공유됩니다.",
|
||||
"failedToShareFleet": "함대 공유에 실패했습니다",
|
||||
"noMembersToShare": "이 함대에는 아직 공유할 호스트 회원이 없습니다."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "함대"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "문서 보기",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "액세스 만료까지 시간",
|
||||
"expiry": {
|
||||
"never": "안 함",
|
||||
"oneHour": "1시간",
|
||||
"oneDay": "24시간",
|
||||
"sevenDays": "7일",
|
||||
"thirtyDays": "30일",
|
||||
"custom": "사용자 지정"
|
||||
},
|
||||
"expiryLabel": "액세스 만료",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "연결",
|
||||
"description": "세션 열기만: 터미널, 원격 데스크톱, 파일 관리자, 터널 및 Docker. 호스트 구성에 액세스할 수 없습니다."
|
||||
},
|
||||
"view": {
|
||||
"label": "보기",
|
||||
"description": "연결 및 호스트 구성 보기. 비밀 정보는 표시되지 않습니다."
|
||||
},
|
||||
"edit": {
|
||||
"label": "편집",
|
||||
"description": "인증이 필요하지 않은 호스트 설정을 보고 수정할 수 있습니다. 소유자의 SSH 인증 정보는 비공개로 유지되며 소유자만 접근할 수 있습니다."
|
||||
},
|
||||
"manage": {
|
||||
"label": "관리",
|
||||
"description": "편집, 호스트 공유, 권한 수준 변경, 액세스 해지."
|
||||
}
|
||||
},
|
||||
"noMatches": "일치하는 항목이 없습니다.",
|
||||
"permissionLevelLabel": "권한 수준",
|
||||
"rolesTab": "역할",
|
||||
"searchPlaceholder": "사용자 또는 역할 검색...",
|
||||
"shareButton": "공유",
|
||||
"shareWithCount": "공유 ({{count}})",
|
||||
"usersTab": "사용자"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Bekijk vloten",
|
||||
"description": "Bekijk de vloten, hun leden en inventaris."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Vlootbeheer",
|
||||
"description": "Vloten aanmaken, bewerken en verwijderen, het lidmaatschap wijzigen en vloten delen met andere gebruikers of rollen."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Voer vlootacties uit",
|
||||
"description": "Voer opdrachten en pakketacties uit op een hele vloot en breng bestanden over van en naar de hosts die lid zijn van de vloot."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Vloten",
|
||||
"createFleet": "Vloot creëren",
|
||||
"createFleetTitle": "Vloot creëren",
|
||||
"editFleetTitle": "Vloot bewerken",
|
||||
"createFleetDescription": "Een vloot is een benoemde groep hosts waartegen je als eenheid acties kunt uitvoeren.",
|
||||
"nameLabel": "Naam",
|
||||
"namePlaceholder": "bijvoorbeeld webservers",
|
||||
"nameRequired": "Vlootnaam is verplicht",
|
||||
"descriptionLabel": "Beschrijving",
|
||||
"descriptionPlaceholder": "Optionele beschrijving",
|
||||
"colorLabel": "Kleur",
|
||||
"tagRulesLabel": "Tagregels",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts met een van deze tags worden automatisch opgenomen, naast hosts die handmatig zijn toegevoegd.",
|
||||
"fleetCreated": "Vloot gecreëerd",
|
||||
"fleetUpdated": "Vloot bijgewerkt",
|
||||
"fleetDeleted": "Vloot verwijderd",
|
||||
"saveFailed": "Vloot kon niet worden gered",
|
||||
"deleteFailed": "Het verwijderen van de vloot is mislukt.",
|
||||
"loadFailed": "Het laden van de vloot is mislukt.",
|
||||
"deleteFleetTitle": "Vloot verwijderen",
|
||||
"deleteFleetDescription": "Weet je zeker dat je \"{{name}}\" wilt verwijderen? Hiermee worden de lidhosts zelf niet verwijderd.",
|
||||
"searchFleets": "Zoekvloten...",
|
||||
"noFleets": "Nog geen vloten. Maak er een aan om te beginnen.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Leden beheren",
|
||||
"manageMembersTitle": "Vlootleden beheren",
|
||||
"manageMembersDescription": "Voeg hosts toe aan of verwijder ze uit het statische lidmaatschap van deze vloot. Hosts die overeenkomen met tagregels zijn altijd inbegrepen.",
|
||||
"searchHosts": "Hosts zoeken...",
|
||||
"noHostsFound": "Geen hosts gevonden",
|
||||
"noMembers": "Deze vloot heeft nog geen leden. Voeg hosts toe of stel een tagregel in.",
|
||||
"membersLoadFailed": "Het laden van de vlootleden is mislukt.",
|
||||
"memberUpdateFailed": "Het bijwerken van het vlootlidmaatschap is mislukt.",
|
||||
"tabRun": "Voer het commando uit",
|
||||
"tabTransfer": "Bestanden overdragen",
|
||||
"tabInventory": "Inventaris",
|
||||
"openInventoryTab": "Openen in een volledig tabblad",
|
||||
"tabPackages": "Pakketten",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "Een opdracht is vereist.",
|
||||
"commandInputsLabel": "Commando-invoer",
|
||||
"commandSucceeded": "De opdracht is op alle hosts succesvol uitgevoerd.",
|
||||
"commandPartialFailure": "{{failed}} van {{total}} hosts mislukt",
|
||||
"commandFailed": "Het uitvoeren van de opdracht is mislukt.",
|
||||
"resultSuccess": "Succes",
|
||||
"resultFailed": "Mislukt",
|
||||
"push": "Duw",
|
||||
"pull": "Trekken",
|
||||
"remotePathLabel": "Externe route",
|
||||
"remotePathRequired": "Een externe verbinding is vereist.",
|
||||
"fileLabel": "Bestand",
|
||||
"fileRequired": "Een bestand is vereist.",
|
||||
"pushHint": "Het geselecteerde bestand wordt op elke host in de vloot naar precies dit pad geschreven.",
|
||||
"pullHint": "Het bestand op dit pad wordt van elke host gelezen en gedownload als één zipbestand, één map per host.",
|
||||
"transferComplete": "Overdracht voltooid",
|
||||
"transferFailed": "Overdracht mislukt",
|
||||
"refreshInventory": "Voorraad vernieuwen",
|
||||
"inventoryLoadFailed": "Het laden van de inventaris is mislukt.",
|
||||
"inventoryRefreshed": "Voorraad bijgewerkt",
|
||||
"inventoryRefreshFailed": "Het vernieuwen van de inventaris is mislukt.",
|
||||
"inventoryNoData": "Er zijn nog geen voorraadgegevens beschikbaar - vernieuw de pagina om ze te verzamelen.",
|
||||
"inventoryHost": "Gastheer",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} verzameld",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Boog",
|
||||
"inventoryUptime": "Beschikbaarheid",
|
||||
"inventoryPackageManager": "Pakketbeheerder",
|
||||
"inventoryCollectedAt": "Verzameld",
|
||||
"packageActionLabel": "Actie",
|
||||
"packageInstall": "Installeren",
|
||||
"packageRemove": "Verwijderen",
|
||||
"packageUpgradeAll": "Upgrade alle pakketten",
|
||||
"packageNameLabel": "Pakketnaam",
|
||||
"packageNameRequired": "Een pakketnaam is vereist.",
|
||||
"packageHint": "Vereist beheerrechten per host en verhoogt deze rechten met behulp van het opgeslagen sudo-wachtwoord van de host.",
|
||||
"shareFleet": "Deel vloot",
|
||||
"shareFleetTitle": "Delen \"{{name}}\"",
|
||||
"shareFleetDescription": "Deelt de huidige hosts van deze vloot met de geselecteerde gebruikers of rollen. Hosts die later aan de vloot worden toegevoegd, worden niet automatisch gedeeld; deel ze opnieuw om ze toe te voegen.",
|
||||
"shareSelectionRequired": "Selecteer ten minste één gebruiker of rol.",
|
||||
"fleetSharedSuccessfully": "Gedeelde {{shared}} van {{total}} host(s) in deze vloot",
|
||||
"failedToShareFleet": "Het delen van de vloot is mislukt.",
|
||||
"noMembersToShare": "Deze vloot heeft nog geen leden die de diensten van andere beheerders kunnen aanbieden."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Vloten"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Documenten bekijken",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Aantal uren tot de toegang verloopt",
|
||||
"expiry": {
|
||||
"never": "Nooit",
|
||||
"oneHour": "1 uur",
|
||||
"oneDay": "24 uur",
|
||||
"sevenDays": "7 dagen",
|
||||
"thirtyDays": "30 dagen",
|
||||
"custom": "Aangepast"
|
||||
},
|
||||
"expiryLabel": "Toegang verloopt",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Verbinden",
|
||||
"description": "Alleen open sessies: terminal, extern bureaublad, bestandsbeheer, tunnels en Docker. Geen toegang tot de hostconfiguratie."
|
||||
},
|
||||
"view": {
|
||||
"label": "Weergave",
|
||||
"description": "Maak verbinding en bekijk de hostconfiguratie. Geheime gegevens worden nooit weergegeven."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Bewerking",
|
||||
"description": "Bekijk en wijzig de instellingen van de host die geen authenticatie vereist. De SSH-authenticatie van de eigenaar blijft privé en alleen toegankelijk voor de eigenaar."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Beheren",
|
||||
"description": "Bewerken, de host delen met anderen, toegangsrechten wijzigen en toegang intrekken."
|
||||
}
|
||||
},
|
||||
"noMatches": "Geen overeenkomsten gevonden",
|
||||
"permissionLevelLabel": "Toegangsniveau",
|
||||
"rolesTab": "Rollen",
|
||||
"searchPlaceholder": "Gebruikers of rollen zoeken...",
|
||||
"shareButton": "Deel",
|
||||
"shareWithCount": "Delen ({{count}})",
|
||||
"usersTab": "Gebruikers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Navn",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Beskrivelse",
|
||||
"descriptionPlaceholder": "Valgfri beskrivelse",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} verter",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Suksess",
|
||||
"resultFailed": "Mislyktes",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "Fil",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kjerne",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Oppetid",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Fjern",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Koble til",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Rediger",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Brukere"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Floty",
|
||||
"createFleet": "Utwórz flotę",
|
||||
"createFleetTitle": "Utwórz flotę",
|
||||
"editFleetTitle": "Edytuj flotę",
|
||||
"createFleetDescription": "Flota to nazwana grupa hostów, na której możesz wykonywać akcje jak na jednym obiekcie.",
|
||||
"nameLabel": "Nazwa",
|
||||
"namePlaceholder": "np. Serwery WWW",
|
||||
"nameRequired": "Nazwa floty jest wymagana",
|
||||
"descriptionLabel": "Opis",
|
||||
"descriptionPlaceholder": "Opis opcjonalny",
|
||||
"colorLabel": "Kolor",
|
||||
"tagRulesLabel": "Reguły tagów",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosty z dowolnym z tych tagów są dołączane automatycznie, obok hostów dodanych ręcznie.",
|
||||
"fleetCreated": "Utworzono flotę",
|
||||
"fleetUpdated": "Zaktualizowano flotę",
|
||||
"fleetDeleted": "Usunięto flotę",
|
||||
"saveFailed": "Nie udało się zapisać floty",
|
||||
"deleteFailed": "Nie udało się usunąć floty",
|
||||
"loadFailed": "Nie udało się załadować flot",
|
||||
"deleteFleetTitle": "Usuń flotę",
|
||||
"deleteFleetDescription": "Czy na pewno chcesz usunąć „{{name}}”? Nie spowoduje to usunięcia samych hostów należących do floty.",
|
||||
"searchFleets": "Przeszukaj floty...",
|
||||
"noFleets": "Brak flot. Utwórz pierwszą, aby zacząć.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hostów",
|
||||
"manageMembers": "Zarządzaj członkami",
|
||||
"manageMembersTitle": "Zarządzaj członkami floty",
|
||||
"manageMembersDescription": "Przełączaj hosty do lub z członkostwa statycznego tej floty. Hosty dopasowane według reguł tagów są zawsze uwzględniane.",
|
||||
"searchHosts": "Wyszukaj hosty...",
|
||||
"noHostsFound": "Nie znaleziono żadnych hostów",
|
||||
"noMembers": "Ta flota nie ma jeszcze członków. Dodaj hosty lub ustaw regułę tagów.",
|
||||
"membersLoadFailed": "Nie udało się załadować członków floty",
|
||||
"memberUpdateFailed": "Nie udało się zaktualizować członkostwa we flocie",
|
||||
"tabRun": "Uruchom polecenie",
|
||||
"tabTransfer": "Przesyłanie plików",
|
||||
"tabInventory": "Inwentarz",
|
||||
"openInventoryTab": "Otwórz w pełnej karcie",
|
||||
"tabPackages": "Pakiety",
|
||||
"runOnFleet": "Uruchom na flocie",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "Wymagane jest polecenie",
|
||||
"commandInputsLabel": "Dane wejściowe polecenia",
|
||||
"commandSucceeded": "Polecenie powiodło się na wszystkich hostach",
|
||||
"commandPartialFailure": "Na {{failed}} z {{total}} hostów wystąpił błąd",
|
||||
"commandFailed": "Nie udało się uruchomić polecenia",
|
||||
"resultSuccess": "Powodzenie",
|
||||
"resultFailed": "Niepowodzenie",
|
||||
"push": "Wyślij",
|
||||
"pull": "Pobierz",
|
||||
"remotePathLabel": "Ścieżka zdalna",
|
||||
"remotePathRequired": "Wymagana jest ścieżka zdalna",
|
||||
"fileLabel": "Plik",
|
||||
"fileRequired": "Wymagany jest plik",
|
||||
"pushHint": "Wybrany plik zostanie zapisany dokładnie pod tą ścieżką na każdym hoście we flocie.",
|
||||
"pullHint": "Plik znajdujący się pod tą ścieżką jest odczytywany z każdego hosta i pobierany jako pojedynczy plik zip, jeden folder na hosta.",
|
||||
"transferComplete": "Transfer zakończony",
|
||||
"transferFailed": "Transfer nie powiódł się",
|
||||
"refreshInventory": "Odśwież inwentarz",
|
||||
"inventoryLoadFailed": "Nie udało się załadować inwentarza",
|
||||
"inventoryRefreshed": "Odświeżono inwentarz",
|
||||
"inventoryRefreshFailed": "Nie udało się odświeżyć inwentarza",
|
||||
"inventoryNoData": "Brak danych inwentarza. Odśwież, aby je zebrać.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} zebrano",
|
||||
"inventoryOs": "System operacyjny",
|
||||
"inventoryKernel": "Jądro",
|
||||
"inventoryArch": "Architektura",
|
||||
"inventoryUptime": "Czas działania",
|
||||
"inventoryPackageManager": "Menedżer pakietów",
|
||||
"inventoryCollectedAt": "Zebrano",
|
||||
"packageActionLabel": "Akcja",
|
||||
"packageInstall": "Zainstaluj",
|
||||
"packageRemove": "Usuń",
|
||||
"packageUpgradeAll": "Uaktualnij wszystkie pakiety",
|
||||
"packageNameLabel": "Nazwa pakietu",
|
||||
"packageNameRequired": "Wymagana jest nazwa pakietu",
|
||||
"packageHint": "Wymaga dostępu na poziomie zarządzania na każdym hoście i podnosi uprawnienia zapisanym hasłem sudo hosta.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Floty"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Zobacz dokumentację",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Godziny do wygaśnięcia dostępu",
|
||||
"expiry": {
|
||||
"never": "Nigdy",
|
||||
"oneHour": "1 godzina",
|
||||
"oneDay": "24 godziny",
|
||||
"sevenDays": "7 dni",
|
||||
"thirtyDays": "30 dni",
|
||||
"custom": "Niestandardowy"
|
||||
},
|
||||
"expiryLabel": "Wygaśnięcie dostępu",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Połączenie",
|
||||
"description": "Tylko otwieranie sesji: terminal, pulpit zdalny, menedżer plików, tunele i Docker. Bez dostępu do konfiguracji hosta."
|
||||
},
|
||||
"view": {
|
||||
"label": "Podgląd",
|
||||
"description": "Jak Połączenie, plus podgląd konfiguracji hosta. Sekrety nigdy nie są pokazywane."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edycja",
|
||||
"description": "Jak Podgląd, plus zmiana ustawień hosta niezwiązanych z uwierzytelnianiem. Uwierzytelnianie SSH właściciela pozostaje prywatne i dostępne tylko dla niego."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Zarządzanie",
|
||||
"description": "Jak Edycja, plus udostępnianie hosta innym, zmiana poziomów uprawnień i unieważnianie dostępu."
|
||||
}
|
||||
},
|
||||
"noMatches": "Nie znaleziono wyników",
|
||||
"permissionLevelLabel": "Poziom uprawnień",
|
||||
"rolesTab": "Role",
|
||||
"searchPlaceholder": "Wyszukaj użytkowników lub role...",
|
||||
"shareButton": "Udostępnij",
|
||||
"shareWithCount": "Udostępnij ({{count}})",
|
||||
"usersTab": "Użytkownicy"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remover",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Documentação",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Conectar",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Editar",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Nome",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Descrição",
|
||||
"descriptionPlaceholder": "Descrição opcional",
|
||||
"colorLabel": "Cor",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} máquinas",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Pesquisar hosts...",
|
||||
"noHostsFound": "Nenhum host encontrado",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Pacotes",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Sucesso",
|
||||
"resultFailed": "Falhou",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "Ficheiro",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transferência concluída",
|
||||
"transferFailed": "Transferência falhou",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "SO",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Tempo de Atividade",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Ação",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remover",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Ver documentação",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Horas até à expiração do acesso",
|
||||
"expiry": {
|
||||
"never": "Nunca",
|
||||
"oneHour": "1 hora",
|
||||
"oneDay": "24 horas",
|
||||
"sevenDays": "7 dias",
|
||||
"thirtyDays": "30 dias",
|
||||
"custom": "Personalizado"
|
||||
},
|
||||
"expiryLabel": "Expiração do acesso",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Ligar",
|
||||
"description": "Abrir apenas sessões: terminal, ambiente de trabalho remoto, gestor de ficheiros, túneis e Docker. Sem acesso à configuração do anfitrião."
|
||||
},
|
||||
"view": {
|
||||
"label": "Ver",
|
||||
"description": "Ligar e ver a configuração do anfitrião. Os segredos nunca são mostrados."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Editar",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Gerir",
|
||||
"description": "Editar, além de partilhar o anfitrião com outras pessoas, alterar os níveis de permissão e revogar o acesso."
|
||||
}
|
||||
},
|
||||
"noMatches": "Nenhuma correspondência encontrada",
|
||||
"permissionLevelLabel": "Nível de permissão",
|
||||
"rolesTab": "Funções",
|
||||
"searchPlaceholder": "Procurar utilizadores ou funções...",
|
||||
"shareButton": "Partilhar",
|
||||
"shareWithCount": "Partilhar ({{count}})",
|
||||
"usersTab": "Utilizadores"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "Vizualizați flotele",
|
||||
"description": "Vedeți flotele, membrii și inventarul acestora."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Gestionați flotele",
|
||||
"description": "Creați, editați și ștergeți flote, modificați calitatea de membru și partajați flote cu alți utilizatori sau roluri."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Execută acțiuni de flotă",
|
||||
"description": "Execută comenzi și acțiuni de pachete într-o flotă și transferă fișiere către și de la gazdele membre."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Flote",
|
||||
"createFleet": "Creați o flotă",
|
||||
"createFleetTitle": "Creați o flotă",
|
||||
"editFleetTitle": "Editare flotă",
|
||||
"createFleetDescription": "O flotă este un grup denumit de gazde împotriva cărora puteți rula acțiuni ca o unitate.",
|
||||
"nameLabel": "Nume",
|
||||
"namePlaceholder": "de exemplu, servere web",
|
||||
"nameRequired": "Numele flotei este obligatoriu",
|
||||
"descriptionLabel": "Descriere",
|
||||
"descriptionPlaceholder": "Descriere opțională",
|
||||
"colorLabel": "Culoare",
|
||||
"tagRulesLabel": "Reguli de etichetare",
|
||||
"tagRulesPlaceholder": "prod-web, margine",
|
||||
"tagRulesHint": "Gazdele cu oricare dintre aceste etichete sunt incluse automat, pe lângă gazdele adăugate manual.",
|
||||
"fleetCreated": "Flotă creată",
|
||||
"fleetUpdated": "Flota actualizată",
|
||||
"fleetDeleted": "Flota a fost ștearsă",
|
||||
"saveFailed": "Nu s-a putut salva flota",
|
||||
"deleteFailed": "Nu s-a putut șterge flota",
|
||||
"loadFailed": "Nu s-au putut încărca flotele",
|
||||
"deleteFleetTitle": "Ștergeți flota",
|
||||
"deleteFleetDescription": "Sigur vrei să ștergi „{{name}}”? Această acțiune nu șterge gazdele membre în sine.",
|
||||
"searchFleets": "Căutați flote...",
|
||||
"noFleets": "Încă nu există flote. Creați una pentru a începe.",
|
||||
"memberCount": "gazdă {{count}}",
|
||||
"memberCount_other": "gazde {{count}}",
|
||||
"manageMembers": "Gestionați membrii",
|
||||
"manageMembersTitle": "Gestionați membrii flotei",
|
||||
"manageMembersDescription": "Activează sau dezactivează includerea sau dezactivarea gazdelor în calitate de membru static al acestei flote. Gazdele care corespund regulilor de etichetare sunt întotdeauna incluse.",
|
||||
"searchHosts": "Căutați gazde...",
|
||||
"noHostsFound": "Nu s-au găsit gazde",
|
||||
"noMembers": "Această flotă nu are încă membri. Adăugați gazde sau setați o regulă de etichetare.",
|
||||
"membersLoadFailed": "Nu s-au putut încărca membrii flotei",
|
||||
"memberUpdateFailed": "Nu s-a putut actualiza calitatea de membru al flotei",
|
||||
"tabRun": "Comandă de rulare",
|
||||
"tabTransfer": "Transfer fișiere",
|
||||
"tabInventory": "Inventar",
|
||||
"openInventoryTab": "Deschideți în fila completă",
|
||||
"tabPackages": "Pachete",
|
||||
"runOnFleet": "Rulați pe flotă",
|
||||
"commandPlaceholder": "timp de funcționare && df -h",
|
||||
"commandRequired": "Este necesară o comandă",
|
||||
"commandInputsLabel": "Intrări de comandă",
|
||||
"commandSucceeded": "Comanda a reușit pe toate gazdele",
|
||||
"commandPartialFailure": "{{failed}} din {{total}} gazde au eșuat",
|
||||
"commandFailed": "Comanda nu a putut fi executată",
|
||||
"resultSuccess": "Succes",
|
||||
"resultFailed": "Eșuat",
|
||||
"push": "Apăsaţi",
|
||||
"pull": "Trage",
|
||||
"remotePathLabel": "Cale la distanță",
|
||||
"remotePathRequired": "Este necesară o cale la distanță",
|
||||
"fileLabel": "Fişier",
|
||||
"fileRequired": "Este necesar un fișier",
|
||||
"pushHint": "Fișierul selectat este scris exact pe această cale pe fiecare gazdă din flotă.",
|
||||
"pullHint": "Fișierul de pe această cale este citit de pe fiecare gazdă și descărcat ca un singur fișier zip, câte un folder per gazdă.",
|
||||
"transferComplete": "Transfer finalizat",
|
||||
"transferFailed": "Transferul a eșuat",
|
||||
"refreshInventory": "Reîmprospătați inventarul",
|
||||
"inventoryLoadFailed": "Nu s-a putut încărca inventarul",
|
||||
"inventoryRefreshed": "Inventarul a fost actualizat",
|
||||
"inventoryRefreshFailed": "Nu s-a putut actualiza inventarul",
|
||||
"inventoryNoData": "Nu există încă date despre inventar - actualizați pagina pentru a le colecta.",
|
||||
"inventoryHost": "Gazdă",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} colectate",
|
||||
"inventoryOs": "Sistem de operare",
|
||||
"inventoryKernel": "Nucleu",
|
||||
"inventoryArch": "Arc",
|
||||
"inventoryUptime": "Timp de funcționare",
|
||||
"inventoryPackageManager": "Manager de pachete",
|
||||
"inventoryCollectedAt": "Colectate",
|
||||
"packageActionLabel": "Acţiune",
|
||||
"packageInstall": "Instala",
|
||||
"packageRemove": "Elimina",
|
||||
"packageUpgradeAll": "Actualizați toate pachetele",
|
||||
"packageNameLabel": "Numele pachetului",
|
||||
"packageNameRequired": "Este necesar un nume de pachet",
|
||||
"packageHint": "Necesită acces la nivel de administrare per gazdă și ridică privilegiile folosind parola sudo stocată a gazdei.",
|
||||
"shareFleet": "Flotă partajată",
|
||||
"shareFleetTitle": "Distribuie „{{name}}”",
|
||||
"shareFleetDescription": "Partajează gazdele membre actuale ale acestei flote cu utilizatorii sau rolurile selectate. Gazdele adăugate ulterior la flotă nu sunt partajate automat - partajați din nou pentru a le include.",
|
||||
"shareSelectionRequired": "Selectați cel puțin un utilizator sau un rol",
|
||||
"fleetSharedSuccessfully": "Gazdă(e) {{shared}} partajată din {{total}} din această flotă",
|
||||
"failedToShareFleet": "Nu s-a putut partaja flota",
|
||||
"noMembersToShare": "Această flotă nu are încă gazde membre de partajat."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Flote"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Vizualizați documentele",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Ore până la expirarea accesului",
|
||||
"expiry": {
|
||||
"never": "Nu",
|
||||
"oneHour": "1 oră",
|
||||
"oneDay": "24 de ore",
|
||||
"sevenDays": "7 zile",
|
||||
"thirtyDays": "30 de zile",
|
||||
"custom": "Personalizat"
|
||||
},
|
||||
"expiryLabel": "Expirarea accesului",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Conectează-te",
|
||||
"description": "Deschideți doar sesiunile: terminal, desktop la distanță, manager de fișiere, tuneluri și Docker. Fără acces la configurația gazdei."
|
||||
},
|
||||
"view": {
|
||||
"label": "Vedere",
|
||||
"description": "Conectează-te și vezi configurația gazdei. Secretele nu sunt afișate niciodată."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edita",
|
||||
"description": "Vizualizați și modificați setările gazdei fără autentificare. Autentificarea SSH a proprietarului rămâne privată și exclusiv pentru proprietar."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Gestionează",
|
||||
"description": "Editați, plus partajați gazda cu alții, modificați nivelurile de permisiune și revocați accesul."
|
||||
}
|
||||
},
|
||||
"noMatches": "Nu s-au găsit potriviri",
|
||||
"permissionLevelLabel": "Nivel de permisiune",
|
||||
"rolesTab": "Roluri",
|
||||
"searchPlaceholder": "Căutați utilizatori sau roluri...",
|
||||
"shareButton": "Distribuie",
|
||||
"shareWithCount": "Distribuie ({{count}})",
|
||||
"usersTab": "Utilizatori"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Название",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Описание",
|
||||
"descriptionPlaceholder": "Необязательное описание",
|
||||
"colorLabel": "Цвет",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} хостов",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Поиск хостов...",
|
||||
"noHostsFound": "Хосты не найдены",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Пакеты",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Успех",
|
||||
"resultFailed": "Ошибка",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "Файл",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Передача завершена",
|
||||
"transferFailed": "Ошибка передачи",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Хост",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "ОС",
|
||||
"inventoryKernel": "Ядро",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Время работы",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Действие",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Удалить",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Документация",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Часов до окончания срока доступа",
|
||||
"expiry": {
|
||||
"never": "Никогда",
|
||||
"oneHour": "1 час",
|
||||
"oneDay": "24 часа",
|
||||
"sevenDays": "7 дней",
|
||||
"thirtyDays": "30 дней",
|
||||
"custom": "Настроить"
|
||||
},
|
||||
"expiryLabel": "Срок действия доступа",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Подключение",
|
||||
"description": "Только открытие сеансов: терминал, удалённый рабочий стол, файловый менеджер, туннели и Docker. Без доступа к конфигурации хоста."
|
||||
},
|
||||
"view": {
|
||||
"label": "Просмотр",
|
||||
"description": "Подключение и просмотр конфигурации хоста. Секреты не отображаются."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Редактирование",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Управление",
|
||||
"description": "Редактирование, а также предоставление доступа другим, изменение уровней доступа и отзыв доступа."
|
||||
}
|
||||
},
|
||||
"noMatches": "Совпадений не найдено",
|
||||
"permissionLevelLabel": "Уровень доступа",
|
||||
"rolesTab": "Роли",
|
||||
"searchPlaceholder": "Поиск пользователей или ролей...",
|
||||
"shareButton": "Поделиться",
|
||||
"shareWithCount": "Поделиться ({{count}})",
|
||||
"usersTab": "Пользователи"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remove",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connect",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remove",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connect",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remove",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connect",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Ad",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Açıklama",
|
||||
"descriptionPlaceholder": "İsteğe bağlı açıklama",
|
||||
"colorLabel": "Renk",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} ana makine",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Host ara...",
|
||||
"noHostsFound": "Hiç sunucu bulunamadı",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Paketler",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Başarılı",
|
||||
"resultFailed": "Başarısız",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "Dosya",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Aktarım tamamlandı",
|
||||
"transferFailed": "Aktarım başarısız",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Ana Bilgisayar",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "İşletim Sistemi",
|
||||
"inventoryKernel": "Çekirdek",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Çalışma Süresi",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "İşlem",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Kaldır",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Dokümanları görüntüle",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Erişim süresi (saat)",
|
||||
"expiry": {
|
||||
"never": "Süresiz",
|
||||
"oneHour": "1 saat",
|
||||
"oneDay": "24 saat",
|
||||
"sevenDays": "7 gün",
|
||||
"thirtyDays": "30 gün",
|
||||
"custom": "Özel"
|
||||
},
|
||||
"expiryLabel": "Erişim süresi sonu",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Bağlan",
|
||||
"description": "Yalnızca oturum açar: terminal, uzak masaüstü, dosya yöneticisi, tüneller ve Docker. Sunucu yapılandırmasına erişim yok."
|
||||
},
|
||||
"view": {
|
||||
"label": "Görüntüle",
|
||||
"description": "Bağlan iznine ek olarak sunucu yapılandırmasını görebilir. Gizli bilgiler asla gösterilmez."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Düzenle",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Yönet",
|
||||
"description": "Düzenleme iznine ek olarak sunucuyu başkalarıyla paylaşabilir, izin düzeylerini değiştirebilir ve erişimi iptal edebilir."
|
||||
}
|
||||
},
|
||||
"noMatches": "Eşleşme bulunamadı",
|
||||
"permissionLevelLabel": "İzin düzeyi",
|
||||
"rolesTab": "Roller",
|
||||
"searchPlaceholder": "Kullanıcı veya rol ara...",
|
||||
"shareButton": "Paylaş",
|
||||
"shareWithCount": "Paylaş ({{count}})",
|
||||
"usersTab": "Kullanıcılar"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"colorLabel": "Color",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} hosts",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Search hosts...",
|
||||
"noHostsFound": "No hosts found",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Packages",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Success",
|
||||
"resultFailed": "Failed",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "File",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Transfer complete",
|
||||
"transferFailed": "Transfer failed",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Host",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "OS",
|
||||
"inventoryKernel": "Kernel",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Uptime",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Action",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Remove",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "View docs",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Hours until access expires",
|
||||
"expiry": {
|
||||
"never": "Never",
|
||||
"oneHour": "1 hour",
|
||||
"oneDay": "24 hours",
|
||||
"sevenDays": "7 days",
|
||||
"thirtyDays": "30 days",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"expiryLabel": "Access expiry",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Connect",
|
||||
"description": "Open sessions only: terminal, remote desktop, file manager, tunnels and Docker. No access to the host configuration."
|
||||
},
|
||||
"view": {
|
||||
"label": "View",
|
||||
"description": "Connect, plus see the host configuration. Secrets are never shown."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Manage",
|
||||
"description": "Edit, plus share the host with others, change permission levels and revoke access."
|
||||
}
|
||||
},
|
||||
"noMatches": "No matches found",
|
||||
"permissionLevelLabel": "Permission level",
|
||||
"rolesTab": "Roles",
|
||||
"searchPlaceholder": "Search users or roles...",
|
||||
"shareButton": "Share",
|
||||
"shareWithCount": "Share ({{count}})",
|
||||
"usersTab": "Users"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "Tên",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "Mô tả",
|
||||
"descriptionPlaceholder": "Mô tả tùy chọn",
|
||||
"colorLabel": "Màu sắc",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} máy chủ",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "Tìm kiếm máy chủ...",
|
||||
"noHostsFound": "Không tìm thấy máy chủ",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "Gói",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "Thành công",
|
||||
"resultFailed": "Thất bại",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "Tệp",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "Truyền tải hoàn tất",
|
||||
"transferFailed": "Truyền tải thất bại",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "Máy chủ",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "Hệ điều hành",
|
||||
"inventoryKernel": "Nhân",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "Thời gian hoạt động",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "Hành động",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "Xóa",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "Xem tài liệu",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "Số giờ cho đến khi hết hạn truy cập",
|
||||
"expiry": {
|
||||
"never": "Không bao giờ",
|
||||
"oneHour": "1 giờ",
|
||||
"oneDay": "24 giờ",
|
||||
"sevenDays": "7 ngày",
|
||||
"thirtyDays": "30 ngày",
|
||||
"custom": "Tùy chỉnh"
|
||||
},
|
||||
"expiryLabel": "Hết hạn truy cập",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "Kết nối",
|
||||
"description": "Chỉ mở phiên: terminal, remote desktop, trình quản lý file, tunnel và Docker. Không truy cập cấu hình máy chủ."
|
||||
},
|
||||
"view": {
|
||||
"label": "Xem",
|
||||
"description": "Kết nối, cùng xem cấu hình máy chủ. Bí mật không bao giờ được hiển thị."
|
||||
},
|
||||
"edit": {
|
||||
"label": "Chỉnh sửa",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "Quản lý",
|
||||
"description": "Chỉnh sửa, và chia sẻ máy chủ với người khác, thay đổi cấp độ quyền và thu hồi quyền truy cập."
|
||||
}
|
||||
},
|
||||
"noMatches": "Không tìm thấy kết quả phù hợp",
|
||||
"permissionLevelLabel": "Cấp độ quyền",
|
||||
"rolesTab": "Vai trò",
|
||||
"searchPlaceholder": "Tìm kiếm người dùng hoặc vai trò...",
|
||||
"shareButton": "Chia sẻ",
|
||||
"shareWithCount": "Chia sẻ ({{count}})",
|
||||
"usersTab": "Người dùng"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "舰队是一个命名的主机组,你可以将其作为一个整体来执行操作。",
|
||||
"nameLabel": "名称",
|
||||
"namePlaceholder": "例如:Web 服务器",
|
||||
"nameRequired": "需要填写舰队名称。",
|
||||
"descriptionLabel": "描述",
|
||||
"descriptionPlaceholder": "可选描述",
|
||||
"colorLabel": "颜色",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "舰队已创建",
|
||||
"fleetUpdated": "舰队已更新",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "保存舰队失败",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} 台主机",
|
||||
"memberCount_other": "{{count}} 台主机",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "管理舰队成员",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "搜索主机...",
|
||||
"noHostsFound": "未找到主机",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "清单",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "软件包",
|
||||
"runOnFleet": "在舰队上运行",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{total}} 台主机中有 {{failed}} 台失败",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "成功",
|
||||
"resultFailed": "失败",
|
||||
"push": "推送",
|
||||
"pull": "拉取",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "文件",
|
||||
"fileRequired": "需要选择文件",
|
||||
"pushHint": "选定的文件将被写入舰队中每台主机上的指定路径。",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "传输完成",
|
||||
"transferFailed": "传输失败",
|
||||
"refreshInventory": "刷新清单",
|
||||
"inventoryLoadFailed": "加载清单失败",
|
||||
"inventoryRefreshed": "清单已刷新",
|
||||
"inventoryRefreshFailed": "刷新清单失败",
|
||||
"inventoryNoData": "暂无清单数据 - 请刷新页面以获取数据。",
|
||||
"inventoryHost": "主机",
|
||||
"inventoryCollectedCount": "已收集 {{count}}/{{total}}",
|
||||
"inventoryOs": "操作系统",
|
||||
"inventoryKernel": "内核",
|
||||
"inventoryArch": "架构",
|
||||
"inventoryUptime": "运行时间",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "操作",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "移除",
|
||||
"packageUpgradeAll": "升级所有软件包",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "查看文档",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "过期前的小时数",
|
||||
"expiry": {
|
||||
"never": "永不过期",
|
||||
"oneHour": "1 小时",
|
||||
"oneDay": "24 小时",
|
||||
"sevenDays": "7 天",
|
||||
"thirtyDays": "30 天",
|
||||
"custom": "自定义"
|
||||
},
|
||||
"expiryLabel": "访问有效期",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "连接",
|
||||
"description": "仅打开会话:终端、远程桌面、文件管理器、隧道和 Docker。无权访问主机配置。"
|
||||
},
|
||||
"view": {
|
||||
"label": "查看",
|
||||
"description": "连接,并查看主机配置。凭据信息从不显示。"
|
||||
},
|
||||
"edit": {
|
||||
"label": "编辑",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "管理",
|
||||
"description": "编辑,并与其他用户共享主机、更改权限级别及撤销访问。"
|
||||
}
|
||||
},
|
||||
"noMatches": "未找到匹配项",
|
||||
"permissionLevelLabel": "权限级别",
|
||||
"rolesTab": "角色",
|
||||
"searchPlaceholder": "搜索用户或角色...",
|
||||
"shareButton": "共享",
|
||||
"shareWithCount": "共享({{count}})",
|
||||
"usersTab": "用户"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"permissions": {
|
||||
"view": {
|
||||
"title": "View fleets",
|
||||
"description": "See fleets, their members and inventory."
|
||||
},
|
||||
"manage": {
|
||||
"title": "Manage fleets",
|
||||
"description": "Create, edit and delete fleets, change membership, and share fleets with other users or roles."
|
||||
},
|
||||
"execute": {
|
||||
"title": "Run fleet actions",
|
||||
"description": "Run commands and package actions across a fleet, and transfer files to and from its member hosts."
|
||||
}
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
"fleets": {
|
||||
"title": "Fleets",
|
||||
"createFleet": "Create Fleet",
|
||||
"createFleetTitle": "Create Fleet",
|
||||
"editFleetTitle": "Edit Fleet",
|
||||
"createFleetDescription": "A fleet is a named group of hosts you can run actions against as a unit.",
|
||||
"nameLabel": "名稱",
|
||||
"namePlaceholder": "e.g., Web Servers",
|
||||
"nameRequired": "Fleet name is required",
|
||||
"descriptionLabel": "說明",
|
||||
"descriptionPlaceholder": "選用說明",
|
||||
"colorLabel": "顏色",
|
||||
"tagRulesLabel": "Tag rules",
|
||||
"tagRulesPlaceholder": "prod-web, edge",
|
||||
"tagRulesHint": "Hosts with any of these tags are automatically included, in addition to hosts added manually.",
|
||||
"fleetCreated": "Fleet created",
|
||||
"fleetUpdated": "Fleet updated",
|
||||
"fleetDeleted": "Fleet deleted",
|
||||
"saveFailed": "Failed to save fleet",
|
||||
"deleteFailed": "Failed to delete fleet",
|
||||
"loadFailed": "Failed to load fleets",
|
||||
"deleteFleetTitle": "Delete Fleet",
|
||||
"deleteFleetDescription": "Are you sure you want to delete \"{{name}}\"? This does not delete the member hosts themselves.",
|
||||
"searchFleets": "Search fleets...",
|
||||
"noFleets": "No fleets yet. Create one to get started.",
|
||||
"memberCount": "{{count}} host",
|
||||
"memberCount_other": "{{count}} 台主機",
|
||||
"manageMembers": "Manage Members",
|
||||
"manageMembersTitle": "Manage Fleet Members",
|
||||
"manageMembersDescription": "Toggle hosts in or out of this fleet's static membership. Hosts matched by tag rules are always included.",
|
||||
"searchHosts": "搜尋主機...",
|
||||
"noHostsFound": "找不到主機",
|
||||
"noMembers": "This fleet has no members yet. Add hosts or set a tag rule.",
|
||||
"membersLoadFailed": "Failed to load fleet members",
|
||||
"memberUpdateFailed": "Failed to update fleet membership",
|
||||
"tabRun": "Run Command",
|
||||
"tabTransfer": "Transfer Files",
|
||||
"tabInventory": "Inventory",
|
||||
"openInventoryTab": "Open in full tab",
|
||||
"tabPackages": "套件",
|
||||
"runOnFleet": "Run on Fleet",
|
||||
"commandPlaceholder": "uptime && df -h",
|
||||
"commandRequired": "A command is required",
|
||||
"commandInputsLabel": "Command inputs",
|
||||
"commandSucceeded": "Command succeeded on all hosts",
|
||||
"commandPartialFailure": "{{failed}} of {{total}} hosts failed",
|
||||
"commandFailed": "Failed to run command",
|
||||
"resultSuccess": "成功",
|
||||
"resultFailed": "失敗",
|
||||
"push": "Push",
|
||||
"pull": "Pull",
|
||||
"remotePathLabel": "Remote path",
|
||||
"remotePathRequired": "A remote path is required",
|
||||
"fileLabel": "檔案",
|
||||
"fileRequired": "A file is required",
|
||||
"pushHint": "The selected file is written to this exact path on every host in the fleet.",
|
||||
"pullHint": "The file at this path is read from every host and downloaded as a single zip, one folder per host.",
|
||||
"transferComplete": "傳送完成",
|
||||
"transferFailed": "傳送失敗",
|
||||
"refreshInventory": "Refresh Inventory",
|
||||
"inventoryLoadFailed": "Failed to load inventory",
|
||||
"inventoryRefreshed": "Inventory refreshed",
|
||||
"inventoryRefreshFailed": "Failed to refresh inventory",
|
||||
"inventoryNoData": "No inventory data yet - refresh to collect it.",
|
||||
"inventoryHost": "主機",
|
||||
"inventoryCollectedCount": "{{count}}/{{total}} collected",
|
||||
"inventoryOs": "作業系統",
|
||||
"inventoryKernel": "核心",
|
||||
"inventoryArch": "Arch",
|
||||
"inventoryUptime": "運行時間",
|
||||
"inventoryPackageManager": "Package manager",
|
||||
"inventoryCollectedAt": "Collected",
|
||||
"packageActionLabel": "動作",
|
||||
"packageInstall": "Install",
|
||||
"packageRemove": "移除",
|
||||
"packageUpgradeAll": "Upgrade all packages",
|
||||
"packageNameLabel": "Package name",
|
||||
"packageNameRequired": "A package name is required",
|
||||
"packageHint": "Requires manage-level access per host and elevates using the host's stored sudo password.",
|
||||
"shareFleet": "Share Fleet",
|
||||
"shareFleetTitle": "Share \"{{name}}\"",
|
||||
"shareFleetDescription": "Shares this fleet's current member hosts with the selected users or roles. Hosts added to the fleet later are not shared automatically - share again to include them.",
|
||||
"shareSelectionRequired": "Select at least one user or role",
|
||||
"fleetSharedSuccessfully": "Shared {{shared}} of {{total}} host(s) in this fleet",
|
||||
"failedToShareFleet": "Failed to share fleet",
|
||||
"noMembersToShare": "This fleet has no member hosts to share yet."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"fleets": "Fleets"
|
||||
},
|
||||
"hosts": {
|
||||
"docsLink": "檢視文件",
|
||||
"sharing": {
|
||||
"customHoursPlaceholder": "存取到期前的小時數",
|
||||
"expiry": {
|
||||
"never": "永不",
|
||||
"oneHour": "1 小時",
|
||||
"oneDay": "24 小時",
|
||||
"sevenDays": "7 天",
|
||||
"thirtyDays": "30 天",
|
||||
"custom": "自訂"
|
||||
},
|
||||
"expiryLabel": "存取期限",
|
||||
"levels": {
|
||||
"connect": {
|
||||
"label": "連線",
|
||||
"description": "僅開啟工作階段:終端機、遠端桌面、檔案管理員、通道和 Docker。無法存取主機設定。"
|
||||
},
|
||||
"view": {
|
||||
"label": "檢視",
|
||||
"description": "連線,並可查看主機設定。機密資料絕不顯示。"
|
||||
},
|
||||
"edit": {
|
||||
"label": "編輯",
|
||||
"description": "View, plus modify non-authentication host settings. The owner's SSH authentication stays private and owner-only."
|
||||
},
|
||||
"manage": {
|
||||
"label": "管理",
|
||||
"description": "編輯,並可與他人分享主機、變更權限層級及撤銷存取權。"
|
||||
}
|
||||
},
|
||||
"noMatches": "找不到符合項目",
|
||||
"permissionLevelLabel": "權限層級",
|
||||
"rolesTab": "角色",
|
||||
"searchPlaceholder": "搜尋使用者或角色...",
|
||||
"shareButton": "分享",
|
||||
"shareWithCount": "分享 ({{count}})",
|
||||
"usersTab": "使用者"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"id": "fleets",
|
||||
"name": "Fleets",
|
||||
"version": "1.0.0",
|
||||
"description": "Group hosts into fleets and run commands, package actions and file transfers on all of them at once.",
|
||||
"author": {
|
||||
"name": "Termix"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Termix-SSH/Termix",
|
||||
"category": "Infrastructure",
|
||||
"icon": "Boxes",
|
||||
"engine": {
|
||||
"termix": ">=2.9.0",
|
||||
"api": "1"
|
||||
},
|
||||
"capabilities": [
|
||||
"hosts:read",
|
||||
"hosts:write",
|
||||
"credentials:use",
|
||||
"ssh:connect",
|
||||
"db:own",
|
||||
"network:serve",
|
||||
"ui:surface"
|
||||
],
|
||||
"optionalDependencies": {
|
||||
"snippets": "^1.0.0"
|
||||
},
|
||||
"provides": [
|
||||
{
|
||||
"service": "fleets.access",
|
||||
"version": "1.0.0",
|
||||
"permission": "fleets.view"
|
||||
}
|
||||
],
|
||||
"locales": "locales",
|
||||
"platforms": ["linux", "win32", "darwin"],
|
||||
"contributes": {
|
||||
"tabs": [
|
||||
{
|
||||
"id": "fleet-inventory",
|
||||
"titleKey": "nav.fleets",
|
||||
"icon": "Boxes",
|
||||
"openFrom": ["rail"]
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"id": "fleets",
|
||||
"titleKey": "nav.fleets",
|
||||
"icon": "Boxes"
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"name": "view",
|
||||
"titleKey": "permissions.view.title",
|
||||
"descriptionKey": "permissions.view.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "manage",
|
||||
"titleKey": "permissions.manage.title",
|
||||
"descriptionKey": "permissions.manage.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "execute",
|
||||
"titleKey": "permissions.execute.title",
|
||||
"descriptionKey": "permissions.execute.description",
|
||||
"defaultRoles": ["user"]
|
||||
}
|
||||
],
|
||||
"uses": ["snippets.extractInputs"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
-- fleets 0001: adopt_fleets
|
||||
-- Generated by termix-plugin migrations. Review before committing.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `fleets` (
|
||||
`id` int AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` varchar(255) NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`description` text,
|
||||
`color` text,
|
||||
`icon` text,
|
||||
`tag_rules` text,
|
||||
`sync_id` varchar(255) UNIQUE,
|
||||
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
);
|
||||
RENAME TABLE `fleets` TO `p_fleets_fleets`;
|
||||
CREATE TABLE IF NOT EXISTS `fleet_members` (
|
||||
`id` int AUTO_INCREMENT PRIMARY KEY,
|
||||
`fleet_id` int NOT NULL,
|
||||
`host_id` int NOT NULL,
|
||||
`added_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
FOREIGN KEY (`fleet_id`) REFERENCES `p_fleets_fleets` (`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE
|
||||
);
|
||||
RENAME TABLE `fleet_members` TO `p_fleets_members`;
|
||||
CREATE TABLE IF NOT EXISTS `fleet_inventory` (
|
||||
`id` int AUTO_INCREMENT PRIMARY KEY,
|
||||
`host_id` int NOT NULL,
|
||||
`user_id` varchar(255) NOT NULL,
|
||||
`os_pretty_name` text,
|
||||
`kernel` text,
|
||||
`architecture` text,
|
||||
`hostname` text,
|
||||
`uptime_seconds` int,
|
||||
`ip` text,
|
||||
`package_manager` text,
|
||||
`collected_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
FOREIGN KEY (`host_id`) REFERENCES `ssh_data` (`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
);
|
||||
RENAME TABLE `fleet_inventory` TO `p_fleets_inventory`;
|
||||
@@ -0,0 +1,46 @@
|
||||
-- fleets 0001: adopt_fleets
|
||||
-- Generated by termix-plugin migrations. Review before committing.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "fleets" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"color" text,
|
||||
"icon" text,
|
||||
"tag_rules" text,
|
||||
"sync_id" varchar(255) UNIQUE,
|
||||
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
ALTER TABLE "fleets" RENAME TO "p_fleets_fleets";
|
||||
CREATE TABLE IF NOT EXISTS "fleet_members" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"fleet_id" integer NOT NULL,
|
||||
"host_id" integer NOT NULL,
|
||||
"added_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("fleet_id") REFERENCES "p_fleets_fleets" ("id") ON DELETE CASCADE,
|
||||
FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_members_fleet_host" ON "fleet_members" ("fleet_id", "host_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_fleet_members_host" ON "fleet_members" ("host_id");
|
||||
ALTER TABLE "fleet_members" RENAME TO "p_fleets_members";
|
||||
CREATE TABLE IF NOT EXISTS "fleet_inventory" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"host_id" integer NOT NULL,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"os_pretty_name" text,
|
||||
"kernel" text,
|
||||
"architecture" text,
|
||||
"hostname" text,
|
||||
"uptime_seconds" integer,
|
||||
"ip" text,
|
||||
"package_manager" text,
|
||||
"collected_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_inventory_host" ON "fleet_inventory" ("host_id", "user_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_fleet_inventory_user" ON "fleet_inventory" ("user_id");
|
||||
ALTER TABLE "fleet_inventory" RENAME TO "p_fleets_inventory";
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"version": 1,
|
||||
"tables": {
|
||||
"fleets": {
|
||||
"columns": {
|
||||
"id": {
|
||||
"type": "id",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"type": "refUser",
|
||||
"length": 255,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"type": "text",
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"color": {
|
||||
"type": "text"
|
||||
},
|
||||
"icon": {
|
||||
"type": "text"
|
||||
},
|
||||
"tagRules": {
|
||||
"type": "text"
|
||||
},
|
||||
"syncId": {
|
||||
"type": "text",
|
||||
"unique": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"defaultNow": true
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"defaultNow": true
|
||||
}
|
||||
},
|
||||
"indexes": [],
|
||||
"adopts": "fleets"
|
||||
},
|
||||
"members": {
|
||||
"columns": {
|
||||
"id": {
|
||||
"type": "id",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"fleetId": {
|
||||
"type": "integer",
|
||||
"notNull": true
|
||||
},
|
||||
"hostId": {
|
||||
"type": "refHost",
|
||||
"notNull": true
|
||||
},
|
||||
"addedAt": {
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"defaultNow": true
|
||||
}
|
||||
},
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_fleet_members_fleet_host",
|
||||
"columns": ["fleetId", "hostId"],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"name": "idx_fleet_members_host",
|
||||
"columns": ["hostId"]
|
||||
}
|
||||
],
|
||||
"adopts": "fleet_members"
|
||||
},
|
||||
"inventory": {
|
||||
"columns": {
|
||||
"id": {
|
||||
"type": "id",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"hostId": {
|
||||
"type": "refHost",
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"type": "refUser",
|
||||
"length": 255,
|
||||
"notNull": true
|
||||
},
|
||||
"osPrettyName": {
|
||||
"type": "text"
|
||||
},
|
||||
"kernel": {
|
||||
"type": "text"
|
||||
},
|
||||
"architecture": {
|
||||
"type": "text"
|
||||
},
|
||||
"hostname": {
|
||||
"type": "text"
|
||||
},
|
||||
"uptimeSeconds": {
|
||||
"type": "integer"
|
||||
},
|
||||
"ip": {
|
||||
"type": "text"
|
||||
},
|
||||
"packageManager": {
|
||||
"type": "text"
|
||||
},
|
||||
"collectedAt": {
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"defaultNow": true
|
||||
}
|
||||
},
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_fleet_inventory_host",
|
||||
"columns": ["hostId", "userId"],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"name": "idx_fleet_inventory_user",
|
||||
"columns": ["userId"]
|
||||
}
|
||||
],
|
||||
"adopts": "fleet_inventory"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
-- fleets 0001: adopt_fleets
|
||||
-- Generated by termix-plugin migrations. Review before committing.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "fleets" (
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT,
|
||||
"user_id" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"color" text,
|
||||
"icon" text,
|
||||
"tag_rules" text,
|
||||
"sync_id" text UNIQUE,
|
||||
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
ALTER TABLE "fleets" RENAME TO "p_fleets_fleets";
|
||||
CREATE TABLE IF NOT EXISTS "fleet_members" (
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT,
|
||||
"fleet_id" integer NOT NULL,
|
||||
"host_id" integer NOT NULL,
|
||||
"added_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("fleet_id") REFERENCES "p_fleets_fleets" ("id") ON DELETE CASCADE,
|
||||
FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_members_fleet_host" ON "fleet_members" ("fleet_id", "host_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_fleet_members_host" ON "fleet_members" ("host_id");
|
||||
ALTER TABLE "fleet_members" RENAME TO "p_fleets_members";
|
||||
CREATE TABLE IF NOT EXISTS "fleet_inventory" (
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT,
|
||||
"host_id" integer NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"os_pretty_name" text,
|
||||
"kernel" text,
|
||||
"architecture" text,
|
||||
"hostname" text,
|
||||
"uptime_seconds" integer,
|
||||
"ip" text,
|
||||
"package_manager" text,
|
||||
"collected_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("host_id") REFERENCES "ssh_data" ("id") ON DELETE CASCADE,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_fleet_inventory_host" ON "fleet_inventory" ("host_id", "user_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_fleet_inventory_user" ON "fleet_inventory" ("user_id");
|
||||
ALTER TABLE "fleet_inventory" RENAME TO "p_fleets_inventory";
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@termix-plugin/fleets",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "termix-plugin build",
|
||||
"test": "termix-plugin test",
|
||||
"typecheck": "tsc -p .",
|
||||
"validate": "termix-plugin validate"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"jszip": "^3.10.2",
|
||||
"lucide-react": "^1.33.0",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@termix/plugin-sdk": "^1.0.0",
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"better-sqlite3": "^13.0.3",
|
||||
"jsdom": "^30.0.1",
|
||||
"vitest": "^4.1.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@termix/plugin-sdk": "^1.0.0",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"express": "^5.2.1",
|
||||
"multer": "^2.3.0",
|
||||
"react": "^19.2.8",
|
||||
"sonner": "^2.0.8",
|
||||
"ssh2": "^1.17.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { Router } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import { fleets, fleetMembers, fleetInventory } from "./tables.js";
|
||||
import { createFleetRepository } from "./repository.js";
|
||||
import { registerFleetRoutes } from "./routes.js";
|
||||
|
||||
/** What other plugins get from ctx.services.get("fleets.access", {userId}). */
|
||||
export interface FleetsService {
|
||||
/** The calling user's fleets, without resolving membership. */
|
||||
list: () => Promise<
|
||||
Array<{ id: number; name: string; color: string | null }>
|
||||
>;
|
||||
/** A fleet's effective member hosts (static membership union tag matches). */
|
||||
members: (
|
||||
fleetId: number,
|
||||
) => Promise<Array<{ id: number; name: string | null; ip: string }>>;
|
||||
/** Creates a fleet owned by the calling user. */
|
||||
create: (input: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
}) => Promise<{ id: number; name: string }>;
|
||||
/** Adds a host the calling user owns to a fleet's static membership. */
|
||||
addMember: (fleetId: number, hostId: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export async function activate(ctx: PluginContext) {
|
||||
const fleetsTable = await ctx.db.define(fleets);
|
||||
const membersTable = await ctx.db.define(fleetMembers);
|
||||
const inventoryTable = await ctx.db.define(fleetInventory);
|
||||
|
||||
const repo = createFleetRepository(
|
||||
ctx.db,
|
||||
ctx.hosts,
|
||||
fleetsTable,
|
||||
membersTable,
|
||||
inventoryTable,
|
||||
);
|
||||
|
||||
registerFleetRoutes(ctx.http.router<Router>(), repo, ctx);
|
||||
|
||||
// The service is gated on fleets.view; changing a fleet also needs
|
||||
// fleets.manage, as its routes do.
|
||||
async function requireManage(): Promise<void> {
|
||||
if (!(await ctx.rbac.has("manage"))) {
|
||||
throw new Error("Missing permission fleets.manage");
|
||||
}
|
||||
}
|
||||
|
||||
const service: FleetsService = {
|
||||
list: async () => {
|
||||
const userId = ctx.currentActor();
|
||||
if (!userId) return [];
|
||||
const rows = await repo.listByUser(userId);
|
||||
return rows.map((f) => ({ id: f.id, name: f.name, color: f.color }));
|
||||
},
|
||||
members: async (fleetId) => {
|
||||
const userId = ctx.currentActor();
|
||||
if (!userId) return [];
|
||||
const members = await repo.listEffectiveMembers(userId, fleetId);
|
||||
return members.map((m) => ({ id: m.id, name: m.name, ip: m.ip }));
|
||||
},
|
||||
create: async (input) => {
|
||||
const userId = ctx.currentActor();
|
||||
if (!userId) throw new Error("fleets.access.create needs an actor");
|
||||
await requireManage();
|
||||
const created = await repo.create(userId, input);
|
||||
return { id: created.id, name: created.name };
|
||||
},
|
||||
addMember: async (fleetId, hostId) => {
|
||||
const userId = ctx.currentActor();
|
||||
if (!userId) throw new Error("fleets.access.addMember needs an actor");
|
||||
await requireManage();
|
||||
// A host joins a fleet only if the caller can see it.
|
||||
if (!(await ctx.hosts.get(hostId))) throw new Error("Host not found");
|
||||
const fleet = await repo.findById(userId, fleetId);
|
||||
if (!fleet) throw new Error("Fleet not found");
|
||||
await repo.addMember(fleetId, hostId);
|
||||
},
|
||||
};
|
||||
ctx.services.provide("fleets.access", service);
|
||||
|
||||
ctx.log.info("Fleets routes mounted at /plugin-api/fleets");
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
// Everything above was registered through ctx and is disposed by core.
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import type { PluginDatabase, PluginHosts } from "@termix/plugin-sdk/backend";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// The tables come from ctx.db.define, which the SDK hands back untyped, and
|
||||
// the drizzle handle is the server's own. Typed at this module's edge instead.
|
||||
type Table = any;
|
||||
type Drizzle = any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface FleetRecord {
|
||||
id: number;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
color: string | null;
|
||||
icon: string | null;
|
||||
tagRules: string | null;
|
||||
syncId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface FleetCreateInput {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
color?: string | null;
|
||||
icon?: string | null;
|
||||
tagRules?: string[];
|
||||
}
|
||||
|
||||
export interface FleetUpdateInput {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
color?: string | null;
|
||||
icon?: string | null;
|
||||
tagRules?: string[];
|
||||
}
|
||||
|
||||
export interface FleetInventoryRecord {
|
||||
id: number;
|
||||
hostId: number;
|
||||
userId: string;
|
||||
osPrettyName: string | null;
|
||||
kernel: string | null;
|
||||
architecture: string | null;
|
||||
hostname: string | null;
|
||||
uptimeSeconds: number | null;
|
||||
ip: string | null;
|
||||
packageManager: string | null;
|
||||
collectedAt: string;
|
||||
}
|
||||
|
||||
export interface FleetInventoryInput {
|
||||
osPrettyName: string | null;
|
||||
kernel: string | null;
|
||||
architecture: string | null;
|
||||
hostname: string | null;
|
||||
uptimeSeconds: number | null;
|
||||
ip: string | null;
|
||||
packageManager: string | null;
|
||||
}
|
||||
|
||||
function parseTagRules(raw: string | null): string[] {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed)
|
||||
? parsed.filter((t): t is string => typeof t === "string")
|
||||
: [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export type FleetRepository = ReturnType<typeof createFleetRepository>;
|
||||
|
||||
/**
|
||||
* Fleets, membership and inventory, written without RETURNING so it runs the
|
||||
* same on all three engines: an insert is read back by its sync id, an
|
||||
* update by its own id.
|
||||
*/
|
||||
export function createFleetRepository(
|
||||
db: PluginDatabase,
|
||||
hosts: PluginHosts,
|
||||
fleetsTable: Table,
|
||||
membersTable: Table,
|
||||
inventoryTable: Table,
|
||||
) {
|
||||
const client = () => db.client<Drizzle>();
|
||||
|
||||
async function findById(
|
||||
userId: string,
|
||||
fleetId: number,
|
||||
): Promise<FleetRecord | null> {
|
||||
const drizzle = await client();
|
||||
const rows = await drizzle
|
||||
.select()
|
||||
.from(fleetsTable)
|
||||
.where(and(eq(fleetsTable.id, fleetId), eq(fleetsTable.userId, userId)))
|
||||
.limit(1);
|
||||
return (rows[0] as FleetRecord) ?? null;
|
||||
}
|
||||
|
||||
async function findBySyncId(syncId: string): Promise<FleetRecord> {
|
||||
const drizzle = await client();
|
||||
const rows = await drizzle
|
||||
.select()
|
||||
.from(fleetsTable)
|
||||
.where(eq(fleetsTable.syncId, syncId))
|
||||
.limit(1);
|
||||
return rows[0] as FleetRecord;
|
||||
}
|
||||
|
||||
async function listStaticMemberIds(fleetId: number): Promise<number[]> {
|
||||
const drizzle = await client();
|
||||
const rows = await drizzle
|
||||
.select({ hostId: membersTable.hostId })
|
||||
.from(membersTable)
|
||||
.where(eq(membersTable.fleetId, fleetId));
|
||||
return rows.map((r: { hostId: number }) => r.hostId);
|
||||
}
|
||||
|
||||
return {
|
||||
async listByUser(userId: string): Promise<FleetRecord[]> {
|
||||
const drizzle = await client();
|
||||
return drizzle
|
||||
.select()
|
||||
.from(fleetsTable)
|
||||
.where(eq(fleetsTable.userId, userId));
|
||||
},
|
||||
|
||||
findById,
|
||||
listStaticMemberIds,
|
||||
|
||||
async create(
|
||||
userId: string,
|
||||
input: FleetCreateInput,
|
||||
now = new Date().toISOString(),
|
||||
): Promise<FleetRecord> {
|
||||
const syncId = randomUUID();
|
||||
const drizzle = await client();
|
||||
await drizzle.insert(fleetsTable).values({
|
||||
userId,
|
||||
name: input.name,
|
||||
description: input.description ?? null,
|
||||
color: input.color ?? null,
|
||||
icon: input.icon ?? null,
|
||||
tagRules: input.tagRules ? JSON.stringify(input.tagRules) : null,
|
||||
syncId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
await db.persist();
|
||||
return findBySyncId(syncId);
|
||||
},
|
||||
|
||||
async update(
|
||||
userId: string,
|
||||
fleetId: number,
|
||||
input: FleetUpdateInput,
|
||||
now = new Date().toISOString(),
|
||||
): Promise<FleetRecord | null> {
|
||||
const existing = await findById(userId, fleetId);
|
||||
if (!existing) return null;
|
||||
|
||||
const drizzle = await client();
|
||||
await drizzle
|
||||
.update(fleetsTable)
|
||||
.set({
|
||||
name: input.name ?? existing.name,
|
||||
description:
|
||||
input.description === undefined
|
||||
? existing.description
|
||||
: input.description,
|
||||
color: input.color === undefined ? existing.color : input.color,
|
||||
icon: input.icon === undefined ? existing.icon : input.icon,
|
||||
tagRules:
|
||||
input.tagRules === undefined
|
||||
? existing.tagRules
|
||||
: JSON.stringify(input.tagRules),
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(
|
||||
and(eq(fleetsTable.id, fleetId), eq(fleetsTable.userId, userId)),
|
||||
);
|
||||
await db.persist();
|
||||
return findById(userId, fleetId);
|
||||
},
|
||||
|
||||
async delete(userId: string, fleetId: number): Promise<boolean> {
|
||||
const existing = await findById(userId, fleetId);
|
||||
if (!existing) return false;
|
||||
const drizzle = await client();
|
||||
await drizzle
|
||||
.delete(fleetsTable)
|
||||
.where(
|
||||
and(eq(fleetsTable.id, fleetId), eq(fleetsTable.userId, userId)),
|
||||
);
|
||||
await db.persist();
|
||||
return true;
|
||||
},
|
||||
|
||||
async addMember(
|
||||
fleetId: number,
|
||||
hostId: number,
|
||||
now = new Date().toISOString(),
|
||||
): Promise<void> {
|
||||
const drizzle = await client();
|
||||
const existing = await drizzle
|
||||
.select({ id: membersTable.id })
|
||||
.from(membersTable)
|
||||
.where(
|
||||
and(
|
||||
eq(membersTable.fleetId, fleetId),
|
||||
eq(membersTable.hostId, hostId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
if (existing.length > 0) return;
|
||||
|
||||
await drizzle
|
||||
.insert(membersTable)
|
||||
.values({ fleetId, hostId, addedAt: now });
|
||||
await db.persist();
|
||||
},
|
||||
|
||||
async removeMember(fleetId: number, hostId: number): Promise<boolean> {
|
||||
const drizzle = await client();
|
||||
const existing = await drizzle
|
||||
.select({ id: membersTable.id })
|
||||
.from(membersTable)
|
||||
.where(
|
||||
and(
|
||||
eq(membersTable.fleetId, fleetId),
|
||||
eq(membersTable.hostId, hostId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
if (existing.length === 0) return false;
|
||||
|
||||
await drizzle
|
||||
.delete(membersTable)
|
||||
.where(
|
||||
and(
|
||||
eq(membersTable.fleetId, fleetId),
|
||||
eq(membersTable.hostId, hostId),
|
||||
),
|
||||
);
|
||||
await db.persist();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Effective membership = static members rows union hosts whose
|
||||
* comma-separated tags string intersects any tag in the fleet's
|
||||
* tagRules. Both sets are scoped to hosts the fleet owner can see
|
||||
* (ctx.hosts.list already resolves owner-only, since a fleet only holds
|
||||
* one user's hosts) and deduplicated by id.
|
||||
*/
|
||||
async listEffectiveMembers(userId: string, fleetId: number) {
|
||||
const fleet = await findById(userId, fleetId);
|
||||
if (!fleet) return [];
|
||||
|
||||
const staticIds = await listStaticMemberIds(fleetId);
|
||||
const tagRules = parseTagRules(fleet.tagRules);
|
||||
|
||||
const ownedHosts = (await hosts.list()).filter(
|
||||
(host) => host.userId === userId,
|
||||
);
|
||||
|
||||
const byId = new Map<number, (typeof ownedHosts)[number]>();
|
||||
for (const host of ownedHosts) {
|
||||
if (staticIds.includes(host.id)) {
|
||||
byId.set(host.id, host);
|
||||
continue;
|
||||
}
|
||||
if (tagRules.length === 0) continue;
|
||||
const hostTags = (host.tags ?? "")
|
||||
.split(",")
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean);
|
||||
if (hostTags.some((tag) => tagRules.includes(tag))) {
|
||||
byId.set(host.id, host);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(byId.values());
|
||||
},
|
||||
|
||||
async deleteByUserId(userId: string): Promise<number> {
|
||||
const drizzle = await client();
|
||||
const userFleets = await drizzle
|
||||
.select({ id: fleetsTable.id })
|
||||
.from(fleetsTable)
|
||||
.where(eq(fleetsTable.userId, userId));
|
||||
if (userFleets.length === 0) return 0;
|
||||
await drizzle.delete(fleetsTable).where(eq(fleetsTable.userId, userId));
|
||||
await db.persist();
|
||||
return userFleets.length;
|
||||
},
|
||||
|
||||
async upsertInventory(
|
||||
userId: string,
|
||||
hostId: number,
|
||||
input: FleetInventoryInput,
|
||||
now = new Date().toISOString(),
|
||||
): Promise<FleetInventoryRecord> {
|
||||
const drizzle = await client();
|
||||
const existing = await drizzle
|
||||
.select()
|
||||
.from(inventoryTable)
|
||||
.where(
|
||||
and(
|
||||
eq(inventoryTable.hostId, hostId),
|
||||
eq(inventoryTable.userId, userId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (existing[0]) {
|
||||
await drizzle
|
||||
.update(inventoryTable)
|
||||
.set({ ...input, collectedAt: now })
|
||||
.where(eq(inventoryTable.id, existing[0].id));
|
||||
await db.persist();
|
||||
const [updated] = await drizzle
|
||||
.select()
|
||||
.from(inventoryTable)
|
||||
.where(eq(inventoryTable.id, existing[0].id))
|
||||
.limit(1);
|
||||
return updated as FleetInventoryRecord;
|
||||
}
|
||||
|
||||
await drizzle
|
||||
.insert(inventoryTable)
|
||||
.values({ userId, hostId, ...input, collectedAt: now });
|
||||
await db.persist();
|
||||
const [created] = await drizzle
|
||||
.select()
|
||||
.from(inventoryTable)
|
||||
.where(
|
||||
and(
|
||||
eq(inventoryTable.hostId, hostId),
|
||||
eq(inventoryTable.userId, userId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
return created as FleetInventoryRecord;
|
||||
},
|
||||
|
||||
async listInventoryForHosts(
|
||||
userId: string,
|
||||
hostIds: number[],
|
||||
): Promise<FleetInventoryRecord[]> {
|
||||
if (hostIds.length === 0) return [];
|
||||
const drizzle = await client();
|
||||
return drizzle
|
||||
.select()
|
||||
.from(inventoryTable)
|
||||
.where(
|
||||
and(
|
||||
eq(inventoryTable.userId, userId),
|
||||
inArray(inventoryTable.hostId, hostIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
adoptLegacyTable,
|
||||
defineTable,
|
||||
id,
|
||||
integer,
|
||||
refHost,
|
||||
refUser,
|
||||
text,
|
||||
} from "@termix/plugin-sdk/db";
|
||||
|
||||
/**
|
||||
* A fleet: a named group of hosts, by static membership or tag rules.
|
||||
*
|
||||
* Adopted from core's "fleets", so column and index names are the legacy
|
||||
* ones: the rename carries the existing rows across.
|
||||
*/
|
||||
export const fleets = adoptLegacyTable(
|
||||
"fleets",
|
||||
defineTable("fleets", {
|
||||
id: id(),
|
||||
userId: refUser(),
|
||||
name: text().notNull(),
|
||||
description: text(),
|
||||
color: text(),
|
||||
icon: text(),
|
||||
// JSON array of tag strings, unioned with static members at resolution
|
||||
// time. Kept to tag-equality matching for v1.
|
||||
tagRules: text(),
|
||||
syncId: text().unique(),
|
||||
createdAt: text().notNull().defaultNow(),
|
||||
updatedAt: text().notNull().defaultNow(),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Static fleet membership. A host can also be an effective member through a
|
||||
* tag rule match, resolved in the repository rather than stored here.
|
||||
*
|
||||
* fleetId has no declared refUser/refHost FK: the SDK's referenceable targets
|
||||
* are core's users and ssh_data tables only, not another table this same
|
||||
* plugin owns. The legacy foreign key to fleets(id) is preserved by the
|
||||
* adoption migration's CREATE TABLE, which is why the migration file keeps
|
||||
* the FOREIGN KEY clause the generator does not emit for this column.
|
||||
*/
|
||||
export const fleetMembers = adoptLegacyTable(
|
||||
"fleet_members",
|
||||
defineTable(
|
||||
"members",
|
||||
{
|
||||
id: id(),
|
||||
fleetId: integer().notNull(),
|
||||
hostId: refHost(),
|
||||
addedAt: text().notNull().defaultNow(),
|
||||
},
|
||||
{
|
||||
// fleetId leads the unique pair, so listing a fleet's hosts is already
|
||||
// served. Finding the fleets a host belongs to starts from hostId.
|
||||
uniques: [
|
||||
{
|
||||
name: "idx_fleet_members_fleet_host",
|
||||
columns: ["fleetId", "hostId"],
|
||||
},
|
||||
],
|
||||
indexes: [{ name: "idx_fleet_members_host", columns: ["hostId"] }],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Latest-only inventory snapshot per host, overwritten on each refresh - no
|
||||
// historical log, matching the "latest snapshot only" scope decision.
|
||||
export const fleetInventory = adoptLegacyTable(
|
||||
"fleet_inventory",
|
||||
defineTable(
|
||||
"inventory",
|
||||
{
|
||||
id: id(),
|
||||
hostId: refHost(),
|
||||
userId: refUser(),
|
||||
osPrettyName: text(),
|
||||
kernel: text(),
|
||||
architecture: text(),
|
||||
hostname: text(),
|
||||
uptimeSeconds: integer(),
|
||||
ip: text(),
|
||||
packageManager: text(),
|
||||
collectedAt: text().notNull().defaultNow(),
|
||||
},
|
||||
{
|
||||
// hostId leads the unique pair; a user's whole inventory reads by userId.
|
||||
uniques: [
|
||||
{ name: "idx_fleet_inventory_host", columns: ["hostId", "userId"] },
|
||||
],
|
||||
indexes: [{ name: "idx_fleet_inventory_user", columns: ["userId"] }],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export const tables = [fleets, fleetMembers, fleetInventory];
|
||||
@@ -0,0 +1,417 @@
|
||||
import { getErrorMessage, cn } from "./helpers.js";
|
||||
import { useEffect, useMemo, useState, useCallback } from "react";
|
||||
import { useTranslation } from "@termix/plugin-sdk/frontend";
|
||||
import { Input, Button } from "@termix/plugin-sdk/ui";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
ArrowUpDown,
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
Search,
|
||||
Server,
|
||||
} from "lucide-react";
|
||||
import type { FleetsApi, FleetRow, FleetInventoryEntry } from "./fleets-api.js";
|
||||
|
||||
type SortKey =
|
||||
| "hostName"
|
||||
| "osPrettyName"
|
||||
| "kernel"
|
||||
| "architecture"
|
||||
| "uptimeSeconds"
|
||||
| "packageManager"
|
||||
| "collectedAt";
|
||||
type SortDirection = "asc" | "desc";
|
||||
|
||||
function formatUptime(seconds: number | null): string {
|
||||
if (seconds === null) return "-";
|
||||
const d = Math.floor(seconds / 86400);
|
||||
const h = Math.floor((seconds % 86400) / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
if (d > 0) return `${d}d ${h}h`;
|
||||
if (h > 0) return `${h}h ${m}m`;
|
||||
return `${m}m`;
|
||||
}
|
||||
|
||||
function compareValues(
|
||||
a: string | number | null,
|
||||
b: string | number | null,
|
||||
): number {
|
||||
if (a === null && b === null) return 0;
|
||||
if (a === null) return 1;
|
||||
if (b === null) return -1;
|
||||
if (typeof a === "number" && typeof b === "number") return a - b;
|
||||
return String(a).localeCompare(String(b));
|
||||
}
|
||||
|
||||
function SortHeader({
|
||||
label,
|
||||
column,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
onSort,
|
||||
}: {
|
||||
label: string;
|
||||
column: SortKey;
|
||||
sortKey: SortKey;
|
||||
sortDirection: SortDirection;
|
||||
onSort: (column: SortKey) => void;
|
||||
}) {
|
||||
const active = sortKey === column;
|
||||
return (
|
||||
<th className="px-3 py-2 text-left font-semibold">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSort(column)}
|
||||
className="flex items-center gap-1 hover:text-foreground"
|
||||
>
|
||||
{label}
|
||||
{active ? (
|
||||
sortDirection === "asc" ? (
|
||||
<ArrowUp className="size-3" />
|
||||
) : (
|
||||
<ArrowDown className="size-3" />
|
||||
)
|
||||
) : (
|
||||
<ArrowUpDown className="size-3 opacity-40" />
|
||||
)}
|
||||
</button>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
export function FleetInventoryTab({
|
||||
api,
|
||||
fleetId,
|
||||
isVisible = true,
|
||||
}: {
|
||||
api: FleetsApi;
|
||||
fleetId?: number;
|
||||
isVisible?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [fleets, setFleets] = useState<FleetRow[]>([]);
|
||||
const [selectedFleetId, setSelectedFleetId] = useState<number | undefined>(
|
||||
fleetId,
|
||||
);
|
||||
const [entries, setEntries] = useState<FleetInventoryEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [sortKey, setSortKey] = useState<SortKey>("hostName");
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
|
||||
|
||||
useEffect(() => {
|
||||
if (fleetId !== undefined) setSelectedFleetId(fleetId);
|
||||
}, [fleetId]);
|
||||
|
||||
useEffect(() => {
|
||||
api
|
||||
.list()
|
||||
.then(setFleets)
|
||||
.catch(() => {});
|
||||
}, [api]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fleets.length) return;
|
||||
if (selectedFleetId === undefined) {
|
||||
setSelectedFleetId(fleets[0].id);
|
||||
}
|
||||
}, [fleets, selectedFleetId]);
|
||||
|
||||
const loadInventory = useCallback(async () => {
|
||||
if (selectedFleetId === undefined) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await api.inventory(selectedFleetId);
|
||||
setEntries(data);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error, "");
|
||||
toast.error(message || t("newUi.sidebar.fleets.inventoryLoadFailed"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [api, selectedFleetId, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVisible) return;
|
||||
loadInventory();
|
||||
}, [loadInventory, isVisible]);
|
||||
|
||||
async function handleRefresh() {
|
||||
if (selectedFleetId === undefined) return;
|
||||
setRefreshing(true);
|
||||
try {
|
||||
const { results } = await api.refreshInventory(selectedFleetId);
|
||||
const failed = results.filter((r) => !r.success).length;
|
||||
if (failed > 0) {
|
||||
toast.warning(
|
||||
t("newUi.sidebar.fleets.commandPartialFailure", {
|
||||
failed,
|
||||
total: results.length,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
toast.success(t("newUi.sidebar.fleets.inventoryRefreshed"));
|
||||
}
|
||||
await loadInventory();
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error, "");
|
||||
toast.error(message || t("newUi.sidebar.fleets.inventoryRefreshFailed"));
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSort(column: SortKey) {
|
||||
if (sortKey === column) {
|
||||
setSortDirection((prev) => (prev === "asc" ? "desc" : "asc"));
|
||||
} else {
|
||||
setSortKey(column);
|
||||
setSortDirection("asc");
|
||||
}
|
||||
}
|
||||
|
||||
const selectedFleet = fleets.find((f) => f.id === selectedFleetId);
|
||||
|
||||
const filteredSorted = useMemo(() => {
|
||||
const q = search.trim().toLowerCase();
|
||||
const filtered = entries.filter(
|
||||
(e) =>
|
||||
!q ||
|
||||
e.hostName.toLowerCase().includes(q) ||
|
||||
(e.inventory?.osPrettyName ?? "").toLowerCase().includes(q) ||
|
||||
(e.inventory?.hostname ?? "").toLowerCase().includes(q),
|
||||
);
|
||||
|
||||
const dir = sortDirection === "asc" ? 1 : -1;
|
||||
return [...filtered].sort((a, b) => {
|
||||
let cmp = 0;
|
||||
switch (sortKey) {
|
||||
case "hostName":
|
||||
cmp = compareValues(a.hostName, b.hostName);
|
||||
break;
|
||||
case "osPrettyName":
|
||||
cmp = compareValues(
|
||||
a.inventory?.osPrettyName ?? null,
|
||||
b.inventory?.osPrettyName ?? null,
|
||||
);
|
||||
break;
|
||||
case "kernel":
|
||||
cmp = compareValues(
|
||||
a.inventory?.kernel ?? null,
|
||||
b.inventory?.kernel ?? null,
|
||||
);
|
||||
break;
|
||||
case "architecture":
|
||||
cmp = compareValues(
|
||||
a.inventory?.architecture ?? null,
|
||||
b.inventory?.architecture ?? null,
|
||||
);
|
||||
break;
|
||||
case "uptimeSeconds":
|
||||
cmp = compareValues(
|
||||
a.inventory?.uptimeSeconds ?? null,
|
||||
b.inventory?.uptimeSeconds ?? null,
|
||||
);
|
||||
break;
|
||||
case "packageManager":
|
||||
cmp = compareValues(
|
||||
a.inventory?.packageManager ?? null,
|
||||
b.inventory?.packageManager ?? null,
|
||||
);
|
||||
break;
|
||||
case "collectedAt":
|
||||
cmp = compareValues(
|
||||
a.inventory?.collectedAt ?? null,
|
||||
b.inventory?.collectedAt ?? null,
|
||||
);
|
||||
break;
|
||||
}
|
||||
return cmp * dir;
|
||||
});
|
||||
}, [entries, search, sortKey, sortDirection]);
|
||||
|
||||
const collectedCount = entries.filter((e) => e.inventory !== null).length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 min-h-0 overflow-hidden">
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2 border-b border-border px-3 py-2.5">
|
||||
<Server className="size-4 text-muted-foreground" />
|
||||
<span className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{t("newUi.sidebar.fleets.tabInventory")}
|
||||
</span>
|
||||
|
||||
<select
|
||||
className="px-2 py-1 text-[10px] bg-background border border-border text-foreground outline-none"
|
||||
value={selectedFleetId ?? ""}
|
||||
onChange={(e) => setSelectedFleetId(Number(e.target.value))}
|
||||
>
|
||||
{fleets.map((fleet) => (
|
||||
<option key={fleet.id} value={fleet.id}>
|
||||
{fleet.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{selectedFleet && (
|
||||
<span className="flex gap-3 text-[11px] text-muted-foreground">
|
||||
<span>
|
||||
{t("newUi.sidebar.fleets.memberCount", {
|
||||
count: entries.length,
|
||||
})}
|
||||
</span>
|
||||
<span>
|
||||
{t("newUi.sidebar.fleets.inventoryCollectedCount", {
|
||||
count: collectedCount,
|
||||
total: entries.length,
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="ml-auto flex items-center gap-1.5">
|
||||
<div className="relative">
|
||||
<Search className="pointer-events-none absolute left-2 top-1/2 size-3 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t("newUi.sidebar.fleets.searchHosts")}
|
||||
className="h-7 w-48 pl-6 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleRefresh}
|
||||
disabled={refreshing || selectedFleetId === undefined}
|
||||
>
|
||||
{refreshing ? (
|
||||
<Loader2 className="size-3.5 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="size-3.5 mr-1.5" />
|
||||
)}
|
||||
{t("newUi.sidebar.fleets.refreshInventory")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto thin-scrollbar">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center py-16 text-muted-foreground">
|
||||
<Loader2 className="size-5 animate-spin" />
|
||||
</div>
|
||||
) : fleets.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center py-16 text-xs text-muted-foreground">
|
||||
{t("newUi.sidebar.fleets.noFleets")}
|
||||
</div>
|
||||
) : filteredSorted.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center py-16 text-xs text-muted-foreground">
|
||||
{t("newUi.sidebar.fleets.noMembers")}
|
||||
</div>
|
||||
) : (
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<thead className="sticky top-0 z-10 bg-card">
|
||||
<tr className="border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||
<SortHeader
|
||||
label={t("newUi.sidebar.fleets.inventoryHost")}
|
||||
column="hostName"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t("newUi.sidebar.fleets.inventoryOs")}
|
||||
column="osPrettyName"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t("newUi.sidebar.fleets.inventoryKernel")}
|
||||
column="kernel"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t("newUi.sidebar.fleets.inventoryArch")}
|
||||
column="architecture"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t("newUi.sidebar.fleets.inventoryUptime")}
|
||||
column="uptimeSeconds"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t("newUi.sidebar.fleets.inventoryPackageManager")}
|
||||
column="packageManager"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t("newUi.sidebar.fleets.inventoryCollectedAt")}
|
||||
column="collectedAt"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{filteredSorted.map((entry) => (
|
||||
<tr
|
||||
key={entry.hostId}
|
||||
className="transition-colors hover:bg-muted/40"
|
||||
>
|
||||
<td className="px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"size-1.5 shrink-0",
|
||||
entry.inventory
|
||||
? "bg-green-500"
|
||||
: "bg-muted-foreground/50",
|
||||
)}
|
||||
/>
|
||||
<span className="min-w-0 truncate font-semibold">
|
||||
{entry.hostName}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-muted-foreground">
|
||||
{entry.inventory?.osPrettyName ?? "-"}
|
||||
</td>
|
||||
<td className="px-3 py-2 font-mono text-muted-foreground">
|
||||
{entry.inventory?.kernel ?? "-"}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-muted-foreground">
|
||||
{entry.inventory?.architecture ?? "-"}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-muted-foreground">
|
||||
{formatUptime(entry.inventory?.uptimeSeconds ?? null)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-muted-foreground">
|
||||
{entry.inventory?.packageManager ?? "-"}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-muted-foreground">
|
||||
{entry.inventory?.collectedAt
|
||||
? new Date(entry.inventory.collectedAt).toLocaleString()
|
||||
: "-"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,215 @@
|
||||
import type { PluginApiClient } from "@termix/plugin-sdk/frontend";
|
||||
|
||||
export type SharePermissionLevel = "connect" | "view" | "edit" | "manage";
|
||||
|
||||
export interface ShareTarget {
|
||||
type: "user" | "role";
|
||||
id: string | number;
|
||||
}
|
||||
|
||||
export interface ShareableUser {
|
||||
id: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface ShareableRole {
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string | null;
|
||||
}
|
||||
|
||||
export interface FleetRow {
|
||||
id: number;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
color: string | null;
|
||||
icon: string | null;
|
||||
tagRules: string[];
|
||||
syncId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
memberCount: number;
|
||||
}
|
||||
|
||||
export interface FleetMemberRow {
|
||||
id: number;
|
||||
name: string;
|
||||
ip: string;
|
||||
tags: string[];
|
||||
static: boolean;
|
||||
permissionLevel: SharePermissionLevel | null;
|
||||
}
|
||||
|
||||
export interface FleetHostResult {
|
||||
hostId: number;
|
||||
hostName: string;
|
||||
success: boolean;
|
||||
output?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface FleetInventoryRecord {
|
||||
id: number;
|
||||
hostId: number;
|
||||
userId: string;
|
||||
osPrettyName: string | null;
|
||||
kernel: string | null;
|
||||
architecture: string | null;
|
||||
hostname: string | null;
|
||||
uptimeSeconds: number | null;
|
||||
ip: string | null;
|
||||
packageManager: string | null;
|
||||
collectedAt: string;
|
||||
}
|
||||
|
||||
export interface FleetInventoryEntry {
|
||||
hostId: number;
|
||||
hostName: string;
|
||||
inventory: FleetInventoryRecord | null;
|
||||
}
|
||||
|
||||
export type FleetPackageAction = "install" | "remove" | "upgrade-all";
|
||||
|
||||
export interface FleetShareHostResult {
|
||||
hostId: number;
|
||||
shared: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface FleetShareResult {
|
||||
success: boolean;
|
||||
permissionLevel: SharePermissionLevel;
|
||||
expiresAt: string | null;
|
||||
hostsShared: number;
|
||||
hostsTotal: number;
|
||||
hostResults: FleetShareHostResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The fleets routes, through the plugin's own client. Paths are relative to
|
||||
* /plugin-api/fleets/, which the client already points at.
|
||||
*/
|
||||
export function createFleetsApi(api: PluginApiClient) {
|
||||
const data = async <T>(request: Promise<{ data: T }>) => (await request).data;
|
||||
|
||||
return {
|
||||
list: () => data(api.get<FleetRow[]>("/")),
|
||||
|
||||
create: (fleetData: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
color?: string | null;
|
||||
icon?: string | null;
|
||||
tagRules?: string[];
|
||||
}) => data(api.post<FleetRow>("/", fleetData)),
|
||||
|
||||
update: (
|
||||
fleetId: number,
|
||||
fleetData: {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
color?: string | null;
|
||||
icon?: string | null;
|
||||
tagRules?: string[];
|
||||
},
|
||||
) => data(api.patch<FleetRow>(`/${fleetId}`, fleetData)),
|
||||
|
||||
remove: (fleetId: number) =>
|
||||
data(api.delete<{ success: boolean }>(`/${fleetId}`)),
|
||||
|
||||
members: (fleetId: number) =>
|
||||
data(api.get<FleetMemberRow[]>(`/${fleetId}/members`)),
|
||||
|
||||
addMember: (fleetId: number, hostId: number) =>
|
||||
data(api.post<{ success: boolean }>(`/${fleetId}/members`, { hostId })),
|
||||
|
||||
removeMember: (fleetId: number, hostId: number) =>
|
||||
data(api.delete<{ success: boolean }>(`/${fleetId}/members/${hostId}`)),
|
||||
|
||||
runCommand: (
|
||||
fleetId: number,
|
||||
command: string,
|
||||
inputValues?: Record<string, string>,
|
||||
) =>
|
||||
data(
|
||||
api.post<{ results: FleetHostResult[] }>(`/${fleetId}/execute`, {
|
||||
command,
|
||||
...(inputValues ? { inputValues } : {}),
|
||||
}),
|
||||
),
|
||||
|
||||
pushFile: async (fleetId: number, file: File, remotePath: string) => {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
form.append("remotePath", remotePath);
|
||||
return data(
|
||||
api.post<{ results: FleetHostResult[] }>(
|
||||
`/${fleetId}/transfer/push`,
|
||||
form,
|
||||
{ headers: { "Content-Type": "multipart/form-data" } },
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
pullFile: async (
|
||||
fleetId: number,
|
||||
remotePath: string,
|
||||
): Promise<{
|
||||
results: FleetHostResult[];
|
||||
blob: Blob;
|
||||
fileName: string;
|
||||
}> => {
|
||||
const response = await api.post<Blob>(
|
||||
`/${fleetId}/transfer/pull`,
|
||||
{ remotePath },
|
||||
{ responseType: "blob" },
|
||||
);
|
||||
const headers = (response as { headers?: Record<string, string> })
|
||||
.headers;
|
||||
const resultsHeader = headers?.["x-fleet-transfer-results"];
|
||||
const results: FleetHostResult[] = resultsHeader
|
||||
? JSON.parse(atob(resultsHeader))
|
||||
: [];
|
||||
const disposition = headers?.["content-disposition"];
|
||||
const match = disposition?.match(/filename="([^"]+)"/);
|
||||
const fileName = match?.[1] ?? "fleet-transfer.zip";
|
||||
return { results, blob: response.data, fileName };
|
||||
},
|
||||
|
||||
inventory: (fleetId: number) =>
|
||||
data(api.get<FleetInventoryEntry[]>(`/${fleetId}/inventory`)),
|
||||
|
||||
refreshInventory: (fleetId: number) =>
|
||||
data(api.post<{ results: FleetHostResult[] }>(`/${fleetId}/inventory`)),
|
||||
|
||||
runPackageAction: (
|
||||
fleetId: number,
|
||||
action: FleetPackageAction,
|
||||
packageName?: string,
|
||||
) =>
|
||||
data(
|
||||
api.post<{ results: FleetHostResult[] }>(`/${fleetId}/packages`, {
|
||||
action,
|
||||
...(packageName ? { package: packageName } : {}),
|
||||
}),
|
||||
),
|
||||
|
||||
share: (
|
||||
fleetId: number,
|
||||
shareData: {
|
||||
targets: ShareTarget[];
|
||||
permissionLevel: SharePermissionLevel;
|
||||
durationHours?: number;
|
||||
},
|
||||
) => data(api.post<FleetShareResult>(`/${fleetId}/share`, shareData)),
|
||||
|
||||
shareTargetUsers: () =>
|
||||
data(api.get<{ users: ShareableUser[] }>("/share-targets/users")),
|
||||
|
||||
shareTargetRoles: () =>
|
||||
data(api.get<{ roles: ShareableRole[] }>("/share-targets/roles")),
|
||||
};
|
||||
}
|
||||
|
||||
export type FleetsApi = ReturnType<typeof createFleetsApi>;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a stable, human-readable message from an unknown thrown value.
|
||||
* Mirrors src/ui/lib/error-message.ts - kept as a small standalone copy
|
||||
* rather than a cross-plugin dependency for one pure function.
|
||||
*/
|
||||
export function getErrorMessage(
|
||||
error: unknown,
|
||||
fallback = "Unknown error",
|
||||
): string {
|
||||
return error instanceof Error ? error.message : fallback;
|
||||
}
|
||||
|
||||
export interface SnippetInput {
|
||||
key: string;
|
||||
label: string;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Boxes } from "lucide-react";
|
||||
import type {
|
||||
PanelProps,
|
||||
TabProps,
|
||||
TermixApp,
|
||||
} from "@termix/plugin-sdk/frontend";
|
||||
import { FleetsPanel } from "./FleetsPanel.js";
|
||||
import { FleetInventoryTab } from "./FleetInventoryTab.js";
|
||||
import { createFleetsApi } from "./fleets-api.js";
|
||||
|
||||
const INVENTORY_TAB = "fleet-inventory";
|
||||
|
||||
export function activate(app: TermixApp): void {
|
||||
const api = createFleetsApi(app.api);
|
||||
|
||||
function Panel({ active, shell }: PanelProps) {
|
||||
return (
|
||||
<FleetsPanel
|
||||
api={api}
|
||||
active={active}
|
||||
onOpenFleetInventory={(fleetId) =>
|
||||
shell.openSingletonTab(INVENTORY_TAB, { data: { fleetId } })
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function InventoryTab({ tab, isVisible }: TabProps) {
|
||||
const fleetId = tab.data?.fleetId;
|
||||
return (
|
||||
<FleetInventoryTab
|
||||
api={api}
|
||||
fleetId={typeof fleetId === "number" ? fleetId : undefined}
|
||||
isVisible={isVisible}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
app.registerRailItem({
|
||||
id: "fleets",
|
||||
icon: Boxes,
|
||||
titleKey: "nav.fleets",
|
||||
after: "macros",
|
||||
order: 10,
|
||||
});
|
||||
|
||||
// Kept mounted so a fleet being edited survives switching panels.
|
||||
app.registerPanel("fleets", Panel, { keepMounted: true });
|
||||
|
||||
app.registerTab(INVENTORY_TAB, InventoryTab, {
|
||||
icon: Boxes,
|
||||
titleKey: "nav.fleets",
|
||||
singleton: true,
|
||||
hostless: true,
|
||||
});
|
||||
|
||||
// Other plugins (automations' host picker) list fleets through this.
|
||||
app.registerAction("fleets.list", () => api.list());
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveCommandVariables } from "../../src/backend/routes.js";
|
||||
|
||||
function registry(value?: unknown) {
|
||||
return {
|
||||
registry: {
|
||||
provide: () => {},
|
||||
revoke: () => false,
|
||||
consume: <T>() => value as T | undefined,
|
||||
},
|
||||
} as never;
|
||||
}
|
||||
|
||||
describe("resolveCommandVariables", () => {
|
||||
it("fills variables through the snippets plugin's resolver", () => {
|
||||
const ctx = registry({
|
||||
resolve: (content: string, host: { ip?: string } | null) =>
|
||||
content.replace("$HOST", host?.ip ?? ""),
|
||||
});
|
||||
expect(resolveCommandVariables(ctx, "ping $HOST", { ip: "10.0.0.1" })).toBe(
|
||||
"ping 10.0.0.1",
|
||||
);
|
||||
});
|
||||
|
||||
it("runs the command as typed while snippets is off", () => {
|
||||
expect(
|
||||
resolveCommandVariables(registry(), "ping $HOST", { ip: "10.0.0.1" }),
|
||||
).toBe("ping $HOST");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,159 @@
|
||||
import http from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import express, { type Router } from "express";
|
||||
import {
|
||||
createMockCtx,
|
||||
createTestDb,
|
||||
type MockPluginContext,
|
||||
type TestDb,
|
||||
} from "@termix/plugin-sdk/testing";
|
||||
import type { PluginManifest } from "@termix/plugin-sdk/manifest";
|
||||
import manifestJson from "../../manifest.json";
|
||||
|
||||
export const pluginDir = fileURLToPath(new URL("../..", import.meta.url));
|
||||
export const manifest = manifestJson as unknown as PluginManifest;
|
||||
|
||||
export interface TestServer {
|
||||
db: TestDb;
|
||||
mock: MockPluginContext;
|
||||
request: (
|
||||
method: string,
|
||||
path: string,
|
||||
options?: { user?: string; body?: unknown },
|
||||
// Route bodies vary per test; asserting on them is the point.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) => Promise<{ status: number; body: any }>;
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The plugin activated against a real in-memory database, its router mounted
|
||||
* on an express app the way core mounts it: JSON parsed, the acting user set
|
||||
* from the request, everything else left to the plugin.
|
||||
*/
|
||||
export async function startServer(
|
||||
options: {
|
||||
permissions?: string[];
|
||||
users?: string[];
|
||||
hosts?: Array<{
|
||||
id: number;
|
||||
userId: string;
|
||||
name?: string;
|
||||
ip?: string;
|
||||
tags?: string | null;
|
||||
}>;
|
||||
} = {},
|
||||
): Promise<TestServer> {
|
||||
const hosts = options.hosts ?? [
|
||||
{ id: 10, userId: "user-1", name: "host-a", ip: "10.0.0.1" },
|
||||
{ id: 11, userId: "user-1", name: "host-b", ip: "10.0.0.2" },
|
||||
];
|
||||
|
||||
const db = await createTestDb(pluginDir, {
|
||||
before: (sqlite) => {
|
||||
for (const user of options.users ?? ["user-1", "user-2"]) {
|
||||
sqlite
|
||||
.prepare("INSERT INTO users (id, username) VALUES (?, ?)")
|
||||
.run(user, user);
|
||||
}
|
||||
// The plugin's fleet_members/fleet_inventory tables FK to ssh_data,
|
||||
// which ctx.hosts (faked below) never touches - seed it directly so
|
||||
// the constraint is satisfied the way it is against the real table.
|
||||
for (const host of hosts) {
|
||||
sqlite.prepare("INSERT INTO ssh_data (id) VALUES (?)").run(host.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let router: Router | null = null;
|
||||
const mock = createMockCtx({
|
||||
pluginId: manifest.id,
|
||||
manifest,
|
||||
capabilities: manifest.capabilities,
|
||||
db: db.database,
|
||||
router: () => (router = express.Router()),
|
||||
permissions: options.permissions ?? [
|
||||
"fleets.view",
|
||||
"fleets.manage",
|
||||
"fleets.execute",
|
||||
],
|
||||
});
|
||||
|
||||
// ctx.hosts is capability-checked separately from ctx.db; seed it with the
|
||||
// same fixture hosts a real deployment would resolve through core.
|
||||
mock.ctx.hosts.list = async () =>
|
||||
hosts.map((h) => ({
|
||||
id: h.id,
|
||||
userId: h.userId,
|
||||
name: h.name ?? null,
|
||||
ip: h.ip ?? "",
|
||||
port: 22,
|
||||
username: "root",
|
||||
tags: h.tags ?? null,
|
||||
folder: null,
|
||||
authType: "password",
|
||||
}));
|
||||
mock.ctx.hosts.get = async (hostId) => {
|
||||
const found = hosts.find((h) => h.id === hostId);
|
||||
if (!found) return null;
|
||||
return {
|
||||
id: found.id,
|
||||
userId: found.userId,
|
||||
name: found.name ?? null,
|
||||
ip: found.ip ?? "",
|
||||
port: 22,
|
||||
username: "root",
|
||||
tags: found.tags ?? null,
|
||||
folder: null,
|
||||
authType: "password",
|
||||
};
|
||||
};
|
||||
mock.ctx.hosts.checkAccess = async (hostId) => {
|
||||
const found = hosts.find((h) => h.id === hostId);
|
||||
if (!found) return { hasAccess: false, isOwner: false, isShared: false };
|
||||
return {
|
||||
hasAccess: true,
|
||||
isOwner: true,
|
||||
isShared: false,
|
||||
permissionLevel: "manage",
|
||||
};
|
||||
};
|
||||
|
||||
const { activate } = await import("../../src/backend/index.js");
|
||||
await activate(mock.ctx);
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, _res, next) => {
|
||||
mock.setActor(req.header("x-test-user") ?? undefined);
|
||||
next();
|
||||
});
|
||||
app.use((req, res, next) => router!(req, res, next));
|
||||
|
||||
const server = http.createServer(app);
|
||||
await new Promise<void>((resolve) => server.listen(0, resolve));
|
||||
const { port } = server.address() as AddressInfo;
|
||||
|
||||
return {
|
||||
db,
|
||||
mock,
|
||||
async request(method, path, { user = "user-1", body: given } = {}) {
|
||||
const body = method === "GET" ? undefined : given;
|
||||
const response = await fetch(`http://127.0.0.1:${port}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
"x-test-user": user,
|
||||
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
||||
},
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const text = await response.text();
|
||||
return { status: response.status, body: text ? JSON.parse(text) : null };
|
||||
},
|
||||
async close() {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
db.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing";
|
||||
import { pluginDir } from "./helpers";
|
||||
|
||||
// fleets/fleet_members/fleet_inventory as core's SQLite bootstrap created
|
||||
// them before 2.9.0, plus the indexes performance-indexes.ts added.
|
||||
const LEGACY_DDL = `
|
||||
CREATE TABLE fleets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
color TEXT,
|
||||
icon TEXT,
|
||||
tag_rules TEXT,
|
||||
sync_id TEXT UNIQUE,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE fleet_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fleet_id INTEGER NOT NULL REFERENCES fleets(id) ON DELETE CASCADE,
|
||||
host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE,
|
||||
added_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_fleet_members_fleet_host ON fleet_members(fleet_id, host_id);
|
||||
CREATE INDEX idx_fleet_members_host ON fleet_members(host_id);
|
||||
CREATE TABLE fleet_inventory (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
os_pretty_name TEXT,
|
||||
kernel TEXT,
|
||||
architecture TEXT,
|
||||
hostname TEXT,
|
||||
uptime_seconds INTEGER,
|
||||
ip TEXT,
|
||||
package_manager TEXT,
|
||||
collected_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_fleet_inventory_host ON fleet_inventory(host_id, user_id);
|
||||
CREATE INDEX idx_fleet_inventory_user ON fleet_inventory(user_id);
|
||||
`;
|
||||
|
||||
let db: TestDb | null = null;
|
||||
|
||||
afterEach(() => {
|
||||
db?.close();
|
||||
db = null;
|
||||
});
|
||||
|
||||
const tableExists = (name: string) =>
|
||||
!!db!.sqlite
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
|
||||
.get(name);
|
||||
|
||||
const indexOn = (table: string) =>
|
||||
(
|
||||
db!.sqlite
|
||||
.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND name NOT LIKE 'sqlite_%'",
|
||||
)
|
||||
.all(table) as { name: string }[]
|
||||
)
|
||||
.map((row) => row.name)
|
||||
.sort();
|
||||
|
||||
describe("adopting fleets, fleet_members, fleet_inventory", () => {
|
||||
it("keeps every existing row when the legacy tables are there", async () => {
|
||||
db = await createTestDb(pluginDir, {
|
||||
before: (sqlite) => {
|
||||
sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')");
|
||||
sqlite.exec("INSERT INTO ssh_data (id) VALUES (1), (2)");
|
||||
sqlite.exec(LEGACY_DDL);
|
||||
sqlite
|
||||
.prepare(
|
||||
"INSERT INTO fleets (user_id, name, tag_rules, sync_id) VALUES (?, ?, ?, ?)",
|
||||
)
|
||||
.run("user-1", "Web fleet", '["prod-web"]', "sync-a");
|
||||
sqlite
|
||||
.prepare(
|
||||
"INSERT INTO fleet_members (fleet_id, host_id) VALUES (1, 1)",
|
||||
)
|
||||
.run();
|
||||
sqlite
|
||||
.prepare(
|
||||
"INSERT INTO fleet_inventory (host_id, user_id, os_pretty_name) VALUES (1, 'user-1', 'Debian')",
|
||||
)
|
||||
.run();
|
||||
},
|
||||
});
|
||||
|
||||
expect(db.applied).toEqual(["0001_adopt_fleets"]);
|
||||
expect(tableExists("fleets")).toBe(false);
|
||||
expect(tableExists("fleet_members")).toBe(false);
|
||||
expect(tableExists("fleet_inventory")).toBe(false);
|
||||
expect(tableExists("p_fleets_fleets")).toBe(true);
|
||||
expect(tableExists("p_fleets_members")).toBe(true);
|
||||
expect(tableExists("p_fleets_inventory")).toBe(true);
|
||||
|
||||
const fleetRows = db.sqlite
|
||||
.prepare("SELECT user_id, name, tag_rules, sync_id FROM p_fleets_fleets")
|
||||
.all();
|
||||
expect(fleetRows).toEqual([
|
||||
{
|
||||
user_id: "user-1",
|
||||
name: "Web fleet",
|
||||
tag_rules: '["prod-web"]',
|
||||
sync_id: "sync-a",
|
||||
},
|
||||
]);
|
||||
|
||||
const memberRows = db.sqlite
|
||||
.prepare("SELECT fleet_id, host_id FROM p_fleets_members")
|
||||
.all();
|
||||
expect(memberRows).toEqual([{ fleet_id: 1, host_id: 1 }]);
|
||||
|
||||
const inventoryRows = db.sqlite
|
||||
.prepare(
|
||||
"SELECT host_id, user_id, os_pretty_name FROM p_fleets_inventory",
|
||||
)
|
||||
.all();
|
||||
expect(inventoryRows).toEqual([
|
||||
{ host_id: 1, user_id: "user-1", os_pretty_name: "Debian" },
|
||||
]);
|
||||
|
||||
expect(indexOn("p_fleets_members")).toEqual(
|
||||
["idx_fleet_members_fleet_host", "idx_fleet_members_host"].sort(),
|
||||
);
|
||||
expect(indexOn("p_fleets_inventory")).toEqual(
|
||||
["idx_fleet_inventory_host", "idx_fleet_inventory_user"].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
it("creates the tables fresh when there are no legacy ones", async () => {
|
||||
db = await createTestDb(pluginDir);
|
||||
|
||||
expect(tableExists("fleets")).toBe(false);
|
||||
expect(tableExists("p_fleets_fleets")).toBe(true);
|
||||
expect(tableExists("p_fleets_members")).toBe(true);
|
||||
expect(tableExists("p_fleets_inventory")).toBe(true);
|
||||
expect(db.sqlite.prepare("SELECT * FROM p_fleets_fleets").all()).toEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it("cascades a fleet delete onto its member rows via the fleet_id FK", async () => {
|
||||
db = await createTestDb(pluginDir, {
|
||||
before: (sqlite) => {
|
||||
sqlite.exec("INSERT INTO users (id) VALUES ('user-1')");
|
||||
sqlite.exec("INSERT INTO ssh_data (id) VALUES (1)");
|
||||
},
|
||||
});
|
||||
db.sqlite
|
||||
.prepare(
|
||||
"INSERT INTO p_fleets_fleets (id, user_id, name) VALUES (1, 'user-1', 'Fleet')",
|
||||
)
|
||||
.run();
|
||||
db.sqlite
|
||||
.prepare("INSERT INTO p_fleets_members (fleet_id, host_id) VALUES (1, 1)")
|
||||
.run();
|
||||
|
||||
db.sqlite.exec("DELETE FROM p_fleets_fleets WHERE id = 1");
|
||||
|
||||
expect(db.sqlite.prepare("SELECT * FROM p_fleets_members").all()).toEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it("removes a user's fleets when the user is deleted", async () => {
|
||||
db = await createTestDb(pluginDir, {
|
||||
before: (sqlite) => {
|
||||
sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')");
|
||||
},
|
||||
});
|
||||
const insert = db.sqlite.prepare(
|
||||
"INSERT INTO p_fleets_fleets (user_id, name) VALUES (?, ?)",
|
||||
);
|
||||
insert.run("user-1", "Mine");
|
||||
insert.run("user-2", "Theirs");
|
||||
|
||||
db.sqlite.exec("DELETE FROM users WHERE id = 'user-1'");
|
||||
|
||||
const names = (
|
||||
db.sqlite.prepare("SELECT name FROM p_fleets_fleets").all() as {
|
||||
name: string;
|
||||
}[]
|
||||
).map((row) => row.name);
|
||||
expect(names).toEqual(["Theirs"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,209 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing";
|
||||
import type {
|
||||
PluginHostSummary,
|
||||
PluginHosts,
|
||||
} from "@termix/plugin-sdk/backend";
|
||||
import { pluginDir } from "./helpers";
|
||||
import {
|
||||
fleets,
|
||||
fleetMembers,
|
||||
fleetInventory,
|
||||
} from "../../src/backend/tables.js";
|
||||
import { createFleetRepository } from "../../src/backend/repository.js";
|
||||
|
||||
let db: TestDb | null = null;
|
||||
|
||||
afterEach(() => {
|
||||
db?.close();
|
||||
db = null;
|
||||
});
|
||||
|
||||
function fakeHosts(hosts: PluginHostSummary[]): PluginHosts {
|
||||
return <PluginHosts>(<unknown>{
|
||||
list: async () => hosts,
|
||||
get: async (hostId) => hosts.find((h) => h.id === hostId) ?? null,
|
||||
checkAccess: async (hostId) => {
|
||||
const found = hosts.find((h) => h.id === hostId);
|
||||
return found
|
||||
? {
|
||||
hasAccess: true,
|
||||
isOwner: true,
|
||||
isShared: false,
|
||||
permissionLevel: "manage",
|
||||
}
|
||||
: { hasAccess: false, isOwner: false, isShared: false };
|
||||
},
|
||||
share: async (hostId) => ({ hostId, shared: true }),
|
||||
listUsers: async () => [],
|
||||
listRoles: async () => [],
|
||||
});
|
||||
}
|
||||
|
||||
async function setup(hosts: PluginHostSummary[]) {
|
||||
db = await createTestDb(pluginDir, {
|
||||
before: (sqlite) => {
|
||||
sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')");
|
||||
sqlite.exec(
|
||||
`INSERT INTO ssh_data (id) VALUES ${hosts.map((h) => `(${h.id})`).join(", ")}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const fleetsTable = await db.database.define(fleets);
|
||||
const membersTable = await db.database.define(fleetMembers);
|
||||
const inventoryTable = await db.database.define(fleetInventory);
|
||||
const repo = createFleetRepository(
|
||||
db.database,
|
||||
fakeHosts(hosts),
|
||||
fleetsTable,
|
||||
membersTable,
|
||||
inventoryTable,
|
||||
);
|
||||
return repo;
|
||||
}
|
||||
|
||||
function host(
|
||||
id: number,
|
||||
overrides: Partial<PluginHostSummary> = {},
|
||||
): PluginHostSummary {
|
||||
return {
|
||||
id,
|
||||
userId: "user-1",
|
||||
name: `host-${id}`,
|
||||
ip: `10.0.0.${id}`,
|
||||
port: 22,
|
||||
username: "root",
|
||||
tags: null,
|
||||
folder: null,
|
||||
authType: "password",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("FleetRepository.listEffectiveMembers", () => {
|
||||
it("unions static membership and tag-matched hosts, deduplicated by id", async () => {
|
||||
const repo = await setup([
|
||||
host(1, { tags: "prod-web,edge" }),
|
||||
host(2, { tags: "prod-web" }),
|
||||
host(3, { tags: "prod-db" }),
|
||||
host(4, { tags: null }),
|
||||
host(5, { userId: "user-2", tags: "prod-web" }),
|
||||
]);
|
||||
|
||||
const fleet = await repo.create("user-1", {
|
||||
name: "web fleet",
|
||||
tagRules: ["prod-web"],
|
||||
});
|
||||
// host 1 matches by tag AND is added statically - must appear once.
|
||||
await repo.addMember(fleet.id, 1);
|
||||
// host 4 has no tags - only reachable via static membership.
|
||||
await repo.addMember(fleet.id, 4);
|
||||
|
||||
const members = await repo.listEffectiveMembers("user-1", fleet.id);
|
||||
const ids = members.map((m) => m.id).sort((a, b) => a - b);
|
||||
|
||||
expect(ids).toEqual([1, 2, 4]);
|
||||
});
|
||||
|
||||
it("never returns another user's hosts even if tags match", async () => {
|
||||
const repo = await setup([
|
||||
host(1, { tags: "prod-web" }),
|
||||
host(5, { userId: "user-2", tags: "prod-web" }),
|
||||
]);
|
||||
|
||||
const fleet = await repo.create("user-1", {
|
||||
name: "web fleet",
|
||||
tagRules: ["prod-web"],
|
||||
});
|
||||
|
||||
const members = await repo.listEffectiveMembers("user-1", fleet.id);
|
||||
expect(members.map((m) => m.id)).not.toContain(5);
|
||||
});
|
||||
|
||||
it("returns only static members when a fleet has no tag rules", async () => {
|
||||
const repo = await setup([host(3, { tags: "prod-db" })]);
|
||||
|
||||
const fleet = await repo.create("user-1", { name: "static fleet" });
|
||||
await repo.addMember(fleet.id, 3);
|
||||
|
||||
const members = await repo.listEffectiveMembers("user-1", fleet.id);
|
||||
expect(members.map((m) => m.id)).toEqual([3]);
|
||||
});
|
||||
|
||||
it("removeMember only drops the static row, not tag-based membership", async () => {
|
||||
const repo = await setup([host(1, { tags: "prod-web" })]);
|
||||
|
||||
const fleet = await repo.create("user-1", {
|
||||
name: "web fleet",
|
||||
tagRules: ["prod-web"],
|
||||
});
|
||||
await repo.addMember(fleet.id, 1);
|
||||
|
||||
await expect(repo.removeMember(fleet.id, 1)).resolves.toBe(true);
|
||||
|
||||
// host 1 still matches the tag rule, so it remains an effective member.
|
||||
const members = await repo.listEffectiveMembers("user-1", fleet.id);
|
||||
expect(members.map((m) => m.id)).toContain(1);
|
||||
});
|
||||
|
||||
it("returns an empty list for a fleet the caller does not own", async () => {
|
||||
const repo = await setup([host(1)]);
|
||||
const fleet = await repo.create("user-1", { name: "private" });
|
||||
|
||||
await expect(
|
||||
repo.listEffectiveMembers("user-2", fleet.id),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FleetRepository CRUD", () => {
|
||||
it("creates, updates and deletes a fleet", async () => {
|
||||
const repo = await setup([host(1)]);
|
||||
const created = await repo.create("user-1", {
|
||||
name: "Fleet",
|
||||
tagRules: ["a"],
|
||||
});
|
||||
expect(created.name).toBe("Fleet");
|
||||
expect(created.tagRules).toBe('["a"]');
|
||||
|
||||
const updated = await repo.update("user-1", created.id, {
|
||||
name: "Renamed",
|
||||
});
|
||||
expect(updated?.name).toBe("Renamed");
|
||||
|
||||
await expect(repo.delete("user-1", created.id)).resolves.toBe(true);
|
||||
await expect(repo.findById("user-1", created.id)).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("upserts and lists inventory per host", async () => {
|
||||
const repo = await setup([host(1)]);
|
||||
await repo.upsertInventory("user-1", 1, {
|
||||
osPrettyName: "Debian",
|
||||
kernel: "6.1.0",
|
||||
architecture: "x86_64",
|
||||
hostname: "web-1",
|
||||
uptimeSeconds: 100,
|
||||
ip: "10.0.0.1",
|
||||
packageManager: "apt",
|
||||
});
|
||||
|
||||
const rows = await repo.listInventoryForHosts("user-1", [1]);
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]).toMatchObject({ hostId: 1, osPrettyName: "Debian" });
|
||||
|
||||
// Refreshing overwrites the latest-only snapshot rather than adding a row.
|
||||
await repo.upsertInventory("user-1", 1, {
|
||||
osPrettyName: "Ubuntu",
|
||||
kernel: "6.2.0",
|
||||
architecture: "x86_64",
|
||||
hostname: "web-1",
|
||||
uptimeSeconds: 200,
|
||||
ip: "10.0.0.1",
|
||||
packageManager: "apt",
|
||||
});
|
||||
const refreshed = await repo.listInventoryForHosts("user-1", [1]);
|
||||
expect(refreshed).toHaveLength(1);
|
||||
expect(refreshed[0].osPrettyName).toBe("Ubuntu");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { startServer, type TestServer } from "./helpers";
|
||||
import { parseInventoryProbe } from "../../src/backend/routes.js";
|
||||
|
||||
let server: TestServer;
|
||||
|
||||
afterEach(async () => {
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
async function createFleet(
|
||||
server: TestServer,
|
||||
body: Record<string, unknown> = { name: "Web fleet" },
|
||||
) {
|
||||
const res = await server.request("POST", "/", { body });
|
||||
expect(res.status).toBe(200);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
describe("fleet CRUD routes", () => {
|
||||
beforeEach(async () => {
|
||||
server = await startServer();
|
||||
});
|
||||
|
||||
it("creates and lists fleets with member counts", async () => {
|
||||
const created = await createFleet(server);
|
||||
expect(created).toMatchObject({ name: "Web fleet", tagRules: [] });
|
||||
|
||||
const list = await server.request("GET", "/");
|
||||
expect(list.status).toBe(200);
|
||||
expect(list.body).toHaveLength(1);
|
||||
expect(list.body[0]).toMatchObject({ id: created.id, memberCount: 0 });
|
||||
});
|
||||
|
||||
it("400s on a missing name", async () => {
|
||||
const res = await server.request("POST", "/", { body: {} });
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("updates and deletes a fleet", async () => {
|
||||
const created = await createFleet(server);
|
||||
const updated = await server.request("PATCH", `/${created.id}`, {
|
||||
body: { name: "Renamed" },
|
||||
});
|
||||
expect(updated.status).toBe(200);
|
||||
expect(updated.body.name).toBe("Renamed");
|
||||
|
||||
const deleted = await server.request("DELETE", `/${created.id}`);
|
||||
expect(deleted.status).toBe(200);
|
||||
expect((await server.request("GET", "/")).body).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("404s for a fleet the caller does not own", async () => {
|
||||
const res = await server.request("GET", "/999/members");
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it("400s on a non-numeric fleet id", async () => {
|
||||
const res = await server.request("GET", "/not-a-number/members");
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fleet membership routes", () => {
|
||||
beforeEach(async () => {
|
||||
server = await startServer();
|
||||
});
|
||||
|
||||
it("adds and lists members, then removes one", async () => {
|
||||
const fleet = await createFleet(server);
|
||||
const added = await server.request("POST", `/${fleet.id}/members`, {
|
||||
body: { hostId: 10 },
|
||||
});
|
||||
expect(added.status).toBe(200);
|
||||
|
||||
const members = await server.request("GET", `/${fleet.id}/members`);
|
||||
expect(members.status).toBe(200);
|
||||
expect(members.body).toHaveLength(1);
|
||||
expect(members.body[0]).toMatchObject({ id: 10, static: true });
|
||||
|
||||
const removed = await server.request("DELETE", `/${fleet.id}/members/10`);
|
||||
expect(removed.status).toBe(200);
|
||||
expect(
|
||||
(await server.request("GET", `/${fleet.id}/members`)).body,
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("404s when adding a host outside the fixture host list", async () => {
|
||||
const fleet = await createFleet(server);
|
||||
const res = await server.request("POST", `/${fleet.id}/members`, {
|
||||
body: { hostId: 999 },
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("permission gates", () => {
|
||||
it("view routes 403 without fleets.view", async () => {
|
||||
server = await startServer({
|
||||
permissions: ["fleets.manage", "fleets.execute"],
|
||||
});
|
||||
const res = await server.request("GET", "/");
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("manage routes 403 without fleets.manage", async () => {
|
||||
server = await startServer({
|
||||
permissions: ["fleets.view", "fleets.execute"],
|
||||
});
|
||||
const res = await server.request("POST", "/", { body: { name: "x" } });
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("execute routes 403 without fleets.execute", async () => {
|
||||
server = await startServer({
|
||||
permissions: ["fleets.view", "fleets.manage"],
|
||||
});
|
||||
const fleet = await createFleet(server);
|
||||
const res = await server.request("POST", `/${fleet.id}/execute`, {
|
||||
body: { command: "uptime" },
|
||||
});
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseInventoryProbe", () => {
|
||||
it("extracts kernel, arch, hostname, and uptime from key=value lines", () => {
|
||||
const out = [
|
||||
"kernel=6.1.0-generic",
|
||||
"arch=x86_64",
|
||||
"hostname=web-1",
|
||||
"uptime_seconds=123456",
|
||||
].join("\n");
|
||||
|
||||
expect(parseInventoryProbe(out)).toEqual({
|
||||
kernel: "6.1.0-generic",
|
||||
architecture: "x86_64",
|
||||
hostname: "web-1",
|
||||
uptimeSeconds: 123456,
|
||||
});
|
||||
});
|
||||
|
||||
it("nulls out fields missing from the probe output", () => {
|
||||
expect(parseInventoryProbe("kernel=6.1.0")).toEqual({
|
||||
kernel: "6.1.0",
|
||||
architecture: null,
|
||||
hostname: null,
|
||||
uptimeSeconds: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("nulls uptimeSeconds when the value is not a bare integer", () => {
|
||||
expect(parseInventoryProbe("uptime_seconds=").uptimeSeconds).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores lines with no '=' separator", () => {
|
||||
const out = ["garbage line", "kernel=6.1.0"].join("\n");
|
||||
expect(parseInventoryProbe(out).kernel).toBe("6.1.0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("share target pickers", () => {
|
||||
beforeEach(async () => {
|
||||
server = await startServer();
|
||||
});
|
||||
|
||||
it("lists users and roles through ctx.hosts", async () => {
|
||||
const users = await server.request("GET", "/share-targets/users");
|
||||
expect(users.status).toBe(200);
|
||||
expect(users.body).toEqual({ users: [] });
|
||||
|
||||
const roles = await server.request("GET", "/share-targets/roles");
|
||||
expect(roles.status).toBe(200);
|
||||
expect(roles.body).toEqual({ roles: [] });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { startServer, type TestServer } from "./helpers";
|
||||
|
||||
let server: TestServer | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
await server?.close();
|
||||
server = null;
|
||||
});
|
||||
|
||||
interface FleetsService {
|
||||
create: (input: { name: string }) => Promise<{ id: number; name: string }>;
|
||||
addMember: (fleetId: number, hostId: number) => Promise<void>;
|
||||
}
|
||||
|
||||
describe("fleets.access", () => {
|
||||
it("needs fleets.manage to change a fleet", async () => {
|
||||
server = await startServer({ permissions: ["fleets.view"] });
|
||||
const service = server.mock.services.get("fleets.access") as FleetsService;
|
||||
|
||||
await expect(
|
||||
server.mock.actAs("user-1", () => service.create({ name: "prod" })),
|
||||
).rejects.toThrow("fleets.manage");
|
||||
});
|
||||
|
||||
it("adds only hosts the caller can see", async () => {
|
||||
server = await startServer();
|
||||
const service = server.mock.services.get("fleets.access") as FleetsService;
|
||||
const run = <T>(fn: () => Promise<T>) => server!.mock.actAs("user-1", fn);
|
||||
|
||||
const fleet = await run(() => service.create({ name: "prod" }));
|
||||
await run(() => service.addMember(fleet.id, 10));
|
||||
await expect(run(() => service.addMember(fleet.id, 99))).rejects.toThrow(
|
||||
"Host not found",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
renderWithApp,
|
||||
type RenderedPluginApp,
|
||||
} from "@termix/plugin-sdk/testing";
|
||||
import type { PluginManifest } from "@termix/plugin-sdk/manifest";
|
||||
import * as plugin from "../../src/frontend/index";
|
||||
import manifestJson from "../../manifest.json";
|
||||
import locales from "../../locales/en.json";
|
||||
|
||||
const manifest = manifestJson as unknown as PluginManifest;
|
||||
|
||||
const declared = (views?: Array<{ id: string }>) =>
|
||||
(views ?? []).map((view) => view.id).sort();
|
||||
|
||||
let rendered: RenderedPluginApp | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
await rendered?.deactivate();
|
||||
rendered = null;
|
||||
});
|
||||
|
||||
describe(`${manifest.id} activate`, () => {
|
||||
it("activates and registers only views its manifest declares", async () => {
|
||||
rendered = await renderWithApp(plugin, { manifest, locales });
|
||||
const contributes = manifest.contributes as unknown as Record<
|
||||
string,
|
||||
Array<{ id: string }> | undefined
|
||||
>;
|
||||
const tabs = declared(contributes.tabs);
|
||||
// A rail panel may reuse one of the plugin's tab ids.
|
||||
const panels = [...declared(contributes.panels), ...tabs];
|
||||
const cards = declared(contributes.dashboardCards);
|
||||
for (const id of rendered.registered.tabs()) expect(tabs).toContain(id);
|
||||
for (const id of rendered.registered.panels()) expect(panels).toContain(id);
|
||||
for (const id of rendered.registered.dashboardCards()) {
|
||||
expect(cards).toContain(id);
|
||||
}
|
||||
});
|
||||
|
||||
it("removes everything it registered on deactivate", async () => {
|
||||
const app = await renderWithApp(plugin, { manifest, locales });
|
||||
await app.deactivate();
|
||||
expect(app.registered.tabs()).toEqual([]);
|
||||
expect(app.registered.panels()).toEqual([]);
|
||||
expect(app.registered.railItems()).toEqual([]);
|
||||
expect(app.registered.hostActions()).toEqual([]);
|
||||
expect(app.registered.hostEditorSections()).toEqual([]);
|
||||
expect(app.registered.dashboardCards()).toEqual([]);
|
||||
expect(app.registered.settingsComponents()).toEqual([]);
|
||||
expect(app.registered.actions()).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@termix/plugin-sdk/tsconfig.plugin.json",
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { pluginVitestConfig } from "@termix/plugin-sdk/vitest-preset";
|
||||
|
||||
export default pluginVitestConfig(import.meta.url);
|
||||
Reference in new issue
Block a user