From e0c30a4e6f6bff0dd2182de09ed50c62e7bcfc53 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Tue, 29 Sep 2026 11:39:14 -0500 Subject: [PATCH] feat: move snippets, keybinding actions and macros out of core into plugins --- .../plugin-sdk/schema/manifest.schema.json | 49 +++ packages/plugin-sdk/src/backend.ts | 2 +- packages/plugin-sdk/src/frontend.ts | 136 +++++- packages/plugin-sdk/src/manifest.ts | 181 +++++++- packages/plugin-sdk/src/testing.ts | 7 + packages/plugin-sdk/src/testing/setup.ts | 1 + plugins/fleets/manifest.json | 3 + .../fleets/src/backend/command-variables.ts | 55 --- plugins/fleets/src/backend/routes.ts | 31 +- plugins/fleets/src/frontend/FleetsPanel.tsx | 17 +- plugins/fleets/src/frontend/helpers.ts | 21 - .../tests/backend/command-variables.test.ts | 30 ++ plugins/host-metrics/locales/en.json | 22 +- .../locales/translated/af_ZA.json | 19 - .../locales/translated/ar_SA.json | 19 - .../locales/translated/bg_BG.json | 19 - .../locales/translated/bn_BD.json | 19 - .../locales/translated/ca_ES.json | 19 - .../locales/translated/cs_CZ.json | 19 - .../locales/translated/da_DK.json | 19 - .../locales/translated/de_DE.json | 19 - .../locales/translated/el_GR.json | 19 - .../locales/translated/es_ES.json | 19 - .../locales/translated/fi_FI.json | 19 - .../locales/translated/fr_FR.json | 19 - .../locales/translated/he_IL.json | 19 - .../locales/translated/hi_IN.json | 19 - .../locales/translated/hu_HU.json | 19 - .../locales/translated/id_ID.json | 19 - .../locales/translated/it_IT.json | 19 - .../locales/translated/ja_JP.json | 19 - .../locales/translated/ko_KR.json | 19 - .../locales/translated/nl_NL.json | 19 - .../locales/translated/no_NO.json | 19 - .../locales/translated/pl_PL.json | 19 - .../locales/translated/pt_BR.json | 19 - .../locales/translated/pt_PT.json | 19 - .../locales/translated/ro_RO.json | 19 - .../locales/translated/ru_RU.json | 19 - .../locales/translated/sr_SP.json | 19 - .../locales/translated/sv_SE.json | 19 - .../locales/translated/th_TH.json | 19 - .../locales/translated/tr_TR.json | 19 - .../locales/translated/uk_UA.json | 19 - .../locales/translated/vi_VN.json | 19 - .../locales/translated/zh_CN.json | 19 - .../locales/translated/zh_TW.json | 19 - plugins/host-metrics/manifest.json | 4 + .../src/frontend/HostEditorStatsTab.tsx | 94 +---- .../src/frontend/HostMetricsTab.tsx | 206 +-------- plugins/host-metrics/src/frontend/index.tsx | 6 + .../frontend/HostEditorStatsTab.test.tsx | 2 - plugins/snippets/locales/en.json | 34 +- .../snippets/locales/translated/af_ZA.json | 29 ++ .../snippets/locales/translated/ar_SA.json | 29 ++ .../snippets/locales/translated/bg_BG.json | 29 ++ .../snippets/locales/translated/bn_BD.json | 29 ++ .../snippets/locales/translated/ca_ES.json | 29 ++ .../snippets/locales/translated/cs_CZ.json | 29 ++ .../snippets/locales/translated/da_DK.json | 29 ++ .../snippets/locales/translated/de_DE.json | 29 ++ .../snippets/locales/translated/el_GR.json | 29 ++ .../snippets/locales/translated/es_ES.json | 29 ++ .../snippets/locales/translated/fi_FI.json | 29 ++ .../snippets/locales/translated/fr_FR.json | 29 ++ .../snippets/locales/translated/he_IL.json | 29 ++ .../snippets/locales/translated/hi_IN.json | 29 ++ .../snippets/locales/translated/hu_HU.json | 29 ++ .../snippets/locales/translated/id_ID.json | 29 ++ .../snippets/locales/translated/it_IT.json | 29 ++ .../snippets/locales/translated/ja_JP.json | 29 ++ .../snippets/locales/translated/ko_KR.json | 29 ++ .../snippets/locales/translated/nl_NL.json | 29 ++ .../snippets/locales/translated/no_NO.json | 29 ++ .../snippets/locales/translated/pl_PL.json | 29 ++ .../snippets/locales/translated/pt_BR.json | 29 ++ .../snippets/locales/translated/pt_PT.json | 29 ++ .../snippets/locales/translated/ro_RO.json | 29 ++ .../snippets/locales/translated/ru_RU.json | 29 ++ .../snippets/locales/translated/sr_SP.json | 29 ++ .../snippets/locales/translated/sv_SE.json | 29 ++ .../snippets/locales/translated/th_TH.json | 29 ++ .../snippets/locales/translated/tr_TR.json | 29 ++ .../snippets/locales/translated/uk_UA.json | 29 ++ .../snippets/locales/translated/vi_VN.json | 29 ++ .../snippets/locales/translated/zh_CN.json | 29 ++ .../snippets/locales/translated/zh_TW.json | 29 ++ plugins/snippets/manifest.json | 47 ++- plugins/snippets/src/backend/execution.ts | 54 +-- plugins/snippets/src/backend/host-import.ts | 117 ++++++ plugins/snippets/src/backend/index.ts | 32 +- plugins/snippets/src/backend/repository.ts | 21 + .../src/frontend/HostSnippetsSection.tsx | 171 ++++++++ .../src/frontend/QuickActionButtons.tsx | 147 +++++++ .../snippets/src/frontend/SnippetOverlay.tsx | 32 ++ .../src/frontend}/SnippetVariablesDialog.tsx | 35 +- plugins/snippets/src/frontend/index.tsx | 204 +++++---- plugins/snippets/src/frontend/keybinding.tsx | 89 ++++ plugins/snippets/src/frontend/prompt-store.ts | 72 ++++ plugins/snippets/src/frontend/run-flows.ts | 121 ++++++ .../src/frontend/use-snippet-options.ts | 53 +++ .../src/frontend/use-snippet-runner.tsx | 6 +- plugins/snippets/src/shared/host-settings.ts | 48 +++ .../variables.ts} | 6 + .../snippets/tests/backend/activate.test.ts | 22 + .../tests/backend/host-import.test.ts | 79 ++++ .../variables.test.ts} | 2 +- .../snippets/tests/frontend/activate.test.tsx | 159 ++++++- .../frontend/admin-user-snippets.test.tsx | 19 +- plugins/ssh-terminal/locales/en.json | 121 +++++- .../locales/translated/af_ZA.json | 111 ++++- .../locales/translated/ar_SA.json | 111 ++++- .../locales/translated/bg_BG.json | 111 ++++- .../locales/translated/bn_BD.json | 111 ++++- .../locales/translated/ca_ES.json | 111 ++++- .../locales/translated/cs_CZ.json | 111 ++++- .../locales/translated/da_DK.json | 111 ++++- .../locales/translated/de_DE.json | 111 ++++- .../locales/translated/el_GR.json | 111 ++++- .../locales/translated/es_ES.json | 111 ++++- .../locales/translated/fi_FI.json | 111 ++++- .../locales/translated/fr_FR.json | 111 ++++- .../locales/translated/he_IL.json | 111 ++++- .../locales/translated/hi_IN.json | 111 ++++- .../locales/translated/hu_HU.json | 111 ++++- .../locales/translated/id_ID.json | 111 ++++- .../locales/translated/it_IT.json | 111 ++++- .../locales/translated/ja_JP.json | 111 ++++- .../locales/translated/ko_KR.json | 111 ++++- .../locales/translated/nl_NL.json | 111 ++++- .../locales/translated/no_NO.json | 111 ++++- .../locales/translated/pl_PL.json | 111 ++++- .../locales/translated/pt_BR.json | 111 ++++- .../locales/translated/pt_PT.json | 111 ++++- .../locales/translated/ro_RO.json | 111 ++++- .../locales/translated/ru_RU.json | 111 ++++- .../locales/translated/sr_SP.json | 111 ++++- .../locales/translated/sv_SE.json | 111 ++++- .../locales/translated/th_TH.json | 111 ++++- .../locales/translated/tr_TR.json | 111 ++++- .../locales/translated/uk_UA.json | 111 ++++- .../locales/translated/vi_VN.json | 111 ++++- .../locales/translated/zh_CN.json | 111 ++++- .../locales/translated/zh_TW.json | 111 ++++- plugins/ssh-terminal/manifest.json | 45 +- plugins/ssh-terminal/src/backend/index.ts | 6 +- .../src/backend/settings-validation.ts | 35 ++ plugins/ssh-terminal/src/frontend/index.tsx | 51 +++ .../src/frontend/lib/keybinding-dispatch.ts | 170 +++++--- .../src/frontend/lib/keybinding-editors.tsx | 81 ++++ .../src/frontend/macros}/MacrosPanel.tsx | 118 +++--- .../src/frontend/macros}/terminal-macros.ts | 56 ++- .../frontend/settings/HostTerminalSection.tsx | 2 - .../settings/HostTerminalSettings.tsx | 37 +- .../src/frontend/terminal/Terminal.tsx | 155 ++----- .../tests/backend/settings-validation.test.ts | 30 +- .../frontend/lib/keybinding-dispatch.test.ts | 87 ++-- .../frontend/macros}/terminal-macros.test.ts | 49 ++- .../settings/HostTerminalSettings.test.tsx | 11 +- .../database/routes/host-bulk-routes.ts | 4 - .../database/routes/host-normalizers.ts | 20 +- src/backend/database/routes/host.ts | 15 - .../database/routes/keybinding-validation.ts | 72 +++- .../database/routes/user-admin-routes.ts | 2 +- .../routes/user-password-reset-routes.ts | 4 +- .../database/routes/user-preferences.ts | 60 +-- src/backend/hosts/host-resolver.ts | 7 - src/backend/plugins/index.ts | 19 + src/backend/sync/entities.ts | 67 ++- .../database/routes/host-normalizers.test.ts | 16 +- .../routes/keybinding-validation.test.ts | 56 ++- src/backend/tests/fixtures/upgrade/db.sqlite | Bin 909312 -> 909312 bytes src/backend/tests/fixtures/upgrade/rows.ts | 28 ++ src/backend/tests/plugins/manifest.test.ts | 73 ++++ .../tests/plugins/sync-registry.test.ts | 57 ++- .../tests/plugins/upgrade-fixture.test.ts | 9 + .../snippets-settings-migration.test.ts | 174 ++++++++ .../ssh-terminal-macros-migration.test.ts | 104 +++++ .../utils/auth-manager-impersonation.test.ts | 47 ++- src/backend/upgrade/boot-migrations.ts | 4 + .../upgrade/snippets-settings-migration.ts | 185 ++++++++ .../upgrade/ssh-terminal-macros-migration.ts | 98 +++++ src/backend/utils/auth-manager.ts | 36 +- src/backend/utils/swagger.ts | 4 - src/types/index.ts | 22 +- src/types/keybindings.ts | 21 +- src/types/ui-preferences.ts | 1 - src/types/ui-types.ts | 18 +- src/ui/AppShell.tsx | 66 +-- src/ui/api/open-tabs-api.ts | 2 - src/ui/hooks/use-snippet-runner.tsx | 130 ------ src/ui/lib/default-keybindings.ts | 106 ----- src/ui/lib/snippet-provider.ts | 22 - src/ui/lib/snippet-variables.ts | 70 ---- src/ui/locales/en.json | 245 +---------- src/ui/locales/translated/af_ZA.json | 230 +--------- src/ui/locales/translated/ar_SA.json | 230 +--------- src/ui/locales/translated/bg_BG.json | 230 +--------- src/ui/locales/translated/bn_BD.json | 230 +--------- src/ui/locales/translated/ca_ES.json | 230 +--------- src/ui/locales/translated/cs_CZ.json | 230 +--------- src/ui/locales/translated/da_DK.json | 230 +--------- src/ui/locales/translated/de_DE.json | 230 +--------- src/ui/locales/translated/el_GR.json | 230 +--------- src/ui/locales/translated/es_ES.json | 230 +--------- src/ui/locales/translated/fi_FI.json | 230 +--------- src/ui/locales/translated/fr_FR.json | 230 +--------- src/ui/locales/translated/he_IL.json | 230 +--------- src/ui/locales/translated/hi_IN.json | 230 +--------- src/ui/locales/translated/hu_HU.json | 230 +--------- src/ui/locales/translated/id_ID.json | 230 +--------- src/ui/locales/translated/it_IT.json | 230 +--------- src/ui/locales/translated/ja_JP.json | 230 +--------- src/ui/locales/translated/ko_KR.json | 230 +--------- src/ui/locales/translated/nl_NL.json | 230 +--------- src/ui/locales/translated/no_NO.json | 230 +--------- src/ui/locales/translated/pl_PL.json | 230 +--------- src/ui/locales/translated/pt_BR.json | 230 +--------- src/ui/locales/translated/pt_PT.json | 230 +--------- src/ui/locales/translated/ro_RO.json | 230 +--------- src/ui/locales/translated/ru_RU.json | 230 +--------- src/ui/locales/translated/sr_SP.json | 230 +--------- src/ui/locales/translated/sv_SE.json | 230 +--------- src/ui/locales/translated/th_TH.json | 230 +--------- src/ui/locales/translated/tr_TR.json | 230 +--------- src/ui/locales/translated/uk_UA.json | 230 +--------- src/ui/locales/translated/vi_VN.json | 230 +--------- src/ui/locales/translated/zh_CN.json | 230 +--------- src/ui/locales/translated/zh_TW.json | 230 +--------- src/ui/plugin-host/app.ts | 68 ++- src/ui/plugin-host/bridge.ts | 3 + src/ui/plugin-host/sdk-ui.ts | 2 +- src/ui/plugin-host/testing-host.tsx | 17 + src/ui/shell/CommandPalette.tsx | 161 +++---- src/ui/shell/keybinding-registry.ts | 150 +++++++ src/ui/shell/palette-registry.ts | 58 ++- src/ui/shell/shell-layout.ts | 16 +- src/ui/shell/tab-registry.ts | 2 +- src/ui/shell/tabUtils.tsx | 32 -- src/ui/sidebar/AdminAuditLogSection.tsx | 2 +- src/ui/sidebar/AdminUserDialogs.tsx | 2 +- src/ui/sidebar/HostEditor.tsx | 13 +- src/ui/sidebar/HostEditorData.ts | 27 -- src/ui/sidebar/HostExportDialog.tsx | 2 - src/ui/sidebar/HostManagerData.ts | 5 - src/ui/sidebar/HostManagerTabs.tsx | 2 +- src/ui/sidebar/KeybindingsDialog.tsx | 396 +++++++----------- src/ui/sidebar/UserProfilePanel.tsx | 28 -- src/ui/sidebar/host-export-payload.ts | 2 - src/ui/sidebar/quick-connect-host.ts | 1 - src/ui/sidebar/rail-items.ts | 9 - src/ui/sidebar/tree/SidebarTree.tsx | 7 - src/ui/tests/lib/apply-ui-preset.test.ts | 4 +- .../tests/shell/keybinding-registry.test.ts | 81 ++++ src/ui/tests/shell/palette-registry.test.ts | 61 +++ .../tests/sidebar/HostCredentialList.test.tsx | 1 - src/ui/tests/sidebar/HostEditorData.test.ts | 7 +- .../tests/sidebar/KeybindingsDialog.test.tsx | 79 ++++ .../tests/sidebar/host-export-payload.test.ts | 1 - src/ui/tests/sidebar/rail-items.test.ts | 13 +- .../sidebar/sidebar-tree-visible-rows.test.ts | 1 - .../sidebar/tree/HostItem/HostItem.test.tsx | 1 - 262 files changed, 9091 insertions(+), 10744 deletions(-) delete mode 100644 plugins/fleets/src/backend/command-variables.ts create mode 100644 plugins/fleets/tests/backend/command-variables.test.ts create mode 100644 plugins/snippets/locales/translated/af_ZA.json create mode 100644 plugins/snippets/locales/translated/ar_SA.json create mode 100644 plugins/snippets/locales/translated/bg_BG.json create mode 100644 plugins/snippets/locales/translated/bn_BD.json create mode 100644 plugins/snippets/locales/translated/ca_ES.json create mode 100644 plugins/snippets/locales/translated/cs_CZ.json create mode 100644 plugins/snippets/locales/translated/da_DK.json create mode 100644 plugins/snippets/locales/translated/de_DE.json create mode 100644 plugins/snippets/locales/translated/el_GR.json create mode 100644 plugins/snippets/locales/translated/es_ES.json create mode 100644 plugins/snippets/locales/translated/fi_FI.json create mode 100644 plugins/snippets/locales/translated/fr_FR.json create mode 100644 plugins/snippets/locales/translated/he_IL.json create mode 100644 plugins/snippets/locales/translated/hi_IN.json create mode 100644 plugins/snippets/locales/translated/hu_HU.json create mode 100644 plugins/snippets/locales/translated/id_ID.json create mode 100644 plugins/snippets/locales/translated/it_IT.json create mode 100644 plugins/snippets/locales/translated/ja_JP.json create mode 100644 plugins/snippets/locales/translated/ko_KR.json create mode 100644 plugins/snippets/locales/translated/nl_NL.json create mode 100644 plugins/snippets/locales/translated/no_NO.json create mode 100644 plugins/snippets/locales/translated/pl_PL.json create mode 100644 plugins/snippets/locales/translated/pt_BR.json create mode 100644 plugins/snippets/locales/translated/pt_PT.json create mode 100644 plugins/snippets/locales/translated/ro_RO.json create mode 100644 plugins/snippets/locales/translated/ru_RU.json create mode 100644 plugins/snippets/locales/translated/sr_SP.json create mode 100644 plugins/snippets/locales/translated/sv_SE.json create mode 100644 plugins/snippets/locales/translated/th_TH.json create mode 100644 plugins/snippets/locales/translated/tr_TR.json create mode 100644 plugins/snippets/locales/translated/uk_UA.json create mode 100644 plugins/snippets/locales/translated/vi_VN.json create mode 100644 plugins/snippets/locales/translated/zh_CN.json create mode 100644 plugins/snippets/locales/translated/zh_TW.json create mode 100644 plugins/snippets/src/backend/host-import.ts create mode 100644 plugins/snippets/src/frontend/HostSnippetsSection.tsx create mode 100644 plugins/snippets/src/frontend/QuickActionButtons.tsx create mode 100644 plugins/snippets/src/frontend/SnippetOverlay.tsx rename {src/ui/components => plugins/snippets/src/frontend}/SnippetVariablesDialog.tsx (79%) create mode 100644 plugins/snippets/src/frontend/keybinding.tsx create mode 100644 plugins/snippets/src/frontend/prompt-store.ts create mode 100644 plugins/snippets/src/frontend/run-flows.ts create mode 100644 plugins/snippets/src/frontend/use-snippet-options.ts create mode 100644 plugins/snippets/src/shared/host-settings.ts rename plugins/snippets/src/{frontend/snippet-variables.ts => shared/variables.ts} (90%) create mode 100644 plugins/snippets/tests/backend/host-import.test.ts rename plugins/snippets/tests/{frontend/snippet-variables.test.ts => backend/variables.test.ts} (98%) create mode 100644 plugins/ssh-terminal/src/frontend/lib/keybinding-editors.tsx rename {src/ui/sidebar => plugins/ssh-terminal/src/frontend/macros}/MacrosPanel.tsx (92%) rename {src/ui/lib => plugins/ssh-terminal/src/frontend/macros}/terminal-macros.ts (84%) rename {src/ui/tests/lib => plugins/ssh-terminal/tests/frontend/macros}/terminal-macros.test.ts (74%) create mode 100644 src/backend/tests/upgrade/snippets-settings-migration.test.ts create mode 100644 src/backend/tests/upgrade/ssh-terminal-macros-migration.test.ts create mode 100644 src/backend/upgrade/snippets-settings-migration.ts create mode 100644 src/backend/upgrade/ssh-terminal-macros-migration.ts delete mode 100644 src/ui/hooks/use-snippet-runner.tsx delete mode 100644 src/ui/lib/default-keybindings.ts delete mode 100644 src/ui/lib/snippet-provider.ts delete mode 100644 src/ui/lib/snippet-variables.ts create mode 100644 src/ui/shell/keybinding-registry.ts create mode 100644 src/ui/tests/shell/keybinding-registry.test.ts create mode 100644 src/ui/tests/shell/palette-registry.test.ts create mode 100644 src/ui/tests/sidebar/KeybindingsDialog.test.tsx diff --git a/packages/plugin-sdk/schema/manifest.schema.json b/packages/plugin-sdk/schema/manifest.schema.json index d98deed63..bc206c474 100644 --- a/packages/plugin-sdk/schema/manifest.schema.json +++ b/packages/plugin-sdk/schema/manifest.schema.json @@ -279,6 +279,51 @@ "guest": { "type": "boolean" }, + "keybindingActions": { + "type": "array", + "description": "Keybinding actions the plugin adds, with the parameters a saved binding carries.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9.-]{0,63}$", + "not": { + "enum": [ + "nextTab", + "previousTab", + "openCommandPalette", + "reconnectSession" + ] + } + }, + "params": { + "type": "object", + "propertyNames": { + "pattern": "^[a-zA-Z][a-zA-Z0-9]{0,31}$", + "not": { "const": "type" } + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": ["type"], + "properties": { + "type": { "enum": ["string", "boolean"] }, + "required": { "type": "boolean" }, + "pattern": { "type": "string" }, + "maxLength": { "type": "integer", "minimum": 1 }, + "syncEntity": { + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9]*$" + } + } + } + } + } + } + }, "syncEntities": { "type": "array", "minItems": 1, @@ -840,6 +885,10 @@ "additionalProperties": false, "description": "HTTP options for the plugin's /plugin-api// router.", "properties": { + "adminImpersonation": { + "type": "boolean", + "description": "An admin may call the plugin's routes on behalf of another user with the X-Admin-Target-User header." + }, "legacyPaths": { "type": "array", "description": "Old URLs under // served by the plugin's router with that prefix removed.", diff --git a/packages/plugin-sdk/src/backend.ts b/packages/plugin-sdk/src/backend.ts index 8fe28c664..ddbd6dc70 100644 --- a/packages/plugin-sdk/src/backend.ts +++ b/packages/plugin-sdk/src/backend.ts @@ -191,7 +191,7 @@ export interface SyncEntityRegistration { singleton?: boolean; /** * Names core uses when its own rows point at this entity without knowing - * which plugin provides it, e.g. "commandSnippet" for a host quick action. + * which plugin provides it. Nothing in core uses one today. */ answersTo?: readonly string[]; /** diff --git a/packages/plugin-sdk/src/frontend.ts b/packages/plugin-sdk/src/frontend.ts index db62f4874..acb4c9a88 100644 --- a/packages/plugin-sdk/src/frontend.ts +++ b/packages/plugin-sdk/src/frontend.ts @@ -301,7 +301,8 @@ export interface HostEditorSectionProps { ) => void; host?: PluginHostRecord; credentials?: unknown[]; - snippets?: unknown[]; + /** Set while an admin edits another user's host from the admin panel. */ + adminTargetUserId?: string; /** Which connection protocols are switched on for this host. */ protocols: Record; } @@ -408,6 +409,97 @@ export interface PaletteEntryContribution { run: (shell: ShellApi, host?: PluginHostRecord) => void; } +/** What a palette item's run gets. */ +export interface PaletteRunContext { + /** The command-target tab the user is working in, if any. */ + targetTab?: PluginTabRecord; + shell: ShellApi; +} + +/** One searchable row in a palette group. */ +export interface PaletteItem { + id: string; + /** Already translated; items are data, not keys. */ + title: string; + description?: string; + icon?: IconComponent; + /** Extra words the search matches. */ + keywords?: string[]; + /** Acts on the command-target tab, so it is greyed out without one. */ + needsTarget?: boolean; + /** Short text at the row's end, e.g. "Run in terminal". */ + hint?: string; + run: (context: PaletteRunContext) => void; +} + +/** + * A group of items in the command palette, e.g. the user's snippets. `load` + * runs each time the palette opens; the palette filters the items by what + * the user types. + */ +export interface PaletteGroupContribution { + id: string; + titleKey: string; + /** Lower sorts first. */ + order?: number; + load: () => PaletteItem[] | Promise; + /** Show the items before anything is typed. Default false. */ + showWhenEmpty?: boolean; +} + +/** A keybinding action's parameter editor in Appearance > Keybindings. */ +export interface KeybindingActionEditorProps { + action: KeybindingAction; + onChange: (action: KeybindingAction) => void; +} + +/** Where a keybinding action runs when the terminal hands it on. */ +export interface KeybindingRunContext { + /** The session the key was pressed in. */ + sessionId?: string; + host?: { + ip?: string; + username?: string; + port?: number | string; + name?: string; + } | null; + /** Writes raw input to that session. */ + send?: (data: string) => void; +} + +/** + * A keybinding action. `id` is stored as the binding's action.type and must + * be declared in contributes.keybindingActions, which is what the server + * validates saved bindings against. + */ +export interface KeybindingActionContribution { + id: string; + titleKey: string; + /** + * "session" runs while a session tab such as a terminal has focus (the + * default). "global" runs anywhere in the app. + */ + scope?: "session" | "global"; + /** Draws the action's parameters in the binding form. */ + editor?: ComponentType; + /** Drawn after the action's name in the binding list, e.g. a warning. */ + summary?: ComponentType; + /** A plugin translation key when the action cannot be saved yet. */ + validate?: (action: KeybindingAction) => string | null; + /** + * Runs it. A terminal hands on every bound action it does not handle + * itself; a global action runs from anywhere. + */ + run?: (action: KeybindingAction, context: KeybindingRunContext) => void; +} + +/** A built-in key the user can rebind in Appearance > Keybindings. */ +export interface KeybindingDefaultContribution { + id: string; + combo: KeyCombo; + descriptionKey: string; +} + export interface DashboardCardProps { isVisible: boolean; shell: ShellApi; @@ -627,6 +719,11 @@ export interface TermixApp extends TermixAppInfo { item: HostContextMenuItemContribution, ) => Disposer; registerPaletteEntry: (entry: PaletteEntryContribution) => Disposer; + registerPaletteGroup: (group: PaletteGroupContribution) => Disposer; + registerKeybindingAction: (action: KeybindingActionContribution) => Disposer; + registerKeybindingDefault: ( + binding: KeybindingDefaultContribution, + ) => Disposer; registerDashboardCard: (card: DashboardCardContribution) => Disposer; registerHomepageWidget: (widget: HomepageWidgetContribution) => Disposer; registerSettingsComponent: ( @@ -815,23 +912,16 @@ export interface KeyCombo { meta: boolean; } -export type KeybindingActionType = - | "copy" - | "paste" - | "sendControlCode" - | "sendText" - | "runSnippet" - | "nextTab" - | "previousTab" - | "openCommandPalette" - | "reconnectSession"; +/** + * The shell's own actions (nextTab, previousTab, openCommandPalette, + * reconnectSession) or one a plugin declared in contributes.keybindingActions. + */ +export type KeybindingActionType = string; +/** A bound action: its type plus the parameters its declaration lists. */ export interface KeybindingAction { type: KeybindingActionType; - text?: string; - controlCode?: string; - snippetId?: string; - appendEnter?: boolean; + [param: string]: unknown; } export interface CustomKeybinding { @@ -869,6 +959,15 @@ export interface PluginCoreApi { ) => Promise; /** The user's custom keybindings, enabled or not. */ getCustomKeybindings: () => Promise; + /** + * Runs a bound action through whoever registered it (a plugin's + * registerKeybindingAction, or the shell for its own). False when nothing + * running handles that type. + */ + runKeybindingAction: ( + action: KeybindingAction, + context?: KeybindingRunContext, + ) => boolean; /** A browser-side UI preference (a cookie, or the desktop app's store). */ getClientPreference: (name: string) => string | undefined; /** Saves a browser-side UI preference where getClientPreference reads it. */ @@ -1021,6 +1120,13 @@ export function getCustomKeybindings(): Promise { return requireHost().core.getCustomKeybindings(); } +export function runKeybindingAction( + action: KeybindingAction, + context?: KeybindingRunContext, +): boolean { + return requireHost().core.runKeybindingAction(action, context); +} + export function getClientPreference(name: string): string | undefined { return requireHost().core.getClientPreference(name); } diff --git a/packages/plugin-sdk/src/manifest.ts b/packages/plugin-sdk/src/manifest.ts index 5ef2f31de..38f256146 100644 --- a/packages/plugin-sdk/src/manifest.ts +++ b/packages/plugin-sdk/src/manifest.ts @@ -344,8 +344,46 @@ export interface PluginContributions { * off and a desktop can list them in its sync settings. */ syncEntities?: string[]; + /** + * Keybinding actions this plugin adds to Appearance > Keybindings, with + * the parameters a saved binding carries. Core validates saved bindings + * against these without running plugin code, and translates a parameter + * naming a sync entity over sync. + */ + keybindingActions?: PluginKeybindingActionContribution[]; } +/** One parameter a saved keybinding action carries next to its type. */ +export interface PluginKeybindingParam { + type: "string" | "boolean"; + required?: boolean; + /** A regular expression a string value must match. */ + pattern?: string; + maxLength?: number; + /** + * The value is a local row id of this sync entity, stored as a string. + * Sync sends the row's syncId instead. + */ + syncEntity?: string; +} + +export interface PluginKeybindingActionContribution { + /** + * Stored as the binding's action.type. New actions use + * "."; the bundled ones keep their 2.8 names. + */ + id: string; + params?: Record; +} + +/** Keybinding action types the shell runs itself; no plugin may declare them. */ +export const CORE_KEYBINDING_ACTIONS: readonly string[] = [ + "nextTab", + "previousTab", + "openCommandPalette", + "reconnectSession", +]; + /** * How a desktop linked to a server treats this plugin. * - "mirror": follows the server, installed and switched on or off with it. @@ -376,6 +414,12 @@ export interface PluginHttpContribution { * Core routes win a clash. */ legacyRedirects?: PluginLegacyRedirect[]; + /** + * An admin may call this plugin's routes on behalf of another user with + * the X-Admin-Target-User header (the admin "manage user" panel). Without + * it core refuses the header on every route under /plugin-api//. + */ + adminImpersonation?: boolean; } export interface PluginLegacyRedirect { @@ -478,6 +522,7 @@ const ALLOWED_CONTRIBUTES = new Set([ "uiPresets", "auth", "syncEntities", + "keybindingActions", ]); const ALLOWED_SETTINGS_FIELD = [ @@ -947,6 +992,132 @@ function validateContributes( validateAuthContribution(contributes.auth, errors); validateHttpContribution(contributes.http, pluginId, errors); validateUiPresets(contributes.uiPresets, errors); + validateKeybindingActions(contributes.keybindingActions, errors); +} + +const KEYBINDING_ACTION_PATTERN = /^[a-zA-Z][a-zA-Z0-9.-]{0,63}$/; +const KEYBINDING_PARAM_PATTERN = /^[a-zA-Z][a-zA-Z0-9]{0,31}$/; + +function validateKeybindingParam( + param: unknown, + at: string, + errors: string[], +): void { + if (!isPlainObject(param)) { + errors.push(`${at} must be an object`); + return; + } + rejectUnknown( + param, + ["type", "required", "pattern", "maxLength", "syncEntity"], + at, + errors, + ); + if (param.type !== "string" && param.type !== "boolean") { + errors.push(`${at}.type must be "string" or "boolean"`); + } + if (param.required !== undefined && typeof param.required !== "boolean") { + errors.push(`${at}.required must be a boolean`); + } + if (param.pattern !== undefined) { + let valid = typeof param.pattern === "string"; + if (valid) { + try { + new RegExp(param.pattern as string); + } catch { + valid = false; + } + } + if (!valid) errors.push(`${at}.pattern must be a regular expression`); + } + if ( + param.maxLength !== undefined && + (typeof param.maxLength !== "number" || + !Number.isInteger(param.maxLength) || + param.maxLength < 1) + ) { + errors.push(`${at}.maxLength must be a positive integer`); + } + if (param.syncEntity !== undefined) { + if ( + typeof param.syncEntity !== "string" || + !/^[a-zA-Z][a-zA-Z0-9]*$/.test(param.syncEntity) + ) { + errors.push(`${at}.syncEntity must be a sync entity wire name`); + } else if (param.type !== "string") { + errors.push(`${at}.syncEntity needs a string parameter`); + } + } +} + +function validateKeybindingActions(value: unknown, errors: string[]): void { + if (value === undefined) return; + const where = "contributes.keybindingActions"; + if (!Array.isArray(value)) { + errors.push(`Field "${where}" must be an array`); + return; + } + const seen = new Set(); + value.forEach((raw, index) => { + const at = `${where}[${index}]`; + if (!isPlainObject(raw)) { + errors.push(`${at} must be an object`); + return; + } + rejectUnknown(raw, ["id", "params"], at, errors); + if (typeof raw.id !== "string" || !KEYBINDING_ACTION_PATTERN.test(raw.id)) { + errors.push(`${at}.id must be letters, digits, dots and dashes`); + } else if (CORE_KEYBINDING_ACTIONS.includes(raw.id)) { + errors.push(`${at}.id "${raw.id}" is one of the shell's own actions`); + } else { + if (seen.has(raw.id)) errors.push(`${at}.id duplicates "${raw.id}"`); + seen.add(raw.id); + } + if (raw.params === undefined) return; + if (!isPlainObject(raw.params)) { + errors.push(`${at}.params must be an object`); + return; + } + for (const [name, param] of Object.entries(raw.params)) { + if (!KEYBINDING_PARAM_PATTERN.test(name) || name === "type") { + errors.push(`${at}.params.${name} is not a valid parameter name`); + } + validateKeybindingParam(param, `${at}.params.${name}`, errors); + } + }); +} + +/** + * Checks one saved keybinding action against the parameters its declaration + * lists. A parameter the declaration does not list is refused, so a binding + * cannot carry arbitrary data under a declared type. + */ +export function validateKeybindingActionParams( + action: Record, + declaration: PluginKeybindingActionContribution, +): boolean { + const params = declaration.params ?? {}; + for (const key of Object.keys(action)) { + if (key !== "type" && !(key in params) && action[key] != null) { + return false; + } + } + for (const [name, param] of Object.entries(params)) { + const value = action[name]; + if (value === undefined || value === null) { + if (param.required) return false; + continue; + } + if (param.type === "boolean") { + if (typeof value !== "boolean") return false; + continue; + } + if (typeof value !== "string") return false; + if (param.required && value.length === 0) return false; + if (value.length > (param.maxLength ?? 65_536)) return false; + if (param.pattern && !new RegExp(param.pattern).test(value)) return false; + } + return true; } function isPresetValue(value: unknown): boolean { @@ -1041,10 +1212,18 @@ function validateHttpContribution( } rejectUnknown( value, - ["legacyPaths", "legacyRedirects"], + ["legacyPaths", "legacyRedirects", "adminImpersonation"], "contributes.http", errors, ); + if ( + value.adminImpersonation !== undefined && + typeof value.adminImpersonation !== "boolean" + ) { + errors.push( + 'Field "contributes.http.adminImpersonation" must be a boolean', + ); + } validateLegacyRedirects(value.legacyRedirects, errors); const paths = value.legacyPaths; if (paths === undefined) return; diff --git a/packages/plugin-sdk/src/testing.ts b/packages/plugin-sdk/src/testing.ts index f1d811755..ed3ead2a6 100644 --- a/packages/plugin-sdk/src/testing.ts +++ b/packages/plugin-sdk/src/testing.ts @@ -2000,7 +2000,14 @@ export interface RenderedPluginApp { actions: () => string[]; loginMethods: () => string[]; secondFactors: () => string[]; + paletteGroups: () => string[]; + keybindingActions: () => string[]; + keybindingDefaults: () => string[]; }; + /** A registered palette group's items, as the palette loads them on open. */ + loadPaletteGroup: ( + id: string, + ) => Promise; /** Shell calls made by the plugin's code, applyLayout included. */ shellCalls: ShellCall[]; /** diff --git a/packages/plugin-sdk/src/testing/setup.ts b/packages/plugin-sdk/src/testing/setup.ts index cfe261376..bc898a3c5 100644 --- a/packages/plugin-sdk/src/testing/setup.ts +++ b/packages/plugin-sdk/src/testing/setup.ts @@ -137,6 +137,7 @@ const offline = () => getHostPassword: async () => null, patchOpenTab: async () => {}, getCustomKeybindings: async () => [], + runKeybindingAction: () => false, getClientPreference: () => undefined, setClientPreference: () => {}, listHosts: async () => [], diff --git a/plugins/fleets/manifest.json b/plugins/fleets/manifest.json index 2e1533728..328a1891b 100644 --- a/plugins/fleets/manifest.json +++ b/plugins/fleets/manifest.json @@ -23,6 +23,9 @@ "network:serve", "ui:surface" ], + "optionalDependencies": { + "snippets": "^1.0.0" + }, "provides": [ { "service": "fleets.access", diff --git a/plugins/fleets/src/backend/command-variables.ts b/plugins/fleets/src/backend/command-variables.ts deleted file mode 100644 index 1285218a5..000000000 --- a/plugins/fleets/src/backend/command-variables.ts +++ /dev/null @@ -1,55 +0,0 @@ -export interface CommandHostVars { - ip?: string; - username?: string; - port?: number | string; - name?: string; -} - -const INPUT_PATTERN = - /\$\{INPUT_(\d+)(?::([^}$]+))?\}|\$INPUT_(\d+)(?![a-zA-Z0-9_])/g; - -function replaceVar(content: string, name: string, value?: string): string { - if (value === undefined) return content; - const pattern = new RegExp(`\\$\\{?${name}\\}?`, "g"); - return content.replace(pattern, value); -} - -/** - * $HOST/$USER/$PORT/$NAME/$INPUT_n substitution for an ad hoc fleet command, - * typed directly into the "run on fleet" box rather than loaded from a saved - * snippet. Mirrors the snippets plugin's own resolveSnippetCommand and the - * frontend's src/ui/lib/snippet-variables.ts - kept as a small standalone - * copy rather than a cross-plugin dependency for one pure function with no - * snippet data of its own to fetch. - */ -export function resolveCommandVariables( - content: string, - host: CommandHostVars | null, - inputValues: Record = {}, -): string { - let resolved = content; - - resolved = replaceVar(resolved, "HOST", host?.ip); - resolved = replaceVar(resolved, "USER", host?.username); - resolved = replaceVar( - resolved, - "PORT", - host?.port !== undefined ? String(host.port) : undefined, - ); - resolved = replaceVar(resolved, "NAME", host?.name); - - resolved = resolved.replace( - INPUT_PATTERN, - ( - fullMatch, - braceDigits: string | undefined, - _label, - plainDigits: string | undefined, - ) => { - const key = `INPUT_${braceDigits ?? plainDigits}`; - return key in inputValues ? inputValues[key] : fullMatch; - }, - ); - - return resolved; -} diff --git a/plugins/fleets/src/backend/routes.ts b/plugins/fleets/src/backend/routes.ts index 3611cd530..99d91f77c 100644 --- a/plugins/fleets/src/backend/routes.ts +++ b/plugins/fleets/src/backend/routes.ts @@ -16,7 +16,6 @@ import { ElevationError, } from "@termix/plugin-sdk/host-commands"; import type { FleetRepository } from "./repository.js"; -import { resolveCommandVariables } from "./command-variables.js"; const FLEET_TRANSFER_MAX_BYTES = 200 * 1024 * 1024; const upload = multer({ @@ -74,6 +73,35 @@ function parseShareTargets( return targets; } +interface CommandVariables { + resolve: ( + content: string, + host: { + ip?: string; + username?: string; + port?: number | string; + name?: string; + } | null, + inputValues?: Record, + ) => string; +} + +/** + * $HOST, $USER, $PORT, $NAME and $INPUT_n in an ad hoc fleet command, through + * the snippets plugin, which owns the syntax. Without it the command runs as + * typed. + */ +export function resolveCommandVariables( + ctx: Pick, + command: string, + host: Parameters[1], + inputValues: Record = {}, +): string { + const variables = + ctx.registry.consume("snippets.variables"); + return variables ? variables.resolve(command, host, inputValues) : command; +} + function actor(ctx: PluginContext): string { return ctx.currentActor() as string; } @@ -884,6 +912,7 @@ export function registerFleetRoutes( } const resolvedCommand = resolveCommandVariables( + ctx, command, { ip: fullHost.ip, diff --git a/plugins/fleets/src/frontend/FleetsPanel.tsx b/plugins/fleets/src/frontend/FleetsPanel.tsx index 7a43819d3..c707e00b3 100644 --- a/plugins/fleets/src/frontend/FleetsPanel.tsx +++ b/plugins/fleets/src/frontend/FleetsPanel.tsx @@ -1,10 +1,7 @@ -import { - getErrorMessage, - extractSnippetInputs, - type SnippetInput, -} from "./helpers.js"; +import { getErrorMessage, type SnippetInput } from "./helpers.js"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { + invokeAction, useTranslation, useHosts, type PluginHostRecord, @@ -792,8 +789,16 @@ function RunCommandTab({ api, fleetId }: { api: FleetsApi; fleetId: number }) { const [running, setRunning] = useState(false); const [results, setResults] = useState(null); + // The snippets plugin owns $INPUT_n parsing; without it the command + // runs as typed. useEffect(() => { - setInputs(extractSnippetInputs(command)); + let cancelled = false; + void invokeAction("snippets.extractInputs", command).then((found) => { + if (!cancelled) setInputs(Array.isArray(found) ? found : []); + }); + return () => { + cancelled = true; + }; }, [command]); async function handleRun() { diff --git a/plugins/fleets/src/frontend/helpers.ts b/plugins/fleets/src/frontend/helpers.ts index 564215094..1695f8017 100644 --- a/plugins/fleets/src/frontend/helpers.ts +++ b/plugins/fleets/src/frontend/helpers.ts @@ -21,24 +21,3 @@ export interface SnippetInput { key: string; label: string; } - -const INPUT_PATTERN = - /\$\{INPUT_(\d+)(?::([^}$]+))?\}|\$INPUT_(\d+)(?![a-zA-Z0-9_])/g; - -/** - * Finds the $INPUT_n placeholders in a command, for the fleet run box's - * input fields. Mirrors src/ui/lib/snippet-variables.ts extractSnippetInputs. - */ -export function extractSnippetInputs(content: string): SnippetInput[] { - const seen = new Map(); - let match: RegExpExecArray | null; - INPUT_PATTERN.lastIndex = 0; - while ((match = INPUT_PATTERN.exec(content)) !== null) { - const digits = match[1] ?? match[3]; - const key = `INPUT_${digits}`; - if (!seen.has(key)) { - seen.set(key, { key, label: match[2]?.trim() || `Input ${digits}` }); - } - } - return Array.from(seen.values()); -} diff --git a/plugins/fleets/tests/backend/command-variables.test.ts b/plugins/fleets/tests/backend/command-variables.test.ts new file mode 100644 index 000000000..eadd38b49 --- /dev/null +++ b/plugins/fleets/tests/backend/command-variables.test.ts @@ -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: () => 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"); + }); +}); diff --git a/plugins/host-metrics/locales/en.json b/plugins/host-metrics/locales/en.json index b76e474a4..b355fd3df 100644 --- a/plugins/host-metrics/locales/en.json +++ b/plugins/host-metrics/locales/en.json @@ -54,10 +54,6 @@ "processesRunning": "running", "loginStats": "SSH Login Statistics", "noRecentLoginData": "No recent login data", - "executingQuickAction": "Executing {{name}}...", - "quickActionSuccess": "{{name}} completed successfully", - "quickActionFailed": "{{name}} failed", - "quickActionError": "Failed to execute {{name}}", "ports": { "title": "Listening Ports", "protocol": "Protocol", @@ -182,8 +178,7 @@ "wgLastHandshake": "Last handshake", "wgHandshakeNever": "Never", "wgTransfer": "Transfer" - }, - "snippetsUnavailable": "Snippets are turned off, so quick actions cannot run." + } }, "metricsHistory": { "title": "Metrics History", @@ -219,12 +214,6 @@ "addExcludedMount": "Add", "visibleWidgets": "Visible Widgets", "widgetsMovedToHostMetrics": "Cards are now added, arranged, and resized directly in the Host Metrics tab. Open Host Metrics for this host and use Customize to choose which cards are shown.", - "quickActionsLabel": "Quick Actions", - "quickActionsToolbar": "Quick actions appear as buttons in the Host Metrics toolbar for one-click command execution.", - "noQuickActions": "No quick actions yet.", - "buttonLabel": "Button label", - "selectSnippetPlaceholder": "Select snippet...", - "addActionBtn": "Add Action", "hostNotFound": "Host not found", "loadingHost": "Loading host..." }, @@ -269,15 +258,6 @@ "dashboardTab": { "disk": "Disk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Cancel", - "confirmRunButton": "Run", - "confirmRunMessage": "Run \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Connected" }, diff --git a/plugins/host-metrics/locales/translated/af_ZA.json b/plugins/host-metrics/locales/translated/af_ZA.json index 48a45e1b7..f83c47314 100644 --- a/plugins/host-metrics/locales/translated/af_ZA.json +++ b/plugins/host-metrics/locales/translated/af_ZA.json @@ -51,10 +51,6 @@ "processesRunning": "hardloop", "loginStats": "SSH-aanmeldingstatistieke", "noRecentLoginData": "Geen onlangse aanmelddata nie", - "executingQuickAction": "Voer tans {{name}} uit ...", - "quickActionSuccess": "{{name}} suksesvol voltooi", - "quickActionFailed": "{{name}} het misluk", - "quickActionError": "Kon nie {{name}} uitvoer nie", "ports": { "title": "Luisterpoorte", "protocol": "Protokol", @@ -235,12 +231,6 @@ "addExcludedMount": "Voeg by", "visibleWidgets": "Sigbare Widgets", "widgetsMovedToHostMetrics": "Kaarte word nou direk in die Gasheermetrieke-oortjie bygevoeg, gerangskik en herskaal. Maak Gasheermetrieke vir hierdie gasheer oop en gebruik Pasmaak om te kies watter kaarte gewys word.", - "quickActionsLabel": "Vinnige Aksies", - "quickActionsToolbar": "Vinnige aksies verskyn as knoppies in die Host Metrics-nutsbalk vir die uitvoering van opdragte met een klik.", - "noQuickActions": "Geen vinnige aksies nog nie.", - "buttonLabel": "Knoppie-etiket", - "selectSnippetPlaceholder": "Kies uittreksel...", - "addActionBtn": "Voeg Aksie by", "hostNotFound": "Gasheer nie gevind nie", "loadingHost": "Laai gasheer..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Skyf" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Kanselleer", - "confirmRunButton": "Hardloop", - "confirmRunMessage": "Begin \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Verbonde" } diff --git a/plugins/host-metrics/locales/translated/ar_SA.json b/plugins/host-metrics/locales/translated/ar_SA.json index d8c9f111d..26ec0935d 100644 --- a/plugins/host-metrics/locales/translated/ar_SA.json +++ b/plugins/host-metrics/locales/translated/ar_SA.json @@ -51,10 +51,6 @@ "processesRunning": "قيد التشغيل", "loginStats": "إحصائيات تسجيل الدخول عبر SSH", "noRecentLoginData": "لا توجد بيانات تسجيل دخول حديثة", - "executingQuickAction": "جارٍ تنفيذ {{name}}...", - "quickActionSuccess": "تم إكمال {{name}} بنجاح", - "quickActionFailed": "فشل {{name}}", - "quickActionError": "فشل تنفيذ {{name}}", "ports": { "title": "المنافذ المستمعة", "protocol": "البروتوكول", @@ -235,12 +231,6 @@ "addExcludedMount": "يضيف", "visibleWidgets": "الأدوات المرئية", "widgetsMovedToHostMetrics": "تتم الآن إضافة البطاقات وترتيبها وتغيير حجمها مباشرةً في علامة تبويب مقاييس المضيف. افتح مقاييس المضيف لهذا المضيف واستخدم تخصيص لاختيار البطاقات التي ستظهر.", - "quickActionsLabel": "إجراءات سريعة", - "quickActionsToolbar": "تظهر الإجراءات السريعة كأزرار في شريط أدوات مقاييس المضيف لتنفيذ الأوامر بنقرة واحدة.", - "noQuickActions": "لا توجد إجراءات سريعة بعد.", - "buttonLabel": "تسمية الزر", - "selectSnippetPlaceholder": "اختر مقتطفًا...", - "addActionBtn": "إضافة إجراء", "hostNotFound": "المضيف غير موجود", "loadingHost": "جارٍ تحميل المضيف..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "القرص" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "إلغاء", - "confirmRunButton": "تشغيل", - "confirmRunMessage": "تشغيل \"{{name}}\"؟" - } - } - }, "terminal": { "connected": "متصل" } diff --git a/plugins/host-metrics/locales/translated/bg_BG.json b/plugins/host-metrics/locales/translated/bg_BG.json index 5322ceace..de408ee68 100644 --- a/plugins/host-metrics/locales/translated/bg_BG.json +++ b/plugins/host-metrics/locales/translated/bg_BG.json @@ -51,10 +51,6 @@ "processesRunning": "бягане", "loginStats": "Статистика за SSH вход", "noRecentLoginData": "Няма скорошни данни за вход", - "executingQuickAction": "Изпълнява се {{name}}...", - "quickActionSuccess": "{{name}} завършено успешно", - "quickActionFailed": "{{name}} неуспешно", - "quickActionError": "Неуспешно изпълнение на {{name}}", "ports": { "title": "Портове за слушане", "protocol": "Протокол", @@ -235,12 +231,6 @@ "addExcludedMount": "Добавяне", "visibleWidgets": "Видими джаджи", "widgetsMovedToHostMetrics": "Картите вече се добавят, подреждат и преоразмеряват директно в раздела „Показатели за хоста“. Отворете „Показатели за хоста“ за този хост и използвайте „Персонализиране“, за да изберете кои карти да се показват.", - "quickActionsLabel": "Бързи действия", - "quickActionsToolbar": "Бързите действия се показват като бутони в лентата с инструменти Host Metrics за изпълнение на команди с едно щракване.", - "noQuickActions": "Все още няма бързи действия.", - "buttonLabel": "Етикет на бутона", - "selectSnippetPlaceholder": "Изберете фрагмент...", - "addActionBtn": "Добавяне на действие", "hostNotFound": "Хостът не е намерен", "loadingHost": "Зареждане на хоста..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Диск" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Отказ", - "confirmRunButton": "Бягане", - "confirmRunMessage": "Изпълни „{{name}}“?" - } - } - }, "terminal": { "connected": "Свързан" } diff --git a/plugins/host-metrics/locales/translated/bn_BD.json b/plugins/host-metrics/locales/translated/bn_BD.json index 8f7447a44..29e6b258d 100644 --- a/plugins/host-metrics/locales/translated/bn_BD.json +++ b/plugins/host-metrics/locales/translated/bn_BD.json @@ -51,10 +51,6 @@ "processesRunning": "দৌড়ানো", "loginStats": "SSH লগইন পরিসংখ্যান", "noRecentLoginData": "সাম্প্রতিক কোনো লগইন ডেটা নেই", - "executingQuickAction": "{{name}} কার্যকর করা হচ্ছে ...", - "quickActionSuccess": "{{name}} সফলভাবে সম্পন্ন হয়েছে", - "quickActionFailed": "{{name}} ব্যর্থ হয়েছে", - "quickActionError": "{{name}} কার্যকর করতে ব্যর্থ হয়েছে", "ports": { "title": "লিসেনিং পোর্ট", "protocol": "প্রোটোকল", @@ -235,12 +231,6 @@ "addExcludedMount": "যোগ করুন", "visibleWidgets": "দৃশ্যমান উইজেট", "widgetsMovedToHostMetrics": "এখন কার্ডগুলি সরাসরি হোস্ট মেট্রিক্স ট্যাবে যোগ, সাজানো এবং আকার পরিবর্তন করা যায়। এই হোস্টের জন্য হোস্ট মেট্রিক্স খুলুন এবং কোন কার্ডগুলি দেখানো হবে তা বেছে নিতে কাস্টমাইজ ব্যবহার করুন।", - "quickActionsLabel": "দ্রুত পদক্ষেপ", - "quickActionsToolbar": "এক ক্লিকে কমান্ড কার্যকর করার জন্য কুইক অ্যাকশনগুলো হোস্ট মেট্রিক্স টুলবারে বাটন হিসেবে প্রদর্শিত হয়।", - "noQuickActions": "এখনো কোনো দ্রুত পদক্ষেপ নেওয়া হয়নি।", - "buttonLabel": "বোতাম লেবেল", - "selectSnippetPlaceholder": "স্নিপেট নির্বাচন করুন...", - "addActionBtn": "অ্যাকশন যোগ করুন", "hostNotFound": "হোস্ট খুঁজে পাওয়া যায়নি", "loadingHost": "হোস্ট লোড হচ্ছে..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "ডিস্ক" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "বাতিল করুন", - "confirmRunButton": "দৌড়", - "confirmRunMessage": "\"{{name}} \" চালান?" - } - } - }, "terminal": { "connected": "সংযুক্ত" } diff --git a/plugins/host-metrics/locales/translated/ca_ES.json b/plugins/host-metrics/locales/translated/ca_ES.json index 284c80128..e8dc580bd 100644 --- a/plugins/host-metrics/locales/translated/ca_ES.json +++ b/plugins/host-metrics/locales/translated/ca_ES.json @@ -51,10 +51,6 @@ "processesRunning": "corrent", "loginStats": "Estadístiques d'inici de sessió SSH", "noRecentLoginData": "Sense dades d'inici de sessió recents", - "executingQuickAction": "Executant {{name}}...", - "quickActionSuccess": "{{name}} s'ha completat correctament", - "quickActionFailed": "{{name}} ha fallat", - "quickActionError": "No s'ha pogut executar {{name}}", "ports": { "title": "Ports d'escolta", "protocol": "Protocol", @@ -235,12 +231,6 @@ "addExcludedMount": "Afegeix", "visibleWidgets": "Widgets visibles", "widgetsMovedToHostMetrics": "Ara les targetes s'afegeixen, s'organitzen i es canvien de mida directament a la pestanya Mètriques de l'amfitrió. Obriu Mètriques de l'amfitrió per a aquest amfitrió i utilitzeu Personalitza per triar quines targetes es mostren.", - "quickActionsLabel": "Accions ràpides", - "quickActionsToolbar": "Les accions ràpides apareixen com a botons a la barra d'eines de mètriques de l'amfitrió per executar ordres amb un sol clic.", - "noQuickActions": "Encara no hi ha accions ràpides.", - "buttonLabel": "Etiqueta del botó", - "selectSnippetPlaceholder": "Selecciona un fragment...", - "addActionBtn": "Afegeix una acció", "hostNotFound": "No s'ha trobat l'amfitrió", "loadingHost": "S'està carregant l'amfitrió..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disc" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Cancel·la", - "confirmRunButton": "Corre", - "confirmRunMessage": "Executar \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Connectat" } diff --git a/plugins/host-metrics/locales/translated/cs_CZ.json b/plugins/host-metrics/locales/translated/cs_CZ.json index 69ab3e17b..ac42894b4 100644 --- a/plugins/host-metrics/locales/translated/cs_CZ.json +++ b/plugins/host-metrics/locales/translated/cs_CZ.json @@ -51,10 +51,6 @@ "processesRunning": "běh", "loginStats": "Statistiky přihlášení SSH", "noRecentLoginData": "Žádné nedávné přihlašovací údaje", - "executingQuickAction": "Spouštění {{name}}...", - "quickActionSuccess": "{{name}} úspěšně dokončeno", - "quickActionFailed": "{{name}} selhalo", - "quickActionError": "Nepodařilo se spustit {{name}}", "ports": { "title": "Poslouchající porty", "protocol": "Protokol", @@ -235,12 +231,6 @@ "addExcludedMount": "Přidat", "visibleWidgets": "Viditelné widgety", "widgetsMovedToHostMetrics": "Karty se nyní přidávají, uspořádávají a mění jejich velikost přímo na kartě Metriky hostitele. Otevřete Metriky hostitele pro tohoto hostitele a pomocí možnosti Přizpůsobit vyberte, které karty se zobrazí.", - "quickActionsLabel": "Rychlé akce", - "quickActionsToolbar": "Rychlé akce se zobrazují jako tlačítka v panelu nástrojů Metriky hostitele pro spuštění příkazů jedním kliknutím.", - "noQuickActions": "Zatím žádné rychlé akce.", - "buttonLabel": "Štítek tlačítka", - "selectSnippetPlaceholder": "Vybrat úryvek...", - "addActionBtn": "Přidat akci", "hostNotFound": "Hostitel nenalezen", "loadingHost": "Načítání hostitele..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Zrušit", - "confirmRunButton": "Běh", - "confirmRunMessage": "Spustit „{{name}}“?" - } - } - }, "terminal": { "connected": "Připojeno" } diff --git a/plugins/host-metrics/locales/translated/da_DK.json b/plugins/host-metrics/locales/translated/da_DK.json index bb4dc6cb3..ae747dac1 100644 --- a/plugins/host-metrics/locales/translated/da_DK.json +++ b/plugins/host-metrics/locales/translated/da_DK.json @@ -51,10 +51,6 @@ "processesRunning": "løb", "loginStats": "SSH-loginstatistik", "noRecentLoginData": "Ingen nylige logindata", - "executingQuickAction": "Udfører {{name}}...", - "quickActionSuccess": "{{name}} gennemført", - "quickActionFailed": "{{name}} mislykkedes", - "quickActionError": "Kunne ikke udføre {{name}}", "ports": { "title": "Lytteporte", "protocol": "Protokol", @@ -235,12 +231,6 @@ "addExcludedMount": "Tilføje", "visibleWidgets": "Synlige widgets", "widgetsMovedToHostMetrics": "Kort tilføjes, arrangeres og skaleres nu direkte i fanen Værtsmål. Åbn Værtsmål for denne vært, og brug Tilpas til at vælge, hvilke kort der vises.", - "quickActionsLabel": "Hurtige handlinger", - "quickActionsToolbar": "Hurtige handlinger vises som knapper i værktøjslinjen Host Metrics til udførelse af kommandoer med et enkelt klik.", - "noQuickActions": "Ingen hurtige handlinger endnu.", - "buttonLabel": "Knapmærke", - "selectSnippetPlaceholder": "Vælg uddrag...", - "addActionBtn": "Tilføj handling", "hostNotFound": "Værten blev ikke fundet", "loadingHost": "Indlæser vært..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Ophæve", - "confirmRunButton": "Løbe", - "confirmRunMessage": "Kør \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Forbundet" } diff --git a/plugins/host-metrics/locales/translated/de_DE.json b/plugins/host-metrics/locales/translated/de_DE.json index a656a90a4..628603074 100644 --- a/plugins/host-metrics/locales/translated/de_DE.json +++ b/plugins/host-metrics/locales/translated/de_DE.json @@ -51,10 +51,6 @@ "processesRunning": "laufend", "loginStats": "SSH-Anmeldestatistiken", "noRecentLoginData": "Keine aktuellen Anmeldedaten", - "executingQuickAction": "{{name}} wird ausgeführt...", - "quickActionSuccess": "{{name}} erfolgreich abgeschlossen", - "quickActionFailed": "{{name}} fehlgeschlagen", - "quickActionError": "Fehler beim Ausführen von {{name}}", "ports": { "title": "Lauschende Ports", "protocol": "Protokoll", @@ -235,12 +231,6 @@ "addExcludedMount": "Hinzufügen", "visibleWidgets": "Sichtbare Widgets", "widgetsMovedToHostMetrics": "Karten werden jetzt direkt im Tab „Host-Metriken“ hinzugefügt, angeordnet und in der Größe verändert. Öffnen Sie die Host-Metriken für diesen Host und verwenden Sie „Anpassen“, um auszuwählen, welche Karten angezeigt werden.", - "quickActionsLabel": "Schnellaktionen", - "quickActionsToolbar": "Schnellaktionen erscheinen als Schaltflächen in der Host-Metriken-Symbolleiste zur Ausführung von Befehlen mit einem Klick.", - "noQuickActions": "Noch keine Schnellaktionen.", - "buttonLabel": "Schaltflächenbeschriftung", - "selectSnippetPlaceholder": "Snippet auswählen...", - "addActionBtn": "Aktion hinzufügen", "hostNotFound": "Host nicht gefunden", "loadingHost": "Host wird geladen..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Datenträger" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Abbrechen", - "confirmRunButton": "Ausführen", - "confirmRunMessage": "„{{name}}“ ausführen?" - } - } - }, "terminal": { "connected": "Verbunden" } diff --git a/plugins/host-metrics/locales/translated/el_GR.json b/plugins/host-metrics/locales/translated/el_GR.json index c561a0926..f7481a0d3 100644 --- a/plugins/host-metrics/locales/translated/el_GR.json +++ b/plugins/host-metrics/locales/translated/el_GR.json @@ -51,10 +51,6 @@ "processesRunning": "τρέξιμο", "loginStats": "Στατιστικά στοιχεία σύνδεσης SSH", "noRecentLoginData": "Δεν υπάρχουν πρόσφατα δεδομένα σύνδεσης", - "executingQuickAction": "Εκτέλεση {{name}}...", - "quickActionSuccess": "{{name}} ολοκληρώθηκε με επιτυχία", - "quickActionFailed": "{{name}} απέτυχε", - "quickActionError": "Αποτυχία εκτέλεσης {{name}}", "ports": { "title": "Θύρες ακρόασης", "protocol": "Πρωτόκολλο", @@ -235,12 +231,6 @@ "addExcludedMount": "Προσθέτω", "visibleWidgets": "Ορατά γραφικά στοιχεία", "widgetsMovedToHostMetrics": "Οι κάρτες προστίθενται, ταξινομούνται και αλλάζουν μέγεθος απευθείας στην καρτέλα Μετρήσεις κεντρικού υπολογιστή. Ανοίξτε τις Μετρήσεις κεντρικού υπολογιστή για αυτόν τον κεντρικό υπολογιστή και χρησιμοποιήστε την Προσαρμογή για να επιλέξετε ποιες κάρτες θα εμφανίζονται.", - "quickActionsLabel": "Γρήγορες ενέργειες", - "quickActionsToolbar": "Οι γρήγορες ενέργειες εμφανίζονται ως κουμπιά στη γραμμή εργαλείων Μετρήσεων κεντρικού υπολογιστή για εκτέλεση εντολών με ένα κλικ.", - "noQuickActions": "Δεν υπάρχουν ακόμη γρήγορες ενέργειες.", - "buttonLabel": "Ετικέτα κουμπιού", - "selectSnippetPlaceholder": "Επιλογή αποσπάσματος...", - "addActionBtn": "Προσθήκη ενέργειας", "hostNotFound": "Δεν βρέθηκε κεντρικός υπολογιστής", "loadingHost": "Φόρτωση κεντρικού υπολογιστή..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Δίσκος" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Ματαίωση", - "confirmRunButton": "Τρέξιμο", - "confirmRunMessage": "Εκτέλεση \"{{name}}\";" - } - } - }, "terminal": { "connected": "Συνδεδεμένος" } diff --git a/plugins/host-metrics/locales/translated/es_ES.json b/plugins/host-metrics/locales/translated/es_ES.json index 1bc356fa9..e396565dc 100644 --- a/plugins/host-metrics/locales/translated/es_ES.json +++ b/plugins/host-metrics/locales/translated/es_ES.json @@ -51,10 +51,6 @@ "processesRunning": "en ejecución", "loginStats": "Estadísticas de inicio de sesión SSH", "noRecentLoginData": "No hay datos de inicio de sesión recientes", - "executingQuickAction": "Ejecutando {{name}}...", - "quickActionSuccess": "{{name}} completado correctamente", - "quickActionFailed": "{{name}} falló", - "quickActionError": "Error al ejecutar {{name}}", "ports": { "title": "Puertos en escucha", "protocol": "Protocolo", @@ -235,12 +231,6 @@ "addExcludedMount": "Agregar", "visibleWidgets": "Widgets visibles", "widgetsMovedToHostMetrics": "Ahora las tarjetas se añaden, organizan y redimensionan directamente en la pestaña Métricas del host. Abre Métricas del host para este host y usa Personalizar para elegir qué tarjetas se muestran.", - "quickActionsLabel": "Acciones rápidas", - "quickActionsToolbar": "Las acciones rápidas aparecen como botones en la barra de herramientas de Métricas del host para la ejecución de comandos con un solo clic.", - "noQuickActions": "Aún no hay acciones rápidas.", - "buttonLabel": "Etiqueta del botón", - "selectSnippetPlaceholder": "Seleccionar fragmento...", - "addActionBtn": "Añadir acción", "hostNotFound": "Host no encontrado", "loadingHost": "Cargando host..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disco" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Cancelar", - "confirmRunButton": "Ejecutar", - "confirmRunMessage": "¿Ejecutar \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Conectado" } diff --git a/plugins/host-metrics/locales/translated/fi_FI.json b/plugins/host-metrics/locales/translated/fi_FI.json index 92295782a..85593950c 100644 --- a/plugins/host-metrics/locales/translated/fi_FI.json +++ b/plugins/host-metrics/locales/translated/fi_FI.json @@ -51,10 +51,6 @@ "processesRunning": "juokseminen", "loginStats": "SSH-kirjautumistilastot", "noRecentLoginData": "Ei viimeaikaisia kirjautumistietoja", - "executingQuickAction": "Suoritetaan {{name}}...", - "quickActionSuccess": "{{name}} suoritettu onnistuneesti", - "quickActionFailed": "{{name}} epäonnistui", - "quickActionError": "Suoritus epäonnistui {{name}}", "ports": { "title": "Kuunteluportit", "protocol": "Protokolla", @@ -235,12 +231,6 @@ "addExcludedMount": "Lisätä", "visibleWidgets": "Näkyvät widgetit", "widgetsMovedToHostMetrics": "Kortteja lisätään, järjestetään ja niiden kokoa muutetaan nyt suoraan Isännän mittarit -välilehdellä. Avaa tämän isännän Isännän mittarit ja valitse näytettävät kortit Mukauta-toiminnolla.", - "quickActionsLabel": "Pikatoiminnot", - "quickActionsToolbar": "Pikatoiminnot näkyvät painikkeina isäntämetriikkatyökalurivillä komentojen suorittamista varten yhdellä napsautuksella.", - "noQuickActions": "Ei vielä nopeita toimia.", - "buttonLabel": "Painikkeen nimi", - "selectSnippetPlaceholder": "Valitse katkelma...", - "addActionBtn": "Lisää toiminto", "hostNotFound": "Isäntää ei löydy", "loadingHost": "Ladataan isäntää..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Levy" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Peruuttaa", - "confirmRunButton": "Juokse", - "confirmRunMessage": "Suorittaa \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Yhdistetty" } diff --git a/plugins/host-metrics/locales/translated/fr_FR.json b/plugins/host-metrics/locales/translated/fr_FR.json index a2247ec6c..19aebaec8 100644 --- a/plugins/host-metrics/locales/translated/fr_FR.json +++ b/plugins/host-metrics/locales/translated/fr_FR.json @@ -51,10 +51,6 @@ "processesRunning": "en cours", "loginStats": "Statistiques de connexion SSH", "noRecentLoginData": "Aucune donnée de connexion récente", - "executingQuickAction": "Exécution de {{name}}...", - "quickActionSuccess": "{{name}} terminé avec succès", - "quickActionFailed": "Échec de {{name}}", - "quickActionError": "Échec de l'exécution de {{name}}", "ports": { "title": "Ports en écoute", "protocol": "Protocole", @@ -235,12 +231,6 @@ "addExcludedMount": "Ajouter", "visibleWidgets": "Widgets visibles", "widgetsMovedToHostMetrics": "Les cartes sont désormais ajoutées, organisées et redimensionnées directement dans l'onglet Métriques de l'hôte. Ouvrez les métriques de cet hôte et utilisez Personnaliser pour choisir les cartes à afficher.", - "quickActionsLabel": "Actions rapides", - "quickActionsToolbar": "Les actions rapides apparaissent sous forme de boutons dans la barre d'outils Métriques de l'hôte pour une exécution en un clic.", - "noQuickActions": "Aucune action rapide pour le moment.", - "buttonLabel": "Étiquette du bouton", - "selectSnippetPlaceholder": "Sélectionner un snippet...", - "addActionBtn": "Ajouter une action", "hostNotFound": "Hôte introuvable", "loadingHost": "Chargement de l'hôte..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disque" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Annuler", - "confirmRunButton": "Exécuter", - "confirmRunMessage": "Exécuter « {{name}} » ?" - } - } - }, "terminal": { "connected": "Connecté" } diff --git a/plugins/host-metrics/locales/translated/he_IL.json b/plugins/host-metrics/locales/translated/he_IL.json index a6af986a9..4112714b3 100644 --- a/plugins/host-metrics/locales/translated/he_IL.json +++ b/plugins/host-metrics/locales/translated/he_IL.json @@ -51,10 +51,6 @@ "processesRunning": "רִיצָה", "loginStats": "סטטיסטיקות כניסה ל-SSH", "noRecentLoginData": "אין נתוני התחברות אחרונים", - "executingQuickAction": "מבצע {{name}}...", - "quickActionSuccess": "{{name}} הושלם בהצלחה", - "quickActionFailed": "{{name}} נכשל", - "quickActionError": "נכשל בביצוע {{name}}", "ports": { "title": "יציאות האזנה", "protocol": "פּרוֹטוֹקוֹל", @@ -235,12 +231,6 @@ "addExcludedMount": "לְהוֹסִיף", "visibleWidgets": "ווידג'טים גלויים", "widgetsMovedToHostMetrics": "כעת ניתן להוסיף, לסידור ולשנות את גודל הכרטיסים ישירות בכרטיסייה 'מדדי מארח'. פתחו את 'מדדי מארח' עבור מארח זה והשתמשו באפשרות 'התאמה אישית' כדי לבחור אילו כרטיסים יוצגו.", - "quickActionsLabel": "פעולות מהירות", - "quickActionsToolbar": "פעולות מהירות מופיעות ככפתורים בסרגל הכלים של Host Metrics לביצוע פקודה בלחיצה אחת.", - "noQuickActions": "אין עדיין פעולות מהירות.", - "buttonLabel": "תווית כפתור", - "selectSnippetPlaceholder": "בחר קטע...", - "addActionBtn": "הוסף פעולה", "hostNotFound": "המארח לא נמצא", "loadingHost": "טוען מארח..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "דִיסק" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "לְבַטֵל", - "confirmRunButton": "לָרוּץ", - "confirmRunMessage": "להפעיל את \"{{name}}\"?" - } - } - }, "terminal": { "connected": "מְחוּבָּר" } diff --git a/plugins/host-metrics/locales/translated/hi_IN.json b/plugins/host-metrics/locales/translated/hi_IN.json index 005133f7f..e1bbc92f7 100644 --- a/plugins/host-metrics/locales/translated/hi_IN.json +++ b/plugins/host-metrics/locales/translated/hi_IN.json @@ -51,10 +51,6 @@ "processesRunning": "चल रहे", "loginStats": "SSH लॉगिन आँकड़े", "noRecentLoginData": "कोई हालिया लॉगिन डेटा नहीं", - "executingQuickAction": "{{name}} निष्पादित हो रहा है...", - "quickActionSuccess": "{{name}} सफलतापूर्वक पूर्ण हुआ", - "quickActionFailed": "{{name}} विफल रहा", - "quickActionError": "{{name}} निष्पादित करने में विफल", "ports": { "title": "लिसनिंग पोर्ट", "protocol": "प्रोटोकॉल", @@ -235,12 +231,6 @@ "addExcludedMount": "जोड़ना", "visibleWidgets": "दृश्यमान विजेट्स", "widgetsMovedToHostMetrics": "कार्ड अब सीधे होस्ट मेट्रिक्स टैब में जोड़े, व्यवस्थित और आकार बदले जा सकते हैं। इस होस्ट के लिए होस्ट मेट्रिक्स खोलें और यह चुनने के लिए कस्टमाइज़ का उपयोग करें कि कौन से कार्ड दिखाए जाएँ।", - "quickActionsLabel": "त्वरित कार्रवाइयाँ", - "quickActionsToolbar": "त्वरित कार्रवाइयाँ एक-क्लिक कमांड निष्पादन के लिए होस्ट मेट्रिक्स टूलबार में बटन के रूप में दिखाई देती हैं।", - "noQuickActions": "अभी तक कोई त्वरित कार्रवाई नहीं।", - "buttonLabel": "बटन लेबल", - "selectSnippetPlaceholder": "स्निपेट चुनें...", - "addActionBtn": "कार्रवाई जोड़ें", "hostNotFound": "होस्ट नहीं मिला", "loadingHost": "होस्ट लोड हो रहा है..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "डिस्क" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "रद्द करें", - "confirmRunButton": "चलाएँ", - "confirmRunMessage": "\"{{name}}\" चलाएँ?" - } - } - }, "terminal": { "connected": "कनेक्टेड" } diff --git a/plugins/host-metrics/locales/translated/hu_HU.json b/plugins/host-metrics/locales/translated/hu_HU.json index 0deb6c830..bbe384c1d 100644 --- a/plugins/host-metrics/locales/translated/hu_HU.json +++ b/plugins/host-metrics/locales/translated/hu_HU.json @@ -51,10 +51,6 @@ "processesRunning": "futás", "loginStats": "SSH bejelentkezési statisztikák", "noRecentLoginData": "Nincsenek friss bejelentkezési adatok", - "executingQuickAction": "{{name}} végrehajtása ...", - "quickActionSuccess": "{{name}} sikeresen befejeződött", - "quickActionFailed": "{{name}} sikertelen", - "quickActionError": "Nem sikerült végrehajtani a {{name}} parancsot.", "ports": { "title": "Figyelő portok", "protocol": "Jegyzőkönyv", @@ -235,12 +231,6 @@ "addExcludedMount": "Hozzáadás", "visibleWidgets": "Látható widgetek", "widgetsMovedToHostMetrics": "A kártyák mostantól közvetlenül a Gazdagép metrikái lapon adhatók hozzá, rendezhetők el és méretezhetők át. Nyissa meg a Gazdagép metrikái oldalt ehhez a gazdagéphez, és a Testreszabás gombbal válassza ki, hogy mely kártyák jelenjenek meg.", - "quickActionsLabel": "Gyors műveletek", - "quickActionsToolbar": "A gyors műveletek gombokként jelennek meg a Host Metrics eszköztáron, így egyetlen kattintással végrehajthatók a parancsok.", - "noQuickActions": "Még nincsenek gyors intézkedések.", - "buttonLabel": "Gomb felirata", - "selectSnippetPlaceholder": "Válasszon részletet...", - "addActionBtn": "Művelet hozzáadása", "hostNotFound": "Nem található gazdagép", "loadingHost": "Gazdagép betöltése..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Korong" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Mégsem", - "confirmRunButton": "Fut", - "confirmRunMessage": "Futtassa a \"{{name}} \" programot?" - } - } - }, "terminal": { "connected": "Csatlakoztatva" } diff --git a/plugins/host-metrics/locales/translated/id_ID.json b/plugins/host-metrics/locales/translated/id_ID.json index 646f1d2e3..79f135831 100644 --- a/plugins/host-metrics/locales/translated/id_ID.json +++ b/plugins/host-metrics/locales/translated/id_ID.json @@ -51,10 +51,6 @@ "processesRunning": "berlari", "loginStats": "Statistik Login SSH", "noRecentLoginData": "Tidak ada data login terbaru.", - "executingQuickAction": "Menjalankan {{name}}...", - "quickActionSuccess": "{{name}} berhasil diselesaikan", - "quickActionFailed": "{{name}} gagal", - "quickActionError": "Gagal mengeksekusi {{name}}", "ports": { "title": "Port Pendengaran", "protocol": "Protokol", @@ -235,12 +231,6 @@ "addExcludedMount": "Menambahkan", "visibleWidgets": "Widget yang Terlihat", "widgetsMovedToHostMetrics": "Kartu sekarang ditambahkan, diatur, dan diubah ukurannya langsung di tab Metrik Host. Buka Metrik Host untuk host ini dan gunakan Sesuaikan untuk memilih kartu mana yang akan ditampilkan.", - "quickActionsLabel": "Tindakan Cepat", - "quickActionsToolbar": "Tindakan cepat muncul sebagai tombol di bilah alat Metrik Host untuk eksekusi perintah sekali klik.", - "noQuickActions": "Belum ada tindakan cepat.", - "buttonLabel": "Label tombol", - "selectSnippetPlaceholder": "Pilih cuplikan...", - "addActionBtn": "Tambahkan Tindakan", "hostNotFound": "Host tidak ditemukan", "loadingHost": "Memuat host..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Membatalkan", - "confirmRunButton": "Berlari", - "confirmRunMessage": "Jalankan \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Terhubung" } diff --git a/plugins/host-metrics/locales/translated/it_IT.json b/plugins/host-metrics/locales/translated/it_IT.json index ce65596e7..8e3807949 100644 --- a/plugins/host-metrics/locales/translated/it_IT.json +++ b/plugins/host-metrics/locales/translated/it_IT.json @@ -51,10 +51,6 @@ "processesRunning": "in esecuzione", "loginStats": "Statistiche di accesso SSH", "noRecentLoginData": "Nessun dato di accesso recente", - "executingQuickAction": "Esecuzione di {{name}}...", - "quickActionSuccess": "{{name}} completato con successo", - "quickActionFailed": "{{name}} non riuscito", - "quickActionError": "Impossibile eseguire {{name}}", "ports": { "title": "Porte in ascolto", "protocol": "Protocollo", @@ -235,12 +231,6 @@ "addExcludedMount": "Aggiungere", "visibleWidgets": "Widget visibili", "widgetsMovedToHostMetrics": "Le schede ora vengono aggiunte, disposte e ridimensionate direttamente nella scheda Metriche host. Apri Metriche host per questo host e usa Personalizza per scegliere quali schede mostrare.", - "quickActionsLabel": "Azioni Rapide", - "quickActionsToolbar": "Le azioni rapide appaiono come pulsanti nella barra delle metriche host per l'esecuzione di comandi con un clic.", - "noQuickActions": "Nessuna azione rapida ancora.", - "buttonLabel": "Etichetta pulsante", - "selectSnippetPlaceholder": "Seleziona snippet...", - "addActionBtn": "Aggiungi Azione", "hostNotFound": "Host non trovato", "loadingHost": "Caricamento host..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disco" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Annulla", - "confirmRunButton": "Esegui", - "confirmRunMessage": "Eseguire \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Connesso" } diff --git a/plugins/host-metrics/locales/translated/ja_JP.json b/plugins/host-metrics/locales/translated/ja_JP.json index 2dafa5075..04607d19d 100644 --- a/plugins/host-metrics/locales/translated/ja_JP.json +++ b/plugins/host-metrics/locales/translated/ja_JP.json @@ -51,10 +51,6 @@ "processesRunning": "実行中", "loginStats": "SSHログイン統計", "noRecentLoginData": "最近のログインデータがありません", - "executingQuickAction": "{{name}} を実行中...", - "quickActionSuccess": "{{name}} が正常に完了しました", - "quickActionFailed": "{{name}} が失敗しました", - "quickActionError": "{{name}} の実行に失敗しました", "ports": { "title": "リスニングポート", "protocol": "プロトコル", @@ -235,12 +231,6 @@ "addExcludedMount": "追加", "visibleWidgets": "表示するウィジェット", "widgetsMovedToHostMetrics": "カードは、ホストメトリクスタブで直接追加、配置、サイズ変更できるようになりました。このホストのホストメトリクスを開き、カスタマイズを使用して表示するカードを選択してください。", - "quickActionsLabel": "クイックアクション", - "quickActionsToolbar": "クイックアクションはホストメトリクスツールバーにボタンとして表示され、ワンクリックでコマンドを実行できます。", - "noQuickActions": "クイックアクションはまだありません", - "buttonLabel": "ボタンラベル", - "selectSnippetPlaceholder": "スニペットを選択...", - "addActionBtn": "アクションを追加", "hostNotFound": "ホストが見つかりません", "loadingHost": "ホストを読み込み中..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "ディスク" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "キャンセル", - "confirmRunButton": "実行", - "confirmRunMessage": "「{{name}}」を実行しますか?" - } - } - }, "terminal": { "connected": "接続中" } diff --git a/plugins/host-metrics/locales/translated/ko_KR.json b/plugins/host-metrics/locales/translated/ko_KR.json index 2cc9ba92d..07177ee58 100644 --- a/plugins/host-metrics/locales/translated/ko_KR.json +++ b/plugins/host-metrics/locales/translated/ko_KR.json @@ -51,10 +51,6 @@ "processesRunning": "실행 중", "loginStats": "SSH 로그인 통계", "noRecentLoginData": "최근 로그인 데이터가 없습니다.", - "executingQuickAction": "{{name}} 실행 중...", - "quickActionSuccess": "{{name}}이(가) 성공적으로 완료되었습니다.", - "quickActionFailed": "{{name}} 실패", - "quickActionError": "{{name}} 실행에 실패했습니다.", "ports": { "title": "수신 대기 포트", "protocol": "프로토콜", @@ -235,12 +231,6 @@ "addExcludedMount": "추가하다", "visibleWidgets": "표시 위젯", "widgetsMovedToHostMetrics": "카드는 이제 호스트 메트릭 탭에서 직접 추가, 정렬 및 크기 조정됩니다. 이 호스트의 호스트 메트릭을 열고 사용자 정의를 사용하여 표시할 카드를 선택하세요.", - "quickActionsLabel": "빠른 작업", - "quickActionsToolbar": "빠른 작업은 한 번의 클릭으로 명령을 실행할 수 있도록 호스트 메트릭 도구 모음에 버튼으로 표시됩니다.", - "noQuickActions": "아직 빠른 작업이 없습니다.", - "buttonLabel": "버튼 레이블", - "selectSnippetPlaceholder": "스니펫 선택...", - "addActionBtn": "작업 추가", "hostNotFound": "호스트를 찾을 수 없습니다", "loadingHost": "호스트 로드 중..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "디스크" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "취소", - "confirmRunButton": "실행", - "confirmRunMessage": "\"{{name}}\"을(를) 실행하시겠습니까?" - } - } - }, "terminal": { "connected": "연결됨" } diff --git a/plugins/host-metrics/locales/translated/nl_NL.json b/plugins/host-metrics/locales/translated/nl_NL.json index fb37c8c92..532137c7c 100644 --- a/plugins/host-metrics/locales/translated/nl_NL.json +++ b/plugins/host-metrics/locales/translated/nl_NL.json @@ -51,10 +51,6 @@ "processesRunning": "rennen", "loginStats": "SSH-aanmeldingsstatistieken", "noRecentLoginData": "Geen recente inloggegevens", - "executingQuickAction": "Het uitvoeren van {{name}}...", - "quickActionSuccess": "{{name}} succesvol voltooid", - "quickActionFailed": "{{name}} mislukt", - "quickActionError": "Het uitvoeren van {{name}} is mislukt.", "ports": { "title": "Luisterpoorten", "protocol": "Protocol", @@ -235,12 +231,6 @@ "addExcludedMount": "Toevoegen", "visibleWidgets": "Zichtbare widgets", "widgetsMovedToHostMetrics": "Kaarten kunnen nu rechtstreeks in het tabblad Hoststatistieken worden toegevoegd, gerangschikt en van formaat worden veranderd. Open Hoststatistieken voor deze host en gebruik Aanpassen om te kiezen welke kaarten worden weergegeven.", - "quickActionsLabel": "Snelle acties", - "quickActionsToolbar": "Snelle acties verschijnen als knoppen in de Host Metrics-werkbalk, zodat u opdrachten met één klik kunt uitvoeren.", - "noQuickActions": "Nog geen snelle acties ondernomen.", - "buttonLabel": "Knoplabel", - "selectSnippetPlaceholder": "Selecteer fragment...", - "addActionBtn": "Actie toevoegen", "hostNotFound": "Host niet gevonden", "loadingHost": "Host wordt geladen..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Schijf" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Annuleren", - "confirmRunButton": "Loop", - "confirmRunMessage": "Run \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Aangesloten" } diff --git a/plugins/host-metrics/locales/translated/no_NO.json b/plugins/host-metrics/locales/translated/no_NO.json index 9ea9715dd..1a08e5167 100644 --- a/plugins/host-metrics/locales/translated/no_NO.json +++ b/plugins/host-metrics/locales/translated/no_NO.json @@ -51,10 +51,6 @@ "processesRunning": "løping", "loginStats": "SSH-påloggingsstatistikk", "noRecentLoginData": "Ingen nylige påloggingsdata", - "executingQuickAction": "Utfører {{name}}...", - "quickActionSuccess": "{{name}} fullført", - "quickActionFailed": "{{name}} mislyktes", - "quickActionError": "Kunne ikke utføre {{name}}", "ports": { "title": "Lytteporter", "protocol": "Protokoll", @@ -235,12 +231,6 @@ "addExcludedMount": "Legge til", "visibleWidgets": "Synlige widgeter", "widgetsMovedToHostMetrics": "Kort legges nå til, arrangeres og endres i størrelse direkte i fanen Vertsmålinger. Åpne Vertsmålinger for denne verten og bruk Tilpass for å velge hvilke kort som vises.", - "quickActionsLabel": "Hurtighandlinger", - "quickActionsToolbar": "Hurtighandlinger vises som knapper i verktøylinjen for vertsmålinger for utførelse av kommandoer med ett klikk.", - "noQuickActions": "Ingen hurtighandlinger ennå.", - "buttonLabel": "Knappetikett", - "selectSnippetPlaceholder": "Velg utdrag...", - "addActionBtn": "Legg til handling", "hostNotFound": "Verten ble ikke funnet", "loadingHost": "Laster inn vert ..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Kansellere", - "confirmRunButton": "Løp", - "confirmRunMessage": "Kjør \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Tilkoblet" } diff --git a/plugins/host-metrics/locales/translated/pl_PL.json b/plugins/host-metrics/locales/translated/pl_PL.json index a3f1a10c3..0b6d8a70f 100644 --- a/plugins/host-metrics/locales/translated/pl_PL.json +++ b/plugins/host-metrics/locales/translated/pl_PL.json @@ -51,10 +51,6 @@ "processesRunning": "działa", "loginStats": "Statystyki logowania SSH", "noRecentLoginData": "Brak ostatnich danych logowania", - "executingQuickAction": "Wykonywanie {{name}}...", - "quickActionSuccess": "{{name}} ukończono pomyślnie", - "quickActionFailed": "{{name}} nie powiodło się", - "quickActionError": "Nie udało się wykonać {{name}}", "ports": { "title": "Porty nasłuchujące", "protocol": "Protokół", @@ -235,12 +231,6 @@ "addExcludedMount": "Dodaj", "visibleWidgets": "Widoczne widżety", "widgetsMovedToHostMetrics": "Karty dodaje się, układa i skaluje bezpośrednio na karcie „Metryki hosta”. Otwórz Metryki hosta dla tego hosta i użyj opcji „Dostosuj”, aby wybrać wyświetlane karty.", - "quickActionsLabel": "Szybkie akcje", - "quickActionsToolbar": "Szybkie akcje pojawiają się jako przyciski na pasku narzędzi Metryki hosta, umożliwiając wykonanie polecenia jednym kliknięciem.", - "noQuickActions": "Brak szybkich akcji.", - "buttonLabel": "Etykieta przycisku", - "selectSnippetPlaceholder": "Wybierz snippet...", - "addActionBtn": "Dodaj akcję", "hostNotFound": "Nie znaleziono hosta", "loadingHost": "Ładowanie hosta..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Dysk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Anuluj", - "confirmRunButton": "Uruchom", - "confirmRunMessage": "Uruchomić „{{name}}”?" - } - } - }, "terminal": { "connected": "Połączono" } diff --git a/plugins/host-metrics/locales/translated/pt_BR.json b/plugins/host-metrics/locales/translated/pt_BR.json index 4c7343950..2813920ce 100644 --- a/plugins/host-metrics/locales/translated/pt_BR.json +++ b/plugins/host-metrics/locales/translated/pt_BR.json @@ -51,10 +51,6 @@ "processesRunning": "correndo", "loginStats": "Estatísticas de login SSH", "noRecentLoginData": "Nenhum dado de login recente", - "executingQuickAction": "Executando {{name}}...", - "quickActionSuccess": "{{name}} concluído com sucesso", - "quickActionFailed": "{{name}} falhou", - "quickActionError": "Falha ao executar {{name}}", "ports": { "title": "Portas de escuta", "protocol": "Protocolo", @@ -235,12 +231,6 @@ "addExcludedMount": "Adicionar", "visibleWidgets": "Widgets Visíveis", "widgetsMovedToHostMetrics": "Agora, os cartões podem ser adicionados, organizados e redimensionados diretamente na guia Métricas do Host. Abra as Métricas do Host para este host e use a opção Personalizar para escolher quais cartões serão exibidos.", - "quickActionsLabel": "Ações rápidas", - "quickActionsToolbar": "As ações rápidas aparecem como botões na barra de ferramentas do Host Metrics para execução de comandos com um único clique.", - "noQuickActions": "Nenhuma ação imediata ainda.", - "buttonLabel": "Rótulo do botão", - "selectSnippetPlaceholder": "Selecione o trecho...", - "addActionBtn": "Adicionar ação", "hostNotFound": "Host não encontrado", "loadingHost": "Carregando host..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disco" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Cancelar", - "confirmRunButton": "Correr", - "confirmRunMessage": "Executar \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Conectado" } diff --git a/plugins/host-metrics/locales/translated/pt_PT.json b/plugins/host-metrics/locales/translated/pt_PT.json index 108a76e10..8458a4232 100644 --- a/plugins/host-metrics/locales/translated/pt_PT.json +++ b/plugins/host-metrics/locales/translated/pt_PT.json @@ -51,10 +51,6 @@ "processesRunning": "em execução", "loginStats": "Estatísticas de início de sessão SSH", "noRecentLoginData": "Sem dados de início de sessão recentes", - "executingQuickAction": "A executar {{name}}...", - "quickActionSuccess": "{{name}} concluído com sucesso", - "quickActionFailed": "{{name}} falhou", - "quickActionError": "Falha ao executar {{name}}", "ports": { "title": "Portas em escuta", "protocol": "Protocolo", @@ -235,12 +231,6 @@ "addExcludedMount": "Adicionar", "visibleWidgets": "Widgets Visíveis", "widgetsMovedToHostMetrics": "Os cartões são agora adicionados, organizados e redimensionados diretamente no separador Métricas do Host. Abra as Métricas do Host para este host e use Personalizar para escolher quais os cartões a mostrar.", - "quickActionsLabel": "Ações Rápidas", - "quickActionsToolbar": "As ações rápidas aparecem como botões na barra de ferramentas de Métricas do Host para execução de comandos com um clique.", - "noQuickActions": "Ainda não existem ações rápidas.", - "buttonLabel": "Texto do botão", - "selectSnippetPlaceholder": "Selecionar snippet...", - "addActionBtn": "Adicionar Ação", "hostNotFound": "Host não encontrado", "loadingHost": "A carregar o host..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disco" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Cancelar", - "confirmRunButton": "Executar", - "confirmRunMessage": "Executar \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Ligado" } diff --git a/plugins/host-metrics/locales/translated/ro_RO.json b/plugins/host-metrics/locales/translated/ro_RO.json index 454a5a0d1..a20a26d78 100644 --- a/plugins/host-metrics/locales/translated/ro_RO.json +++ b/plugins/host-metrics/locales/translated/ro_RO.json @@ -51,10 +51,6 @@ "processesRunning": "funcţionare", "loginStats": "Statistici de conectare SSH", "noRecentLoginData": "Nu există date de conectare recente", - "executingQuickAction": "Se execută {{name}}...", - "quickActionSuccess": "{{name}} finalizat cu succes", - "quickActionFailed": "{{name}} a eșuat", - "quickActionError": "Executarea {{name}} a eșuat", "ports": { "title": "Porturi de ascultare", "protocol": "Protocol", @@ -235,12 +231,6 @@ "addExcludedMount": "Adăuga", "visibleWidgets": "Widgeturi vizibile", "widgetsMovedToHostMetrics": "Cardurile sunt acum adăugate, aranjate și redimensionate direct în fila Indicatori gazdă. Deschideți Indicatori gazdă pentru această gazdă și utilizați Personalizare pentru a alege ce carduri sunt afișate.", - "quickActionsLabel": "Acțiuni rapide", - "quickActionsToolbar": "Acțiunile rapide apar sub formă de butoane în bara de instrumente Host Metrics pentru executarea comenzilor cu un singur clic.", - "noQuickActions": "Nicio acțiune rapidă încă.", - "buttonLabel": "Etichetă buton", - "selectSnippetPlaceholder": "Selectați fragmentul...", - "addActionBtn": "Adăugați o acțiune", "hostNotFound": "Gazdă negăsită", "loadingHost": "Se încarcă gazda..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disc" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Anula", - "confirmRunButton": "Aleargă", - "confirmRunMessage": "Executați „{{name}}”?" - } - } - }, "terminal": { "connected": "Conectat" } diff --git a/plugins/host-metrics/locales/translated/ru_RU.json b/plugins/host-metrics/locales/translated/ru_RU.json index ce1049e18..a3c33f7dd 100644 --- a/plugins/host-metrics/locales/translated/ru_RU.json +++ b/plugins/host-metrics/locales/translated/ru_RU.json @@ -51,10 +51,6 @@ "processesRunning": "выполняется", "loginStats": "Статистика входов SSH", "noRecentLoginData": "Нет данных о недавних входах", - "executingQuickAction": "Выполняется {{name}}...", - "quickActionSuccess": "{{name}} успешно завершено", - "quickActionFailed": "{{name}} не удалось", - "quickActionError": "Не удалось выполнить {{name}}", "ports": { "title": "Прослушиваемые порты", "protocol": "Протокол", @@ -235,12 +231,6 @@ "addExcludedMount": "Добавлять", "visibleWidgets": "Видимые виджеты", "widgetsMovedToHostMetrics": "Карточки теперь добавляются, упорядочиваются и изменяются в размере непосредственно на вкладке «Метрики хоста». Откройте метрики для этого хоста и используйте «Настроить», чтобы выбрать отображаемые карточки.", - "quickActionsLabel": "Быстрые действия", - "quickActionsToolbar": "Быстрые действия отображаются в виде кнопок на панели инструментов метрик хоста для выполнения команд в один клик.", - "noQuickActions": "Пока нет быстрых действий.", - "buttonLabel": "Надпись кнопки", - "selectSnippetPlaceholder": "Выбрать сниппет...", - "addActionBtn": "Добавить действие", "hostNotFound": "Хост не найден", "loadingHost": "Загрузка хоста..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Диск" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Отмена", - "confirmRunButton": "Запустить", - "confirmRunMessage": "Запустить \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Подключено" } diff --git a/plugins/host-metrics/locales/translated/sr_SP.json b/plugins/host-metrics/locales/translated/sr_SP.json index a3dd81af5..1c780c245 100644 --- a/plugins/host-metrics/locales/translated/sr_SP.json +++ b/plugins/host-metrics/locales/translated/sr_SP.json @@ -51,10 +51,6 @@ "processesRunning": "трчање", "loginStats": "Статистика SSH пријава", "noRecentLoginData": "Нема скорашњих података за пријављивање", - "executingQuickAction": "Извршава се {{name}}...", - "quickActionSuccess": "{{name}} успешно завршено", - "quickActionFailed": "{{name}} није успело", - "quickActionError": "Није успело извршавање {{name}}", "ports": { "title": "Портови за слушање", "protocol": "Протокол", @@ -235,12 +231,6 @@ "addExcludedMount": "Додај", "visibleWidgets": "Видљиви виџети", "widgetsMovedToHostMetrics": "Картице се сада додају, распоређују и мењају им се величина директно на картици „Host Metrics“. Отворите „Host Metrics“ за овог хоста и користите „Customize“ да бисте изабрали које ће картице бити приказане.", - "quickActionsLabel": "Брзе акције", - "quickActionsToolbar": "Брзе акције се појављују као дугмад у траци са алаткама Host Metrics за извршавање команди једним кликом.", - "noQuickActions": "Још нема брзих акција.", - "buttonLabel": "Ознака дугмета", - "selectSnippetPlaceholder": "Изаберите исечак...", - "addActionBtn": "Додај акцију", "hostNotFound": "Хост није пронађен", "loadingHost": "Учитавање хоста..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Диск" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Откажи", - "confirmRunButton": "Трчи", - "confirmRunMessage": "Покрени „{{name}}“?" - } - } - }, "terminal": { "connected": "Повезано" } diff --git a/plugins/host-metrics/locales/translated/sv_SE.json b/plugins/host-metrics/locales/translated/sv_SE.json index f0e52eb54..e93c47f9b 100644 --- a/plugins/host-metrics/locales/translated/sv_SE.json +++ b/plugins/host-metrics/locales/translated/sv_SE.json @@ -51,10 +51,6 @@ "processesRunning": "spring", "loginStats": "SSH-inloggningsstatistik", "noRecentLoginData": "Inga aktuella inloggningsuppgifter", - "executingQuickAction": "Kör {{name}}...", - "quickActionSuccess": "{{name}} slutfört", - "quickActionFailed": "{{name}} misslyckades", - "quickActionError": "Misslyckades med att köra {{name}}", "ports": { "title": "Lyssningsportar", "protocol": "Protokoll", @@ -235,12 +231,6 @@ "addExcludedMount": "Tillägga", "visibleWidgets": "Synliga widgetar", "widgetsMovedToHostMetrics": "Kort läggs nu till, ordnas och ändras i storlek direkt på fliken Värdstatistik. Öppna Värdstatistik för den här värden och använd Anpassa för att välja vilka kort som visas.", - "quickActionsLabel": "Snabbåtgärder", - "quickActionsToolbar": "Snabbåtgärder visas som knappar i verktygsfältet Host Metrics för att köra kommandon med ett klick.", - "noQuickActions": "Inga snabba åtgärder ännu.", - "buttonLabel": "Knappetikett", - "selectSnippetPlaceholder": "Välj utdrag...", - "addActionBtn": "Lägg till åtgärd", "hostNotFound": "Värden hittades inte", "loadingHost": "Laddar värd..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Avboka", - "confirmRunButton": "Sikt", - "confirmRunMessage": "Kör \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Ansluten" } diff --git a/plugins/host-metrics/locales/translated/th_TH.json b/plugins/host-metrics/locales/translated/th_TH.json index 4dacdc78a..627442521 100644 --- a/plugins/host-metrics/locales/translated/th_TH.json +++ b/plugins/host-metrics/locales/translated/th_TH.json @@ -51,10 +51,6 @@ "processesRunning": "วิ่ง", "loginStats": "สถิติการเข้าสู่ระบบ SSH", "noRecentLoginData": "ไม่มีข้อมูลการเข้าสู่ระบบล่าสุด", - "executingQuickAction": "กำลังดำเนินการ {{name}}...", - "quickActionSuccess": "{{name}} เสร็จสมบูรณ์เรียบร้อยแล้ว", - "quickActionFailed": "{{name}} ล้มเหลว", - "quickActionError": "ไม่สามารถดำเนินการ {{name}} ได้", "ports": { "title": "พอร์ตรับฟัง", "protocol": "โปรโตคอล", @@ -235,12 +231,6 @@ "addExcludedMount": "เพิ่ม", "visibleWidgets": "วิดเจ็ตที่มองเห็นได้", "widgetsMovedToHostMetrics": "ขณะนี้สามารถเพิ่ม จัดเรียง และปรับขนาดการ์ดได้โดยตรงในแท็บเมตริกโฮสต์ เปิดเมตริกโฮสต์สำหรับโฮสต์นี้แล้วใช้ ปรับแต่ง เพื่อเลือกการ์ดที่จะแสดง", - "quickActionsLabel": "การดำเนินการด่วน", - "quickActionsToolbar": "การดำเนินการด่วนจะปรากฏเป็นปุ่มในแถบเครื่องมือ Host Metrics เพื่อให้สามารถเรียกใช้คำสั่งได้ด้วยการคลิกเพียงครั้งเดียว", - "noQuickActions": "ยังไม่มีการดำเนินการใดๆ อย่างเร่งด่วนในขณะนี้", - "buttonLabel": "ป้ายกำกับปุ่ม", - "selectSnippetPlaceholder": "เลือกส่วนย่อย...", - "addActionBtn": "เพิ่มการดำเนินการ", "hostNotFound": "ไม่พบโฮสต์", "loadingHost": "กำลังโหลดโฮสต์..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "ดิสก์" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "ยกเลิก", - "confirmRunButton": "วิ่ง", - "confirmRunMessage": "เรียกใช้ \"{{name}}\"?" - } - } - }, "terminal": { "connected": "เชื่อมต่อแล้ว" } diff --git a/plugins/host-metrics/locales/translated/tr_TR.json b/plugins/host-metrics/locales/translated/tr_TR.json index c51215ce0..083491d7c 100644 --- a/plugins/host-metrics/locales/translated/tr_TR.json +++ b/plugins/host-metrics/locales/translated/tr_TR.json @@ -51,10 +51,6 @@ "processesRunning": "çalışan", "loginStats": "SSH Giriş İstatistikleri", "noRecentLoginData": "Son giriş verisi yok", - "executingQuickAction": "{{name}} çalıştırılıyor...", - "quickActionSuccess": "{{name}} başarıyla tamamlandı", - "quickActionFailed": "{{name}} başarısız oldu", - "quickActionError": "{{name}} çalıştırılamadı", "ports": { "title": "Dinlenen Portlar", "protocol": "Protokol", @@ -235,12 +231,6 @@ "addExcludedMount": "Eklemek", "visibleWidgets": "Görünür Widget'lar", "widgetsMovedToHostMetrics": "Kartlar artık doğrudan Sunucu Metrikleri sekmesinde eklenir, düzenlenir ve yeniden boyutlandırılır. Bu sunucu için Sunucu Metrikleri'ni açın ve hangi kartların gösterileceğini seçmek için Özelleştir'i kullanın.", - "quickActionsLabel": "Hızlı Eylemler", - "quickActionsToolbar": "Hızlı eylemler, tek tıklamayla komut çalıştırmak için Ana Bilgisayar Metrikleri araç çubuğunda düğme olarak görünür.", - "noQuickActions": "Henüz hızlı eylem yok.", - "buttonLabel": "Düğme etiketi", - "selectSnippetPlaceholder": "Parçacık seç...", - "addActionBtn": "Eylem Ekle", "hostNotFound": "Host bulunamadı", "loadingHost": "Host yükleniyor..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Disk" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "İptal", - "confirmRunButton": "Çalıştır", - "confirmRunMessage": "\"{{name}}\" çalıştırılsın mı?" - } - } - }, "terminal": { "connected": "Bağlandı" } diff --git a/plugins/host-metrics/locales/translated/uk_UA.json b/plugins/host-metrics/locales/translated/uk_UA.json index 2c66178ba..f7c2471a8 100644 --- a/plugins/host-metrics/locales/translated/uk_UA.json +++ b/plugins/host-metrics/locales/translated/uk_UA.json @@ -51,10 +51,6 @@ "processesRunning": "біг", "loginStats": "Статистика входу SSH", "noRecentLoginData": "Немає останніх даних для входу", - "executingQuickAction": "Виконання {{name}}...", - "quickActionSuccess": "{{name}} успішно завершено", - "quickActionFailed": "{{name}} не вдалося", - "quickActionError": "Не вдалося виконати {{name}}", "ports": { "title": "Порти прослуховування", "protocol": "Протокол", @@ -235,12 +231,6 @@ "addExcludedMount": "Додати", "visibleWidgets": "Видимі віджети", "widgetsMovedToHostMetrics": "Тепер картки додаються, упорядковуються та змінюються за розміром безпосередньо на вкладці «Метрики хоста». Відкрийте «Метрики хоста» для цього хоста та скористайтеся функцією «Налаштувати», щоб вибрати, які картки відображатимуться.", - "quickActionsLabel": "Швидкі дії", - "quickActionsToolbar": "Швидкі дії відображаються як кнопки на панелі інструментів «Метрики хоста» для виконання команд одним клацанням миші.", - "noQuickActions": "Поки що жодних швидких дій.", - "buttonLabel": "Мітка кнопки", - "selectSnippetPlaceholder": "Вибрати фрагмент...", - "addActionBtn": "Додати дію", "hostNotFound": "Хост не знайдено", "loadingHost": "Завантаження хоста..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Диск" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Скасувати", - "confirmRunButton": "Бігти", - "confirmRunMessage": "Запустити \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Підключено" } diff --git a/plugins/host-metrics/locales/translated/vi_VN.json b/plugins/host-metrics/locales/translated/vi_VN.json index 53828e648..9529d4bd1 100644 --- a/plugins/host-metrics/locales/translated/vi_VN.json +++ b/plugins/host-metrics/locales/translated/vi_VN.json @@ -51,10 +51,6 @@ "processesRunning": "đang chạy", "loginStats": "Thống kê đăng nhập SSH", "noRecentLoginData": "Không có dữ liệu đăng nhập gần đây", - "executingQuickAction": "Đang thực thi {{name}}...", - "quickActionSuccess": "{{name}} đã hoàn tất thành công", - "quickActionFailed": "{{name}} thất bại", - "quickActionError": "Thực thi {{name}} thất bại", "ports": { "title": "Cổng đang lắng nghe", "protocol": "Giao thức", @@ -235,12 +231,6 @@ "addExcludedMount": "Thêm vào", "visibleWidgets": "Widget hiển thị", "widgetsMovedToHostMetrics": "Các thẻ hiện được thêm, sắp xếp và thay đổi kích thước trực tiếp trong tab Số liệu máy. Mở Số liệu máy cho máy này và dùng Tùy chỉnh để chọn thẻ hiển thị.", - "quickActionsLabel": "Hành động nhanh", - "quickActionsToolbar": "Hành động nhanh xuất hiện dưới dạng nút trên thanh công cụ Chỉ số máy để thực thi lệnh một lần nhấp.", - "noQuickActions": "Chưa có hành động nhanh nào.", - "buttonLabel": "Nhãn nút", - "selectSnippetPlaceholder": "Chọn đoạn mã...", - "addActionBtn": "Thêm hành động", "hostNotFound": "Không tìm thấy máy chủ", "loadingHost": "Đang tải máy chủ..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "Ổ đĩa" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "Hủy", - "confirmRunButton": "Chạy", - "confirmRunMessage": "Chạy \"{{name}}\"?" - } - } - }, "terminal": { "connected": "Đã kết nối" } diff --git a/plugins/host-metrics/locales/translated/zh_CN.json b/plugins/host-metrics/locales/translated/zh_CN.json index 9736ea220..cdcfd000f 100644 --- a/plugins/host-metrics/locales/translated/zh_CN.json +++ b/plugins/host-metrics/locales/translated/zh_CN.json @@ -51,10 +51,6 @@ "processesRunning": "运行中", "loginStats": "SSH 登录统计", "noRecentLoginData": "无最近登录数据", - "executingQuickAction": "正在执行 {{name}}...", - "quickActionSuccess": "{{name}} 执行成功", - "quickActionFailed": "{{name}} 执行失败", - "quickActionError": "执行 {{name}} 失败", "ports": { "title": "监听端口", "protocol": "协议", @@ -235,12 +231,6 @@ "addExcludedMount": "添加", "visibleWidgets": "可见小部件", "widgetsMovedToHostMetrics": "卡片现在可在“主机指标”选项卡中直接添加、排列和调整大小。打开此主机的“主机指标”,使用“自定义”选择要显示的卡片。", - "quickActionsLabel": "快速操作", - "quickActionsToolbar": "快速操作以按钮形式显示在主机指标工具栏中,以便一键执行命令。", - "noQuickActions": "尚无快速操作。", - "buttonLabel": "按钮标签", - "selectSnippetPlaceholder": "选择代码片段...", - "addActionBtn": "添加操作", "hostNotFound": "未找到主机", "loadingHost": "正在加载主机..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "磁盘" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "取消", - "confirmRunButton": "运行", - "confirmRunMessage": "运行“{{name}}”?" - } - } - }, "terminal": { "connected": "已连接" } diff --git a/plugins/host-metrics/locales/translated/zh_TW.json b/plugins/host-metrics/locales/translated/zh_TW.json index db704c54c..5b35e1d39 100644 --- a/plugins/host-metrics/locales/translated/zh_TW.json +++ b/plugins/host-metrics/locales/translated/zh_TW.json @@ -51,10 +51,6 @@ "processesRunning": "執行中", "loginStats": "SSH 登入統計資訊", "noRecentLoginData": "沒有最近的登入資料", - "executingQuickAction": "正在執行 {{name}}...", - "quickActionSuccess": "{{name}} 已成功完成", - "quickActionFailed": "{{name}} 失敗", - "quickActionError": "無法執行 {{name}}", "ports": { "title": "監聽中的連接埠", "protocol": "通訊協定", @@ -235,12 +231,6 @@ "addExcludedMount": "添加", "visibleWidgets": "可見的小工具", "widgetsMovedToHostMetrics": "現在卡片可直接在主機指標分頁中新增、排列和調整大小。開啟此主機的主機指標,然後使用自訂功能選擇要顯示的卡片。", - "quickActionsLabel": "快速動作", - "quickActionsToolbar": "快速動作會以按鈕形式顯示在主機計量工具列上,可一鍵執行指令。", - "noQuickActions": "尚無快速動作。", - "buttonLabel": "按鈕標籤", - "selectSnippetPlaceholder": "選取程式碼片段...", - "addActionBtn": "新增動作", "hostNotFound": "找不到主機", "loadingHost": "正在載入主機..." }, @@ -282,15 +272,6 @@ "dashboardTab": { "disk": "磁碟" }, - "newUi": { - "sidebar": { - "snippets": { - "cancel": "取消", - "confirmRunButton": "執行", - "confirmRunMessage": "執行 \"{{name}}\"?" - } - } - }, "terminal": { "connected": "已連線" } diff --git a/plugins/host-metrics/manifest.json b/plugins/host-metrics/manifest.json index 337f4ce03..3de1c0be7 100644 --- a/plugins/host-metrics/manifest.json +++ b/plugins/host-metrics/manifest.json @@ -137,6 +137,10 @@ { "id": "host-metrics.managers", "accepts": ["component"] + }, + { + "id": "host-metrics.toolbar", + "accepts": ["component"] } ], "uiPresets": { diff --git a/plugins/host-metrics/src/frontend/HostEditorStatsTab.tsx b/plugins/host-metrics/src/frontend/HostEditorStatsTab.tsx index 207d896d4..2a586a4ce 100644 --- a/plugins/host-metrics/src/frontend/HostEditorStatsTab.tsx +++ b/plugins/host-metrics/src/frontend/HostEditorStatsTab.tsx @@ -4,7 +4,6 @@ import { SectionCard, SettingRow, FakeSwitch, - Select2, } from "@termix/plugin-sdk/ui"; import { useEffect, useState } from "react"; import { @@ -12,33 +11,24 @@ import { useTranslation, type HostEditorSectionProps, } from "@termix/plugin-sdk/frontend"; -import { - HardDrive, - LayoutDashboard, - Plus, - Server, - Trash2, - Zap, -} from "lucide-react"; +import { HardDrive, LayoutDashboard, Plus, Server, Trash2 } from "lucide-react"; import { readHostMetricsSettings } from "../shared/stats-widgets.js"; const PLUGIN_ID = "host-metrics"; type PluginSettingsBag = Record>; -type QuickActionRow = { name: string; snippetId: string }; /** * The Host Metrics section of the host editor. Its values are this plugin's * host settings, carried on form.pluginSettings and saved by the editor after - * the host itself. Quick actions are a core host field. + * the host itself. */ export function HostStatsTab({ form, setField, updateForm, host, - snippets, }: HostEditorSectionProps) { const { t } = useTranslation(); const [newMount, setNewMount] = useState(""); @@ -49,8 +39,6 @@ export function HostStatsTab({ ]; const settings = readHostMetricsSettings(bag); const { excludedMounts, monitoredMounts } = settings; - const quickActions = (form.quickActions ?? []) as QuickActionRow[]; - const snippetOptions = (snippets ?? []) as { id: number; name: string }[]; const api = usePluginApi(); const patch = (values: Record) => @@ -288,84 +276,6 @@ export function HostStatsTab({

- } - action={ - - } - > -
-

- {t("hosts.quickActionsToolbar")} -

- {quickActions.length === 0 && ( -
- - {t("hosts.noQuickActions")} -
- )} - {quickActions.map((a, i) => ( -
- { - const updated = [...quickActions]; - updated[i] = { ...updated[i], name: e.target.value }; - setField("quickActions", updated); - }} - /> - { - const updated = [...quickActions]; - updated[i] = { - ...updated[i], - snippetId: e.target.value, - }; - setField("quickActions", updated); - }} - > - - {snippetOptions.map((s) => ( - - ))} - - -
- ))} -
-
); } diff --git a/plugins/host-metrics/src/frontend/HostMetricsTab.tsx b/plugins/host-metrics/src/frontend/HostMetricsTab.tsx index 6c4ab74d7..9ac059f98 100644 --- a/plugins/host-metrics/src/frontend/HostMetricsTab.tsx +++ b/plugins/host-metrics/src/frontend/HostMetricsTab.tsx @@ -1,7 +1,6 @@ import { - Separator, Button, - useConfirmation, + ComponentSlot, TOTPDialog, useTabsSafe, ConnectionLogProvider, @@ -11,7 +10,6 @@ import { CardGridCanvas, ColumnCountStepper, type GridCardCatalogEntry, - SnippetVariablesDialog, } from "@termix/plugin-sdk/ui"; import { readHostMetricsSettings } from "../shared/stats-widgets.js"; import { @@ -23,33 +21,12 @@ import type { ServerMetrics } from "../shared/metrics.js"; import React from "react"; import { useHostMetricsApi } from "./host-metrics-api"; -interface Snippet { - id: number; - name: string; - content: string; -} - -function errorText(error: unknown): string { - if (error instanceof Error && error.message) return error.message; - return typeof error === "string" ? error : ""; -} - -// Mirrors the snippets plugin's own hasSnippetInputs: a pure check for -// whether a snippet still has $INPUT_n placeholders once host variables are -// substituted, so the quick-action dialog knows to ask before running. -const SNIPPET_INPUT_PATTERN = - /\$\{INPUT_(\d+)(?::([^}$]+))?\}|\$INPUT_(\d+)(?![a-zA-Z0-9_])/g; -function hasSnippetInputs(content: string): boolean { - SNIPPET_INPUT_PATTERN.lastIndex = 0; - return SNIPPET_INPUT_PATTERN.test(content); -} import { useTranslation, useSlotContributions, useConnectionRetry, useHost, useHostStatus, - invokeAction, logActivity, usePluginUiPreferences, } from "@termix/plugin-sdk/frontend"; @@ -70,11 +47,6 @@ import { metricsChangeKey } from "./metrics-change-key"; const HISTORY_LEN = 30; -interface QuickAction { - name: string; - snippetId: number; -} - type ConnectionLogPayload = Parameters< ReturnType["addLog"] >[0]; @@ -87,7 +59,6 @@ interface HostConfig { name: string; ip: string; username: string; - quickActions?: QuickAction[]; statusCheckEnabled?: boolean; pluginSettings?: Record>; authType?: string; @@ -127,9 +98,6 @@ function HostMetricsInner({ }); const [currentHostConfig, setCurrentHostConfig] = React.useState(hostConfig); const [isRefreshing, setIsRefreshing] = React.useState(false); - const [executingActions, setExecutingActions] = React.useState>( - new Set(), - ); const [totpRequired, setTotpRequired] = React.useState(false); const [totpSessionId, setTotpSessionId] = React.useState(null); const [totpPrompt, setTotpPrompt] = React.useState(""); @@ -139,15 +107,9 @@ function HostMetricsInner({ null, ); const [editMode, setEditMode] = React.useState(false); - const [runningAction, setRunningAction] = React.useState<{ - action: QuickAction; - snippet: Snippet; - } | null>(null); - const { confirmWithToast } = useConfirmation(); const activityLoggedRef = React.useRef(false); const activityLoggingRef = React.useRef(false); - const snippetsCacheRef = React.useRef(new Map()); const statsConfig = React.useMemo(() => { const settings = readHostMetricsSettings( @@ -532,102 +494,6 @@ function HostMetricsInner({ } }; - async function executeQuickAction( - action: QuickAction, - inputValues: Record, - ) { - if (!currentHostConfig) return; - setExecutingActions((prev) => new Set(prev).add(action.snippetId)); - toast.loading( - t("hostMetrics.executingQuickAction", { name: action.name }), - { - id: `quick-action-${action.snippetId}`, - }, - ); - try { - const result = (await invokeAction( - "snippets.execute", - action.snippetId, - currentHostConfig.id, - Object.keys(inputValues).length > 0 ? inputValues : undefined, - )) as { success: boolean; output?: string; error?: string } | undefined; - if (!result) { - toast.error(t("hostMetrics.quickActionError", { name: action.name }), { - id: `quick-action-${action.snippetId}`, - description: t("hostMetrics.snippetsUnavailable"), - duration: 5000, - }); - } else if (result.success) { - toast.success( - t("hostMetrics.quickActionSuccess", { name: action.name }), - { - id: `quick-action-${action.snippetId}`, - description: result.output?.substring(0, 200), - duration: 5000, - }, - ); - } else { - toast.error(t("hostMetrics.quickActionFailed", { name: action.name }), { - id: `quick-action-${action.snippetId}`, - description: result.error || result.output, - duration: 5000, - }); - } - } catch (error) { - toast.error(t("hostMetrics.quickActionError", { name: action.name }), { - id: `quick-action-${action.snippetId}`, - description: errorText(error), - duration: 5000, - }); - } finally { - setExecutingActions((prev) => { - const next = new Set(prev); - next.delete(action.snippetId); - return next; - }); - } - } - - function runQuickActionWithConfirm( - action: QuickAction, - inputValues: Record, - ) { - const shouldConfirm = - localStorage.getItem("confirmSnippetExecution") === "true"; - const run = () => void executeQuickAction(action, inputValues); - if (!shouldConfirm) { - run(); - return; - } - confirmWithToast( - t("newUi.sidebar.snippets.confirmRunMessage", { name: action.name }), - run, - t("newUi.sidebar.snippets.confirmRunButton"), - t("newUi.sidebar.snippets.cancel"), - { confirmOnEnter: true, duration: 6000 }, - ); - } - - async function runQuickAction(action: QuickAction) { - if (!currentHostConfig) return; - try { - let snippet = snippetsCacheRef.current.get(action.snippetId); - if (!snippet) { - snippet = - ((await invokeAction("snippets.get", action.snippetId)) as - Snippet | null | undefined) ?? undefined; - if (snippet) snippetsCacheRef.current.set(snippet.id, snippet); - } - if (snippet && hasSnippetInputs(snippet.content)) { - setRunningAction({ action, snippet }); - return; - } - } catch { - // fall through and run with no inputs if the snippet lookup fails - } - runQuickActionWithConfirm(action, {}); - } - const showCards = metricsEnabled && metricsRetry.status === "connected" && metrics; const showOffline = @@ -653,43 +519,13 @@ function HostMetricsInner({

{title}

- {currentHostConfig?.quickActions && - currentHostConfig.quickActions.length > 0 && ( - <> -
- {currentHostConfig.quickActions.map( - (action, index) => { - const isExecuting = executingActions.has( - action.snippetId, - ); - return ( - - ); - }, - )} -
- - - )} + {editMode && ( <> - - {runningAction && ( - [0]["snippet"] - } - host={ - currentHostConfig - ? { - ip: currentHostConfig.ip, - username: currentHostConfig.username, - port: currentHostConfig.port, - name: currentHostConfig.name, - } - : null - } - onCancel={() => setRunningAction(null)} - onConfirm={(_resolvedContent, inputValues) => { - const action = runningAction.action; - setRunningAction(null); - runQuickActionWithConfirm(action, inputValues); - }} - /> - )}
); } diff --git a/plugins/host-metrics/src/frontend/index.tsx b/plugins/host-metrics/src/frontend/index.tsx index e690d1a2c..13555a756 100644 --- a/plugins/host-metrics/src/frontend/index.tsx +++ b/plugins/host-metrics/src/frontend/index.tsx @@ -140,4 +140,10 @@ export async function activate(app: TermixApp): Promise { id: "host-metrics.managers", accepts: ["component"], }); + // Buttons before the toolbar's own controls, drawn with { hostId, host }. + // The snippets plugin puts a host's quick actions here. + app.declareActionSlot({ + id: "host-metrics.toolbar", + accepts: ["component"], + }); } diff --git a/plugins/host-metrics/tests/frontend/HostEditorStatsTab.test.tsx b/plugins/host-metrics/tests/frontend/HostEditorStatsTab.test.tsx index 59fa6b779..fb2c7c4a8 100644 --- a/plugins/host-metrics/tests/frontend/HostEditorStatsTab.test.tsx +++ b/plugins/host-metrics/tests/frontend/HostEditorStatsTab.test.tsx @@ -7,7 +7,6 @@ afterEach(cleanup); function renderSection(pluginSettings: Record = {}) { let form: Record = { - quickActions: [], pluginSettings: { "host-metrics": pluginSettings, other: { keep: 1 } }, }; const updateForm = vi.fn( @@ -21,7 +20,6 @@ function renderSection(pluginSettings: Record = {}) { setField={vi.fn()} updateForm={updateForm as never} host={{ id: "7", name: "h", ip: "10.0.0.7", port: 22 }} - snippets={[]} protocols={{ ssh: true }} />, ); diff --git a/plugins/snippets/locales/en.json b/plugins/snippets/locales/en.json index cdca75ada..44c199485 100644 --- a/plugins/snippets/locales/en.json +++ b/plugins/snippets/locales/en.json @@ -195,5 +195,37 @@ "shareFolderHint": "Shares every snippet in this folder. Snippets added later are not shared automatically.", "shareFolderSummary": "Shared {{count}} snippet(s) in this folder", "grantedBy": "Granted by", - "expired": "Expired" + "expired": "Expired", + "variablesDialogTitle": "Run \"{{name}}\"", + "variablesDialogDescription": "Fill in the values this snippet needs before running it.", + "variablesPreviewLabel": "Preview", + "variablesConfirmButton": "Run", + "keybindings": { + "runSnippet": "Run existing snippet", + "snippetLabel": "Snippet", + "selectSnippet": "Select a snippet", + "snippetMissing": "snippet not found", + "snippetRequired": "Select a snippet" + }, + "paletteHintRun": "Run in the active terminal tab", + "host": { + "startupTitle": "Startup Snippet", + "quickActionsTitle": "Quick Actions", + "quickActionsDescription": "Quick actions appear as buttons in the Host Metrics toolbar for one-click command execution.", + "noQuickActions": "No quick actions yet.", + "buttonLabel": "Button label", + "selectSnippet": "Select snippet...", + "addQuickAction": "Add Action", + "tabTitle": "Snippets", + "startupDescription": "Runs in the terminal each time it connects to this host. Variables like $HOST are filled in; a snippet that asks for inputs is skipped.", + "none": "None", + "removeQuickAction": "Remove quick action" + }, + "quickActions": { + "running": "Executing {{name}}...", + "succeeded": "{{name}} completed successfully", + "failed": "{{name}} failed", + "error": "Failed to execute {{name}}" + }, + "paletteHintPaste": "Paste in the active terminal tab" } diff --git a/plugins/snippets/locales/translated/af_ZA.json b/plugins/snippets/locales/translated/af_ZA.json new file mode 100644 index 000000000..e811c6320 --- /dev/null +++ b/plugins/snippets/locales/translated/af_ZA.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Begin \"{{name}}\"", + "variablesDialogDescription": "Vul die waardes in wat hierdie brokkie benodig voordat dit uitgevoer word.", + "variablesPreviewLabel": "Voorskou", + "variablesConfirmButton": "Hardloop", + "keybindings": { + "runSnippet": "Voer bestaande brokkie uit", + "snippetLabel": "Brokkie", + "selectSnippet": "Kies 'n uittreksel", + "snippetMissing": "brokkie nie gevind nie", + "snippetRequired": "Kies 'n uittreksel" + }, + "paletteHintRun": "Begin in die aktiewe terminaal-oortjie", + "host": { + "startupTitle": "Opstart-brokkie", + "quickActionsTitle": "Vinnige Aksies", + "quickActionsDescription": "Vinnige aksies verskyn as knoppies in die Host Metrics-nutsbalk vir die uitvoering van opdragte met een klik.", + "noQuickActions": "Geen vinnige aksies nog nie.", + "buttonLabel": "Knoppie-etiket", + "selectSnippet": "Kies uittreksel...", + "addQuickAction": "Voeg Aksie by" + }, + "quickActions": { + "running": "Voer tans {{name}} uit ...", + "succeeded": "{{name}} suksesvol voltooi", + "failed": "{{name}} het misluk", + "error": "Kon nie {{name}} uitvoer nie" + } +} diff --git a/plugins/snippets/locales/translated/ar_SA.json b/plugins/snippets/locales/translated/ar_SA.json new file mode 100644 index 000000000..91c3e6487 --- /dev/null +++ b/plugins/snippets/locales/translated/ar_SA.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "تشغيل \"{{name}}\"", + "variablesDialogDescription": "أدخل القيم التي يحتاجها هذا المقتطف البرمجي قبل تشغيله.", + "variablesPreviewLabel": "معاينة", + "variablesConfirmButton": "يجري", + "keybindings": { + "runSnippet": "تشغيل المقتطف البرمجي الحالي", + "snippetLabel": "مقتطف", + "selectSnippet": "حدد جزءًا من التعليمات البرمجية", + "snippetMissing": "لم يتم العثور على المقتطف", + "snippetRequired": "حدد جزءًا من التعليمات البرمجية" + }, + "paletteHintRun": "قم بتشغيله في علامة تبويب الطرفية النشطة", + "host": { + "startupTitle": "مقتطف بدء التشغيل", + "quickActionsTitle": "إجراءات سريعة", + "quickActionsDescription": "تظهر الإجراءات السريعة كأزرار في شريط أدوات مقاييس المضيف لتنفيذ الأوامر بنقرة واحدة.", + "noQuickActions": "لا توجد إجراءات سريعة بعد.", + "buttonLabel": "تسمية الزر", + "selectSnippet": "اختر مقتطفًا...", + "addQuickAction": "إضافة إجراء" + }, + "quickActions": { + "running": "جارٍ تنفيذ {{name}}...", + "succeeded": "تم إكمال {{name}} بنجاح", + "failed": "فشل {{name}}", + "error": "فشل تنفيذ {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/bg_BG.json b/plugins/snippets/locales/translated/bg_BG.json new file mode 100644 index 000000000..a276c3d56 --- /dev/null +++ b/plugins/snippets/locales/translated/bg_BG.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Изпълни „{{name}}“", + "variablesDialogDescription": "Попълнете стойностите, необходими на този фрагмент, преди да го изпълните.", + "variablesPreviewLabel": "Преглед", + "variablesConfirmButton": "Бягане", + "keybindings": { + "runSnippet": "Изпълнете съществуващия фрагмент", + "snippetLabel": "Фрагмент", + "selectSnippet": "Изберете фрагмент", + "snippetMissing": "фрагмент не е намерен", + "snippetRequired": "Изберете фрагмент" + }, + "paletteHintRun": "Изпълняване в активния раздел на терминала", + "host": { + "startupTitle": "Стартов фрагмент", + "quickActionsTitle": "Бързи действия", + "quickActionsDescription": "Бързите действия се показват като бутони в лентата с инструменти Host Metrics за изпълнение на команди с едно щракване.", + "noQuickActions": "Все още няма бързи действия.", + "buttonLabel": "Етикет на бутона", + "selectSnippet": "Изберете фрагмент...", + "addQuickAction": "Добавяне на действие" + }, + "quickActions": { + "running": "Изпълнява се {{name}}...", + "succeeded": "{{name}} завършено успешно", + "failed": "{{name}} неуспешно", + "error": "Неуспешно изпълнение на {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/bn_BD.json b/plugins/snippets/locales/translated/bn_BD.json new file mode 100644 index 000000000..66478dc76 --- /dev/null +++ b/plugins/snippets/locales/translated/bn_BD.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "দৌড়াও \"{{name}}\"", + "variablesDialogDescription": "এই কোড স্নিপেটটি চালানোর আগে প্রয়োজনীয় মানগুলি পূরণ করুন।", + "variablesPreviewLabel": "প্রিভিউ", + "variablesConfirmButton": "দৌড়", + "keybindings": { + "runSnippet": "বিদ্যমান কোড চালান", + "snippetLabel": "স্নিপেট", + "selectSnippet": "একটি স্নিপেট নির্বাচন করুন", + "snippetMissing": "স্নিপেটটি খুঁজে পাওয়া যায়নি", + "snippetRequired": "একটি স্নিপেট নির্বাচন করুন" + }, + "paletteHintRun": "সক্রিয় টার্মিনাল ট্যাবে চালান", + "host": { + "startupTitle": "স্টার্টআপ স্নিপেট", + "quickActionsTitle": "দ্রুত পদক্ষেপ", + "quickActionsDescription": "এক ক্লিকে কমান্ড কার্যকর করার জন্য কুইক অ্যাকশনগুলো হোস্ট মেট্রিক্স টুলবারে বাটন হিসেবে প্রদর্শিত হয়।", + "noQuickActions": "এখনো কোনো দ্রুত পদক্ষেপ নেওয়া হয়নি।", + "buttonLabel": "বোতাম লেবেল", + "selectSnippet": "স্নিপেট নির্বাচন করুন...", + "addQuickAction": "অ্যাকশন যোগ করুন" + }, + "quickActions": { + "running": "{{name}} কার্যকর করা হচ্ছে ...", + "succeeded": "{{name}} সফলভাবে সম্পন্ন হয়েছে", + "failed": "{{name}} ব্যর্থ হয়েছে", + "error": "{{name}} কার্যকর করতে ব্যর্থ হয়েছে" + } +} diff --git a/plugins/snippets/locales/translated/ca_ES.json b/plugins/snippets/locales/translated/ca_ES.json new file mode 100644 index 000000000..4af8288a4 --- /dev/null +++ b/plugins/snippets/locales/translated/ca_ES.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Executa \"{{name}}\"", + "variablesDialogDescription": "Empleneu els valors que necessita aquest fragment abans d'executar-lo.", + "variablesPreviewLabel": "Vista prèvia", + "variablesConfirmButton": "Corre", + "keybindings": { + "runSnippet": "Executa el fragment existent", + "snippetLabel": "Fragment", + "selectSnippet": "Selecciona un fragment", + "snippetMissing": "fragment no trobat", + "snippetRequired": "Selecciona un fragment" + }, + "paletteHintRun": "Executar a la pestanya activa del terminal", + "host": { + "startupTitle": "Fragment d'inici", + "quickActionsTitle": "Accions ràpides", + "quickActionsDescription": "Les accions ràpides apareixen com a botons a la barra d'eines de mètriques de l'amfitrió per executar ordres amb un sol clic.", + "noQuickActions": "Encara no hi ha accions ràpides.", + "buttonLabel": "Etiqueta del botó", + "selectSnippet": "Selecciona un fragment...", + "addQuickAction": "Afegeix una acció" + }, + "quickActions": { + "running": "Executant {{name}}...", + "succeeded": "{{name}} s'ha completat correctament", + "failed": "{{name}} ha fallat", + "error": "No s'ha pogut executar {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/cs_CZ.json b/plugins/snippets/locales/translated/cs_CZ.json new file mode 100644 index 000000000..f2a1b63f7 --- /dev/null +++ b/plugins/snippets/locales/translated/cs_CZ.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Spustit „{{name}}“", + "variablesDialogDescription": "Před spuštěním úryvku kódu vyplňte jeho potřebné hodnoty.", + "variablesPreviewLabel": "Náhled", + "variablesConfirmButton": "Běh", + "keybindings": { + "runSnippet": "Spustit existující úryvek kódu", + "snippetLabel": "Úryvek", + "selectSnippet": "Vyberte úryvek", + "snippetMissing": "úryvek nenalezen", + "snippetRequired": "Vyberte úryvek" + }, + "paletteHintRun": "Spustit v aktivní kartě terminálu", + "host": { + "startupTitle": "Úryvek pro spuštění", + "quickActionsTitle": "Rychlé akce", + "quickActionsDescription": "Rychlé akce se zobrazují jako tlačítka v panelu nástrojů Metriky hostitele pro spuštění příkazů jedním kliknutím.", + "noQuickActions": "Zatím žádné rychlé akce.", + "buttonLabel": "Štítek tlačítka", + "selectSnippet": "Vybrat úryvek...", + "addQuickAction": "Přidat akci" + }, + "quickActions": { + "running": "Spouštění {{name}}...", + "succeeded": "{{name}} úspěšně dokončeno", + "failed": "{{name}} selhalo", + "error": "Nepodařilo se spustit {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/da_DK.json b/plugins/snippets/locales/translated/da_DK.json new file mode 100644 index 000000000..13281893e --- /dev/null +++ b/plugins/snippets/locales/translated/da_DK.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Kør \"{{name}}\"", + "variablesDialogDescription": "Udfyld de værdier, som dette kodestykke skal bruge, før det køres.", + "variablesPreviewLabel": "Forhåndsvisning", + "variablesConfirmButton": "Løbe", + "keybindings": { + "runSnippet": "Kør eksisterende kodestykke", + "snippetLabel": "Uddrag", + "selectSnippet": "Vælg et uddrag", + "snippetMissing": "uddrag ikke fundet", + "snippetRequired": "Vælg et uddrag" + }, + "paletteHintRun": "Kør i den aktive terminalfane", + "host": { + "startupTitle": "Opstartskodestykke", + "quickActionsTitle": "Hurtige handlinger", + "quickActionsDescription": "Hurtige handlinger vises som knapper i værktøjslinjen Host Metrics til udførelse af kommandoer med et enkelt klik.", + "noQuickActions": "Ingen hurtige handlinger endnu.", + "buttonLabel": "Knapmærke", + "selectSnippet": "Vælg uddrag...", + "addQuickAction": "Tilføj handling" + }, + "quickActions": { + "running": "Udfører {{name}}...", + "succeeded": "{{name}} gennemført", + "failed": "{{name}} mislykkedes", + "error": "Kunne ikke udføre {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/de_DE.json b/plugins/snippets/locales/translated/de_DE.json new file mode 100644 index 000000000..d755deded --- /dev/null +++ b/plugins/snippets/locales/translated/de_DE.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Run \"{{name}}\"", + "variablesDialogDescription": "Füllen Sie die für diesen Codeabschnitt erforderlichen Werte aus, bevor Sie ihn ausführen.", + "variablesPreviewLabel": "Vorschau", + "variablesConfirmButton": "Laufen", + "keybindings": { + "runSnippet": "Vorhandenen Codeausschnitt ausführen", + "snippetLabel": "Ausschnitt", + "selectSnippet": "Wählen Sie einen Ausschnitt aus", + "snippetMissing": "Ausschnitt nicht gefunden", + "snippetRequired": "Wählen Sie einen Ausschnitt aus" + }, + "paletteHintRun": "Im aktiven Terminal-Tab ausführen", + "host": { + "startupTitle": "Startup-Snippet", + "quickActionsTitle": "Schnellaktionen", + "quickActionsDescription": "Schnellaktionen erscheinen als Schaltflächen in der Host-Metriken-Symbolleiste zur Ausführung von Befehlen mit einem Klick.", + "noQuickActions": "Noch keine Schnellaktionen.", + "buttonLabel": "Schaltflächenbeschriftung", + "selectSnippet": "Snippet auswählen...", + "addQuickAction": "Aktion hinzufügen" + }, + "quickActions": { + "running": "{{name}} wird ausgeführt...", + "succeeded": "{{name}} erfolgreich abgeschlossen", + "failed": "{{name}} fehlgeschlagen", + "error": "Fehler beim Ausführen von {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/el_GR.json b/plugins/snippets/locales/translated/el_GR.json new file mode 100644 index 000000000..8911f21e4 --- /dev/null +++ b/plugins/snippets/locales/translated/el_GR.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Εκτέλεση \"{{name}}\"", + "variablesDialogDescription": "Συμπληρώστε τις τιμές που χρειάζεται αυτό το τμήμα κώδικα πριν το εκτελέσετε.", + "variablesPreviewLabel": "Πρεμιέρα", + "variablesConfirmButton": "Τρέξιμο", + "keybindings": { + "runSnippet": "Εκτέλεση υπάρχοντος αποσπάσματος", + "snippetLabel": "Απόσπασμα", + "selectSnippet": "Επιλέξτε ένα απόσπασμα", + "snippetMissing": "το απόσπασμα δεν βρέθηκε", + "snippetRequired": "Επιλέξτε ένα απόσπασμα" + }, + "paletteHintRun": "Εκτέλεση στην ενεργή καρτέλα τερματικού", + "host": { + "startupTitle": "Απόσπασμα εκκίνησης", + "quickActionsTitle": "Γρήγορες ενέργειες", + "quickActionsDescription": "Οι γρήγορες ενέργειες εμφανίζονται ως κουμπιά στη γραμμή εργαλείων Μετρήσεων κεντρικού υπολογιστή για εκτέλεση εντολών με ένα κλικ.", + "noQuickActions": "Δεν υπάρχουν ακόμη γρήγορες ενέργειες.", + "buttonLabel": "Ετικέτα κουμπιού", + "selectSnippet": "Επιλογή αποσπάσματος...", + "addQuickAction": "Προσθήκη ενέργειας" + }, + "quickActions": { + "running": "Εκτέλεση {{name}}...", + "succeeded": "{{name}} ολοκληρώθηκε με επιτυχία", + "failed": "{{name}} απέτυχε", + "error": "Αποτυχία εκτέλεσης {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/es_ES.json b/plugins/snippets/locales/translated/es_ES.json new file mode 100644 index 000000000..c0b10bb16 --- /dev/null +++ b/plugins/snippets/locales/translated/es_ES.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Ejecutar \"{{name}}\"", + "variablesDialogDescription": "Rellena los valores que necesita este fragmento de código antes de ejecutarlo.", + "variablesPreviewLabel": "Avance", + "variablesConfirmButton": "Correr", + "keybindings": { + "runSnippet": "Ejecutar el fragmento existente", + "snippetLabel": "Retazo", + "selectSnippet": "Seleccione un fragmento", + "snippetMissing": "Fragmento no encontrado", + "snippetRequired": "Seleccione un fragmento" + }, + "paletteHintRun": "Ejecutar en la pestaña de terminal activa", + "host": { + "startupTitle": "Fragmento de inicio", + "quickActionsTitle": "Acciones rápidas", + "quickActionsDescription": "Las acciones rápidas aparecen como botones en la barra de herramientas de Métricas del host para la ejecución de comandos con un solo clic.", + "noQuickActions": "Aún no hay acciones rápidas.", + "buttonLabel": "Etiqueta del botón", + "selectSnippet": "Seleccionar fragmento...", + "addQuickAction": "Añadir acción" + }, + "quickActions": { + "running": "Ejecutando {{name}}...", + "succeeded": "{{name}} completado correctamente", + "failed": "{{name}} falló", + "error": "Error al ejecutar {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/fi_FI.json b/plugins/snippets/locales/translated/fi_FI.json new file mode 100644 index 000000000..52bf7e1d2 --- /dev/null +++ b/plugins/snippets/locales/translated/fi_FI.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Suorita \"{{name}}\"", + "variablesDialogDescription": "Täytä tämän koodinpätkän tarvitsemat arvot ennen sen suorittamista.", + "variablesPreviewLabel": "Esikatselu", + "variablesConfirmButton": "Juokse", + "keybindings": { + "runSnippet": "Suorita olemassa oleva koodinpätkä", + "snippetLabel": "Katkelma", + "selectSnippet": "Valitse katkelma", + "snippetMissing": "katkelmaa ei löytynyt", + "snippetRequired": "Valitse katkelma" + }, + "paletteHintRun": "Suorita aktiivisessa päätevälilehdessä", + "host": { + "startupTitle": "Käynnistyskoodinpätkä", + "quickActionsTitle": "Pikatoiminnot", + "quickActionsDescription": "Pikatoiminnot näkyvät painikkeina isäntämetriikkatyökalurivillä komentojen suorittamista varten yhdellä napsautuksella.", + "noQuickActions": "Ei vielä nopeita toimia.", + "buttonLabel": "Painikkeen nimi", + "selectSnippet": "Valitse katkelma...", + "addQuickAction": "Lisää toiminto" + }, + "quickActions": { + "running": "Suoritetaan {{name}}...", + "succeeded": "{{name}} suoritettu onnistuneesti", + "failed": "{{name}} epäonnistui", + "error": "Suoritus epäonnistui {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/fr_FR.json b/plugins/snippets/locales/translated/fr_FR.json new file mode 100644 index 000000000..c054a997d --- /dev/null +++ b/plugins/snippets/locales/translated/fr_FR.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Exécutez \"{{name}}\"", + "variablesDialogDescription": "Renseignez les valeurs nécessaires à cet extrait de code avant de l'exécuter.", + "variablesPreviewLabel": "Aperçu", + "variablesConfirmButton": "Courir", + "keybindings": { + "runSnippet": "Exécuter l'extrait de code existant", + "snippetLabel": "Fragment", + "selectSnippet": "Sélectionnez un extrait", + "snippetMissing": "extrait introuvable", + "snippetRequired": "Sélectionnez un extrait" + }, + "paletteHintRun": "Exécuter dans l'onglet du terminal actif", + "host": { + "startupTitle": "Extrait de démarrage", + "quickActionsTitle": "Actions rapides", + "quickActionsDescription": "Les actions rapides apparaissent sous forme de boutons dans la barre d'outils Métriques de l'hôte pour une exécution en un clic.", + "noQuickActions": "Aucune action rapide pour le moment.", + "buttonLabel": "Étiquette du bouton", + "selectSnippet": "Sélectionner un snippet...", + "addQuickAction": "Ajouter une action" + }, + "quickActions": { + "running": "Exécution de {{name}}...", + "succeeded": "{{name}} terminé avec succès", + "failed": "Échec de {{name}}", + "error": "Échec de l'exécution de {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/he_IL.json b/plugins/snippets/locales/translated/he_IL.json new file mode 100644 index 000000000..8da7ee869 --- /dev/null +++ b/plugins/snippets/locales/translated/he_IL.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "הפעל \"{{name}}\"", + "variablesDialogDescription": "מלא את הערכים הדרושים לקטע זה לפני הפעלתו.", + "variablesPreviewLabel": "תצוגה מקדימה", + "variablesConfirmButton": "לָרוּץ", + "keybindings": { + "runSnippet": "הפעלת קטע קוד קיים", + "snippetLabel": "קטע", + "selectSnippet": "בחר קטע", + "snippetMissing": "קטע לא נמצא", + "snippetRequired": "בחר קטע" + }, + "paletteHintRun": "הפעל בלשונית הטרמינל הפעילה", + "host": { + "startupTitle": "קטע הפעלה", + "quickActionsTitle": "פעולות מהירות", + "quickActionsDescription": "פעולות מהירות מופיעות ככפתורים בסרגל הכלים של Host Metrics לביצוע פקודה בלחיצה אחת.", + "noQuickActions": "אין עדיין פעולות מהירות.", + "buttonLabel": "תווית כפתור", + "selectSnippet": "בחר קטע...", + "addQuickAction": "הוסף פעולה" + }, + "quickActions": { + "running": "מבצע {{name}}...", + "succeeded": "{{name}} הושלם בהצלחה", + "failed": "{{name}} נכשל", + "error": "נכשל בביצוע {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/hi_IN.json b/plugins/snippets/locales/translated/hi_IN.json new file mode 100644 index 000000000..e7851af3e --- /dev/null +++ b/plugins/snippets/locales/translated/hi_IN.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "रन \"{{name}}\"", + "variablesDialogDescription": "इस कोड स्निपेट को चलाने से पहले इसमें आवश्यक मान भरें।", + "variablesPreviewLabel": "पूर्व दर्शन", + "variablesConfirmButton": "दौड़ना", + "keybindings": { + "runSnippet": "मौजूदा स्निपेट चलाएँ", + "snippetLabel": "टुकड़ा", + "selectSnippet": "एक अंश का चयन करें", + "snippetMissing": "अंश नहीं मिला", + "snippetRequired": "एक अंश का चयन करें" + }, + "paletteHintRun": "सक्रिय टर्मिनल टैब में चलाएँ", + "host": { + "startupTitle": "स्टार्टअप स्निपेट", + "quickActionsTitle": "त्वरित कार्रवाइयाँ", + "quickActionsDescription": "त्वरित कार्रवाइयाँ एक-क्लिक कमांड निष्पादन के लिए होस्ट मेट्रिक्स टूलबार में बटन के रूप में दिखाई देती हैं।", + "noQuickActions": "अभी तक कोई त्वरित कार्रवाई नहीं।", + "buttonLabel": "बटन लेबल", + "selectSnippet": "स्निपेट चुनें...", + "addQuickAction": "कार्रवाई जोड़ें" + }, + "quickActions": { + "running": "{{name}} निष्पादित हो रहा है...", + "succeeded": "{{name}} सफलतापूर्वक पूर्ण हुआ", + "failed": "{{name}} विफल रहा", + "error": "{{name}} निष्पादित करने में विफल" + } +} diff --git a/plugins/snippets/locales/translated/hu_HU.json b/plugins/snippets/locales/translated/hu_HU.json new file mode 100644 index 000000000..c6b107d7a --- /dev/null +++ b/plugins/snippets/locales/translated/hu_HU.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Futtassa a \"{{name}}\"", + "variablesDialogDescription": "Töltse ki a kódrészlet futtatása előtt szükséges értékeket.", + "variablesPreviewLabel": "Előnézet", + "variablesConfirmButton": "Fut", + "keybindings": { + "runSnippet": "Meglévő kódrészlet futtatása", + "snippetLabel": "Töredék", + "selectSnippet": "Válasszon ki egy részletet", + "snippetMissing": "részlet nem található", + "snippetRequired": "Válasszon ki egy részletet" + }, + "paletteHintRun": "Futtatás az aktív terminálfülön", + "host": { + "startupTitle": "Indítási kódrészlet", + "quickActionsTitle": "Gyors műveletek", + "quickActionsDescription": "A gyors műveletek gombokként jelennek meg a Host Metrics eszköztáron, így egyetlen kattintással végrehajthatók a parancsok.", + "noQuickActions": "Még nincsenek gyors intézkedések.", + "buttonLabel": "Gomb felirata", + "selectSnippet": "Válasszon részletet...", + "addQuickAction": "Művelet hozzáadása" + }, + "quickActions": { + "running": "{{name}} végrehajtása ...", + "succeeded": "{{name}} sikeresen befejeződött", + "failed": "{{name}} sikertelen", + "error": "Nem sikerült végrehajtani a {{name}} parancsot." + } +} diff --git a/plugins/snippets/locales/translated/id_ID.json b/plugins/snippets/locales/translated/id_ID.json new file mode 100644 index 000000000..4c6551675 --- /dev/null +++ b/plugins/snippets/locales/translated/id_ID.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Jalankan \"{{name}}\"", + "variablesDialogDescription": "Isi nilai-nilai yang dibutuhkan cuplikan kode ini sebelum menjalankannya.", + "variablesPreviewLabel": "Pratinjau", + "variablesConfirmButton": "Berlari", + "keybindings": { + "runSnippet": "Jalankan cuplikan kode yang sudah ada.", + "snippetLabel": "Potongan", + "selectSnippet": "Pilih cuplikan", + "snippetMissing": "cuplikan tidak ditemukan", + "snippetRequired": "Pilih cuplikan" + }, + "paletteHintRun": "Jalankan di tab terminal aktif", + "host": { + "startupTitle": "Cuplikan Startup", + "quickActionsTitle": "Tindakan Cepat", + "quickActionsDescription": "Tindakan cepat muncul sebagai tombol di bilah alat Metrik Host untuk eksekusi perintah sekali klik.", + "noQuickActions": "Belum ada tindakan cepat.", + "buttonLabel": "Label tombol", + "selectSnippet": "Pilih cuplikan...", + "addQuickAction": "Tambahkan Tindakan" + }, + "quickActions": { + "running": "Menjalankan {{name}}...", + "succeeded": "{{name}} berhasil diselesaikan", + "failed": "{{name}} gagal", + "error": "Gagal mengeksekusi {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/it_IT.json b/plugins/snippets/locales/translated/it_IT.json new file mode 100644 index 000000000..b600320d9 --- /dev/null +++ b/plugins/snippets/locales/translated/it_IT.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Esegui \"{{name}}\"", + "variablesDialogDescription": "Inserisci i valori richiesti da questo frammento di codice prima di eseguirlo.", + "variablesPreviewLabel": "Anteprima", + "variablesConfirmButton": "Correre", + "keybindings": { + "runSnippet": "Esegui il frammento di codice esistente", + "snippetLabel": "Frammento", + "selectSnippet": "Seleziona un frammento", + "snippetMissing": "frammento non trovato", + "snippetRequired": "Seleziona un frammento" + }, + "paletteHintRun": "Esegui nella scheda del terminale attiva", + "host": { + "startupTitle": "Frammento di avvio", + "quickActionsTitle": "Azioni Rapide", + "quickActionsDescription": "Le azioni rapide appaiono come pulsanti nella barra delle metriche host per l'esecuzione di comandi con un clic.", + "noQuickActions": "Nessuna azione rapida ancora.", + "buttonLabel": "Etichetta pulsante", + "selectSnippet": "Seleziona snippet...", + "addQuickAction": "Aggiungi Azione" + }, + "quickActions": { + "running": "Esecuzione di {{name}}...", + "succeeded": "{{name}} completato con successo", + "failed": "{{name}} non riuscito", + "error": "Impossibile eseguire {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/ja_JP.json b/plugins/snippets/locales/translated/ja_JP.json new file mode 100644 index 000000000..d715062b6 --- /dev/null +++ b/plugins/snippets/locales/translated/ja_JP.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "「{{name}} を実行」", + "variablesDialogDescription": "このコードスニペットを実行する前に、必要な値を入力してください。", + "variablesPreviewLabel": "プレビュー", + "variablesConfirmButton": "走る", + "keybindings": { + "runSnippet": "既存のスニペットを実行する", + "snippetLabel": "抜粋", + "selectSnippet": "抜粋を選択してください", + "snippetMissing": "スニペットが見つかりません", + "snippetRequired": "抜粋を選択してください" + }, + "paletteHintRun": "アクティブなターミナルタブで実行します", + "host": { + "startupTitle": "起動時スニペット", + "quickActionsTitle": "クイックアクション", + "quickActionsDescription": "クイックアクションはホストメトリクスツールバーにボタンとして表示され、ワンクリックでコマンドを実行できます。", + "noQuickActions": "クイックアクションはまだありません", + "buttonLabel": "ボタンラベル", + "selectSnippet": "スニペットを選択...", + "addQuickAction": "アクションを追加" + }, + "quickActions": { + "running": "{{name}} を実行中...", + "succeeded": "{{name}} が正常に完了しました", + "failed": "{{name}} が失敗しました", + "error": "{{name}} の実行に失敗しました" + } +} diff --git a/plugins/snippets/locales/translated/ko_KR.json b/plugins/snippets/locales/translated/ko_KR.json new file mode 100644 index 000000000..8c91a6722 --- /dev/null +++ b/plugins/snippets/locales/translated/ko_KR.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "\"{{name}} \" 실행", + "variablesDialogDescription": "이 코드 조각을 실행하기 전에 필요한 값을 입력하세요.", + "variablesPreviewLabel": "시사", + "variablesConfirmButton": "달리다", + "keybindings": { + "runSnippet": "기존 코드 조각을 실행하세요", + "snippetLabel": "단편", + "selectSnippet": "코드 조각을 선택하세요", + "snippetMissing": "스니펫을 찾을 수 없습니다.", + "snippetRequired": "코드 조각을 선택하세요" + }, + "paletteHintRun": "현재 활성화된 터미널 탭에서 실행하세요", + "host": { + "startupTitle": "시작 스니펫", + "quickActionsTitle": "빠른 작업", + "quickActionsDescription": "빠른 작업은 한 번의 클릭으로 명령을 실행할 수 있도록 호스트 메트릭 도구 모음에 버튼으로 표시됩니다.", + "noQuickActions": "아직 빠른 작업이 없습니다.", + "buttonLabel": "버튼 레이블", + "selectSnippet": "스니펫 선택...", + "addQuickAction": "작업 추가" + }, + "quickActions": { + "running": "{{name}} 실행 중...", + "succeeded": "{{name}}이(가) 성공적으로 완료되었습니다.", + "failed": "{{name}} 실패", + "error": "{{name}} 실행에 실패했습니다." + } +} diff --git a/plugins/snippets/locales/translated/nl_NL.json b/plugins/snippets/locales/translated/nl_NL.json new file mode 100644 index 000000000..148d3d51d --- /dev/null +++ b/plugins/snippets/locales/translated/nl_NL.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Uitvoeren \"{{name}}\"", + "variablesDialogDescription": "Vul de waarden in die dit codefragment nodig heeft voordat je het uitvoert.", + "variablesPreviewLabel": "Voorbeeld", + "variablesConfirmButton": "Loop", + "keybindings": { + "runSnippet": "Voer het bestaande codefragment uit.", + "snippetLabel": "Fragment", + "selectSnippet": "Selecteer een fragment", + "snippetMissing": "fragment niet gevonden", + "snippetRequired": "Selecteer een fragment" + }, + "paletteHintRun": "Voer dit uit in het actieve terminaltabblad.", + "host": { + "startupTitle": "Opstartfragment", + "quickActionsTitle": "Snelle acties", + "quickActionsDescription": "Snelle acties verschijnen als knoppen in de Host Metrics-werkbalk, zodat u opdrachten met één klik kunt uitvoeren.", + "noQuickActions": "Nog geen snelle acties ondernomen.", + "buttonLabel": "Knoplabel", + "selectSnippet": "Selecteer fragment...", + "addQuickAction": "Actie toevoegen" + }, + "quickActions": { + "running": "Het uitvoeren van {{name}}...", + "succeeded": "{{name}} succesvol voltooid", + "failed": "{{name}} mislukt", + "error": "Het uitvoeren van {{name}} is mislukt." + } +} diff --git a/plugins/snippets/locales/translated/no_NO.json b/plugins/snippets/locales/translated/no_NO.json new file mode 100644 index 000000000..9ea421c90 --- /dev/null +++ b/plugins/snippets/locales/translated/no_NO.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Kjør «{{name}}»", + "variablesDialogDescription": "Fyll inn verdiene dette kodestykket trenger før du kjører det.", + "variablesPreviewLabel": "Forhåndsvisning", + "variablesConfirmButton": "Løp", + "keybindings": { + "runSnippet": "Kjør eksisterende kodebit", + "snippetLabel": "Utdrag", + "selectSnippet": "Velg et utdrag", + "snippetMissing": "utdraget ikke funnet", + "snippetRequired": "Velg et utdrag" + }, + "paletteHintRun": "Kjør i den aktive terminalfanen", + "host": { + "startupTitle": "Oppstartsutdrag", + "quickActionsTitle": "Hurtighandlinger", + "quickActionsDescription": "Hurtighandlinger vises som knapper i verktøylinjen for vertsmålinger for utførelse av kommandoer med ett klikk.", + "noQuickActions": "Ingen hurtighandlinger ennå.", + "buttonLabel": "Knappetikett", + "selectSnippet": "Velg utdrag...", + "addQuickAction": "Legg til handling" + }, + "quickActions": { + "running": "Utfører {{name}}...", + "succeeded": "{{name}} fullført", + "failed": "{{name}} mislyktes", + "error": "Kunne ikke utføre {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/pl_PL.json b/plugins/snippets/locales/translated/pl_PL.json new file mode 100644 index 000000000..c9f02abf3 --- /dev/null +++ b/plugins/snippets/locales/translated/pl_PL.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Uruchom „{{name}}”", + "variablesDialogDescription": "Przed uruchomieniem snippetu wprowadź wymagane wartości.", + "variablesPreviewLabel": "Podgląd", + "variablesConfirmButton": "Uruchom", + "keybindings": { + "runSnippet": "Uruchom istniejący snippet", + "snippetLabel": "Snippet", + "selectSnippet": "Wybierz snippet", + "snippetMissing": "nie znaleziono snippetu", + "snippetRequired": "Wybierz snippet" + }, + "paletteHintRun": "Uruchom w aktywnej karcie terminala", + "host": { + "startupTitle": "Snippet startowy", + "quickActionsTitle": "Szybkie akcje", + "quickActionsDescription": "Szybkie akcje pojawiają się jako przyciski na pasku narzędzi Metryki hosta, umożliwiając wykonanie polecenia jednym kliknięciem.", + "noQuickActions": "Brak szybkich akcji.", + "buttonLabel": "Etykieta przycisku", + "selectSnippet": "Wybierz snippet...", + "addQuickAction": "Dodaj akcję" + }, + "quickActions": { + "running": "Wykonywanie {{name}}...", + "succeeded": "{{name}} ukończono pomyślnie", + "failed": "{{name}} nie powiodło się", + "error": "Nie udało się wykonać {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/pt_BR.json b/plugins/snippets/locales/translated/pt_BR.json new file mode 100644 index 000000000..183a6d3d3 --- /dev/null +++ b/plugins/snippets/locales/translated/pt_BR.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Executar \"{{name}}\"", + "variablesDialogDescription": "Preencha os valores necessários para este trecho de código antes de executá-lo.", + "variablesPreviewLabel": "Pré-visualização", + "variablesConfirmButton": "Correr", + "keybindings": { + "runSnippet": "Executar trecho de código existente", + "snippetLabel": "Trecho", + "selectSnippet": "Selecione um trecho", + "snippetMissing": "trecho não encontrado", + "snippetRequired": "Selecione um trecho" + }, + "paletteHintRun": "Execute na aba do terminal ativo", + "host": { + "startupTitle": "Trecho de startup", + "quickActionsTitle": "Ações rápidas", + "quickActionsDescription": "As ações rápidas aparecem como botões na barra de ferramentas do Host Metrics para execução de comandos com um único clique.", + "noQuickActions": "Nenhuma ação imediata ainda.", + "buttonLabel": "Rótulo do botão", + "selectSnippet": "Selecione o trecho...", + "addQuickAction": "Adicionar ação" + }, + "quickActions": { + "running": "Executando {{name}}...", + "succeeded": "{{name}} concluído com sucesso", + "failed": "{{name}} falhou", + "error": "Falha ao executar {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/pt_PT.json b/plugins/snippets/locales/translated/pt_PT.json new file mode 100644 index 000000000..9e10219cf --- /dev/null +++ b/plugins/snippets/locales/translated/pt_PT.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Executar \"{{name}}\"", + "variablesDialogDescription": "Preencha os valores necessários para este excerto de código antes de o executar.", + "variablesPreviewLabel": "Pré-visualização", + "variablesConfirmButton": "Correr", + "keybindings": { + "runSnippet": "Executar excerto de código existente", + "snippetLabel": "Trecho", + "selectSnippet": "Selecione um excerto", + "snippetMissing": "trecho não encontrado", + "snippetRequired": "Selecione um excerto" + }, + "paletteHintRun": "Execute no separador do terminal ativo", + "host": { + "startupTitle": "Fragmento de inicialização", + "quickActionsTitle": "Ações Rápidas", + "quickActionsDescription": "As ações rápidas aparecem como botões na barra de ferramentas de Métricas do Host para execução de comandos com um clique.", + "noQuickActions": "Ainda não existem ações rápidas.", + "buttonLabel": "Texto do botão", + "selectSnippet": "Selecionar snippet...", + "addQuickAction": "Adicionar Ação" + }, + "quickActions": { + "running": "A executar {{name}}...", + "succeeded": "{{name}} concluído com sucesso", + "failed": "{{name}} falhou", + "error": "Falha ao executar {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/ro_RO.json b/plugins/snippets/locales/translated/ro_RO.json new file mode 100644 index 000000000..10f3f92ad --- /dev/null +++ b/plugins/snippets/locales/translated/ro_RO.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Executați „{{name}}”", + "variablesDialogDescription": "Completați valorile necesare acestui fragment de cod înainte de a-l rula.", + "variablesPreviewLabel": "Previzualizare", + "variablesConfirmButton": "Aleargă", + "keybindings": { + "runSnippet": "Execută fragmentul existent", + "snippetLabel": "Fragment", + "selectSnippet": "Selectați un fragment", + "snippetMissing": "fragmentul nu a fost găsit", + "snippetRequired": "Selectați un fragment" + }, + "paletteHintRun": "Execută în fila activă a terminalului", + "host": { + "startupTitle": "Fragment de pornire", + "quickActionsTitle": "Acțiuni rapide", + "quickActionsDescription": "Acțiunile rapide apar sub formă de butoane în bara de instrumente Host Metrics pentru executarea comenzilor cu un singur clic.", + "noQuickActions": "Nicio acțiune rapidă încă.", + "buttonLabel": "Etichetă buton", + "selectSnippet": "Selectați fragmentul...", + "addQuickAction": "Adăugați o acțiune" + }, + "quickActions": { + "running": "Se execută {{name}}...", + "succeeded": "{{name}} finalizat cu succes", + "failed": "{{name}} a eșuat", + "error": "Executarea {{name}} a eșuat" + } +} diff --git a/plugins/snippets/locales/translated/ru_RU.json b/plugins/snippets/locales/translated/ru_RU.json new file mode 100644 index 000000000..605c77264 --- /dev/null +++ b/plugins/snippets/locales/translated/ru_RU.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Запуск \"{{name}}\"", + "variablesDialogDescription": "Перед запуском заполните необходимые для этого фрагмента значения.", + "variablesPreviewLabel": "Предварительный просмотр", + "variablesConfirmButton": "Бегать", + "keybindings": { + "runSnippet": "Запустить существующий фрагмент", + "snippetLabel": "Фрагмент", + "selectSnippet": "Выберите фрагмент", + "snippetMissing": "фрагмент не найден", + "snippetRequired": "Выберите фрагмент" + }, + "paletteHintRun": "Запустите в активной вкладке терминала.", + "host": { + "startupTitle": "Фрагмент запуска", + "quickActionsTitle": "Быстрые действия", + "quickActionsDescription": "Быстрые действия отображаются в виде кнопок на панели инструментов метрик хоста для выполнения команд в один клик.", + "noQuickActions": "Пока нет быстрых действий.", + "buttonLabel": "Надпись кнопки", + "selectSnippet": "Выбрать сниппет...", + "addQuickAction": "Добавить действие" + }, + "quickActions": { + "running": "Выполняется {{name}}...", + "succeeded": "{{name}} успешно завершено", + "failed": "{{name}} не удалось", + "error": "Не удалось выполнить {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/sr_SP.json b/plugins/snippets/locales/translated/sr_SP.json new file mode 100644 index 000000000..1c4608d18 --- /dev/null +++ b/plugins/snippets/locales/translated/sr_SP.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Покрени „{{name}}“", + "variablesDialogDescription": "Унесите вредности које су потребне овом фрагменту пре него што га покренете.", + "variablesPreviewLabel": "Преглед", + "variablesConfirmButton": "Трчи", + "keybindings": { + "runSnippet": "Покрени постојећи исечак кода", + "snippetLabel": "Исечак", + "selectSnippet": "Изаберите исечак", + "snippetMissing": "фрагмент није пронађен", + "snippetRequired": "Изаберите исечак" + }, + "paletteHintRun": "Покрени у активној картици терминала", + "host": { + "startupTitle": "Исечак за покретање", + "quickActionsTitle": "Брзе акције", + "quickActionsDescription": "Брзе акције се појављују као дугмад у траци са алаткама Host Metrics за извршавање команди једним кликом.", + "noQuickActions": "Још нема брзих акција.", + "buttonLabel": "Ознака дугмета", + "selectSnippet": "Изаберите исечак...", + "addQuickAction": "Додај акцију" + }, + "quickActions": { + "running": "Извршава се {{name}}...", + "succeeded": "{{name}} успешно завршено", + "failed": "{{name}} није успело", + "error": "Није успело извршавање {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/sv_SE.json b/plugins/snippets/locales/translated/sv_SE.json new file mode 100644 index 000000000..e3b9b0a3f --- /dev/null +++ b/plugins/snippets/locales/translated/sv_SE.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Kör \"{{name}}\"", + "variablesDialogDescription": "Fyll i de värden som detta kodavsnitt behöver innan du kör det.", + "variablesPreviewLabel": "Förhandsvisning", + "variablesConfirmButton": "Sikt", + "keybindings": { + "runSnippet": "Kör befintligt kodavsnitt", + "snippetLabel": "Utdrag", + "selectSnippet": "Välj ett utdrag", + "snippetMissing": "utdraget hittades inte", + "snippetRequired": "Välj ett utdrag" + }, + "paletteHintRun": "Kör i den aktiva terminalfliken", + "host": { + "startupTitle": "Startkodavsnitt", + "quickActionsTitle": "Snabbåtgärder", + "quickActionsDescription": "Snabbåtgärder visas som knappar i verktygsfältet Host Metrics för att köra kommandon med ett klick.", + "noQuickActions": "Inga snabba åtgärder ännu.", + "buttonLabel": "Knappetikett", + "selectSnippet": "Välj utdrag...", + "addQuickAction": "Lägg till åtgärd" + }, + "quickActions": { + "running": "Kör {{name}}...", + "succeeded": "{{name}} slutfört", + "failed": "{{name}} misslyckades", + "error": "Misslyckades med att köra {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/th_TH.json b/plugins/snippets/locales/translated/th_TH.json new file mode 100644 index 000000000..201f5469b --- /dev/null +++ b/plugins/snippets/locales/translated/th_TH.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "วิ่ง \"{{name}}\"", + "variablesDialogDescription": "กรอกค่าที่จำเป็นสำหรับโค้ดส่วนนี้ก่อนที่จะเรียกใช้งาน", + "variablesPreviewLabel": "ตัวอย่าง", + "variablesConfirmButton": "วิ่ง", + "keybindings": { + "runSnippet": "เรียกใช้โค้ดตัวอย่างที่มีอยู่", + "snippetLabel": "ส่วนหนึ่ง", + "selectSnippet": "เลือกส่วนย่อย", + "snippetMissing": "ไม่พบส่วนย่อย", + "snippetRequired": "เลือกส่วนย่อย" + }, + "paletteHintRun": "เรียกใช้ในแท็บเทอร์มินัลที่ใช้งานอยู่", + "host": { + "startupTitle": "ตัวอย่างโค้ดสำหรับเริ่มต้น", + "quickActionsTitle": "การดำเนินการด่วน", + "quickActionsDescription": "การดำเนินการด่วนจะปรากฏเป็นปุ่มในแถบเครื่องมือ Host Metrics เพื่อให้สามารถเรียกใช้คำสั่งได้ด้วยการคลิกเพียงครั้งเดียว", + "noQuickActions": "ยังไม่มีการดำเนินการใดๆ อย่างเร่งด่วนในขณะนี้", + "buttonLabel": "ป้ายกำกับปุ่ม", + "selectSnippet": "เลือกส่วนย่อย...", + "addQuickAction": "เพิ่มการดำเนินการ" + }, + "quickActions": { + "running": "กำลังดำเนินการ {{name}}...", + "succeeded": "{{name}} เสร็จสมบูรณ์เรียบร้อยแล้ว", + "failed": "{{name}} ล้มเหลว", + "error": "ไม่สามารถดำเนินการ {{name}} ได้" + } +} diff --git a/plugins/snippets/locales/translated/tr_TR.json b/plugins/snippets/locales/translated/tr_TR.json new file mode 100644 index 000000000..0dbae4dc2 --- /dev/null +++ b/plugins/snippets/locales/translated/tr_TR.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Çalıştır \"{{name}}\"", + "variablesDialogDescription": "Bu kod parçasını çalıştırmadan önce gerekli değerleri girin.", + "variablesPreviewLabel": "Önizleme", + "variablesConfirmButton": "Koşmak", + "keybindings": { + "runSnippet": "Mevcut kod parçasını çalıştır", + "snippetLabel": "Alıntı", + "selectSnippet": "Bir parça seçin", + "snippetMissing": "Kod parçası bulunamadı", + "snippetRequired": "Bir parça seçin" + }, + "paletteHintRun": "Etkin terminal sekmesinde çalıştırın.", + "host": { + "startupTitle": "Başlangıç Betiği", + "quickActionsTitle": "Hızlı Eylemler", + "quickActionsDescription": "Hızlı eylemler, tek tıklamayla komut çalıştırmak için Ana Bilgisayar Metrikleri araç çubuğunda düğme olarak görünür.", + "noQuickActions": "Henüz hızlı eylem yok.", + "buttonLabel": "Düğme etiketi", + "selectSnippet": "Parçacık seç...", + "addQuickAction": "Eylem Ekle" + }, + "quickActions": { + "running": "{{name}} çalıştırılıyor...", + "succeeded": "{{name}} başarıyla tamamlandı", + "failed": "{{name}} başarısız oldu", + "error": "{{name}} çalıştırılamadı" + } +} diff --git a/plugins/snippets/locales/translated/uk_UA.json b/plugins/snippets/locales/translated/uk_UA.json new file mode 100644 index 000000000..bb9429c4b --- /dev/null +++ b/plugins/snippets/locales/translated/uk_UA.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Виконати \"{{name}}\"", + "variablesDialogDescription": "Заповніть значення, необхідні цьому фрагменту, перш ніж запускати його.", + "variablesPreviewLabel": "Попередній перегляд", + "variablesConfirmButton": "Бігти", + "keybindings": { + "runSnippet": "Запустити існуючий фрагмент коду", + "snippetLabel": "Фрагмент", + "selectSnippet": "Виберіть фрагмент", + "snippetMissing": "фрагмент не знайдено", + "snippetRequired": "Виберіть фрагмент" + }, + "paletteHintRun": "Запустити в активній вкладці терміналу", + "host": { + "startupTitle": "Фрагмент запуску", + "quickActionsTitle": "Швидкі дії", + "quickActionsDescription": "Швидкі дії відображаються як кнопки на панелі інструментів «Метрики хоста» для виконання команд одним клацанням миші.", + "noQuickActions": "Поки що жодних швидких дій.", + "buttonLabel": "Мітка кнопки", + "selectSnippet": "Вибрати фрагмент...", + "addQuickAction": "Додати дію" + }, + "quickActions": { + "running": "Виконання {{name}}...", + "succeeded": "{{name}} успішно завершено", + "failed": "{{name}} не вдалося", + "error": "Не вдалося виконати {{name}}" + } +} diff --git a/plugins/snippets/locales/translated/vi_VN.json b/plugins/snippets/locales/translated/vi_VN.json new file mode 100644 index 000000000..7bc5af0fa --- /dev/null +++ b/plugins/snippets/locales/translated/vi_VN.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "Chạy \"{{name}}\"", + "variablesDialogDescription": "Hãy điền đầy đủ các giá trị cần thiết cho đoạn mã này trước khi chạy.", + "variablesPreviewLabel": "Xem trước", + "variablesConfirmButton": "Chạy", + "keybindings": { + "runSnippet": "Chạy đoạn mã hiện có", + "snippetLabel": "Đoạn trích", + "selectSnippet": "Chọn một đoạn trích", + "snippetMissing": "Không tìm thấy đoạn mã.", + "snippetRequired": "Chọn một đoạn trích" + }, + "paletteHintRun": "Chạy trong tab cửa sổ terminal đang hoạt động", + "host": { + "startupTitle": "Đoạn mã khởi động", + "quickActionsTitle": "Hành động nhanh", + "quickActionsDescription": "Hành động nhanh xuất hiện dưới dạng nút trên thanh công cụ Chỉ số máy để thực thi lệnh một lần nhấp.", + "noQuickActions": "Chưa có hành động nhanh nào.", + "buttonLabel": "Nhãn nút", + "selectSnippet": "Chọn đoạn mã...", + "addQuickAction": "Thêm hành động" + }, + "quickActions": { + "running": "Đang thực thi {{name}}...", + "succeeded": "{{name}} đã hoàn tất thành công", + "failed": "{{name}} thất bại", + "error": "Thực thi {{name}} thất bại" + } +} diff --git a/plugins/snippets/locales/translated/zh_CN.json b/plugins/snippets/locales/translated/zh_CN.json new file mode 100644 index 000000000..4391aa8f8 --- /dev/null +++ b/plugins/snippets/locales/translated/zh_CN.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "运行“{{name}}”", + "variablesDialogDescription": "运行此代码片段之前,请填写所需的值。", + "variablesPreviewLabel": "预览", + "variablesConfirmButton": "运行", + "keybindings": { + "runSnippet": "运行现有代码片段", + "snippetLabel": "片段", + "selectSnippet": "选择代码片段", + "snippetMissing": "未找到代码片段", + "snippetRequired": "选择代码片段" + }, + "paletteHintRun": "在活动终端标签页中运行", + "host": { + "startupTitle": "启动代码片段", + "quickActionsTitle": "快速操作", + "quickActionsDescription": "快速操作以按钮形式显示在主机指标工具栏中,以便一键执行命令。", + "noQuickActions": "尚无快速操作。", + "buttonLabel": "按钮标签", + "selectSnippet": "选择代码片段...", + "addQuickAction": "添加操作" + }, + "quickActions": { + "running": "正在执行 {{name}}...", + "succeeded": "{{name}} 执行成功", + "failed": "{{name}} 执行失败", + "error": "执行 {{name}} 失败" + } +} diff --git a/plugins/snippets/locales/translated/zh_TW.json b/plugins/snippets/locales/translated/zh_TW.json new file mode 100644 index 000000000..cec161a77 --- /dev/null +++ b/plugins/snippets/locales/translated/zh_TW.json @@ -0,0 +1,29 @@ +{ + "variablesDialogTitle": "運行“{{name}}", + "variablesDialogDescription": "在運行此程式碼片段之前,請填寫所需的值。", + "variablesPreviewLabel": "預覽", + "variablesConfirmButton": "跑步", + "keybindings": { + "runSnippet": "運行現有程式碼片段", + "snippetLabel": "片段", + "selectSnippet": "選擇一段文字", + "snippetMissing": "未找到程式碼片段", + "snippetRequired": "選擇一段文字" + }, + "paletteHintRun": "在活動終端選項卡中運行", + "host": { + "startupTitle": "啟動程式碼片段", + "quickActionsTitle": "快速動作", + "quickActionsDescription": "快速動作會以按鈕形式顯示在主機計量工具列上,可一鍵執行指令。", + "noQuickActions": "尚無快速動作。", + "buttonLabel": "按鈕標籤", + "selectSnippet": "選取程式碼片段...", + "addQuickAction": "新增動作" + }, + "quickActions": { + "running": "正在執行 {{name}}...", + "succeeded": "{{name}} 已成功完成", + "failed": "{{name}} 失敗", + "error": "無法執行 {{name}}" + } +} diff --git a/plugins/snippets/manifest.json b/plugins/snippets/manifest.json index 6ef88d7fb..17a0e2a05 100644 --- a/plugins/snippets/manifest.json +++ b/plugins/snippets/manifest.json @@ -95,8 +95,51 @@ "descriptionKey": "settings.confirmExecution.description", "default": false } - ] + ], + "host": { + "editorGroup": "ssh", + "editorOrder": 15, + "fields": [ + { + "key": "startupSnippetId", + "type": "number", + "labelKey": "host.startupTitle", + "descriptionKey": "host.startupDescription", + "hidden": true, + "min": 1, + "shareRead": "view" + }, + { + "key": "quickActions", + "type": "json", + "labelKey": "host.quickActionsTitle", + "descriptionKey": "host.quickActionsDescription", + "hidden": true, + "default": [], + "shareRead": "view" + } + ] + } }, - "syncEntities": ["snippetFolders", "snippets"] + "syncEntities": ["snippetFolders", "snippets"], + "keybindingActions": [ + { + "id": "runSnippet", + "params": { + "snippetId": { + "type": "string", + "pattern": "^[0-9]+$", + "maxLength": 20, + "syncEntity": "snippets" + }, + "appendEnter": { + "type": "boolean" + } + } + } + ], + "http": { + "adminImpersonation": true + } } } diff --git a/plugins/snippets/src/backend/execution.ts b/plugins/snippets/src/backend/execution.ts index 99da7c0a1..3fba5054a 100644 --- a/plugins/snippets/src/backend/execution.ts +++ b/plugins/snippets/src/backend/execution.ts @@ -4,58 +4,8 @@ export interface SnippetExecutionResult { error?: string; } -export interface SnippetHostVars { - ip?: string; - username?: string; - port?: number | string; - name?: string; -} - -const INPUT_PATTERN = - /\$\{INPUT_(\d+)(?::([^}$]+))?\}|\$INPUT_(\d+)(?![a-zA-Z0-9_])/g; - -function replaceVar(content: string, name: string, value?: string): string { - if (value === undefined) return content; - const pattern = new RegExp(`\\$\\{?${name}\\}?`, "g"); - return content.replace(pattern, value); -} - -/** - * Mirrors src/ui/lib/snippet-variables.ts resolveSnippetContent. Frontend and - * backend are separate builds, so this is kept as a small standalone copy - * rather than a shared package for one pure function. - */ -export function resolveSnippetCommand( - content: string, - host: SnippetHostVars | null, - inputValues: Record = {}, -): string { - let resolved = content; - - resolved = replaceVar(resolved, "HOST", host?.ip); - resolved = replaceVar(resolved, "USER", host?.username); - resolved = replaceVar( - resolved, - "PORT", - host?.port !== undefined ? String(host.port) : undefined, - ); - resolved = replaceVar(resolved, "NAME", host?.name); - - resolved = resolved.replace( - INPUT_PATTERN, - ( - fullMatch, - braceDigits: string | undefined, - _label, - plainDigits: string | undefined, - ) => { - const key = `INPUT_${braceDigits ?? plainDigits}`; - return key in inputValues ? inputValues[key] : fullMatch; - }, - ); - - return resolved; -} +export { resolveSnippetContent as resolveSnippetCommand } from "../shared/variables.js"; +export type { SnippetHostContext as SnippetHostVars } from "../shared/variables.js"; export function getSnippetExecutionTimeoutMs( value = process.env.SNIPPET_EXECUTION_TIMEOUT_SECONDS, diff --git a/plugins/snippets/src/backend/host-import.ts b/plugins/snippets/src/backend/host-import.ts new file mode 100644 index 000000000..9af3cd611 --- /dev/null +++ b/plugins/snippets/src/backend/host-import.ts @@ -0,0 +1,117 @@ +import { + PLUGIN_ID, + readQuickActions, + readStartupSnippetId, +} from "../shared/host-settings.js"; + +/** + * Registered as "snippets.hostImportNormalizer": a 2.8 or early 2.9 export + * carried quick actions as a host field and the startup snippet inside + * terminalConfig. An export that already carries this plugin's + * pluginSettings has had them written by core. + */ +export function hostImportNormalizer( + raw: Record, +): Record | null { + const carried = (raw.pluginSettings as Record | undefined)?.[ + PLUGIN_ID + ]; + if ( + carried && + typeof carried === "object" && + Object.keys(carried).length > 0 + ) { + return null; + } + + const values: Record = {}; + const quickActions = readQuickActions(raw.quickActions); + if (quickActions.length > 0) values.quickActions = quickActions; + + let terminalConfig = raw.terminalConfig; + if (typeof terminalConfig === "string") { + try { + terminalConfig = JSON.parse(terminalConfig); + } catch { + terminalConfig = null; + } + } + const startup = readStartupSnippetId( + (terminalConfig as { startupSnippetId?: unknown } | null)?.startupSnippetId, + ); + if (startup !== null) values.startupSnippetId = startup; + + return Object.keys(values).length > 0 ? values : null; +} + +/** + * Registered as "snippets.hostPayloadLegacy": quickActions and + * terminalConfig.startupSnippetId in their 2.8 shape, for clients that + * still read them (Termix-Mobile). Remove once the mobile app reads + * pluginSettings.snippets. + */ +export function hostPayloadLegacy( + values: Record, +): Record { + const out: Record = { + quickActions: readQuickActions(values.quickActions), + }; + const startup = readStartupSnippetId(values.startupSnippetId); + if (startup !== null) out.terminalConfig = { startupSnippetId: startup }; + return out; +} + +/** What hostSettingsSync needs from the repository. */ +export interface SnippetSyncLookup { + findSyncIdById: (id: number) => Promise; + findIdBySyncId: (syncId: string) => Promise; +} + +/** + * Registered as "snippets.hostSettingsSync": both host settings hold local + * snippet ids, so remote sync carries the snippets' sync ids instead, which + * are the same on both sides. + */ +export function createHostSettingsSync(lookup: SnippetSyncLookup) { + const toSyncId = async (id: number) => lookup.findSyncIdById(id); + const toId = async (value: unknown) => + typeof value === "string" && value ? lookup.findIdBySyncId(value) : null; + + return { + exportValue: async (key: string, value: unknown) => { + if (key === "startupSnippetId") { + const id = readStartupSnippetId(value); + return id === null ? null : await toSyncId(id); + } + if (key === "quickActions") { + const actions = readQuickActions(value); + const out = []; + for (const action of actions) { + const syncId = await toSyncId(action.snippetId); + if (syncId) out.push({ name: action.name, snippetId: syncId }); + } + return out; + } + return value; + }, + importValue: async (key: string, value: unknown) => { + if (key === "startupSnippetId") return await toId(value); + if (key === "quickActions") { + if (!Array.isArray(value)) return []; + const out = []; + for (const entry of value) { + const row = (entry ?? {}) as { name?: unknown; snippetId?: unknown }; + const id = await toId(row.snippetId); + if (id !== null) { + out.push({ + name: typeof row.name === "string" ? row.name : "", + snippetId: id, + }); + } + } + return out; + } + return value; + }, + }; +} diff --git a/plugins/snippets/src/backend/index.ts b/plugins/snippets/src/backend/index.ts index 6402a05e0..ec66438ea 100644 --- a/plugins/snippets/src/backend/index.ts +++ b/plugins/snippets/src/backend/index.ts @@ -3,7 +3,15 @@ import type { PluginContext } from "@termix/plugin-sdk/backend"; import { snippetAccess, snippetFolders, snippets } from "./tables.js"; import { createSnippetRepository } from "./repository.js"; import { registerSnippetRoutes } from "./routes.js"; -import { resolveSnippetCommand } from "./execution.js"; +import { + extractSnippetInputs, + resolveSnippetContent, +} from "../shared/variables.js"; +import { + createHostSettingsSync, + hostImportNormalizer, + hostPayloadLegacy, +} from "./host-import.js"; /** What other plugins get from ctx.services.get("snippets.access", {userId}). */ export interface SnippetsService { @@ -74,8 +82,24 @@ export async function activate(ctx: PluginContext) { type: "snippets", table: snippetsTable, order: 60, - // Host quick actions and keybindings in core point at snippets by this. - answersTo: ["commandSnippet"], + }); + + // Host settings that name a snippet by its local id, and the 2.8 host + // fields they came from. Keybindings name one through the runSnippet + // declaration in the manifest, which core translates over sync. + ctx.registry.provide("snippets.hostImportNormalizer", hostImportNormalizer); + ctx.registry.provide("snippets.hostPayloadLegacy", hostPayloadLegacy); + ctx.registry.provide( + "snippets.hostSettingsSync", + createHostSettingsSync(repo), + ); + + // $HOST and $INPUT_n handling for commands other plugins run (fleets). A + // pure function, so it goes through the registry rather than a service + // gated on snippets.view. + ctx.registry.provide("snippets.variables", { + resolve: resolveSnippetContent, + extractInputs: extractSnippetInputs, }); // The user row survives a password-reset data wipe, so it never triggers @@ -133,7 +157,7 @@ export async function activate(ctx: PluginContext) { if (!userId) return null; const snippet = await findAccessible(userId, id); if (!snippet) return null; - return resolveSnippetCommand(snippet.content, vars, inputValues); + return resolveSnippetContent(snippet.content, vars, inputValues); }, create: async (input) => { const userId = ctx.currentActor(); diff --git a/plugins/snippets/src/backend/repository.ts b/plugins/snippets/src/backend/repository.ts index 716b5a4ed..290bf5e1b 100644 --- a/plugins/snippets/src/backend/repository.ts +++ b/plugins/snippets/src/backend/repository.ts @@ -882,6 +882,27 @@ export function createSnippetRepository( return rows[0] ?? null; }, + /** A snippet's sync id, for a host setting that names it over sync. */ + async findSyncIdById(snippetId: number): Promise { + const drizzle = await client(); + const rows = await drizzle + .select({ syncId: snippets.syncId }) + .from(snippets) + .where(eq(snippets.id, snippetId)) + .limit(1); + return (rows[0]?.syncId as string | null | undefined) ?? null; + }, + + async findIdBySyncId(syncId: string): Promise { + const drizzle = await client(); + const rows = await drizzle + .select({ id: snippets.id }) + .from(snippets) + .where(eq(snippets.syncId, syncId)) + .limit(1); + return (rows[0]?.id as number | undefined) ?? null; + }, + /** A user's role ids, for the sharing filters above. */ async listUserRoleIds(userId: string): Promise { const drizzle = await client(); diff --git a/plugins/snippets/src/frontend/HostSnippetsSection.tsx b/plugins/snippets/src/frontend/HostSnippetsSection.tsx new file mode 100644 index 000000000..d609fdd09 --- /dev/null +++ b/plugins/snippets/src/frontend/HostSnippetsSection.tsx @@ -0,0 +1,171 @@ +import { Plus, Play, Trash2, Zap } from "lucide-react"; +import { + useTranslation, + type HostEditorSectionProps, +} from "@termix/plugin-sdk/frontend"; +import { Button, Input, SectionCard, Select2 } from "@termix/plugin-sdk/ui"; +import { PLUGIN_ID, readStartupSnippetId } from "../shared/host-settings.js"; +import { useSnippetOptions } from "./use-snippet-options"; + +type PluginSettingsBag = Record>; + +/** A quick action row while it is being edited: the snippet may be unset. */ +interface QuickActionRow { + name: string; + snippetId: number | null; +} + +function readRows(value: unknown): QuickActionRow[] { + if (!Array.isArray(value)) return []; + return value.map((entry) => { + const row = (entry ?? {}) as { name?: unknown; snippetId?: unknown }; + const id = Number(row.snippetId); + return { + name: typeof row.name === "string" ? row.name : "", + snippetId: Number.isInteger(id) && id > 0 ? id : null, + }; + }); +} + +/** + * The host editor's Snippets tab: the snippet a terminal runs when it + * connects, and the quick action buttons Host Metrics shows for the host. + * Both are this plugin's host settings, saved with the host. + */ +export function HostSnippetsSection({ + form, + updateForm, + adminTargetUserId, +}: HostEditorSectionProps) { + const { t } = useTranslation(); + const { options } = useSnippetOptions(adminTargetUserId); + const own = + (form.pluginSettings as PluginSettingsBag | undefined)?.[PLUGIN_ID] ?? {}; + const startupSnippetId = readStartupSnippetId(own.startupSnippetId); + const quickActions = readRows(own.quickActions); + + const write = (values: Record) => + updateForm((current) => { + const all = + (current.pluginSettings as PluginSettingsBag | undefined) ?? {}; + return { + ...current, + pluginSettings: { + ...all, + [PLUGIN_ID]: { ...(all[PLUGIN_ID] ?? {}), ...values }, + }, + }; + }); + + const setQuickActions = (rows: QuickActionRow[]) => + write({ quickActions: rows }); + + return ( + <> + } + > +
+

+ {t("host.startupDescription")} +

+ + write({ + startupSnippetId: e.target.value + ? Number(e.target.value) + : null, + }) + } + className="flex h-9 w-full border border-border bg-background px-3 py-1 text-xs outline-none focus:ring-1 focus:ring-ring" + > + + {options.map((snippet) => ( + + ))} + +
+
+ } + action={ + + } + > +
+

+ {t("host.quickActionsDescription")} +

+ {quickActions.length === 0 && ( +
+ + {t("host.noQuickActions")} +
+ )} + {quickActions.map((row, index) => ( +
+ { + const next = [...quickActions]; + next[index] = { ...row, name: e.target.value }; + setQuickActions(next); + }} + /> + { + const next = [...quickActions]; + next[index] = { + ...row, + snippetId: e.target.value ? Number(e.target.value) : null, + }; + setQuickActions(next); + }} + > + + {options.map((snippet) => ( + + ))} + + +
+ ))} +
+
+ + ); +} diff --git a/plugins/snippets/src/frontend/QuickActionButtons.tsx b/plugins/snippets/src/frontend/QuickActionButtons.tsx new file mode 100644 index 000000000..f0bfaaffb --- /dev/null +++ b/plugins/snippets/src/frontend/QuickActionButtons.tsx @@ -0,0 +1,147 @@ +import { useMemo, useState } from "react"; +import { RefreshCw } from "lucide-react"; +import { toast } from "sonner"; +import { + usePermission, + usePluginApi, + useTranslation, +} from "@termix/plugin-sdk/frontend"; +import { Button, Separator } from "@termix/plugin-sdk/ui"; +import { + readQuickActions, + snippetHostSettings, + type QuickAction, +} from "../shared/host-settings.js"; +import { hasSnippetInputs } from "../shared/variables.js"; +import { askForInputs, shouldConfirmExecution } from "./prompt-store"; +import { createSnippetsApi } from "./snippets-api"; +import { errorMessage } from "./types"; + +interface ToolbarHost { + id?: number | string; + name?: string; + ip?: string; + username?: string; + port?: number; + pluginSettings?: Record>; +} + +/** + * The host's quick actions as buttons in the Host Metrics toolbar (the + * host-metrics.toolbar slot). Each runs its snippet on the host over a fresh + * SSH connection and reports the result in a toast. + */ +export function QuickActionButtons(props: Record) { + const host = props.host as ToolbarHost | undefined; + const { t } = useTranslation(); + const api = usePluginApi(); + const client = useMemo(() => createSnippetsApi(api), [api]); + const canView = usePermission("view"); + const [running, setRunning] = useState>(new Set()); + const actions = readQuickActions(snippetHostSettings(host).quickActions); + const hostId = Number(host?.id); + + if (!canView || actions.length === 0 || !Number.isInteger(hostId)) { + return null; + } + + const setBusy = (snippetId: number, busy: boolean) => + setRunning((prev) => { + const next = new Set(prev); + if (busy) next.add(snippetId); + else next.delete(snippetId); + return next; + }); + + async function execute( + action: QuickAction, + inputValues: Record, + ) { + const toastId = `quick-action-${hostId}-${action.snippetId}`; + setBusy(action.snippetId, true); + toast.loading(t("quickActions.running", { name: action.name }), { + id: toastId, + }); + try { + const result = await client.execute( + action.snippetId, + hostId, + Object.keys(inputValues).length > 0 ? inputValues : undefined, + ); + if (result.success) { + toast.success(t("quickActions.succeeded", { name: action.name }), { + id: toastId, + description: result.output?.substring(0, 200), + duration: 5000, + }); + } else { + toast.error(t("quickActions.failed", { name: action.name }), { + id: toastId, + description: result.error || result.output, + duration: 5000, + }); + } + } catch (error) { + toast.error(t("quickActions.error", { name: action.name }), { + id: toastId, + description: errorMessage(error, t("executionFailed")), + duration: 5000, + }); + } finally { + setBusy(action.snippetId, false); + } + } + + async function run(action: QuickAction) { + let inputValues: Record = {}; + const snippet = await client.get(action.snippetId).catch(() => null); + if (snippet && hasSnippetInputs(snippet.content)) { + const values = await askForInputs( + { name: action.name || snippet.name, content: snippet.content }, + host + ? { + ip: host.ip, + username: host.username, + port: host.port, + name: host.name, + } + : null, + ); + if (!values) return; + inputValues = values; + } + const go = () => void execute(action, inputValues); + if (!shouldConfirmExecution()) { + go(); + return; + } + toast(t("confirmRunMessage", { name: action.name }), { + action: { label: t("confirmRunButton"), onClick: go }, + duration: 6000, + }); + } + + return ( + <> +
+ {actions.map((action, index) => { + const busy = running.has(action.snippetId); + return ( + + ); + })} +
+ + + ); +} diff --git a/plugins/snippets/src/frontend/SnippetOverlay.tsx b/plugins/snippets/src/frontend/SnippetOverlay.tsx new file mode 100644 index 000000000..0a776a69a --- /dev/null +++ b/plugins/snippets/src/frontend/SnippetOverlay.tsx @@ -0,0 +1,32 @@ +import { useEffect } from "react"; +import { useSettings } from "@termix/plugin-sdk/frontend"; +import { SnippetVariablesDialog } from "./SnippetVariablesDialog"; +import { readSnippetSettings } from "./settings"; +import { setConfirmExecution, usePendingPrompt } from "./prompt-store"; + +/** + * Always mounted at the shell's root (the shell.overlay slot), so a snippet + * started from the command palette, a keybinding or a host button can ask + * for its inputs after whatever started it has closed. + */ +export function SnippetOverlay() { + const settings = useSettings("user"); + const prompt = usePendingPrompt(); + const confirmExecution = readSnippetSettings( + settings.values, + ).confirmExecution; + + useEffect(() => { + setConfirmExecution(confirmExecution); + }, [confirmExecution]); + + if (!prompt) return null; + return ( + prompt.settle(null)} + onConfirm={(_resolved, values) => prompt.settle(values)} + /> + ); +} diff --git a/src/ui/components/SnippetVariablesDialog.tsx b/plugins/snippets/src/frontend/SnippetVariablesDialog.tsx similarity index 79% rename from src/ui/components/SnippetVariablesDialog.tsx rename to plugins/snippets/src/frontend/SnippetVariablesDialog.tsx index 3d1938765..7d9a0fec4 100644 --- a/src/ui/components/SnippetVariablesDialog.tsx +++ b/plugins/snippets/src/frontend/SnippetVariablesDialog.tsx @@ -1,25 +1,30 @@ import { useState, useEffect, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { Button } from "@/components/button"; -import { Input } from "@/components/input"; +import { useTranslation } from "@termix/plugin-sdk/frontend"; import { + Button, + Input, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, -} from "@/components/dialog"; +} from "@termix/plugin-sdk/ui"; import { extractSnippetInputs, resolveSnippetContent, type SnippetHostContext, -} from "@/lib/snippet-variables"; -import type { Snippet } from "@/types/ui-types"; +} from "../shared/variables.js"; + +/** Enough of a snippet to fill in its inputs. */ +export interface VariablesDialogSnippet { + name: string; + content: string; +} /** - * Shown before running a snippet that contains $INPUT_n placeholders -- + * Shown before running a snippet that contains $INPUT_n placeholders: * collects a value per placeholder and previews the fully resolved command - * (host vars + inputs) before handing the result back to the caller. + * (host vars and inputs) before handing the result back to the caller. */ export function SnippetVariablesDialog({ snippet, @@ -27,7 +32,7 @@ export function SnippetVariablesDialog({ onCancel, onConfirm, }: { - snippet: Snippet; + snippet: VariablesDialogSnippet; host: SnippetHostContext | null; onCancel: () => void; onConfirm: ( @@ -53,12 +58,10 @@ export function SnippetVariablesDialog({ - {t("newUi.sidebar.snippets.variablesDialogTitle", { - name: snippet.name, - })} + {t("variablesDialogTitle", { name: snippet.name })} - {t("newUi.sidebar.snippets.variablesDialogDescription")} + {t("variablesDialogDescription")}
@@ -79,7 +82,7 @@ export function SnippetVariablesDialog({ ))}
{preview} @@ -88,14 +91,14 @@ export function SnippetVariablesDialog({
diff --git a/plugins/snippets/src/frontend/index.tsx b/plugins/snippets/src/frontend/index.tsx index 210d54ae3..c12f06284 100644 --- a/plugins/snippets/src/frontend/index.tsx +++ b/plugins/snippets/src/frontend/index.tsx @@ -1,15 +1,53 @@ -import { Play } from "lucide-react"; -import type { PanelProps, TermixApp } from "@termix/plugin-sdk/frontend"; +import { Clipboard, Play } from "lucide-react"; +import type { + PaletteItem, + PanelProps, + PluginHostRecord, + TermixApp, +} from "@termix/plugin-sdk/frontend"; +import type { Snippet } from "./types"; import { SnippetsPanel } from "./SnippetsPanel"; import { createSnippetsApi } from "./snippets-api"; -import { resolveSnippetContent, hasSnippetInputs } from "./snippet-variables"; import { - AdminUserSnippets, - adminOptions, - mapSnippets, -} from "./AdminUserSnippets"; + extractSnippetInputs, + resolveSnippetContent, + type SnippetHostContext, +} from "../shared/variables.js"; +import { + readStartupSnippetId, + snippetHostSettings, +} from "../shared/host-settings.js"; +import { AdminUserSnippets } from "./AdminUserSnippets"; +import { SnippetOverlay } from "./SnippetOverlay"; +import { HostSnippetsSection } from "./HostSnippetsSection"; +import { QuickActionButtons } from "./QuickActionButtons"; +import { resetPrompts } from "./prompt-store"; +import { fetchSnippet, runInSession } from "./run-flows"; +import { + RUN_SNIPPET_ACTION, + RunSnippetEditor, + RunSnippetSummary, + runSnippetBinding, + validateRunSnippet, +} from "./keybinding"; + +function hostContext(host: unknown): SnippetHostContext | null { + if (!host || typeof host !== "object") return null; + const record = host as Record; + return { + ip: typeof record.ip === "string" ? record.ip : undefined, + username: typeof record.username === "string" ? record.username : undefined, + port: + typeof record.port === "number" || typeof record.port === "string" + ? record.port + : undefined, + name: typeof record.name === "string" ? record.name : undefined, + }; +} export function activate(app: TermixApp): void { + app.onDispose(resetPrompts); + app.registerRailItem({ id: "snippets", icon: Play, @@ -33,6 +71,73 @@ export function activate(app: TermixApp): void { }, }); + // Every snippet in the command palette, run in the terminal the user is + // working in. + app.registerPaletteGroup({ + id: "snippets", + titleKey: "nav.snippets", + order: 10, + load: async (): Promise => { + if (!(await app.hasPermission("view"))) return []; + const snippets: Snippet[] = await createSnippetsApi(app.api) + .list() + .catch(() => []); + return snippets.map((snippet) => ({ + id: String(snippet.id), + title: snippet.name, + description: snippet.content, + icon: snippet.isNote ? Clipboard : Play, + keywords: [snippet.description ?? ""], + needsTarget: true, + hint: app.t(snippet.isNote ? "paletteHintPaste" : "paletteHintRun"), + run: ({ targetTab }) => { + if (!targetTab) return; + void runInSession( + app, + snippet, + { sessionId: targetTab.id }, + hostContext(targetTab.host), + { announce: true }, + ); + }, + })); + }, + }); + + // Keeps the input dialog and the confirm setting alive at the shell root. + app.registerSlotContribution("shell.overlay", { + actionId: "snippets.overlay", + titleKey: "nav.snippets", + kind: "component", + component: SnippetOverlay as never, + }); + + app.registerKeybindingAction({ + id: RUN_SNIPPET_ACTION, + titleKey: "keybindings.runSnippet", + editor: RunSnippetEditor, + summary: RunSnippetSummary, + validate: validateRunSnippet, + run: (action, context) => runSnippetBinding(app, action, context), + }); + + app.registerHostEditorSection({ + id: "snippets", + group: "ssh", + titleKey: "host.tabTitle", + icon: Play, + order: 15, + component: HostSnippetsSection, + }); + + // Host Metrics shows a host's quick actions as toolbar buttons. + app.registerSlotContribution("host-metrics.toolbar", { + actionId: "snippets.quickActions", + titleKey: "host.quickActionsTitle", + kind: "component", + component: QuickActionButtons, + }); + app.registerSlotContribution("onboarding.features", { actionId: "snippets.feature", titleKey: "onboarding.feature_snippets", @@ -49,71 +154,17 @@ export function activate(app: TermixApp): void { order: 10, }); - // Core's snippet pickers (the host editor's startup snippet, keybindings, - // the command palette) read the list through this. An admin editing - // another user's host passes that user's id. - app.registerAction("snippet.list", (async (options?: { - targetUserId?: string; - }) => { - const response = options?.targetUserId - ? await app.api.get("/", adminOptions(options.targetUserId)) - : await app.api.get("/"); - const raw = response.data as unknown; - const list = Array.isArray(raw) - ? raw - : ((raw as { snippets?: unknown[] })?.snippets ?? []); - return (list as Record[]).map((row) => ({ - ...mapSnippets([row])[0], - isNote: row.isNote === true, - })); - }) as never); - - // Resolves a snippet by id for a terminal (startup snippet, custom - // keybindings, the command palette), so core never imports snippet code. - const resolveForTerminal = async ( - snippetId: number, - host: { - ip?: string; - username?: string; - port?: number; - name?: string; - } | null, - inputValues?: Record, + // The command a terminal types when it connects to a host: the host's + // startup snippet with its variables filled in, or null. + app.registerAction("snippets.startupCommand", (async ( + host: PluginHostRecord | null, + vars?: SnippetHostContext, ) => { - const api = createSnippetsApi(app.api); - const snippet = await api.get(snippetId).catch(() => null); + const id = readStartupSnippetId(snippetHostSettings(host).startupSnippetId); + if (id === null) return null; + const snippet = await fetchSnippet(app, id); if (!snippet) return null; - if ( - hasSnippetInputs(snippet.content) && - (!inputValues || Object.keys(inputValues).length === 0) - ) { - return { - needsInputs: true, - content: snippet.content, - isNote: snippet.isNote, - }; - } - return { - needsInputs: false, - content: resolveSnippetContent(snippet.content, host, inputValues ?? {}), - isNote: snippet.isNote, - }; - }; - app.registerAction("snippet.resolveForTerminal", resolveForTerminal as never); - app.registerAction( - "snippets.resolveForTerminal", - resolveForTerminal as never, - ); - - // For other plugins' quick actions (host metrics): the snippet's content, - // so the caller can ask for $INPUT_n values, and a run on a host. - app.registerAction("snippets.get", (async (snippetId: number) => { - const snippet = await createSnippetsApi(app.api) - .get(snippetId) - .catch(() => null); - return snippet - ? { id: snippet.id, name: snippet.name, content: snippet.content } - : null; + return resolveSnippetContent(snippet.content, vars ?? hostContext(host)); }) as never); // For pickers in other plugins (the AI assistant's @-mentions): id and name. @@ -124,14 +175,7 @@ export function activate(app: TermixApp): void { return snippets.map((snippet) => ({ id: snippet.id, name: snippet.name })); }) as never); - app.registerAction("snippets.execute", (async ( - snippetId: number, - hostId: number, - inputValues?: Record, - ) => - createSnippetsApi(app.api).execute( - snippetId, - hostId, - inputValues, - )) as never); + // Variable handling for commands other plugins run (fleets' run box). + app.registerAction("snippets.extractInputs", ((content: string) => + extractSnippetInputs(typeof content === "string" ? content : "")) as never); } diff --git a/plugins/snippets/src/frontend/keybinding.tsx b/plugins/snippets/src/frontend/keybinding.tsx new file mode 100644 index 000000000..bde972c84 --- /dev/null +++ b/plugins/snippets/src/frontend/keybinding.tsx @@ -0,0 +1,89 @@ +import { + useTranslation, + type KeybindingAction, + type KeybindingActionEditorProps, + type KeybindingRunContext, + type TermixApp, +} from "@termix/plugin-sdk/frontend"; +import { fetchSnippet, runInSession } from "./run-flows"; +import { useSnippetOptions } from "./use-snippet-options"; + +/** Stored as action.type; kept from 2.8 so saved bindings keep working. */ +export const RUN_SNIPPET_ACTION = "runSnippet"; + +function snippetIdOf(action: KeybindingAction): string { + return typeof action.snippetId === "string" ? action.snippetId : ""; +} + +/** Picks the snippet a "Run snippet" binding runs. */ +export function RunSnippetEditor({ + action, + onChange, +}: KeybindingActionEditorProps) { + const { t } = useTranslation(); + const { options } = useSnippetOptions(); + return ( +
+ + +
+ ); +} + +/** The bound snippet's name in the binding list, or a warning once it is gone. */ +export function RunSnippetSummary({ action }: KeybindingActionEditorProps) { + const { t } = useTranslation(); + const { options, loaded } = useSnippetOptions(); + if (!loaded) return null; + const snippet = options.find((s) => String(s.id) === snippetIdOf(action)); + if (snippet) return ({snippet.name}); + return ( + + ({t("keybindings.snippetMissing")}) + + ); +} + +export function validateRunSnippet(action: KeybindingAction): string | null { + return /^\d+$/.test(snippetIdOf(action)) + ? null + : "keybindings.snippetRequired"; +} + +/** Runs a bound snippet in the terminal the key was pressed in. */ +export function runSnippetBinding( + app: Pick, + action: KeybindingAction, + context: KeybindingRunContext, +): void { + void fetchSnippet(app, Number(snippetIdOf(action))).then((snippet) => { + if (!snippet) return; + void runInSession( + app, + { ...snippet, isNote: false }, + { sessionId: context.sessionId, send: context.send }, + context.host ?? null, + { pressEnter: action.appendEnter !== false }, + ); + }); +} diff --git a/plugins/snippets/src/frontend/prompt-store.ts b/plugins/snippets/src/frontend/prompt-store.ts new file mode 100644 index 000000000..af4dfd33f --- /dev/null +++ b/plugins/snippets/src/frontend/prompt-store.ts @@ -0,0 +1,72 @@ +import { useSyncExternalStore } from "react"; +import type { SnippetHostContext } from "../shared/variables.js"; + +export interface PendingPrompt { + snippet: { name: string; content: string }; + host: SnippetHostContext | null; + settle: (values: Record | null) => void; +} + +let pending: PendingPrompt | null = null; +let confirmExecution = false; +const listeners = new Set<() => void>(); + +function notify(): void { + for (const listener of [...listeners]) listener(); +} + +function subscribe(listener: () => void): () => void { + listeners.add(listener); + return () => listeners.delete(listener); +} + +/** + * Asks for a snippet's $INPUT_n values through the dialog the overlay draws. + * Resolves null when the user cancels. A second request cancels the first. + */ +export function askForInputs( + snippet: { name: string; content: string }, + host: SnippetHostContext | null, +): Promise | null> { + pending?.settle(null); + return new Promise((resolve) => { + const prompt: PendingPrompt = { + snippet, + host, + settle: (values) => { + if (pending === prompt) { + pending = null; + notify(); + } + resolve(values); + }, + }; + pending = prompt; + notify(); + }); +} + +export function usePendingPrompt(): PendingPrompt | null { + return useSyncExternalStore( + subscribe, + () => pending, + () => pending, + ); +} + +/** Kept current by the overlay from the user's confirmExecution setting. */ +export function setConfirmExecution(value: boolean): void { + confirmExecution = value; +} + +export function shouldConfirmExecution(): boolean { + return confirmExecution; +} + +/** Cancels anything waiting, for deactivate. */ +export function resetPrompts(): void { + pending?.settle(null); + pending = null; + confirmExecution = false; + notify(); +} diff --git a/plugins/snippets/src/frontend/run-flows.ts b/plugins/snippets/src/frontend/run-flows.ts new file mode 100644 index 000000000..2de490468 --- /dev/null +++ b/plugins/snippets/src/frontend/run-flows.ts @@ -0,0 +1,121 @@ +import { toast } from "sonner"; +import { invokeAction, type TermixApp } from "@termix/plugin-sdk/frontend"; +import { + hasSnippetInputs, + resolveSnippetContent, + type SnippetHostContext, +} from "../shared/variables.js"; +import { askForInputs, shouldConfirmExecution } from "./prompt-store"; +import { createSnippetsApi } from "./snippets-api"; + +export interface RunnableSnippet { + id: number; + name: string; + content: string; + isNote?: boolean; +} + +/** Where a run sends its text: a session by id, or a raw writer. */ +export interface RunTarget { + sessionId?: string; + send?: (data: string) => void; +} + +/** + * The snippet with its variables filled in, asking for $INPUT_n values when + * it has any. Null when the user cancels. + */ +export async function resolveForRun( + snippet: { name: string; content: string }, + host: SnippetHostContext | null, +): Promise { + if (!hasSnippetInputs(snippet.content)) { + return resolveSnippetContent(snippet.content, host); + } + const values = await askForInputs(snippet, host); + if (!values) return null; + return resolveSnippetContent(snippet.content, host, values); +} + +/** Runs `execute` now, or after the user confirms when they asked to. */ +export function confirmThenRun( + app: Pick, + name: string, + execute: () => void, +): void { + if (!shouldConfirmExecution()) { + execute(); + return; + } + toast(app.t("confirmRunMessage", { name }), { + action: { label: app.t("confirmRunButton"), onClick: execute }, + duration: 6000, + }); +} + +/** + * Types a snippet into a terminal session: resolves its variables, asks for + * inputs and confirmation as the user's settings say, then sends it. A note + * is pasted without running. + */ +export async function runInSession( + app: Pick, + snippet: RunnableSnippet, + target: RunTarget, + host: SnippetHostContext | null, + options: { pressEnter?: boolean; announce?: boolean } = {}, +): Promise { + const content = await resolveForRun(snippet, host); + if (content === null) return; + const run = !snippet.isNote && options.pressEnter !== false; + const send = async () => { + let sent = true; + if (target.send) { + target.send(content + (run ? "\r" : "")); + } else if (target.sessionId) { + sent = + (await invokeAction( + "terminal.sendToSession", + target.sessionId, + content, + { + run, + }, + )) === true; + } else { + sent = false; + } + if (!options.announce) return; + if (!sent) { + toast.error(app.t("noTerminalTabsOpen")); + return; + } + toast.success( + app.t(snippet.isNote ? "pasteSuccess" : "runSuccess", { + name: snippet.name, + count: 1, + }), + ); + }; + if (snippet.isNote) void send(); + else confirmThenRun(app, snippet.name, () => void send()); +} + +/** One snippet by id, or null when it is gone or unreadable. */ +export async function fetchSnippet( + app: Pick, + id: number, +): Promise { + if (!Number.isInteger(id) || id <= 0) return null; + const snippet = await createSnippetsApi(app.api) + .get(id) + .catch(() => null); + return snippet + ? { + id: snippet.id, + name: snippet.name, + content: snippet.content, + isNote: snippet.isNote, + } + : null; +} diff --git a/plugins/snippets/src/frontend/use-snippet-options.ts b/plugins/snippets/src/frontend/use-snippet-options.ts new file mode 100644 index 000000000..bb0d226c9 --- /dev/null +++ b/plugins/snippets/src/frontend/use-snippet-options.ts @@ -0,0 +1,53 @@ +import { useEffect, useState } from "react"; +import { usePluginApi } from "@termix/plugin-sdk/frontend"; +import { adminOptions } from "./AdminUserSnippets"; + +export interface SnippetOption { + id: number; + name: string; +} + +/** + * The user's snippets as picker options: their own, or the target user's + * while an admin edits that user's host. `loaded` turns true once the list + * came back, so a picker can tell "no snippets" from "not loaded yet". + */ +export function useSnippetOptions(targetUserId?: string): { + options: SnippetOption[]; + loaded: boolean; +} { + const api = usePluginApi(); + const [state, setState] = useState<{ + options: SnippetOption[]; + loaded: boolean; + }>({ options: [], loaded: false }); + + useEffect(() => { + let cancelled = false; + const request = targetUserId + ? api.get("/", adminOptions(targetUserId)) + : api.get("/"); + request + .then((response) => { + const raw = response.data as unknown; + const list = Array.isArray(raw) + ? raw + : ((raw as { snippets?: unknown[] })?.snippets ?? []); + const options = (list as Array<{ id?: unknown; name?: unknown }>) + .filter((row) => typeof row.id === "number") + .map((row) => ({ + id: row.id as number, + name: typeof row.name === "string" ? row.name : `#${row.id}`, + })); + if (!cancelled) setState({ options, loaded: true }); + }) + .catch(() => { + if (!cancelled) setState({ options: [], loaded: true }); + }); + return () => { + cancelled = true; + }; + }, [api, targetUserId]); + + return state; +} diff --git a/plugins/snippets/src/frontend/use-snippet-runner.tsx b/plugins/snippets/src/frontend/use-snippet-runner.tsx index 00f89d784..2ddd82d7a 100644 --- a/plugins/snippets/src/frontend/use-snippet-runner.tsx +++ b/plugins/snippets/src/frontend/use-snippet-runner.tsx @@ -5,12 +5,12 @@ import { useTranslation, } from "@termix/plugin-sdk/frontend"; import { toast } from "sonner"; -import { SnippetVariablesDialog } from "@termix/plugin-sdk/ui"; import { hasSnippetInputs, resolveSnippetContent, type SnippetHostContext, -} from "./snippet-variables"; +} from "../shared/variables.js"; +import { SnippetVariablesDialog } from "./SnippetVariablesDialog"; import { createSnippetsApi } from "./snippets-api"; import { ExecutionResultsDialog, @@ -259,7 +259,7 @@ export function useSnippetRunner(confirmExecution = false) { const variablesDialog = runningSnippet ? ( setRunningSnippet(null)} onConfirm={runningSnippet.onConfirm} diff --git a/plugins/snippets/src/shared/host-settings.ts b/plugins/snippets/src/shared/host-settings.ts new file mode 100644 index 000000000..0a1a9ce31 --- /dev/null +++ b/plugins/snippets/src/shared/host-settings.ts @@ -0,0 +1,48 @@ +/** + * The snippets plugin's host settings: the startup snippet a terminal runs + * when it connects, and the quick action buttons Host Metrics shows. + */ + +export const PLUGIN_ID = "snippets"; + +export interface QuickAction { + name: string; + snippetId: number; +} + +function snippetId(value: unknown): number | null { + const id = typeof value === "string" ? Number(value) : value; + return typeof id === "number" && Number.isInteger(id) && id > 0 ? id : null; +} + +/** Quick actions in their stored shape, dropping any without a snippet. */ +export function readQuickActions(value: unknown): QuickAction[] { + let list = value; + if (typeof list === "string") { + try { + list = JSON.parse(list); + } catch { + return []; + } + } + if (!Array.isArray(list)) return []; + return list.flatMap((entry): QuickAction[] => { + if (!entry || typeof entry !== "object") return []; + const id = snippetId((entry as { snippetId?: unknown }).snippetId); + if (id === null) return []; + const name = (entry as { name?: unknown }).name; + return [{ name: typeof name === "string" ? name : "", snippetId: id }]; + }); +} + +export function readStartupSnippetId(value: unknown): number | null { + return snippetId(value); +} + +/** This plugin's slice of a host record's pluginSettings. */ +export function snippetHostSettings(host: unknown): Record { + const all = (host as { pluginSettings?: Record } | null) + ?.pluginSettings; + const own = all?.[PLUGIN_ID]; + return own && typeof own === "object" ? (own as Record) : {}; +} diff --git a/plugins/snippets/src/frontend/snippet-variables.ts b/plugins/snippets/src/shared/variables.ts similarity index 90% rename from plugins/snippets/src/frontend/snippet-variables.ts rename to plugins/snippets/src/shared/variables.ts index 9cf81db63..4f0a0869d 100644 --- a/plugins/snippets/src/frontend/snippet-variables.ts +++ b/plugins/snippets/src/shared/variables.ts @@ -1,3 +1,9 @@ +/** + * $HOST, $USER, $PORT, $NAME and $INPUT_n in a command. The one copy: the + * frontend, the backend and other plugins (fleets, through the + * "snippets.variables" registry entry and action) all use this. + */ + export interface SnippetInput { key: string; label: string; diff --git a/plugins/snippets/tests/backend/activate.test.ts b/plugins/snippets/tests/backend/activate.test.ts index 134756e4c..d0290f64a 100644 --- a/plugins/snippets/tests/backend/activate.test.ts +++ b/plugins/snippets/tests/backend/activate.test.ts @@ -98,6 +98,28 @@ describe("snippets activate", () => { expect(after).toHaveLength(0); }); + it("registers its host settings hooks and the variables helper", async () => { + server = await startServer(); + const registry = server.mock.ctx.registry; + for (const key of [ + "snippets.hostImportNormalizer", + "snippets.hostPayloadLegacy", + "snippets.hostSettingsSync", + ]) { + expect(registry.consume(key), key).toBeDefined(); + } + const variables = registry.consume<{ + resolve: (c: string, h: { ip?: string } | null) => string; + extractInputs: (c: string) => Array<{ key: string }>; + }>("snippets.variables"); + expect(variables?.resolve("ping $HOST", { ip: "10.0.0.1" })).toBe( + "ping 10.0.0.1", + ); + expect(variables?.extractInputs("echo $INPUT_1")).toEqual([ + { key: "INPUT_1", label: "Input 1" }, + ]); + }); + it("fails closed without db:own", async () => { db = await createTestDb(pluginDir); const mock = createMockCtx({ diff --git a/plugins/snippets/tests/backend/host-import.test.ts b/plugins/snippets/tests/backend/host-import.test.ts new file mode 100644 index 000000000..90cbfce32 --- /dev/null +++ b/plugins/snippets/tests/backend/host-import.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest"; +import { + createHostSettingsSync, + hostImportNormalizer, + hostPayloadLegacy, +} from "../../src/backend/host-import.js"; + +describe("hostImportNormalizer", () => { + it("reads a 2.8 export's quick actions and startup snippet", () => { + expect( + hostImportNormalizer({ + quickActions: [ + { name: "Restart", snippetId: "3" }, + { name: "Broken", snippetId: "" }, + ], + terminalConfig: JSON.stringify({ startupSnippetId: 5, fontSize: 12 }), + }), + ).toEqual({ + quickActions: [{ name: "Restart", snippetId: 3 }], + startupSnippetId: 5, + }); + }); + + it("leaves an export that carries this plugin's settings to core", () => { + expect( + hostImportNormalizer({ + pluginSettings: { snippets: { startupSnippetId: 1 } }, + terminalConfig: { startupSnippetId: 5 }, + }), + ).toBeNull(); + }); + + it("writes nothing for a host without either", () => { + expect(hostImportNormalizer({ name: "web" })).toBeNull(); + }); +}); + +describe("hostPayloadLegacy", () => { + it("puts the 2.8 fields back for older clients", () => { + expect( + hostPayloadLegacy({ + quickActions: [{ name: "Up", snippetId: 2 }], + startupSnippetId: 7, + }), + ).toEqual({ + quickActions: [{ name: "Up", snippetId: 2 }], + terminalConfig: { startupSnippetId: 7 }, + }); + expect(hostPayloadLegacy({})).toEqual({ quickActions: [] }); + }); +}); + +describe("hostSettingsSync", () => { + const sync = createHostSettingsSync({ + findSyncIdById: async (id) => (id === 2 ? "sync-2" : null), + findIdBySyncId: async (syncId) => (syncId === "sync-2" ? 9 : null), + }); + + it("sends sync ids and drops what does not map", async () => { + expect(await sync.exportValue("startupSnippetId", 2)).toBe("sync-2"); + expect(await sync.exportValue("startupSnippetId", 4)).toBeNull(); + expect( + await sync.exportValue("quickActions", [ + { name: "A", snippetId: 2 }, + { name: "B", snippetId: 4 }, + ]), + ).toEqual([{ name: "A", snippetId: "sync-2" }]); + }); + + it("maps sync ids back to this side's rows", async () => { + expect(await sync.importValue("startupSnippetId", "sync-2")).toBe(9); + expect(await sync.importValue("startupSnippetId", "missing")).toBeNull(); + expect( + await sync.importValue("quickActions", [ + { name: "A", snippetId: "sync-2" }, + ]), + ).toEqual([{ name: "A", snippetId: 9 }]); + }); +}); diff --git a/plugins/snippets/tests/frontend/snippet-variables.test.ts b/plugins/snippets/tests/backend/variables.test.ts similarity index 98% rename from plugins/snippets/tests/frontend/snippet-variables.test.ts rename to plugins/snippets/tests/backend/variables.test.ts index ccb84b1d0..bdf0e16ab 100644 --- a/plugins/snippets/tests/frontend/snippet-variables.test.ts +++ b/plugins/snippets/tests/backend/variables.test.ts @@ -3,7 +3,7 @@ import { extractSnippetInputs, hasSnippetInputs, resolveSnippetContent, -} from "../../src/frontend/snippet-variables"; +} from "../../src/shared/variables.js"; describe("extractSnippetInputs", () => { it("extracts a single unlabeled input", () => { diff --git a/plugins/snippets/tests/frontend/activate.test.tsx b/plugins/snippets/tests/frontend/activate.test.tsx index 615c77dee..9dba7ba10 100644 --- a/plugins/snippets/tests/frontend/activate.test.tsx +++ b/plugins/snippets/tests/frontend/activate.test.tsx @@ -1,5 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { screen } from "@testing-library/react"; +import { act, fireEvent, screen } from "@testing-library/react"; +import { runKeybindingAction } from "@termix/plugin-sdk/frontend"; import { renderWithApp, type RenderedPluginApp, @@ -41,11 +42,24 @@ describe(`${manifest.id} activate`, () => { expect(railItem?.permission).toBe("snippets.view"); }); - it("registers the snippets.resolveForTerminal action", async () => { + it("registers what other surfaces reach it through", async () => { rendered = await renderWithApp(plugin, { manifest, locales }); - expect(rendered.registered.actions()).toContain( - "snippets.resolveForTerminal", + expect(rendered.registered.actions()).toEqual( + expect.arrayContaining([ + "snippets.startupCommand", + "snippets.list", + "snippets.extractInputs", + ]), ); + expect(rendered.registered.paletteGroups()).toEqual(["snippets"]); + expect(rendered.registered.keybindingActions()).toEqual(["runSnippet"]); + expect(rendered.registered.hostEditorSections()).toEqual(["snippets"]); + expect(rendered.registered.slot("host-metrics.toolbar")).toEqual([ + "snippets.quickActions", + ]); + expect(rendered.registered.slot("shell.overlay")).toEqual([ + "snippets.overlay", + ]); }); it("removes everything it registered on deactivate", async () => { @@ -54,6 +68,143 @@ describe(`${manifest.id} activate`, () => { expect(app.registered.panels()).toEqual([]); expect(app.registered.railItems()).toEqual([]); expect(app.registered.actions()).toEqual([]); + expect(app.registered.paletteGroups()).toEqual([]); + expect(app.registered.keybindingActions()).toEqual([]); + }); +}); + +const deploy = { + id: 3, + name: "Deploy", + content: "deploy $HOST", + description: null, + isNote: false, +}; + +describe("snippets in the command palette", () => { + it("lists snippets that run in the target tab", async () => { + const api = { get: vi.fn(async () => ({ data: [deploy] })) }; + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: api as never, + permissions: ["snippets.view"], + }); + const items = await rendered.loadPaletteGroup("snippets"); + expect(items).toEqual([ + expect.objectContaining({ + id: "3", + title: "Deploy", + description: "deploy $HOST", + needsTarget: true, + }), + ]); + }); + + it("offers nothing without the view permission", async () => { + rendered = await renderWithApp(plugin, { + manifest, + locales, + permissions: [], + }); + expect(await rendered.loadPaletteGroup("snippets")).toEqual([]); + }); +}); + +describe("the runSnippet keybinding", () => { + it("types the resolved snippet into the session it was pressed in", async () => { + const api = { get: vi.fn(async () => ({ data: deploy })) }; + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: api as never, + }); + const send = vi.fn(); + const handled = runKeybindingAction( + { type: "runSnippet", snippetId: "3", appendEnter: true }, + { host: { ip: "10.0.0.9" }, send }, + ); + expect(handled).toBe(true); + await vi.waitFor(() => + expect(send).toHaveBeenCalledWith("deploy 10.0.0.9\r"), + ); + expect(api.get).toHaveBeenCalledWith("/3"); + }); +}); + +describe("the startup command", () => { + it("resolves the host's startup snippet, or nothing without one", async () => { + const api = { get: vi.fn(async () => ({ data: deploy })) }; + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: api as never, + }); + const host = { + id: "1", + name: "web", + ip: "10.0.0.2", + port: 22, + pluginSettings: { snippets: { startupSnippetId: 3 } }, + }; + expect( + await rendered.app.invokeAction("snippets.startupCommand", host), + ).toBe("deploy 10.0.0.2"); + expect( + await rendered.app.invokeAction("snippets.startupCommand", { + ...host, + pluginSettings: {}, + }), + ).toBeNull(); + }); +}); + +describe("the host editor's Snippets tab", () => { + it("writes the startup snippet and quick actions into this plugin's settings", async () => { + const api = { get: vi.fn(async () => ({ data: [deploy] })) }; + rendered = await renderWithApp(plugin, { + manifest, + locales, + api: api as never, + }); + let form: Record = { + pluginSettings: { other: { keep: 1 } }, + }; + const updateForm = ( + patch: (current: Record) => Record, + ) => { + form = patch(form); + }; + rendered.renderHostEditorSection("snippets", { form, updateForm }); + const startup = await screen.findByRole("combobox", { + name: "Startup Snippet", + }); + await screen.findAllByRole("option", { name: "Deploy" }); + act(() => { + fireEvent.change(startup, { target: { value: "3" } }); + }); + expect(form.pluginSettings).toEqual({ + other: { keep: 1 }, + snippets: { startupSnippetId: 3 }, + }); + }); +}); + +describe("the Host Metrics quick action buttons", () => { + it("draws a button per quick action and nothing without any", async () => { + rendered = await renderWithApp(plugin, { + manifest, + locales, + permissions: ["snippets.view"], + }); + const host = { + id: 4, + pluginSettings: { + snippets: { quickActions: [{ name: "Restart", snippetId: 3 }] }, + }, + }; + rendered.renderSlot("host-metrics.toolbar", { hostId: 4, host }); + expect(await screen.findByRole("button", { name: "Restart" })).toBeTruthy(); }); }); diff --git a/plugins/snippets/tests/frontend/admin-user-snippets.test.tsx b/plugins/snippets/tests/frontend/admin-user-snippets.test.tsx index 70789d04e..75637d0a8 100644 --- a/plugins/snippets/tests/frontend/admin-user-snippets.test.tsx +++ b/plugins/snippets/tests/frontend/admin-user-snippets.test.tsx @@ -31,22 +31,23 @@ afterEach(async () => { rendered = null; }); -describe("snippet.list", () => { - it("lists the user's own snippets", async () => { +describe("snippet pickers while an admin edits another user's host", () => { + it("list the user's own snippets", async () => { const api = fakeApi([{ id: 1, name: "a", content: "ls", isNote: true }]); rendered = await renderWithApp(plugin, { manifest, locales, api }); - const rows = await rendered.app.invokeAction("snippet.list"); + rendered.renderHostEditorSection("snippets", { form: {} }); + await screen.findAllByRole("option", { name: "a" }); expect(api.get).toHaveBeenCalledWith("/"); - expect(rows).toEqual([ - { id: 1, name: "a", content: "ls", folder: null, isNote: true }, - ]); }); - it("lists another user's snippets for an admin", async () => { + it("list the target user's snippets", async () => { const api = fakeApi([]); rendered = await renderWithApp(plugin, { manifest, locales, api }); - await rendered.app.invokeAction("snippet.list", { targetUserId: "u2" }); - expect(api.get).toHaveBeenCalledWith("/", TARGET); + rendered.renderHostEditorSection("snippets", { + form: {}, + adminTargetUserId: "u2", + }); + await waitFor(() => expect(api.get).toHaveBeenCalledWith("/", TARGET)); }); }); diff --git a/plugins/ssh-terminal/locales/en.json b/plugins/ssh-terminal/locales/en.json index 903ae161e..816b3b7b2 100644 --- a/plugins/ssh-terminal/locales/en.json +++ b/plugins/ssh-terminal/locales/en.json @@ -490,7 +490,6 @@ "savedThemesLabel": "Saved Themes", "scrollbackBufferLabel": "Scrollback Buffer", "scrollbackMaxLines": "Maximum number of lines kept in history", - "startupSnippetLabel": "Startup Snippet", "sudoPasswordAutoFill": "Sudo Password Auto-Fill", "sudoPasswordAutoFillDesc": "Store a sudo password so host metrics, terminal prompts, and others can run privileged commands automatically.", "sudoPasswordAutoFillLabel": "Sudo Password Auto-fill", @@ -520,7 +519,8 @@ "history": "History", "localTerminal": "Local Terminal", "sshTools": "SSH Tools", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macros" }, "sshTools": { "broadcastInputPlaceholder": "Type here to broadcast keystrokes...", @@ -551,5 +551,122 @@ "savedThemePlaceholder": "Choose a saved theme", "title": "Connection Defaults", "apply": "Apply" + }, + "keybindings": { + "copy": "Copy selection", + "paste": "Paste", + "sendControlCode": "Send Ctrl+letter signal", + "sendText": "Send text", + "controlCodeLabel": "Letter", + "textLabel": "Text to send", + "appendEnterLabel": "Press Enter after sending", + "clipboardPermissionNote": "May prompt for clipboard permission on first use in some browsers.", + "textRequired": "Enter the text to send", + "controlCodeRequired": "Enter a single letter for the control code", + "builtIn": { + "copyCtrlC": "Copy selection (Ctrl+C, only when text is selected)", + "copyCtrlShiftC": "Copy selection (Ctrl+Shift+C)", + "copyCmdC": "Copy selection (Cmd+C)", + "pasteCtrlShiftV": "Paste from clipboard (Ctrl+Shift+V)", + "ctrlW": "Send Ctrl+W to shell (blocked from closing browser tab)", + "ctrlT": "Send Ctrl+T to shell (blocked from opening browser tab)", + "ctrlN": "Send Ctrl+N to shell (blocked from opening browser window)", + "ctrlQ": "Send Ctrl+Q to shell (blocked from quitting browser)" + } + }, + "macros": { + "title": "Macros", + "description": "Run a saved series of commands in a terminal, and react to what the server sends back.", + "target": "Target", + "noTerminal": "No terminal open", + "empty": "No macros yet", + "emptyHint": "Create a macro to automate a terminal flow, like logging in, restarting a service, or answering prompts.", + "create": "New macro", + "back": "Back to macros", + "name": "Name", + "namePlaceholder": "Restart web server", + "nameRequired": "Give the macro a name before saving", + "descriptionLabel": "Description", + "descriptionPlaceholder": "What this macro does (optional)", + "unsaved": "Unsaved changes", + "save": "Save", + "saved": "Macro saved", + "saveFailed": "Could not save the macro", + "delete": "Delete", + "deleteTitle": "Delete this macro?", + "deleteBody": "\"{{name}}\" will be permanently deleted. This cannot be undone.", + "deleted": "Macro deleted", + "cancel": "Cancel", + "run": "Run", + "stop": "Stop", + "running": "Running...", + "completed": "Macro finished", + "failed": "Macro failed", + "loadFailed": "Could not load macros", + "needTerminal": "Open and connect a terminal first", + "noSteps": "This macro has no steps yet. Add one below.", + "addStep": "Add step", + "stepNumber": "Step {{number}}", + "removeStep": "Remove step", + "confirmRunTitle": "Run this macro?", + "confirmRunBody": "\"{{name}}\" will send commands to {{host}}.", + "confirmRunAction": "Run macro", + "stepTypes": { + "send": { + "label": "Send command", + "help": "Types text into the terminal, the same as if you typed it." + }, + "wait": { + "label": "Wait for output", + "help": "Pauses until the server prints the text you name, like a password prompt." + }, + "delay": { + "label": "Pause", + "help": "Waits a fixed amount of time before the next step." + }, + "if": { + "label": "If output contains", + "help": "Checks everything the server has printed so far, then runs one of two branches." + }, + "repeat": { + "label": "Repeat", + "help": "Runs the steps inside it a set number of times." + } + }, + "send": { + "text": "Command or text", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Press Enter after sending", + "pressEnterHelp": "Turn this off to type text without submitting it." + }, + "wait": { + "pattern": "Wait until the output contains", + "patternPlaceholder": "password:", + "timeout": "Give up after", + "onTimeout": "If it never appears", + "stop": "Stop the macro", + "continue": "Keep going anyway" + }, + "delay": { + "duration": "Wait for" + }, + "ifStep": { + "pattern": "If the output contains", + "patternPlaceholder": "Active: active (running)", + "then": "Then do this", + "else": "Otherwise do this", + "emptyBranch": "No steps" + }, + "repeat": { + "count": "Number of times", + "times": "times" + }, + "match": { + "useRegex": "Match with a regular expression", + "useRegexHelp": "Leave this off to match the text exactly as written.", + "invalidRegex": "Not a valid regular expression, it will be matched as plain text" + }, + "seconds": "seconds", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/af_ZA.json b/plugins/ssh-terminal/locales/translated/af_ZA.json index 7eb7e1202..2530ce4c1 100644 --- a/plugins/ssh-terminal/locales/translated/af_ZA.json +++ b/plugins/ssh-terminal/locales/translated/af_ZA.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Gestoorde temas", "scrollbackBufferLabel": "Terugrolbuffer", "scrollbackMaxLines": "Maksimum aantal lyne wat in die geskiedenis gehou word", - "startupSnippetLabel": "Opstart-brokkie", "sudoPasswordAutoFill": "Sudo Wagwoord Outomatiese Vul", "sudoPasswordAutoFillDesc": "Stoor 'n sudo-wagwoord sodat gasheermetrieke, terminaalprompts en ander outomaties bevoorregte opdragte kan uitvoer.", "sudoPasswordAutoFillLabel": "Sudo Wagwoord Outomatiese Vul", @@ -335,7 +334,8 @@ "history": "Geskiedenis", "localTerminal": "Plaaslike Terminaal", "sshTools": "SSH-gereedskap", - "terminal": "Terminaal" + "terminal": "Terminaal", + "macros": "Makro's" }, "sshTools": { "broadcastInputPlaceholder": "Tik hier om toetsaanslagen uit te saai...", @@ -363,5 +363,112 @@ "off": "Af", "on": "Aan", "title": "Verbindingsverstekke" + }, + "keybindings": { + "copy": "Kopieer seleksie", + "paste": "Plak", + "sendControlCode": "Stuur Ctrl+letter sein", + "sendText": "Stuur SMS", + "controlCodeLabel": "Brief", + "textLabel": "SMS om te stuur", + "appendEnterLabel": "Druk Enter na stuur", + "clipboardPermissionNote": "Kan in sommige blaaiers met die eerste gebruik vir knipbordtoestemming vra.", + "textRequired": "Voer die teks in om te stuur", + "controlCodeRequired": "Voer 'n enkele letter vir die beheerkode in" + }, + "macros": { + "title": "Makro's", + "description": "Voer 'n gestoorde reeks opdragte in 'n terminaal uit en reageer op wat die bediener terugstuur.", + "target": "Teiken", + "noTerminal": "Geen terminaal oop nie", + "empty": "Geen makro's nog nie", + "emptyHint": "Skep 'n makro om 'n terminaalvloei te outomatiseer, soos om aan te meld, 'n diens te herbegin of aanwysings te beantwoord.", + "create": "Nuwe makro", + "back": "Terug na makro's", + "name": "Naam", + "namePlaceholder": "Herbegin die webbediener", + "nameRequired": "Gee die makro 'n naam voordat jy dit stoor", + "descriptionLabel": "Beskrywing", + "descriptionPlaceholder": "Wat hierdie makro doen (opsioneel)", + "unsaved": "Ongestoorde veranderinge", + "save": "Stoor", + "saved": "Makro gestoor", + "saveFailed": "Kon nie die makro stoor nie", + "delete": "Vee uit", + "deleteTitle": "Vee hierdie makro uit?", + "deleteBody": "\"{{name}}\" sal permanent verwyder word. Dit kan nie ongedaan gemaak word nie.", + "deleted": "Makro uitgevee", + "cancel": "Kanselleer", + "run": "Hardloop", + "stop": "Stop", + "running": "Hardloop...", + "completed": "Makro klaar", + "failed": "Makro het misluk", + "loadFailed": "Kon nie makro's laai nie", + "needTerminal": "Maak eers 'n terminaal oop en koppel dit", + "noSteps": "Hierdie makro het nog geen stappe nie. Voeg een hieronder by.", + "addStep": "Voeg stap by", + "stepNumber": "Stap {{number}}", + "removeStep": "Verwyder stap", + "confirmRunTitle": "Voer hierdie makro uit?", + "confirmRunBody": "\"{{name}}\" sal bevele stuur na {{host}}.", + "confirmRunAction": "Begin makro", + "stepTypes": { + "send": { + "label": "Stuur opdrag", + "help": "Tik teks in die terminaal, dieselfde asof jy dit getik het." + }, + "wait": { + "label": "Wag vir uitvoer", + "help": "Pouseer totdat die bediener die teks wat jy noem, druk, soos 'n wagwoordaanwysing." + }, + "delay": { + "label": "Pouseer", + "help": "Wag 'n vaste hoeveelheid tyd voor die volgende stap." + }, + "if": { + "label": "Indien die uitvoer bevat", + "help": "Kontroleer alles wat die bediener tot dusver gedruk het, en voer dan een van twee takke uit." + }, + "repeat": { + "label": "Herhaal", + "help": "Voer die stappe daarin 'n vasgestelde aantal kere uit." + } + }, + "send": { + "text": "Opdrag of teks", + "textPlaceholder": "systemctl herbegin nginx", + "pressEnter": "Druk Enter na stuur", + "pressEnterHelp": "Skakel dit af om teks te tik sonder om dit in te dien." + }, + "wait": { + "pattern": "Wag totdat die uitvoer bevat", + "patternPlaceholder": "wagwoord:", + "timeout": "Gee op daarna", + "onTimeout": "As dit nooit verskyn nie", + "stop": "Stop die makro", + "continue": "Gaan in elk geval voort" + }, + "delay": { + "duration": "Wag vir" + }, + "ifStep": { + "pattern": "Indien die uitvoer bevat", + "patternPlaceholder": "Aktief: aktief (hardloop)", + "then": "Doen dan hierdie", + "else": "Andersins doen dit", + "emptyBranch": "Geen stappe nie" + }, + "repeat": { + "count": "Aantal kere", + "times": "tye" + }, + "match": { + "useRegex": "Stem ooreen met 'n gereelde uitdrukking", + "useRegexHelp": "Los dit af om die teks presies soos geskryf te laat ooreenstem.", + "invalidRegex": "Nie 'n geldige gereelde uitdrukking nie, dit sal as gewone teks ooreenstem" + }, + "seconds": "sekondes", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/ar_SA.json b/plugins/ssh-terminal/locales/translated/ar_SA.json index de9d3d9aa..33fb6bc1c 100644 --- a/plugins/ssh-terminal/locales/translated/ar_SA.json +++ b/plugins/ssh-terminal/locales/translated/ar_SA.json @@ -307,7 +307,6 @@ "savedThemesLabel": "السمات المحفوظة", "scrollbackBufferLabel": "مخزن التمرير المؤقت", "scrollbackMaxLines": "الحد الأقصى لعدد الأسطر المحفوظة في السجل", - "startupSnippetLabel": "مقتطف بدء التشغيل", "sudoPasswordAutoFill": "التعبئة التلقائية لكلمة مرور Sudo", "sudoPasswordAutoFillDesc": "تخزين كلمة مرور sudo لتشغيل مقاييس المضيف ومطالبات الطرفية وغيرها من الأوامر المميزة تلقائياً.", "sudoPasswordAutoFillLabel": "الملء التلقائي لكلمة مرور sudo", @@ -335,7 +334,8 @@ "history": "السجل", "localTerminal": "المحطة المحلية", "sshTools": "أدوات SSH", - "terminal": "الطرفية" + "terminal": "الطرفية", + "macros": "وحدات الماكرو" }, "sshTools": { "broadcastInputPlaceholder": "اكتب هنا لبث ضغطات المفاتيح...", @@ -363,5 +363,112 @@ "off": "عن", "on": "على", "title": "الإعدادات الافتراضية للاتصال" + }, + "keybindings": { + "copy": "تحديد النص", + "paste": "لصق", + "sendControlCode": "إرسال إشارة Ctrl+حرف", + "sendText": "أرسل رسالة نصية", + "controlCodeLabel": "خطاب", + "textLabel": "نص لإرساله", + "appendEnterLabel": "اضغط على زر الإدخال بعد الإرسال", + "clipboardPermissionNote": "قد يطلب منك بعض المتصفحات منح إذن الوصول إلى الحافظة عند الاستخدام الأول.", + "textRequired": "أدخل النص المراد إرساله", + "controlCodeRequired": "أدخل حرفًا واحدًا لرمز التحكم" + }, + "macros": { + "title": "وحدات الماكرو", + "description": "قم بتشغيل سلسلة من الأوامر المحفوظة في طرفية، وتفاعل مع ما يرسله الخادم.", + "target": "هدف", + "noTerminal": "لا يوجد طرفية مفتوحة", + "empty": "لا توجد وحدات ماكرو حتى الآن", + "emptyHint": "أنشئ ماكرو لأتمتة تدفق طرفية، مثل تسجيل الدخول أو إعادة تشغيل خدمة أو الإجابة على المطالبات.", + "create": "ماكرو جديد", + "back": "العودة إلى وحدات الماكرو", + "name": "اسم", + "namePlaceholder": "أعد تشغيل خادم الويب", + "nameRequired": "قم بتسمية الماكرو قبل حفظه", + "descriptionLabel": "وصف", + "descriptionPlaceholder": "ما يفعله هذا الماكرو (اختياري)", + "unsaved": "التغييرات غير المحفوظة", + "save": "يحفظ", + "saved": "تم حفظ الماكرو", + "saveFailed": "تعذر حفظ الماكرو", + "delete": "يمسح", + "deleteTitle": "هل تريد حذف هذا الماكرو؟", + "deleteBody": "سيتم حذف \"{{name}}\" نهائياً. لا يمكن التراجع عن هذا الإجراء.", + "deleted": "تم حذف الماكرو", + "cancel": "يلغي", + "run": "يجري", + "stop": "قف", + "running": "جري...", + "completed": "اللمسة النهائية الدقيقة", + "failed": "فشل الماكرو", + "loadFailed": "تعذر تحميل وحدات الماكرو", + "needTerminal": "افتح وقم بتوصيل طرفية أولاً", + "noSteps": "لا يحتوي هذا الماكرو على أي خطوات حتى الآن. أضف خطوة واحدة أدناه.", + "addStep": "خطوات إضافية", + "stepNumber": "الخطوة {{number}}", + "removeStep": "خطوة الإزالة", + "confirmRunTitle": "هل تريد تشغيل هذا الماكرو؟", + "confirmRunBody": "\"{{name}}\" سيرسل أوامر إلى {{host}}.", + "confirmRunAction": "تشغيل الماكرو", + "stepTypes": { + "send": { + "label": "إرسال الأمر", + "help": "يكتب النص في الطرفية، تمامًا كما لو كنت أنت من كتبته." + }, + "wait": { + "label": "انتظر حتى يتم إخراج البيانات", + "help": "يتوقف مؤقتًا حتى يقوم الخادم بطباعة النص الذي تحدده، مثل مطالبة كلمة المرور." + }, + "delay": { + "label": "يوقف", + "help": "ينتظر فترة زمنية محددة قبل الخطوة التالية." + }, + "if": { + "label": "إذا احتوى الناتج", + "help": "يقوم بفحص كل ما طبعه الخادم حتى الآن، ثم يقوم بتشغيل أحد الفرعين." + }, + "repeat": { + "label": "يكرر", + "help": "يقوم بتنفيذ الخطوات الموجودة بداخله لعدد محدد من المرات." + } + }, + "send": { + "text": "أمر أو نص", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "اضغط على زر الإدخال بعد الإرسال", + "pressEnterHelp": "قم بإيقاف تشغيل هذا الخيار لكتابة نص دون إرساله." + }, + "wait": { + "pattern": "انتظر حتى يحتوي الناتج على", + "patternPlaceholder": "كلمة المرور:", + "timeout": "الاستسلام بعد", + "onTimeout": "إذا لم يظهر أبداً", + "stop": "أوقف الماكرو", + "continue": "استمر على أي حال" + }, + "delay": { + "duration": "انتظر" + }, + "ifStep": { + "pattern": "إذا احتوى الناتج", + "patternPlaceholder": "نشط: نشط (قيد التشغيل)", + "then": "ثم افعل هذا", + "else": "وإلا فافعل هذا", + "emptyBranch": "لا توجد" + }, + "repeat": { + "count": "عدد المرات", + "times": "أوقات" + }, + "match": { + "useRegex": "مطابقة مع تعبير نمطي", + "useRegexHelp": "اترك هذا الخيار فارغًا لمطابقة النص تمامًا كما هو مكتوب.", + "invalidRegex": "هذا ليس تعبيرًا نمطيًا صالحًا، وسيتم مطابقته كنص عادي." + }, + "seconds": "ثوانٍ", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/bg_BG.json b/plugins/ssh-terminal/locales/translated/bg_BG.json index e0a77cfc6..5f367021d 100644 --- a/plugins/ssh-terminal/locales/translated/bg_BG.json +++ b/plugins/ssh-terminal/locales/translated/bg_BG.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Запазени теми", "scrollbackBufferLabel": "Буфер за превъртане назад", "scrollbackMaxLines": "Максимален брой редове, съхранявани в историята", - "startupSnippetLabel": "Стартов фрагмент", "sudoPasswordAutoFill": "Автоматично попълване на парола за Sudo", "sudoPasswordAutoFillDesc": "Съхранявайте парола за sudo, така че показателите за хоста, подканите на терминала и други да могат да изпълняват привилегировани команди автоматично.", "sudoPasswordAutoFillLabel": "Автоматично попълване на парола за Sudo", @@ -335,7 +334,8 @@ "history": "История", "localTerminal": "Местен терминал", "sshTools": "SSH инструменти", - "terminal": "Терминал" + "terminal": "Терминал", + "macros": "Макроси" }, "sshTools": { "broadcastInputPlaceholder": "Въведете тук, за да излъчвате натисканията на клавиши...", @@ -363,5 +363,112 @@ "off": "Изключено", "on": "Включено", "title": "Настройки на връзката по подразбиране" + }, + "keybindings": { + "copy": "Копиране на селекцията", + "paste": "Паста", + "sendControlCode": "Изпрати сигнал Ctrl+буква", + "sendText": "Изпращане на текст", + "controlCodeLabel": "Писмо", + "textLabel": "Текст за изпращане", + "appendEnterLabel": "Натиснете Enter след изпращане", + "clipboardPermissionNote": "Може да поиска разрешение за достъп до клипборда при първото използване в някои браузъри.", + "textRequired": "Въведете текста за изпращане", + "controlCodeRequired": "Въведете една буква за контролния код" + }, + "macros": { + "title": "Макроси", + "description": "Изпълнете запазена серия от команди в терминал и реагирайте на това, което сървърът изпраща обратно.", + "target": "Цел", + "noTerminal": "Няма отворен терминал", + "empty": "Все още няма макроси", + "emptyHint": "Създайте макрос за автоматизиране на процес в терминал, като например влизане в системата, рестартиране на услуга или отговаряне на подкани.", + "create": "Нов макрос", + "back": "Обратно към макросите", + "name": "Име", + "namePlaceholder": "Рестартирайте уеб сървъра", + "nameRequired": "Дайте име на макроса, преди да го запазите", + "descriptionLabel": "Описание", + "descriptionPlaceholder": "Какво прави този макрос (по избор)", + "unsaved": "Незапазени промени", + "save": "Запазване", + "saved": "Макросът е запазен", + "saveFailed": "Макросът не можа да се запази", + "delete": "Изтриване", + "deleteTitle": "Да се изтрие ли този макрос?", + "deleteBody": "„{{name}}“ ще бъде изтрито за постоянно. Това не може да бъде отменено.", + "deleted": "Макросът е изтрит", + "cancel": "Отказ", + "run": "Бягане", + "stop": "Спри", + "running": "Бягане...", + "completed": "Макрото е завършено", + "failed": "Макросът не беше успешен", + "loadFailed": "Не можаха да се заредят макроси", + "needTerminal": "Първо отворете и свържете терминал", + "noSteps": "Този макрос все още няма стъпки. Добавете една по-долу.", + "addStep": "Добавяне на стъпка", + "stepNumber": "Стъпка {{number}}", + "removeStep": "Премахване на стъпка", + "confirmRunTitle": "Да се изпълни ли този макрос?", + "confirmRunBody": "„{{name}}“ ще изпраща команди до {{host}}.", + "confirmRunAction": "Изпълнете макрос", + "stepTypes": { + "send": { + "label": "Изпрати команда", + "help": "Въвежда текст в терминала, по същия начин, както ако сте го написали." + }, + "wait": { + "label": "Изчакайте изхода", + "help": "Пауза, докато сървърът отпечата текста, който посочите, като например подкана за парола." + }, + "delay": { + "label": "Пауза", + "help": "Изчаква фиксирано време преди следващата стъпка." + }, + "if": { + "label": "Ако изходът съдържа", + "help": "Проверява всичко, което сървърът е отпечатал досега, след което изпълнява един от двата клона." + }, + "repeat": { + "label": "Повторете", + "help": "Изпълнява стъпките вътре в него определен брой пъти." + } + }, + "send": { + "text": "Команда или текст", + "textPlaceholder": "systemctl рестартиране на nginx", + "pressEnter": "Натиснете Enter след изпращане", + "pressEnterHelp": "Изключете това, за да пишете текст, без да го изпращате." + }, + "wait": { + "pattern": "Изчакайте, докато изходът съдържа", + "patternPlaceholder": "парола:", + "timeout": "Откажи се след", + "onTimeout": "Ако никога не се появи", + "stop": "Спиране на макроса", + "continue": "Продължавай все пак" + }, + "delay": { + "duration": "Изчакайте" + }, + "ifStep": { + "pattern": "Ако изходът съдържа", + "patternPlaceholder": "Активен: активен (работи)", + "then": "Тогава направете това", + "else": "В противен случай направете това", + "emptyBranch": "Без стъпала" + }, + "repeat": { + "count": "Брой пъти", + "times": "пъти" + }, + "match": { + "useRegex": "Съвпадение с регулярен израз", + "useRegexHelp": "Не включвайте това, за да съвпада текстът точно както е написан.", + "invalidRegex": "Не е валиден регулярен израз, ще бъде съпоставен като обикновен текст" + }, + "seconds": "секунди", + "secondsShort": "{{count}}с" } } diff --git a/plugins/ssh-terminal/locales/translated/bn_BD.json b/plugins/ssh-terminal/locales/translated/bn_BD.json index b23953e90..7417ec0f1 100644 --- a/plugins/ssh-terminal/locales/translated/bn_BD.json +++ b/plugins/ssh-terminal/locales/translated/bn_BD.json @@ -307,7 +307,6 @@ "savedThemesLabel": "সংরক্ষিত থিম", "scrollbackBufferLabel": "স্ক্রোলব্যাক বাফার", "scrollbackMaxLines": "ইতিহাসে সংরক্ষিত সর্বাধিক সংখ্যক লাইন", - "startupSnippetLabel": "স্টার্টআপ স্নিপেট", "sudoPasswordAutoFill": "সুডো পাসওয়ার্ড স্বয়ংক্রিয়ভাবে পূরণ", "sudoPasswordAutoFillDesc": "একটি sudo পাসওয়ার্ড সংরক্ষণ করুন যাতে হোস্ট মেট্রিক্স, টার্মিনাল প্রম্পট এবং অন্যান্যরা স্বয়ংক্রিয়ভাবে বিশেষাধিকারপ্রাপ্ত কমান্ড চালাতে পারে।", "sudoPasswordAutoFillLabel": "সুডো পাসওয়ার্ড স্বয়ংক্রিয়ভাবে পূরণ", @@ -335,7 +334,8 @@ "history": "ইতিহাস", "localTerminal": "স্থানীয় টার্মিনাল", "sshTools": "SSH টুলস", - "terminal": "টার্মিনাল" + "terminal": "টার্মিনাল", + "macros": "ম্যাক্রো" }, "sshTools": { "broadcastInputPlaceholder": "কীস্ট্রোক সম্প্রচার করতে এখানে টাইপ করুন...", @@ -363,5 +363,112 @@ "off": "বন্ধ", "on": "চালু", "title": "সংযোগের ডিফল্ট" + }, + "keybindings": { + "copy": "কপি নির্বাচন", + "paste": "পেস্ট", + "sendControlCode": "Ctrl+অক্ষর সংকেত পাঠান", + "sendText": "টেক্সট পাঠান", + "controlCodeLabel": "চিঠি", + "textLabel": "পাঠানোর জন্য টেক্সট", + "appendEnterLabel": "পাঠানোর পর এন্টার চাপুন", + "clipboardPermissionNote": "কিছু ব্রাউজারে প্রথমবার ব্যবহারের সময় ক্লিপবোর্ডের অনুমতি চাইতে পারে।", + "textRequired": "পাঠানোর জন্য টেক্সটটি লিখুন।", + "controlCodeRequired": "কন্ট্রোল কোডের জন্য একটি অক্ষর লিখুন।" + }, + "macros": { + "title": "ম্যাক্রো", + "description": "টার্মিনালে সংরক্ষিত কমান্ডের একটি সিরিজ চালান এবং সার্ভার যা ফেরত পাঠায় তার উপর প্রতিক্রিয়া জানান।", + "target": "লক্ষ্য", + "noTerminal": "কোন টার্মিনাল খোলা নেই", + "empty": "এখনো কোনো ম্যাক্রো নেই", + "emptyHint": "টার্মিনালের কোনো কাজ, যেমন লগ ইন করা, কোনো সার্ভিস রিস্টার্ট করা বা প্রম্পটের উত্তর দেওয়া, স্বয়ংক্রিয় করতে একটি ম্যাক্রো তৈরি করুন।", + "create": "নতুন ম্যাক্রো", + "back": "ম্যাক্রোতে ফিরে যান", + "name": "নাম", + "namePlaceholder": "ওয়েব সার্ভার পুনরায় চালু করুন", + "nameRequired": "সংরক্ষণ করার আগে ম্যাক্রোটির একটি নাম দিন।", + "descriptionLabel": "বর্ণনা", + "descriptionPlaceholder": "এই ম্যাক্রোটি কী করে (ঐচ্ছিক)", + "unsaved": "সংরক্ষিত নয় এমন পরিবর্তন", + "save": "সংরক্ষণ করুন", + "saved": "ম্যাক্রো সংরক্ষিত", + "saveFailed": "ম্যাক্রোটি সংরক্ষণ করা যায়নি।", + "delete": "মুছে ফেলুন", + "deleteTitle": "এই ম্যাক্রোটি মুছে ফেলবেন?", + "deleteBody": "\"{{name}}\" স্থায়ীভাবে মুছে ফেলা হবে। এটি আর পূর্বাবস্থায় ফেরানো যাবে না।", + "deleted": "ম্যাক্রো মুছে ফেলা হয়েছে", + "cancel": "বাতিল করুন", + "run": "দৌড়", + "stop": "থামুন", + "running": "দৌড়াচ্ছে...", + "completed": "ম্যাক্রো শেষ হয়েছে", + "failed": "ম্যাক্রো ব্যর্থ হয়েছে", + "loadFailed": "ম্যাক্রো লোড করা যায়নি", + "needTerminal": "প্রথমে একটি টার্মিনাল খুলুন এবং সংযোগ করুন।", + "noSteps": "এই ম্যাক্রোতে এখনও কোনো ধাপ নেই। নিচে একটি যোগ করুন।", + "addStep": "ধাপ যোগ করুন", + "stepNumber": "ধাপ {{number}}", + "removeStep": "ধাপটি সরান", + "confirmRunTitle": "এই ম্যাক্রোটি চালাবেন?", + "confirmRunBody": "\"{{name}}\" {{host}} -এ কমান্ড পাঠাবে।", + "confirmRunAction": "ম্যাক্রো চালান", + "stepTypes": { + "send": { + "label": "কমান্ড পাঠান", + "help": "টার্মিনালে টেক্সট টাইপ করে, ঠিক যেন আপনি নিজেই টাইপ করেছেন।" + }, + "wait": { + "label": "আউটপুটের জন্য অপেক্ষা করুন", + "help": "সার্ভার আপনার দেওয়া টেক্সটটি প্রিন্ট না করা পর্যন্ত এটি থেমে থাকে, অনেকটা পাসওয়ার্ড দেওয়ার সময়কার মতো।" + }, + "delay": { + "label": "বিরতি", + "help": "পরবর্তী ধাপে যাওয়ার আগে একটি নির্দিষ্ট পরিমাণ সময় অপেক্ষা করে।" + }, + "if": { + "label": "যদি আউটপুটে থাকে", + "help": "সার্ভার এ পর্যন্ত যা যা প্রিন্ট করেছে, তা যাচাই করে দুটি শাখার মধ্যে একটি চালায়।" + }, + "repeat": { + "label": "পুনরাবৃত্তি", + "help": "এর ভেতরের ধাপগুলো একটি নির্দিষ্ট সংখ্যক বার চালায়।" + } + }, + "send": { + "text": "কমান্ড বা টেক্সট", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "পাঠানোর পর এন্টার চাপুন", + "pressEnterHelp": "সাবমিট না করে টেক্সট টাইপ করতে এটি বন্ধ করুন।" + }, + "wait": { + "pattern": "আউটপুটে থাকা পর্যন্ত অপেক্ষা করুন", + "patternPlaceholder": "পাসওয়ার্ড:", + "timeout": "হাল ছেড়ে দেওয়ার পর", + "onTimeout": "যদি এটি কখনও প্রদর্শিত না হয়", + "stop": "ম্যাক্রোটি বন্ধ করুন", + "continue": "তবুও এগিয়ে যান।" + }, + "delay": { + "duration": "অপেক্ষা করুন" + }, + "ifStep": { + "pattern": "যদি আউটপুটে থাকে", + "patternPlaceholder": "সক্রিয়: সক্রিয় (চলমান)", + "then": "তারপর এটা করুন", + "else": "অন্যথায় এটা করুন", + "emptyBranch": "কোন পদক্ষেপ নেই" + }, + "repeat": { + "count": "সংখ্যা", + "times": "সময়" + }, + "match": { + "useRegex": "রেগুলার এক্সপ্রেশনের সাথে মেলান", + "useRegexHelp": "লেখাটির সাথে হুবহু মিল রাখতে এটি বাদ দিন।", + "invalidRegex": "এটি একটি বৈধ রেগুলার এক্সপ্রেশন নয়, এটিকে সাধারণ টেক্সট হিসেবে মেলানো হবে।" + }, + "seconds": "সেকেন্ড", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/ca_ES.json b/plugins/ssh-terminal/locales/translated/ca_ES.json index 3a7441565..5967cd855 100644 --- a/plugins/ssh-terminal/locales/translated/ca_ES.json +++ b/plugins/ssh-terminal/locales/translated/ca_ES.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Temes desats", "scrollbackBufferLabel": "Buffer de desplaçament enrere", "scrollbackMaxLines": "Nombre màxim de línies que es conserven a l'historial", - "startupSnippetLabel": "Fragment d'inici", "sudoPasswordAutoFill": "Emplenament automàtic de contrasenya de Sudo", "sudoPasswordAutoFillDesc": "Emmagatzema una contrasenya sudo perquè les mètriques de l'amfitrió, les indicacions del terminal i altres puguin executar ordres privilegiades automàticament.", "sudoPasswordAutoFillLabel": "Autoemplenament de contrasenya de Sudo", @@ -335,7 +334,8 @@ "history": "Història", "localTerminal": "Terminal local", "sshTools": "Eines SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macros" }, "sshTools": { "broadcastInputPlaceholder": "Escriu aquí per emetre les pulsacions de tecles...", @@ -363,5 +363,112 @@ "off": "Desactivat", "on": "Activat", "title": "Valors predeterminats de connexió" + }, + "keybindings": { + "copy": "Copia la selecció", + "paste": "Enganxa", + "sendControlCode": "Envia el senyal Ctrl+lletra", + "sendText": "Enviar text", + "controlCodeLabel": "Carta", + "textLabel": "Text per enviar", + "appendEnterLabel": "Premeu Intro després d'enviar", + "clipboardPermissionNote": "Pot demanar permisos al porta-retalls en el primer ús en alguns navegadors.", + "textRequired": "Introduïu el text per enviar", + "controlCodeRequired": "Introduïu una sola lletra per al codi de control" + }, + "macros": { + "title": "Macros", + "description": "Executa una sèrie d'ordres desades en un terminal i reacciona al que el servidor envia de tornada.", + "target": "Objectiu", + "noTerminal": "Cap terminal obert", + "empty": "Encara no hi ha macros", + "emptyHint": "Crea una macro per automatitzar un flux de terminal, com ara iniciar la sessió, reiniciar un servei o respondre a les indicacions.", + "create": "Nova macro", + "back": "Torna a les macros", + "name": "Nom", + "namePlaceholder": "Reinicia el servidor web", + "nameRequired": "Doneu un nom a la macro abans de desar-la", + "descriptionLabel": "Descripció", + "descriptionPlaceholder": "Què fa aquesta macro (opcional)", + "unsaved": "Canvis no desats", + "save": "Desa", + "saved": "Macro desada", + "saveFailed": "No s'ha pogut desar la macro", + "delete": "Suprimeix", + "deleteTitle": "Voleu suprimir aquesta macro?", + "deleteBody": "«{{name}}» s'eliminarà permanentment. Això no es pot desfer.", + "deleted": "Macro suprimida", + "cancel": "Cancel·la", + "run": "Corre", + "stop": "Atura", + "running": "Corrent...", + "completed": "Macro acabada", + "failed": "La macro ha fallat", + "loadFailed": "No s'han pogut carregar les macros", + "needTerminal": "Obriu i connecteu primer un terminal", + "noSteps": "Aquesta macro encara no té cap pas. Afegeix-ne un a continuació.", + "addStep": "Afegeix un pas", + "stepNumber": "Pas {{number}}", + "removeStep": "Elimina el pas", + "confirmRunTitle": "Voleu executar aquesta macro?", + "confirmRunBody": "\"{{name}}\" enviarà ordres a {{host}}.", + "confirmRunAction": "Executa macro", + "stepTypes": { + "send": { + "label": "Envia l'ordre", + "help": "Escriu text al terminal, igual que si l'haguessis escrit." + }, + "wait": { + "label": "Espera la sortida", + "help": "Es posa en pausa fins que el servidor imprimeix el text que anomeneu, com si fos una sol·licitud de contrasenya." + }, + "delay": { + "label": "Pausa", + "help": "Espera un temps fix abans del següent pas." + }, + "if": { + "label": "Si la sortida conté", + "help": "Comprova tot el que el servidor ha imprès fins ara i després executa una de les dues branques." + }, + "repeat": { + "label": "Repetir", + "help": "Executa els passos que hi ha dins un nombre determinat de vegades." + } + }, + "send": { + "text": "Ordre o text", + "textPlaceholder": "reinici de systemctl a nginx", + "pressEnter": "Premeu Intro després d'enviar", + "pressEnterHelp": "Desactiva això per escriure text sense enviar-lo." + }, + "wait": { + "pattern": "Espereu fins que la sortida contingui", + "patternPlaceholder": "contrasenya:", + "timeout": "Renunciar després", + "onTimeout": "Si mai apareix", + "stop": "Atura la macro", + "continue": "Continua igualment" + }, + "delay": { + "duration": "Espera" + }, + "ifStep": { + "pattern": "Si la sortida conté", + "patternPlaceholder": "Actiu: actiu (en funcionament)", + "then": "Aleshores fes això", + "else": "Si no, fes això", + "emptyBranch": "Sense esglaons" + }, + "repeat": { + "count": "Nombre de vegades", + "times": "vegades" + }, + "match": { + "useRegex": "Coincideix amb una expressió regular", + "useRegexHelp": "Deixa això desactivat perquè el text coincideixi exactament com està escrit.", + "invalidRegex": "No és una expressió regular vàlida, es trobarà com a text sense format." + }, + "seconds": "segons", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/cs_CZ.json b/plugins/ssh-terminal/locales/translated/cs_CZ.json index be8627a94..86d0728b1 100644 --- a/plugins/ssh-terminal/locales/translated/cs_CZ.json +++ b/plugins/ssh-terminal/locales/translated/cs_CZ.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Uložená témata", "scrollbackBufferLabel": "Vyrovnávací paměť pro rolování zpět", "scrollbackMaxLines": "Maximální počet řádků uchovávaných v historii", - "startupSnippetLabel": "Úryvek pro spuštění", "sudoPasswordAutoFill": "Automatické vyplňování hesla Sudo", "sudoPasswordAutoFillDesc": "Uložte heslo sudo, aby metriky hostitele, výzvy terminálu a další mohly automaticky spouštět privilegované příkazy.", "sudoPasswordAutoFillLabel": "Automatické vyplňování hesla Sudo", @@ -335,7 +334,8 @@ "history": "Dějiny", "localTerminal": "Místní terminál", "sshTools": "SSH nástroje", - "terminal": "Terminál" + "terminal": "Terminál", + "macros": "Makra" }, "sshTools": { "broadcastInputPlaceholder": "Zde napište pro vysílání stisknutí kláves...", @@ -363,5 +363,112 @@ "off": "Vypnuto", "on": "Na", "title": "Výchozí nastavení připojení" + }, + "keybindings": { + "copy": "Kopírovat výběr", + "paste": "Pasta", + "sendControlCode": "Odeslat signál Ctrl+písmeno", + "sendText": "Odeslat text", + "controlCodeLabel": "Dopis", + "textLabel": "Text k odeslání", + "appendEnterLabel": "Po odeslání stiskněte Enter", + "clipboardPermissionNote": "V některých prohlížečích se může při prvním použití zobrazit výzva k zadání povolení ke schránce.", + "textRequired": "Zadejte text k odeslání", + "controlCodeRequired": "Zadejte jedno písmeno pro kontrolní kód" + }, + "macros": { + "title": "Makra", + "description": "Spusťte v terminálu uloženou sérii příkazů a reagujte na to, co server odešle zpět.", + "target": "Cíl", + "noTerminal": "Žádný otevřený terminál", + "empty": "Zatím žádná makra", + "emptyHint": "Vytvořte makro pro automatizaci procesů v terminálu, jako je přihlašování, restartování služby nebo odpovídání na výzvy.", + "create": "Nové makro", + "back": "Zpět k makrům", + "name": "Jméno", + "namePlaceholder": "Restartujte webový server", + "nameRequired": "Před uložením zadejte makro název", + "descriptionLabel": "Popis", + "descriptionPlaceholder": "Co toto makro dělá (volitelné)", + "unsaved": "Neuložené změny", + "save": "Uložit", + "saved": "Makro uloženo", + "saveFailed": "Makro se nepodařilo uložit.", + "delete": "Vymazat", + "deleteTitle": "Smazat toto makro?", + "deleteBody": "Soubor „{{name}}“ bude trvale smazán. Tuto akci nelze vrátit zpět.", + "deleted": "Makro smazáno", + "cancel": "Zrušit", + "run": "Běh", + "stop": "Zastávka", + "running": "Běh...", + "completed": "Makro dokončeno", + "failed": "Makro selhalo", + "loadFailed": "Nepodařilo se načíst makra", + "needTerminal": "Nejprve otevřete a připojte terminál", + "noSteps": "Toto makro zatím nemá žádné kroky. Přidejte jeden níže.", + "addStep": "Přidat krok", + "stepNumber": "Krok {{number}}", + "removeStep": "Odebrat krok", + "confirmRunTitle": "Spustit toto makro?", + "confirmRunBody": "„{{name}}“ odešle příkazy pro {{host}}.", + "confirmRunAction": "Spustit makro", + "stepTypes": { + "send": { + "label": "Odeslat příkaz", + "help": "Zadá text do terminálu, stejně jako byste ho napsali." + }, + "wait": { + "label": "Počkejte na výstup", + "help": "Pozastaví se, dokud server nevypíše text, který pojmenujete, například výzvu k zadání hesla." + }, + "delay": { + "label": "Pauza", + "help": "Čeká pevně stanovenou dobu před dalším krokem." + }, + "if": { + "label": "Pokud výstup obsahuje", + "help": "Zkontroluje vše, co server dosud vytiskl, a poté spustí jednu ze dvou větví." + }, + "repeat": { + "label": "Opakovat", + "help": "Spustí kroky uvnitř něj tolikrát, kolikrát je nastaveno." + } + }, + "send": { + "text": "Příkaz nebo text", + "textPlaceholder": "restartování systemctl v Nginxu", + "pressEnter": "Po odeslání stiskněte Enter", + "pressEnterHelp": "Vypněte tuto funkci, chcete-li psát text bez jeho odesílání." + }, + "wait": { + "pattern": "Počkejte, dokud výstup neobsahuje", + "patternPlaceholder": "heslo:", + "timeout": "Vzdej se po", + "onTimeout": "Pokud se to nikdy neobjeví", + "stop": "Zastavit makro", + "continue": "Stejně pokračuj" + }, + "delay": { + "duration": "Čekat na" + }, + "ifStep": { + "pattern": "Pokud výstup obsahuje", + "patternPlaceholder": "Aktivní: aktivní (běží)", + "then": "Pak udělej tohle", + "else": "Jinak to udělej", + "emptyBranch": "Žádné schody" + }, + "repeat": { + "count": "Počet opakování", + "times": "krát" + }, + "match": { + "useRegex": "Porovnání s regulárním výrazem", + "useRegexHelp": "Nechte toto vypnuté, aby text přesně odpovídal napsanému textu.", + "invalidRegex": "Není to platný regulární výraz, bude nalezen jako prostý text." + }, + "seconds": "sekundy", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/da_DK.json b/plugins/ssh-terminal/locales/translated/da_DK.json index 42d135bf5..c64990186 100644 --- a/plugins/ssh-terminal/locales/translated/da_DK.json +++ b/plugins/ssh-terminal/locales/translated/da_DK.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Gemte temaer", "scrollbackBufferLabel": "Scrollback-buffer", "scrollbackMaxLines": "Maksimalt antal linjer gemt i historikken", - "startupSnippetLabel": "Opstartskodestykke", "sudoPasswordAutoFill": "Sudo-adgangskode automatisk udfyldning", "sudoPasswordAutoFillDesc": "Gem en sudo-adgangskode, så værtsmålinger, terminalprompter og andre kan køre privilegerede kommandoer automatisk.", "sudoPasswordAutoFillLabel": "Sudo-adgangskode automatisk udfyldning", @@ -335,7 +334,8 @@ "history": "Historie", "localTerminal": "Lokal terminal", "sshTools": "SSH-værktøjer", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Makroer" }, "sshTools": { "broadcastInputPlaceholder": "Skriv her for at udsende tastetryk...", @@ -363,5 +363,112 @@ "off": "Slukket", "on": "På", "title": "Standardindstillinger for forbindelse" + }, + "keybindings": { + "copy": "Kopiér valg", + "paste": "Indsæt", + "sendControlCode": "Send Ctrl+bogstavsignal", + "sendText": "Send sms", + "controlCodeLabel": "Bogstav", + "textLabel": "Tekst der skal sendes", + "appendEnterLabel": "Tryk Enter efter afsendelse", + "clipboardPermissionNote": "Kan bede om tilladelse til udklipsholder ved første brug i nogle browsere.", + "textRequired": "Indtast den tekst, der skal sendes", + "controlCodeRequired": "Indtast et enkelt bogstav for kontrolkoden" + }, + "macros": { + "title": "Makroer", + "description": "Kør en gemt række kommandoer i en terminal, og reager på, hvad serveren sender tilbage.", + "target": "Mål", + "noTerminal": "Ingen terminal åben", + "empty": "Ingen makroer endnu", + "emptyHint": "Opret en makro for at automatisere et terminalflow, f.eks. at logge ind, genstarte en tjeneste eller besvare prompts.", + "create": "Ny makro", + "back": "Tilbage til makroer", + "name": "Navn", + "namePlaceholder": "Genstart webserveren", + "nameRequired": "Giv makroen et navn, før du gemmer den", + "descriptionLabel": "Beskrivelse", + "descriptionPlaceholder": "Hvad denne makro gør (valgfrit)", + "unsaved": "Ikke-gemte ændringer", + "save": "Spare", + "saved": "Makro gemt", + "saveFailed": "Makroen kunne ikke gemmes", + "delete": "Slet", + "deleteTitle": "Slette denne makro?", + "deleteBody": "\"{{name}}\" vil blive slettet permanent. Dette kan ikke fortrydes.", + "deleted": "Makro slettet", + "cancel": "Ophæve", + "run": "Løbe", + "stop": "Stop", + "running": "Løber...", + "completed": "Makro færdig", + "failed": "Makro mislykkedes", + "loadFailed": "Makroer kunne ikke indlæses", + "needTerminal": "Åbn og tilslut først en terminal", + "noSteps": "Denne makro har endnu ingen trin. Tilføj et nedenfor.", + "addStep": "Tilføj trin", + "stepNumber": "Trin {{number}}", + "removeStep": "Fjern trin", + "confirmRunTitle": "Køre denne makro?", + "confirmRunBody": "\"{{name}}\" sender kommandoer til {{host}}.", + "confirmRunAction": "Kør makro", + "stepTypes": { + "send": { + "label": "Send kommando", + "help": "Skriver tekst i terminalen, på samme måde som hvis du havde skrevet den." + }, + "wait": { + "label": "Vent på output", + "help": "Holder pause, indtil serveren udskriver den tekst, du navngiver, f.eks. en adgangskodeprompt." + }, + "delay": { + "label": "Pause", + "help": "Venter et fast tidsrum før næste trin." + }, + "if": { + "label": "Hvis outputtet indeholder", + "help": "Kontrollerer alt, hvad serveren har udskrevet indtil videre, og kører derefter en af to grene." + }, + "repeat": { + "label": "Gentage", + "help": "Kører trinnene inde i den et bestemt antal gange." + } + }, + "send": { + "text": "Kommando eller tekst", + "textPlaceholder": "systemctl genstart nginx", + "pressEnter": "Tryk Enter efter afsendelse", + "pressEnterHelp": "Slå dette fra for at skrive tekst uden at indsende den." + }, + "wait": { + "pattern": "Vent indtil outputtet indeholder", + "patternPlaceholder": "adgangskode:", + "timeout": "Giv op efter", + "onTimeout": "Hvis det aldrig dukker op", + "stop": "Stop makroen", + "continue": "Fortsæt alligevel" + }, + "delay": { + "duration": "Vent på" + }, + "ifStep": { + "pattern": "Hvis outputtet indeholder", + "patternPlaceholder": "Aktiv: aktiv (løber)", + "then": "Så gør dette", + "else": "Ellers gør dette", + "emptyBranch": "Ingen trin" + }, + "repeat": { + "count": "Antal gange", + "times": "gange" + }, + "match": { + "useRegex": "Match med et regulært udtryk", + "useRegexHelp": "Slå dette fra for at teksten skal være præcis som den er skrevet.", + "invalidRegex": "Ikke et gyldigt regulært udtryk, det vil blive matchet som almindelig tekst" + }, + "seconds": "sekunder", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/de_DE.json b/plugins/ssh-terminal/locales/translated/de_DE.json index 413711661..c33d9c3c7 100644 --- a/plugins/ssh-terminal/locales/translated/de_DE.json +++ b/plugins/ssh-terminal/locales/translated/de_DE.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Gespeicherte Designs", "scrollbackBufferLabel": "Rückrollpuffer", "scrollbackMaxLines": "Maximale Anzahl der im Verlauf gespeicherten Zeilen", - "startupSnippetLabel": "Startup-Snippet", "sudoPasswordAutoFill": "Sudo-Passwort automatisch ausfüllen", "sudoPasswordAutoFillDesc": "Sudo-Passwort speichern, damit Hostmetriken, Terminal-Eingabeaufforderungen und andere privilegierte Befehle automatisch ausgeführt werden können.", "sudoPasswordAutoFillLabel": "Sudo-Passwort automatisch ausfüllen", @@ -335,7 +334,8 @@ "history": "Verlauf", "localTerminal": "Lokales Terminal", "sshTools": "SSH-Tools", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Makros" }, "sshTools": { "broadcastInputPlaceholder": "Hier tippen, um Tastenanschläge zu senden...", @@ -363,5 +363,112 @@ "off": "Aus", "on": "An", "title": "Standardeinstellungen für die Verbindung" + }, + "keybindings": { + "copy": "Auswahl kopieren", + "paste": "Paste", + "sendControlCode": "Sende das Signal Strg+Buchstabe", + "sendText": "SMS senden", + "controlCodeLabel": "Brief", + "textLabel": "Text zum Senden", + "appendEnterLabel": "Nach dem Absenden die Eingabetaste drücken", + "clipboardPermissionNote": "Bei einigen Browsern kann es bei der ersten Verwendung zu einer Anfrage nach der Berechtigung zum Ablegen der Zwischenablage kommen.", + "textRequired": "Geben Sie den zu sendenden Text ein.", + "controlCodeRequired": "Geben Sie einen einzelnen Buchstaben als Kontrollcode ein." + }, + "macros": { + "title": "Makros", + "description": "Führe eine gespeicherte Befehlsfolge in einem Terminal aus und reagiere auf die Antwort des Servers.", + "target": "Ziel", + "noTerminal": "Kein Terminal geöffnet", + "empty": "Noch keine Makros", + "emptyHint": "Erstellen Sie ein Makro, um einen Terminal-Workflow zu automatisieren, z. B. das Anmelden, das Neustarten eines Dienstes oder das Beantworten von Eingabeaufforderungen.", + "create": "Neues Makro", + "back": "Zurück zu den Makros", + "name": "Name", + "namePlaceholder": "Webserver neu starten", + "nameRequired": "Gib dem Makro vor dem Speichern einen Namen.", + "descriptionLabel": "Beschreibung", + "descriptionPlaceholder": "Was dieses Makro bewirkt (optional)", + "unsaved": "Nicht gespeicherte Änderungen", + "save": "Speichern", + "saved": "Makro gespeichert", + "saveFailed": "Makro konnte nicht gespeichert werden", + "delete": "Löschen", + "deleteTitle": "Dieses Makro löschen?", + "deleteBody": "\"{{name}}\" wird endgültig gelöscht. Dies kann nicht rückgängig gemacht werden.", + "deleted": "Makro gelöscht", + "cancel": "Stornieren", + "run": "Laufen", + "stop": "Stoppen", + "running": "Läuft...", + "completed": "Makro beendet", + "failed": "Makro fehlgeschlagen", + "loadFailed": "Makros konnten nicht geladen werden.", + "needTerminal": "Öffnen und verbinden Sie zuerst ein Terminal.", + "noSteps": "Dieses Makro hat noch keine Schritte. Füge unten einen hinzu.", + "addStep": "Schritt hinzufügen", + "stepNumber": "Schritt {{number}}", + "removeStep": "Schritt entfernen", + "confirmRunTitle": "Dieses Makro ausführen?", + "confirmRunBody": "\"{{name}}\" sendet Befehle an {{host}}.", + "confirmRunAction": "Makro ausführen", + "stepTypes": { + "send": { + "label": "Befehl senden", + "help": "Gibt den Text im Terminal ein, genau so, als ob Sie ihn eingetippt hätten." + }, + "wait": { + "label": "Auf Ausgabe warten", + "help": "Die Ausgabe wird so lange unterbrochen, bis der Server den von Ihnen angegebenen Text ausgibt, beispielsweise eine Passwortabfrage." + }, + "delay": { + "label": "Pause", + "help": "Wartet eine festgelegte Zeitspanne, bevor der nächste Schritt ausgeführt wird." + }, + "if": { + "label": "Wenn die Ausgabe enthält", + "help": "Prüft alles, was der Server bisher ausgegeben hat, und führt dann einen von zwei Zweigen aus." + }, + "repeat": { + "label": "Wiederholen", + "help": "Führt die darin enthaltenen Schritte eine festgelegte Anzahl von Malen aus." + } + }, + "send": { + "text": "Befehl oder Text", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Nach dem Absenden die Eingabetaste drücken", + "pressEnterHelp": "Deaktivieren Sie diese Option, um Text einzugeben, ohne ihn abzusenden." + }, + "wait": { + "pattern": "Warten Sie, bis die Ausgabe Folgendes enthält", + "patternPlaceholder": "Passwort:", + "timeout": "Aufgeben nach", + "onTimeout": "Wenn es nie erscheint", + "stop": "Stoppe das Makro", + "continue": "Mach trotzdem weiter." + }, + "delay": { + "duration": "Warten Sie auf" + }, + "ifStep": { + "pattern": "Wenn die Ausgabe enthält", + "patternPlaceholder": "Aktiv: aktiv (läuft)", + "then": "Dann tu Folgendes:", + "else": "Andernfalls tun Sie Folgendes:", + "emptyBranch": "Keine Stufen" + }, + "repeat": { + "count": "Anzahl der Male", + "times": "mal" + }, + "match": { + "useRegex": "Übereinstimmung mit einem regulären Ausdruck", + "useRegexHelp": "Lassen Sie dies weg, damit der Text genau so bleibt, wie er geschrieben steht.", + "invalidRegex": "Kein gültiger regulärer Ausdruck, wird als Klartext erkannt." + }, + "seconds": "Sekunden", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/el_GR.json b/plugins/ssh-terminal/locales/translated/el_GR.json index 0df5bd174..f277f94ad 100644 --- a/plugins/ssh-terminal/locales/translated/el_GR.json +++ b/plugins/ssh-terminal/locales/translated/el_GR.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Αποθηκευμένα Θέματα", "scrollbackBufferLabel": "Ενδιάμεσο buffer κύλισης προς τα πίσω", "scrollbackMaxLines": "Μέγιστος αριθμός γραμμών που διατηρούνται στο ιστορικό", - "startupSnippetLabel": "Απόσπασμα εκκίνησης", "sudoPasswordAutoFill": "Αυτόματη συμπλήρωση κωδικού πρόσβασης Sudo", "sudoPasswordAutoFillDesc": "Αποθηκεύστε έναν κωδικό πρόσβασης sudo, ώστε οι μετρήσεις του κεντρικού υπολογιστή, οι προτροπές του τερματικού και άλλα να μπορούν να εκτελούν αυτόματα προνομιακές εντολές.", "sudoPasswordAutoFillLabel": "Αυτόματη συμπλήρωση κωδικού πρόσβασης Sudo", @@ -335,7 +334,8 @@ "history": "Ιστορία", "localTerminal": "Τοπικό τερματικό", "sshTools": "Εργαλεία SSH", - "terminal": "Τερματικό" + "terminal": "Τερματικό", + "macros": "Μακροεντολές" }, "sshTools": { "broadcastInputPlaceholder": "Πληκτρολογήστε εδώ για να μεταδώσετε πληκτρολογήσεις...", @@ -363,5 +363,112 @@ "off": "Μακριά από", "on": "Επί", "title": "Προεπιλογές σύνδεσης" + }, + "keybindings": { + "copy": "Αντιγραφή επιλογής", + "paste": "Πάστα", + "sendControlCode": "Αποστολή σήματος Ctrl+γράμμα", + "sendText": "Αποστολή κειμένου", + "controlCodeLabel": "Επιστολή", + "textLabel": "Μήνυμα προς αποστολή", + "appendEnterLabel": "Πατήστε Enter μετά την αποστολή", + "clipboardPermissionNote": "Ενδέχεται να ζητηθεί άδεια για το πρόχειρο κατά την πρώτη χρήση σε ορισμένα προγράμματα περιήγησης.", + "textRequired": "Εισαγάγετε το κείμενο που θέλετε να στείλετε", + "controlCodeRequired": "Εισαγάγετε ένα μόνο γράμμα για τον κωδικό ελέγχου" + }, + "macros": { + "title": "Μακροεντολές", + "description": "Εκτελέστε μια αποθηκευμένη σειρά εντολών σε ένα τερματικό και αντιδράστε σε ό,τι στέλνει πίσω ο διακομιστής.", + "target": "Στόχος", + "noTerminal": "Δεν υπάρχει ανοιχτό τερματικό", + "empty": "Δεν υπάρχουν ακόμη μακροεντολές", + "emptyHint": "Δημιουργήστε μια μακροεντολή για να αυτοματοποιήσετε μια ροή τερματικού, όπως σύνδεση, επανεκκίνηση μιας υπηρεσίας ή απάντηση σε μηνύματα.", + "create": "Νέα μακροεντολή", + "back": "Επιστροφή στις μακροεντολές", + "name": "Ονομα", + "namePlaceholder": "Επανεκκίνηση διακομιστή ιστού", + "nameRequired": "Δώστε ένα όνομα στη μακροεντολή πριν από την αποθήκευση", + "descriptionLabel": "Περιγραφή", + "descriptionPlaceholder": "Τι κάνει αυτή η μακροεντολή (προαιρετικό)", + "unsaved": "Μη αποθηκευμένες αλλαγές", + "save": "Εκτός", + "saved": "Η μακροεντολή αποθηκεύτηκε", + "saveFailed": "Δεν ήταν δυνατή η αποθήκευση της μακροεντολής", + "delete": "Διαγράφω", + "deleteTitle": "Διαγραφή αυτής της μακροεντολής;", + "deleteBody": "Το \"{{name}}\" θα διαγραφεί οριστικά. Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας.", + "deleted": "Η μακροεντολή διαγράφηκε", + "cancel": "Ματαίωση", + "run": "Τρέξιμο", + "stop": "Στάση", + "running": "Τρέξιμο...", + "completed": "Η μακροεντολή ολοκληρώθηκε", + "failed": "Η μακροεντολή απέτυχε.", + "loadFailed": "Δεν ήταν δυνατή η φόρτωση μακροεντολών", + "needTerminal": "Ανοίξτε και συνδέστε πρώτα ένα τερματικό", + "noSteps": "Αυτή η μακροεντολή δεν έχει ακόμη βήματα. Προσθέστε ένα παρακάτω.", + "addStep": "Προσθήκη βήματος", + "stepNumber": "Βήμα {{number}}", + "removeStep": "Αφαίρεση βήματος", + "confirmRunTitle": "Εκτέλεση αυτής της μακροεντολής;", + "confirmRunBody": "Η εντολή \"{{name}}\" θα στείλει εντολές στον χρήστη {{host}}.", + "confirmRunAction": "Εκτέλεση μακροεντολής", + "stepTypes": { + "send": { + "label": "Αποστολή εντολής", + "help": "Πληκτρολογεί κείμενο στο τερματικό, όπως θα το γράφατε." + }, + "wait": { + "label": "Περιμένετε για έξοδο", + "help": "Παύει μέχρι ο διακομιστής να εκτυπώσει το κείμενο που ονομάζετε, σαν να σας ζητείται κωδικός πρόσβασης." + }, + "delay": { + "label": "Παύση", + "help": "Περιμένει ένα συγκεκριμένο χρονικό διάστημα πριν από το επόμενο βήμα." + }, + "if": { + "label": "Εάν η έξοδος περιέχει", + "help": "Ελέγχει όλα όσα έχει εκτυπώσει μέχρι στιγμής ο διακομιστής και, στη συνέχεια, εκτελεί μία από τις δύο διακλαδώσεις." + }, + "repeat": { + "label": "Επαναλαμβάνω", + "help": "Εκτελεί τα βήματα στο εσωτερικό του έναν καθορισμένο αριθμό φορών." + } + }, + "send": { + "text": "Εντολή ή κείμενο", + "textPlaceholder": "επανεκκίνηση systemctl nginx", + "pressEnter": "Πατήστε Enter μετά την αποστολή", + "pressEnterHelp": "Απενεργοποιήστε αυτήν τη λειτουργία για να πληκτρολογείτε κείμενο χωρίς να το υποβάλλετε." + }, + "wait": { + "pattern": "Περιμένετε μέχρι η έξοδος να περιέχει", + "patternPlaceholder": "σύνθημα:", + "timeout": "Παραιτήσου μετά", + "onTimeout": "Αν δεν εμφανιστεί ποτέ", + "stop": "Διακοπή της μακροεντολής", + "continue": "Συνέχισε ούτως ή άλλως" + }, + "delay": { + "duration": "Περιμένω για" + }, + "ifStep": { + "pattern": "Εάν η έξοδος περιέχει", + "patternPlaceholder": "Ενεργό: ενεργό (σε λειτουργία)", + "then": "Στη συνέχεια, κάντε αυτό", + "else": "Διαφορετικά, κάντε αυτό", + "emptyBranch": "Χωρίς σκαλοπάτια" + }, + "repeat": { + "count": "Αριθμός φορών", + "times": "φορές" + }, + "match": { + "useRegex": "Ταίριασμα με μια κανονική έκφραση", + "useRegexHelp": "Αφήστε το απενεργοποιημένο για να ταιριάζει ακριβώς με το κείμενο όπως είναι γραμμένο.", + "invalidRegex": "Δεν είναι έγκυρη κανονική έκφραση, θα αντιστοιχιστεί ως απλό κείμενο" + }, + "seconds": "δευτερόλεπτα", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/es_ES.json b/plugins/ssh-terminal/locales/translated/es_ES.json index 452993d9e..714f4b69c 100644 --- a/plugins/ssh-terminal/locales/translated/es_ES.json +++ b/plugins/ssh-terminal/locales/translated/es_ES.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Temas guardados", "scrollbackBufferLabel": "Búfer de retroceso", "scrollbackMaxLines": "Número máximo de líneas conservadas en el historial", - "startupSnippetLabel": "Fragmento de inicio", "sudoPasswordAutoFill": "Autocompletado de contraseña de sudo", "sudoPasswordAutoFillDesc": "Almacenar una contraseña sudo para que las métricas del host, las solicitudes del terminal y otros puedan ejecutar comandos privilegiados automáticamente.", "sudoPasswordAutoFillLabel": "Autocompletado de contraseña sudo", @@ -335,7 +334,8 @@ "history": "Historial", "localTerminal": "Terminal local", "sshTools": "Herramientas SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macros" }, "sshTools": { "broadcastInputPlaceholder": "Escriba aquí para transmitir pulsaciones...", @@ -363,5 +363,112 @@ "off": "Apagado", "on": "En", "title": "Valores predeterminados de conexión" + }, + "keybindings": { + "copy": "Copiar selección", + "paste": "Pasta", + "sendControlCode": "Enviar señal Ctrl+letra", + "sendText": "Enviar mensaje de texto", + "controlCodeLabel": "Carta", + "textLabel": "Texto para enviar", + "appendEnterLabel": "Presione Enter después de enviar", + "clipboardPermissionNote": "En algunos navegadores, puede solicitar permiso para copiar y pegar el contenido del portapapeles la primera vez que se utilice.", + "textRequired": "Introduce el texto que deseas enviar", + "controlCodeRequired": "Introduzca una sola letra para el código de control." + }, + "macros": { + "title": "Macros", + "description": "Ejecuta una serie de comandos guardados en una terminal y reacciona a la respuesta que envíe el servidor.", + "target": "Objetivo", + "noTerminal": "No hay terminal abierta", + "empty": "Aún no hay macros", + "emptyHint": "Crea una macro para automatizar un flujo de terminal, como iniciar sesión, reiniciar un servicio o responder a preguntas.", + "create": "Nueva macro", + "back": "Volver a las macros", + "name": "Nombre", + "namePlaceholder": "Reiniciar el servidor web", + "nameRequired": "Dale un nombre a la macro antes de guardarla.", + "descriptionLabel": "Descripción", + "descriptionPlaceholder": "Qué hace esta macro (opcional)", + "unsaved": "Cambios no guardados", + "save": "Ahorrar", + "saved": "Macro guardada", + "saveFailed": "No se pudo guardar la macro", + "delete": "Borrar", + "deleteTitle": "¿Eliminar esta macro?", + "deleteBody": "\"{{name}}\" se eliminará permanentemente. Esto no se puede deshacer.", + "deleted": "Macro eliminada", + "cancel": "Cancelar", + "run": "Correr", + "stop": "Detener", + "running": "Correr...", + "completed": "Macro terminado", + "failed": "La macro falló", + "loadFailed": "No se pudieron cargar las macros", + "needTerminal": "Primero, abre y conecta una terminal.", + "noSteps": "Esta macro aún no tiene pasos. Añada uno a continuación.", + "addStep": "Agregar paso", + "stepNumber": "Paso {{number}}", + "removeStep": "Eliminar paso", + "confirmRunTitle": "¿Ejecutar esta macro?", + "confirmRunBody": "\"{{name}}\" enviará comandos a {{host}}.", + "confirmRunAction": "Ejecutar macro", + "stepTypes": { + "send": { + "label": "Enviar comando", + "help": "Escribe el texto en la terminal, igual que si lo hubieras escrito tú." + }, + "wait": { + "label": "Espere la salida", + "help": "Hace una pausa hasta que el servidor imprime el texto que usted especifique, como si fuera una solicitud de contraseña." + }, + "delay": { + "label": "Pausa", + "help": "Espera un tiempo fijo antes de pasar al siguiente paso." + }, + "if": { + "label": "Si la salida contiene", + "help": "Comprueba todo lo que el servidor ha impreso hasta el momento y, a continuación, ejecuta una de las dos ramas." + }, + "repeat": { + "label": "Repetir", + "help": "Reproduce los pasos que contiene un número determinado de veces." + } + }, + "send": { + "text": "Comando o texto", + "textPlaceholder": "systemctl reiniciar nginx", + "pressEnter": "Presione Enter después de enviar", + "pressEnterHelp": "Desactive esta opción para escribir texto sin enviarlo." + }, + "wait": { + "pattern": "Espere hasta que la salida contenga", + "patternPlaceholder": "contraseña:", + "timeout": "Rendirse después", + "onTimeout": "Si nunca aparece", + "stop": "Detén la macro", + "continue": "Sigue adelante de todos modos." + }, + "delay": { + "duration": "Esperar" + }, + "ifStep": { + "pattern": "Si la salida contiene", + "patternPlaceholder": "Activo: activo (corriendo)", + "then": "Entonces haz esto", + "else": "De lo contrario, haz esto", + "emptyBranch": "Sin escalones" + }, + "repeat": { + "count": "Número de veces", + "times": "veces" + }, + "match": { + "useRegex": "Coincidencia con una expresión regular", + "useRegexHelp": "Omita esta opción para que coincida exactamente con el texto escrito.", + "invalidRegex": "No es una expresión regular válida, se interpretará como texto plano." + }, + "seconds": "artículos de segunda clase", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/fi_FI.json b/plugins/ssh-terminal/locales/translated/fi_FI.json index 5f603b0a3..e104fd4b4 100644 --- a/plugins/ssh-terminal/locales/translated/fi_FI.json +++ b/plugins/ssh-terminal/locales/translated/fi_FI.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Tallennetut teemat", "scrollbackBufferLabel": "Vieroituspuskuri", "scrollbackMaxLines": "Historiassa säilytettävien rivien enimmäismäärä", - "startupSnippetLabel": "Käynnistyskoodinpätkä", "sudoPasswordAutoFill": "Sudo-salasanan automaattinen täyttö", "sudoPasswordAutoFillDesc": "Tallenna sudo-salasana, jotta isäntämittarit, päätekehotteet ja muut voivat suorittaa etuoikeutettuja komentoja automaattisesti.", "sudoPasswordAutoFillLabel": "Sudo-salasanan automaattinen täyttö", @@ -335,7 +334,8 @@ "history": "Historia", "localTerminal": "Paikallinen terminaali", "sshTools": "SSH-työkalut", - "terminal": "Terminaali" + "terminal": "Terminaali", + "macros": "Makrot" }, "sshTools": { "broadcastInputPlaceholder": "Kirjoita tähän lähettääksesi näppäinpainalluksia...", @@ -363,5 +363,112 @@ "off": "Pois", "on": "Päällä", "title": "Yhteysoletukset" + }, + "keybindings": { + "copy": "Kopioi valinta", + "paste": "Liitä", + "sendControlCode": "Lähetä Ctrl+kirjain-signaali", + "sendText": "Lähetä tekstiviesti", + "controlCodeLabel": "Kirje", + "textLabel": "Lähetettävä tekstiviesti", + "appendEnterLabel": "Paina Enter lähetyksen jälkeen", + "clipboardPermissionNote": "Saattaa pyytää leikepöydän käyttöoikeutta ensimmäisellä käyttökerralla joissakin selaimissa.", + "textRequired": "Kirjoita lähetettävä teksti", + "controlCodeRequired": "Syötä yksi kirjain ohjauskoodille" + }, + "macros": { + "title": "Makrot", + "description": "Suorita tallennettu komentosarja terminaalissa ja reagoi palvelimen lähettämiin komentoihin.", + "target": "Kohde", + "noTerminal": "Ei auki olevaa terminaalia", + "empty": "Ei vielä makroja", + "emptyHint": "Luo makro automatisoidaksesi päätetyönkulun, kuten sisäänkirjautumisen, palvelun uudelleenkäynnistyksen tai kehotteisiin vastaamisen.", + "create": "Uusi makro", + "back": "Takaisin makroihin", + "name": "Nimi", + "namePlaceholder": "Käynnistä web-palvelin uudelleen", + "nameRequired": "Anna makrolle nimi ennen tallentamista", + "descriptionLabel": "Kuvaus", + "descriptionPlaceholder": "Mitä tämä makro tekee (valinnainen)", + "unsaved": "Tallentamattomat muutokset", + "save": "Tallentaa", + "saved": "Makro tallennettu", + "saveFailed": "Makroa ei voitu tallentaa", + "delete": "Poistaa", + "deleteTitle": "Poistetaanko tämä makro?", + "deleteBody": "\"{{name}}\" poistetaan pysyvästi. Tätä ei voi perua.", + "deleted": "Makro poistettu", + "cancel": "Peruuttaa", + "run": "Juokse", + "stop": "Stop", + "running": "Juoksemassa...", + "completed": "Makro valmis", + "failed": "Makro epäonnistui", + "loadFailed": "Makrojen lataaminen epäonnistui", + "needTerminal": "Avaa ja kytke ensin päätelaite", + "noSteps": "Tällä makrolla ei ole vielä vaiheita. Lisää yksi alle.", + "addStep": "Lisää vaihe", + "stepNumber": "Vaihe {{number}}", + "removeStep": "Poista vaihe", + "confirmRunTitle": "Suoritetaanko tämä makro?", + "confirmRunBody": "\"{{name}}\" lähettää komentoja {{host}}:een.", + "confirmRunAction": "Suorita makro", + "stepTypes": { + "send": { + "label": "Lähetä komento", + "help": "Kirjoittaa tekstiä terminaaliin aivan kuin olisit kirjoittanut sen." + }, + "wait": { + "label": "Odota tulostetta", + "help": "Pysähtyy, kunnes palvelin tulostaa nimeämäsi tekstin, kuten salasanakehotteen." + }, + "delay": { + "label": "Tauko", + "help": "Odottaa tietyn ajan ennen seuraavaa vaihetta." + }, + "if": { + "label": "Jos tuloste sisältää", + "help": "Tarkistaa kaiken, mitä palvelin on tähän mennessä tulostanut, ja suorittaa sitten toisen kahdesta haarasta." + }, + "repeat": { + "label": "Toistaa", + "help": "Suorittaa sen sisällä olevat vaiheet tietyn määrän kertoja." + } + }, + "send": { + "text": "Komento tai teksti", + "textPlaceholder": "systemctl käynnistä nginx uudelleen", + "pressEnter": "Paina Enter lähetyksen jälkeen", + "pressEnterHelp": "Poista tämä käytöstä kirjoittaaksesi tekstiä lähettämättä sitä." + }, + "wait": { + "pattern": "Odota, kunnes tuloste sisältää", + "patternPlaceholder": "salasana:", + "timeout": "Luovuta jälkeenpäin", + "onTimeout": "Jos se ei koskaan ilmesty", + "stop": "Pysäytä makro", + "continue": "Jatka joka tapauksessa" + }, + "delay": { + "duration": "Odota" + }, + "ifStep": { + "pattern": "Jos tuloste sisältää", + "patternPlaceholder": "Aktiivinen: aktiivinen (käynnissä)", + "then": "Tee sitten tämä", + "else": "Muuten tee näin", + "emptyBranch": "Ei portaita" + }, + "repeat": { + "count": "Kuinka monta kertaa", + "times": "kertaa" + }, + "match": { + "useRegex": "Säännöllisen lausekkeen mukainen osuma", + "useRegexHelp": "Jätä tämä pois, jotta teksti vastaa täysin kirjoitettua tekstiä.", + "invalidRegex": "Ei kelvollinen säännöllinen lauseke, se tulkitaan pelkkänä tekstinä" + }, + "seconds": "sekuntia", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/fr_FR.json b/plugins/ssh-terminal/locales/translated/fr_FR.json index 26e8e4541..316a3b3e5 100644 --- a/plugins/ssh-terminal/locales/translated/fr_FR.json +++ b/plugins/ssh-terminal/locales/translated/fr_FR.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Thèmes enregistrés", "scrollbackBufferLabel": "Mémoire de défilement", "scrollbackMaxLines": "Nombre maximal de lignes conservées dans l'historique", - "startupSnippetLabel": "Extrait de démarrage", "sudoPasswordAutoFill": "Remplissage automatique du mot de passe sudo", "sudoPasswordAutoFillDesc": "Enregistrer un mot de passe sudo pour que les métriques de l'hôte, les invites du terminal et autres puissent exécuter automatiquement des commandes privilégiées.", "sudoPasswordAutoFillLabel": "Remplissage automatique du mot de passe sudo", @@ -335,7 +334,8 @@ "history": "Historique", "localTerminal": "Terminal local", "sshTools": "Outils SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macros" }, "sshTools": { "broadcastInputPlaceholder": "Tapez ici pour diffuser les frappes...", @@ -363,5 +363,112 @@ "off": "Désactivé", "on": "Sur", "title": "Paramètres de connexion par défaut" + }, + "keybindings": { + "copy": "Copier la sélection", + "paste": "Coller", + "sendControlCode": "Envoyer le signal Ctrl+lettre", + "sendText": "Envoyer un SMS", + "controlCodeLabel": "Lettre", + "textLabel": "Texte à envoyer", + "appendEnterLabel": "Appuyez sur Entrée après l'envoi", + "clipboardPermissionNote": "Il se peut que certains navigateurs demandent l'autorisation d'accéder au presse-papiers lors de la première utilisation.", + "textRequired": "Saisissez le texte à envoyer", + "controlCodeRequired": "Saisissez une seule lettre pour le code de contrôle" + }, + "macros": { + "title": "Macros", + "description": "Exécutez une série de commandes enregistrées dans un terminal et réagissez à la réponse du serveur.", + "target": "Cible", + "noTerminal": "Aucun terminal ouvert", + "empty": "Pas encore de macros", + "emptyHint": "Créez une macro pour automatiser un flux de terminal, comme la connexion, le redémarrage d'un service ou la réponse à des invites.", + "create": "Nouvelle macro", + "back": "Retour aux macros", + "name": "Nom", + "namePlaceholder": "Redémarrer le serveur web", + "nameRequired": "Donnez un nom à la macro avant de l'enregistrer.", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Fonctionnement de cette macro (facultatif)", + "unsaved": "Modifications non enregistrées", + "save": "Sauvegarder", + "saved": "Macro enregistrée", + "saveFailed": "Impossible d'enregistrer la macro", + "delete": "Supprimer", + "deleteTitle": "Supprimer cette macro ?", + "deleteBody": "\"{{name}}\" sera définitivement supprimé. Cette action est irréversible.", + "deleted": "Macro supprimée", + "cancel": "Annuler", + "run": "Courir", + "stop": "Arrêt", + "running": "En cours d'exécution...", + "completed": "Macro terminé", + "failed": "Échec de la macro", + "loadFailed": "Impossible de charger les macros", + "needTerminal": "Ouvrez et connectez d'abord un terminal.", + "noSteps": "Cette macro ne comporte pas encore d'étapes. Ajoutez-en une ci-dessous.", + "addStep": "Ajouter une étape", + "stepNumber": "Étape {{number}}", + "removeStep": "Supprimer l'étape", + "confirmRunTitle": "Exécuter cette macro ?", + "confirmRunBody": "\"{{name}}\" enverra des commandes à {{host}}.", + "confirmRunAction": "Exécuter une macro", + "stepTypes": { + "send": { + "label": "Envoyer la commande", + "help": "Saisit du texte dans le terminal, comme si vous l'aviez saisi manuellement." + }, + "wait": { + "label": "Attendez la sortie", + "help": "Il y a une pause jusqu'à ce que le serveur affiche le texte que vous avez indiqué, comme une invite de mot de passe." + }, + "delay": { + "label": "Pause", + "help": "Attendre un laps de temps fixe avant de passer à l'étape suivante." + }, + "if": { + "label": "Si la sortie contient", + "help": "Vérifie tout ce que le serveur a imprimé jusqu'à présent, puis exécute l'une des deux branches." + }, + "repeat": { + "label": "Répéter", + "help": "Exécute les étapes qu'il contient un nombre de fois défini." + } + }, + "send": { + "text": "Commande ou texte", + "textPlaceholder": "systemctl redémarrer nginx", + "pressEnter": "Appuyez sur Entrée après l'envoi", + "pressEnterHelp": "Désactivez cette option pour saisir du texte sans le soumettre." + }, + "wait": { + "pattern": "Attendez que la sortie contienne", + "patternPlaceholder": "mot de passe:", + "timeout": "Abandonner après", + "onTimeout": "S'il n'apparaît jamais", + "stop": "Arrêtez la macro", + "continue": "Continuez quand même" + }, + "delay": { + "duration": "Attendre" + }, + "ifStep": { + "pattern": "Si la sortie contient", + "patternPlaceholder": "Actif : actif (en cours d'exécution)", + "then": "Ensuite, faites ceci", + "else": "Sinon, faites ceci.", + "emptyBranch": "Pas d'étapes" + }, + "repeat": { + "count": "Nombre de fois", + "times": "fois" + }, + "match": { + "useRegex": "Correspondance avec une expression régulière", + "useRegexHelp": "Laissez cette section de côté pour que le texte corresponde exactement à ce qui est écrit.", + "invalidRegex": "Il ne s'agit pas d'une expression régulière valide ; elle sera interprétée comme du texte brut." + }, + "seconds": "secondes", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/he_IL.json b/plugins/ssh-terminal/locales/translated/he_IL.json index 70b13cda7..02aa4b14f 100644 --- a/plugins/ssh-terminal/locales/translated/he_IL.json +++ b/plugins/ssh-terminal/locales/translated/he_IL.json @@ -307,7 +307,6 @@ "savedThemesLabel": "ערכות נושא שמורות", "scrollbackBufferLabel": "מאגר גלילה לאחור", "scrollbackMaxLines": "מספר השורות המרבי שנשמר בהיסטוריה", - "startupSnippetLabel": "קטע הפעלה", "sudoPasswordAutoFill": "מילוי אוטומטי של סיסמאות Sudo", "sudoPasswordAutoFillDesc": "אחסן סיסמת sudo כדי שמדדי מארח, הנחיות מסוף ואחרים יוכלו להריץ פקודות בעלות הרשאות באופן אוטומטי.", "sudoPasswordAutoFillLabel": "מילוי אוטומטי של סיסמת סודו", @@ -335,7 +334,8 @@ "history": "הִיסטוֹרִיָה", "localTerminal": "מסוף מקומי", "sshTools": "כלי SSH", - "terminal": "מָסוֹף" + "terminal": "מָסוֹף", + "macros": "פקודות מאקרו" }, "sshTools": { "broadcastInputPlaceholder": "הקלד כאן כדי לשדר הקשות מקלדת...", @@ -363,5 +363,112 @@ "off": "כבוי", "on": "עַל", "title": "ברירות מחדל של חיבור" + }, + "keybindings": { + "copy": "העתקת הבחירה", + "paste": "לְהַדבִּיק", + "sendControlCode": "שלח אות Ctrl+אות", + "sendText": "שלח טקסט", + "controlCodeLabel": "מִכְתָב", + "textLabel": "טקסט לשליחה", + "appendEnterLabel": "לחץ על Enter לאחר השליחה", + "clipboardPermissionNote": "ייתכן שתבקש הרשאה ללוח בעת השימוש הראשון בדפדפנים מסוימים.", + "textRequired": "הזן את הטקסט לשליחה", + "controlCodeRequired": "הזן אות אחת עבור קוד הבקרה" + }, + "macros": { + "title": "פקודות מאקרו", + "description": "הפעל סדרה שמורה של פקודות בטרמינל, והגב למה שהשרת שולח בחזרה.", + "target": "יַעַד", + "noTerminal": "אין טרמינל פתוח", + "empty": "עדיין אין פקודות מאקרו", + "emptyHint": "צור מאקרו כדי להפוך זרימת מסוף לאוטומטית, כמו כניסה, הפעלה מחדש של שירות או מענה לבקשות.", + "create": "מאקרו חדש", + "back": "חזרה למאקרו", + "name": "שֵׁם", + "namePlaceholder": "הפעל מחדש את שרת האינטרנט", + "nameRequired": "תן שם למאקרו לפני השמירה", + "descriptionLabel": "תֵאוּר", + "descriptionPlaceholder": "מה המאקרו הזה עושה (אופציונלי)", + "unsaved": "שינויים שלא נשמרו", + "save": "לְהַצִיל", + "saved": "מאקרו נשמר", + "saveFailed": "לא ניתן היה לשמור את המאקרו", + "delete": "לִמְחוֹק", + "deleteTitle": "למחוק את המאקרו הזה?", + "deleteBody": "\"{{name}}\" יימחק לצמיתות. לא ניתן לבטל פעולה זו.", + "deleted": "מאקרו נמחק", + "cancel": "לְבַטֵל", + "run": "לָרוּץ", + "stop": "לְהַפְסִיק", + "running": "רִיצָה...", + "completed": "מאקרו הסתיים", + "failed": "מאקרו נכשל", + "loadFailed": "לא ניתן היה לטעון פקודות מאקרו", + "needTerminal": "פתח וחבר תחילה טרמינל", + "noSteps": "למאקרו הזה עדיין אין שלבים. הוסף אחד למטה.", + "addStep": "הוסף שלב", + "stepNumber": "שלב {{number}}", + "removeStep": "הסר שלב", + "confirmRunTitle": "להפעיל את המאקרו הזה?", + "confirmRunBody": "\"{{name}}\" ישלח פקודות אל {{host}}.", + "confirmRunAction": "הפעל מאקרו", + "stepTypes": { + "send": { + "label": "שלח פקודה", + "help": "מקליד טקסט לתוך הטרמינל, אותו הדבר כאילו הקלדת אותו." + }, + "wait": { + "label": "המתן לפלט", + "help": "מושהה עד שהשרת ידפיס את הטקסט שציינת, כמו בקשת סיסמה." + }, + "delay": { + "label": "הַפסָקָה", + "help": "ממתין פרק זמן קבוע לפני השלב הבא." + }, + "if": { + "label": "אם הפלט מכיל", + "help": "בודק את כל מה שהשרת הדפיס עד כה, ולאחר מכן מפעיל אחד משני ענפים." + }, + "repeat": { + "label": "לַחֲזוֹר עַל", + "help": "מריץ את השלבים בתוכו מספר קבוע של פעמים." + } + }, + "send": { + "text": "פקודה או טקסט", + "textPlaceholder": "הפעל מחדש את nginx של systemctl", + "pressEnter": "לחץ על Enter לאחר השליחה", + "pressEnterHelp": "כבה אפשרות זו כדי להקליד טקסט מבלי לשלוח אותו." + }, + "wait": { + "pattern": "המתן עד שהפלט יכיל", + "patternPlaceholder": "סִיסמָה:", + "timeout": "לוותר אחרי", + "onTimeout": "אם זה אף פעם לא מופיע", + "stop": "עצור את המאקרו", + "continue": "המשיכו בכל מקרה" + }, + "delay": { + "duration": "חכה ל" + }, + "ifStep": { + "pattern": "אם הפלט מכיל", + "patternPlaceholder": "פעיל: פעיל (רץ)", + "then": "אז תעשה את זה", + "else": "אחרת תעשו את זה", + "emptyBranch": "אין מדרגות" + }, + "repeat": { + "count": "מספר פעמים", + "times": "פִּי" + }, + "match": { + "useRegex": "התאמה עם ביטוי רגולרי", + "useRegexHelp": "השאר את זה כבוי כדי שהטקסט יתאים בדיוק כפי שנכתב.", + "invalidRegex": "ביטוי רגולרי לא תקין, הוא יתאים כטקסט רגיל" + }, + "seconds": "שניות", + "secondsShort": "{{count}}ים" } } diff --git a/plugins/ssh-terminal/locales/translated/hi_IN.json b/plugins/ssh-terminal/locales/translated/hi_IN.json index 447a7510c..a5054561c 100644 --- a/plugins/ssh-terminal/locales/translated/hi_IN.json +++ b/plugins/ssh-terminal/locales/translated/hi_IN.json @@ -307,7 +307,6 @@ "savedThemesLabel": "सहेजे गए विषय", "scrollbackBufferLabel": "स्क्रॉलबैक बफ़र", "scrollbackMaxLines": "इतिहास में रखी जाने वाली लाइनों की अधिकतम संख्या", - "startupSnippetLabel": "स्टार्टअप स्निपेट", "sudoPasswordAutoFill": "sudo पासवर्ड ऑटो-फिल", "sudoPasswordAutoFillDesc": "sudo पासवर्ड संग्रहीत करें ताकि होस्ट मेट्रिक्स, टर्मिनल संकेत और अन्य स्वचालित रूप से विशेषाधिकार प्राप्त कमांड चला सकें।", "sudoPasswordAutoFillLabel": "sudo पासवर्ड स्वतः-भरण", @@ -335,7 +334,8 @@ "history": "इतिहास", "localTerminal": "स्थानीय टर्मिनल", "sshTools": "SSH टूल्स", - "terminal": "टर्मिनल" + "terminal": "टर्मिनल", + "macros": "मैक्रो" }, "sshTools": { "broadcastInputPlaceholder": "कीस्ट्रोक्स प्रसारित करने के लिए यहाँ टाइप करें...", @@ -363,5 +363,112 @@ "off": "बंद", "on": "पर", "title": "कनेक्शन डिफ़ॉल्ट" + }, + "keybindings": { + "copy": "चयन की प्रतिलिपि बनाएँ", + "paste": "पेस्ट करें", + "sendControlCode": "Ctrl+अक्षर सिग्नल भेजें", + "sendText": "संदेश भेजें", + "controlCodeLabel": "पत्र", + "textLabel": "भेजने के लिए संदेश", + "appendEnterLabel": "भेजने के बाद एंटर दबाएँ", + "clipboardPermissionNote": "कुछ ब्राउज़रों में पहली बार उपयोग करने पर क्लिपबोर्ड की अनुमति मांगी जा सकती है।", + "textRequired": "भेजने के लिए टेक्स्ट दर्ज करें", + "controlCodeRequired": "नियंत्रण कोड के लिए एक अक्षर दर्ज करें" + }, + "macros": { + "title": "मैक्रो", + "description": "टर्मिनल में सहेजे गए कमांडों की एक श्रृंखला चलाएं, और सर्वर द्वारा वापस भेजे गए कमांडों पर प्रतिक्रिया दें।", + "target": "लक्ष्य", + "noTerminal": "कोई टर्मिनल खुला नहीं है", + "empty": "अभी तक कोई मैक्रो नहीं है", + "emptyHint": "लॉग इन करना, किसी सेवा को रीस्टार्ट करना या प्रॉम्प्ट का जवाब देना जैसे टर्मिनल कार्यों को स्वचालित करने के लिए एक मैक्रो बनाएं।", + "create": "नया मैक्रो", + "back": "मैक्रोज़ पर वापस जाएँ", + "name": "नाम", + "namePlaceholder": "वेब सर्वर को पुनः आरंभ करें", + "nameRequired": "मैक्रो को सेव करने से पहले उसे एक नाम दें।", + "descriptionLabel": "विवरण", + "descriptionPlaceholder": "यह मैक्रो क्या करता है (वैकल्पिक)", + "unsaved": "असहेजित परिवर्तन", + "save": "बचाना", + "saved": "मैक्रो सहेजा गया", + "saveFailed": "मैक्रो को सहेजा नहीं जा सका", + "delete": "मिटाना", + "deleteTitle": "क्या इस मैक्रो को हटा दें?", + "deleteBody": "\"{{name}}\" को स्थायी रूप से हटा दिया जाएगा। इसे पूर्ववत नहीं किया जा सकता है।", + "deleted": "मैक्रो हटा दिया गया", + "cancel": "रद्द करना", + "run": "दौड़ना", + "stop": "रुकना", + "running": "दौड़ना...", + "completed": "मैक्रो समाप्त हो गया", + "failed": "मैक्रो विफल रहा", + "loadFailed": "मैक्रो लोड नहीं हो सके", + "needTerminal": "सबसे पहले एक टर्मिनल खोलें और उससे कनेक्ट करें।", + "noSteps": "इस मैक्रो में अभी तक कोई चरण नहीं है। कृपया नीचे एक चरण जोड़ें।", + "addStep": "चरण जोड़ें", + "stepNumber": "चरण {{number}}", + "removeStep": "चरण हटाएँ", + "confirmRunTitle": "क्या आप इस मैक्रो को चलाना चाहते हैं?", + "confirmRunBody": "\"{{name}}\" {{host}} को कमांड भेजेगा।", + "confirmRunAction": "मैक्रो चलाएँ", + "stepTypes": { + "send": { + "label": "कमांड भेजें", + "help": "यह टर्मिनल में ठीक उसी तरह टेक्स्ट टाइप करता है जैसे आप उसे खुद टाइप करते हैं।" + }, + "wait": { + "label": "आउटपुट की प्रतीक्षा करें", + "help": "यह तब तक रुका रहता है जब तक सर्वर आपके द्वारा बताए गए टेक्स्ट को प्रिंट नहीं कर देता, जैसे पासवर्ड प्रॉम्प्ट।" + }, + "delay": { + "label": "विराम", + "help": "अगले चरण से पहले एक निश्चित समय तक प्रतीक्षा करता है।" + }, + "if": { + "label": "यदि आउटपुट में शामिल है", + "help": "यह सर्वर द्वारा अब तक प्रिंट की गई हर चीज़ की जाँच करता है, फिर दो शाखाओं में से एक को चलाता है।" + }, + "repeat": { + "label": "दोहराना", + "help": "इसके अंदर के चरणों को निर्धारित संख्या में बार-बार दोहराता है।" + } + }, + "send": { + "text": "आदेश या पाठ", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "भेजने के बाद एंटर दबाएँ", + "pressEnterHelp": "इसे बंद करने पर आपको टेक्स्ट टाइप करने के बाद उसे सबमिट करने की आवश्यकता नहीं होगी।" + }, + "wait": { + "pattern": "आउटपुट में यह जानकारी आने तक प्रतीक्षा करें", + "patternPlaceholder": "पासवर्ड:", + "timeout": "हार मान लो", + "onTimeout": "अगर यह कभी प्रकट नहीं होता", + "stop": "मैक्रो को रोकें", + "continue": "फिर भी आगे बढ़ते रहो।" + }, + "delay": { + "duration": "के लिए प्रतीक्षा करें" + }, + "ifStep": { + "pattern": "यदि आउटपुट में शामिल है", + "patternPlaceholder": "सक्रिय: सक्रिय (चल रहा)", + "then": "फिर ऐसा करें", + "else": "अन्यथा ऐसा करें", + "emptyBranch": "कोई कदम नहीं" + }, + "repeat": { + "count": "कितनी बार", + "times": "टाइम्स" + }, + "match": { + "useRegex": "नियमित अभिव्यक्ति के साथ मिलान करें", + "useRegexHelp": "इसे हटा दें ताकि यह लिखे गए पाठ से बिल्कुल मेल खाए।", + "invalidRegex": "यह एक मान्य रेगुलर एक्सप्रेशन नहीं है, इसे सादे टेक्स्ट के रूप में ही माना जाएगा।" + }, + "seconds": "सेकंड", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/hu_HU.json b/plugins/ssh-terminal/locales/translated/hu_HU.json index 43250f4ad..f4787cdef 100644 --- a/plugins/ssh-terminal/locales/translated/hu_HU.json +++ b/plugins/ssh-terminal/locales/translated/hu_HU.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Mentett témák", "scrollbackBufferLabel": "Görgetési puffer", "scrollbackMaxLines": "A történetben tárolt sorok maximális száma", - "startupSnippetLabel": "Indítási kódrészlet", "sudoPasswordAutoFill": "Sudo jelszó automatikus kitöltése", "sudoPasswordAutoFillDesc": "Tároljon egy sudo jelszót, hogy a gazdagép metrikái, a terminálpromptok és mások automatikusan futtathassanak privilegizált parancsokat.", "sudoPasswordAutoFillLabel": "Sudo jelszó automatikus kitöltése", @@ -335,7 +334,8 @@ "history": "Történelem", "localTerminal": "Helyi terminál", "sshTools": "SSH eszközök", - "terminal": "Terminál" + "terminal": "Terminál", + "macros": "Makrók" }, "sshTools": { "broadcastInputPlaceholder": "Írjon ide a billentyűleütések küldéséhez...", @@ -363,5 +363,112 @@ "off": "Le", "on": "On", "title": "Kapcsolat alapértelmezései" + }, + "keybindings": { + "copy": "Kijelölés másolása", + "paste": "Paszta", + "sendControlCode": "Ctrl+betűjel küldése", + "sendText": "SMS küldése", + "controlCodeLabel": "Levél", + "textLabel": "Küldendő szöveg", + "appendEnterLabel": "Küldés után nyomja meg az Enter billentyűt", + "clipboardPermissionNote": "Egyes böngészőkben első használatkor vágólap-engedélyt kérhet.", + "textRequired": "Írd be a küldendő szöveget", + "controlCodeRequired": "Írjon be egyetlen betűt az ellenőrző kódhoz" + }, + "macros": { + "title": "Makrók", + "description": "Futtass egy mentett parancssorozatot egy terminálban, és reagálj a szerver által küldöttekre.", + "target": "Cél", + "noTerminal": "Nincs nyitva terminál", + "empty": "Még nincsenek makrók", + "emptyHint": "Hozz létre egy makrót egy terminálfolyamat automatizálásához, például bejelentkezéshez, szolgáltatás újraindításához vagy a kérdések megválaszolásához.", + "create": "Új makró", + "back": "Vissza a makrókhoz", + "name": "Név", + "namePlaceholder": "Indítsa újra a webszervert", + "nameRequired": "Mentés előtt adjon nevet a makrónak", + "descriptionLabel": "Leírás", + "descriptionPlaceholder": "Mit csinál ez a makró (opcionális)", + "unsaved": "Nem mentett változtatások", + "save": "Megtakarítás", + "saved": "Makró mentve", + "saveFailed": "Nem sikerült menteni a makrót", + "delete": "Töröl", + "deleteTitle": "Törli ezt a makrót?", + "deleteBody": "A(z) „{{name}}” véglegesen törlésre kerül. Ez a művelet nem vonható vissza.", + "deleted": "Makró törölve", + "cancel": "Mégsem", + "run": "Fut", + "stop": "Stop", + "running": "Futás...", + "completed": "Makró kész", + "failed": "Makró sikertelen", + "loadFailed": "Nem sikerült betölteni a makrókat", + "needTerminal": "Először nyiss meg és csatlakoztass egy terminált", + "noSteps": "Ennek a makrónak még nincsenek lépései. Adjon hozzá egyet alább.", + "addStep": "Lépés hozzáadása", + "stepNumber": "{{number}} lépés", + "removeStep": "Lépés eltávolítása", + "confirmRunTitle": "Futtassa ezt a makrót?", + "confirmRunBody": "A „{{name}}” parancsokat küld a {{host}} címre.", + "confirmRunAction": "Makró futtatása", + "stepTypes": { + "send": { + "label": "Parancs küldése", + "help": "Beírja a szöveget a terminálba, ugyanúgy, mintha beírta volna." + }, + "wait": { + "label": "Várjon a kimenetre", + "help": "Szünetet tart, amíg a szerver ki nem írja a megnevezett szöveget, például egy jelszókérést." + }, + "delay": { + "label": "Szünet", + "help": "Egy meghatározott ideig vár a következő lépés előtt." + }, + "if": { + "label": "Ha a kimenet tartalmaz", + "help": "Ellenőrzi a szerver által eddig kinyomtatott összes adatot, majd lefuttatja a két ág egyikét." + }, + "repeat": { + "label": "Ismétlés", + "help": "Meghatározott számú alkalommal futtatja le a benne található lépéseket." + } + }, + "send": { + "text": "Parancs vagy szöveg", + "textPlaceholder": "systemctl újraindítja az nginx-et", + "pressEnter": "Küldés után nyomja meg az Enter billentyűt", + "pressEnterHelp": "Kapcsold ki ezt, ha beküldés nélkül szeretnél szöveget beírni." + }, + "wait": { + "pattern": "Várjon, amíg a kimenet tartalmazza a", + "patternPlaceholder": "jelszó:", + "timeout": "Add fel utána", + "onTimeout": "Ha soha nem jelenik meg", + "stop": "A makró leállítása", + "continue": "Folytasd mindenképpen" + }, + "delay": { + "duration": "Várj" + }, + "ifStep": { + "pattern": "Ha a kimenet tartalmaz", + "patternPlaceholder": "Aktív: aktív (fut)", + "then": "Akkor csináld ezt", + "else": "Egyébként tedd ezt", + "emptyBranch": "Nincsenek lépcsők" + }, + "repeat": { + "count": "Alkalmak száma", + "times": "alkalommal" + }, + "match": { + "useRegex": "Egyezés reguláris kifejezéssel", + "useRegexHelp": "Hagyd ezt ki, hogy a szöveg pontosan úgy illeszkedjen, ahogyan le van írva.", + "invalidRegex": "Nem érvényes reguláris kifejezés, sima szövegként lesz egyezve." + }, + "seconds": "másodperc", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/id_ID.json b/plugins/ssh-terminal/locales/translated/id_ID.json index 686db05c6..bba785c0d 100644 --- a/plugins/ssh-terminal/locales/translated/id_ID.json +++ b/plugins/ssh-terminal/locales/translated/id_ID.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Tema Tersimpan", "scrollbackBufferLabel": "Buffer Gulir Balik", "scrollbackMaxLines": "Jumlah maksimum baris yang disimpan dalam riwayat", - "startupSnippetLabel": "Cuplikan Startup", "sudoPasswordAutoFill": "Isi Otomatis Kata Sandi Sudo", "sudoPasswordAutoFillDesc": "Simpan kata sandi sudo agar metrik host, perintah terminal, dan lainnya dapat menjalankan perintah istimewa secara otomatis.", "sudoPasswordAutoFillLabel": "Isi Otomatis Kata Sandi Sudo", @@ -335,7 +334,8 @@ "history": "Sejarah", "localTerminal": "Terminal Lokal", "sshTools": "Alat SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Makro" }, "sshTools": { "broadcastInputPlaceholder": "Ketik di sini untuk menyiarkan ketukan keyboard...", @@ -363,5 +363,112 @@ "off": "Mati", "on": "Pada", "title": "Pengaturan Koneksi Default" + }, + "keybindings": { + "copy": "Salin pilihan", + "paste": "Pasta", + "sendControlCode": "Kirim sinyal Ctrl+huruf", + "sendText": "Kirim pesan teks", + "controlCodeLabel": "Surat", + "textLabel": "Teks untuk dikirim", + "appendEnterLabel": "Tekan Enter setelah mengirim", + "clipboardPermissionNote": "Pada beberapa browser, mungkin akan meminta izin akses clipboard saat pertama kali digunakan.", + "textRequired": "Masukkan teks yang ingin dikirim", + "controlCodeRequired": "Masukkan satu huruf untuk kode kontrol." + }, + "macros": { + "title": "Makro", + "description": "Jalankan serangkaian perintah yang tersimpan di terminal, dan tanggapi apa yang dikirimkan kembali oleh server.", + "target": "Target", + "noTerminal": "Tidak ada terminal yang terbuka", + "empty": "Belum ada makro", + "emptyHint": "Buat makro untuk mengotomatiskan alur terminal, seperti masuk, memulai ulang layanan, atau menjawab perintah.", + "create": "Makro baru", + "back": "Kembali ke makro", + "name": "Nama", + "namePlaceholder": "Mulai ulang server web", + "nameRequired": "Beri nama makro sebelum menyimpannya.", + "descriptionLabel": "Keterangan", + "descriptionPlaceholder": "Fungsi makro ini (opsional)", + "unsaved": "Perubahan yang belum disimpan", + "save": "Menyimpan", + "saved": "Makro tersimpan", + "saveFailed": "Makro tidak dapat disimpan.", + "delete": "Menghapus", + "deleteTitle": "Hapus makro ini?", + "deleteBody": "\"{{name}}\" akan dihapus secara permanen. Ini tidak dapat dibatalkan.", + "deleted": "Makro dihapus", + "cancel": "Membatalkan", + "run": "Berlari", + "stop": "Berhenti", + "running": "Berlari...", + "completed": "Makro selesai", + "failed": "Makro gagal", + "loadFailed": "Makro tidak dapat dimuat", + "needTerminal": "Buka dan sambungkan terminal terlebih dahulu.", + "noSteps": "Makro ini belum memiliki langkah. Tambahkan langkah di bawah ini.", + "addStep": "Tambahkan langkah", + "stepNumber": "Langkah {{number}}", + "removeStep": "Hapus langkah", + "confirmRunTitle": "Jalankan makro ini?", + "confirmRunBody": "\"{{name}}\" akan mengirimkan perintah ke {{host}}.", + "confirmRunAction": "Jalankan makro", + "stepTypes": { + "send": { + "label": "Kirim perintah", + "help": "Mengetik teks ke terminal, sama seperti jika Anda mengetiknya." + }, + "wait": { + "label": "Tunggu hingga muncul output.", + "help": "Akan berhenti sejenak hingga server menampilkan teks yang Anda sebutkan, seperti saat meminta kata sandi." + }, + "delay": { + "label": "Berhenti sebentar", + "help": "Menunggu dalam jangka waktu tertentu sebelum melanjutkan ke langkah berikutnya." + }, + "if": { + "label": "Jika output berisi", + "help": "Memeriksa semua yang telah dicetak server sejauh ini, lalu menjalankan salah satu dari dua cabang." + }, + "repeat": { + "label": "Mengulang", + "help": "Menjalankan langkah-langkah di dalamnya sebanyak jumlah kali yang telah ditentukan." + } + }, + "send": { + "text": "Perintah atau teks", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Tekan Enter setelah mengirim", + "pressEnterHelp": "Matikan opsi ini untuk mengetik teks tanpa mengirimkannya." + }, + "wait": { + "pattern": "Tunggu hingga output berisi", + "patternPlaceholder": "kata sandi:", + "timeout": "Menyerah setelah", + "onTimeout": "Jika itu tidak pernah muncul", + "stop": "Hentikan makro", + "continue": "Tetaplah melanjutkan" + }, + "delay": { + "duration": "Tunggu" + }, + "ifStep": { + "pattern": "Jika outputnya berisi", + "patternPlaceholder": "Aktif: aktif (berjalan)", + "then": "Kemudian lakukan ini", + "else": "Jika tidak, lakukan ini", + "emptyBranch": "Tidak ada tangga" + }, + "repeat": { + "count": "Jumlah kali", + "times": "waktu" + }, + "match": { + "useRegex": "Cocokkan dengan ekspresi reguler", + "useRegexHelp": "Hilangkan bagian ini agar sesuai persis dengan teks yang tertulis.", + "invalidRegex": "Bukan ekspresi reguler yang valid, akan dicocokkan sebagai teks biasa." + }, + "seconds": "detik", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/it_IT.json b/plugins/ssh-terminal/locales/translated/it_IT.json index a8505ab96..09bb8c4f7 100644 --- a/plugins/ssh-terminal/locales/translated/it_IT.json +++ b/plugins/ssh-terminal/locales/translated/it_IT.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Temi salvati", "scrollbackBufferLabel": "Buffer di scorrimento", "scrollbackMaxLines": "Numero massimo di righe conservate nella cronologia", - "startupSnippetLabel": "Frammento di avvio", "sudoPasswordAutoFill": "Compilazione automatica password sudo", "sudoPasswordAutoFillDesc": "Memorizza una password sudo in modo che le metriche dell'host, i prompt del terminale e altro possano eseguire automaticamente comandi privilegiati.", "sudoPasswordAutoFillLabel": "Compilazione automatica password sudo", @@ -335,7 +334,8 @@ "history": "Cronologia", "localTerminal": "Terminale locale", "sshTools": "Strumenti SSH", - "terminal": "Terminale" + "terminal": "Terminale", + "macros": "Macro" }, "sshTools": { "broadcastInputPlaceholder": "Digita qui per trasmettere le pressioni dei tasti...", @@ -363,5 +363,112 @@ "off": "Spento", "on": "SU", "title": "Impostazioni predefinite di connessione" + }, + "keybindings": { + "copy": "Copia selezione", + "paste": "Impasto", + "sendControlCode": "Invia il segnale Ctrl+lettera", + "sendText": "Invia testo", + "controlCodeLabel": "Lettera", + "textLabel": "Testo da inviare", + "appendEnterLabel": "Premi Invio dopo aver inviato", + "clipboardPermissionNote": "In alcuni browser potrebbe essere richiesta l'autorizzazione per l'apposizione degli appunti al primo utilizzo.", + "textRequired": "Inserisci il testo da inviare", + "controlCodeRequired": "Inserisci una sola lettera come codice di controllo" + }, + "macros": { + "title": "Macro", + "description": "Esegui una serie di comandi salvati in un terminale e reagisci a ciò che il server ti invia in risposta.", + "target": "Bersaglio", + "noTerminal": "Nessun terminale aperto", + "empty": "Ancora nessuna macro", + "emptyHint": "Crea una macro per automatizzare un flusso di lavoro da terminale, come l'accesso, il riavvio di un servizio o la risposta a richieste.", + "create": "Nuova macro", + "back": "Torna alle macro", + "name": "Nome", + "namePlaceholder": "Riavvia il server web", + "nameRequired": "Assegna un nome alla macro prima di salvarla.", + "descriptionLabel": "Descrizione", + "descriptionPlaceholder": "Cosa fa questa macro (facoltativo)", + "unsaved": "Modifiche non salvate", + "save": "Salva", + "saved": "Macro salvata", + "saveFailed": "Impossibile salvare la macro", + "delete": "Eliminare", + "deleteTitle": "Eliminare questa macro?", + "deleteBody": "\"{{name}}\" verrà eliminato definitivamente. Questa operazione non può essere annullata.", + "deleted": "Macro eliminato", + "cancel": "Cancellare", + "run": "Correre", + "stop": "Fermare", + "running": "Corsa...", + "completed": "Macro terminato", + "failed": "Il fallimento di Macro", + "loadFailed": "Impossibile caricare le macro", + "needTerminal": "Aprire e collegare prima un terminale", + "noSteps": "Questa macro non ha ancora passaggi. Aggiungine uno qui sotto.", + "addStep": "Aggiungi passaggio", + "stepNumber": "Passo {{number}}", + "removeStep": "Rimuovere il passaggio", + "confirmRunTitle": "Eseguire questa macro?", + "confirmRunBody": "\"{{name}}\" invierà comandi a {{host}}.", + "confirmRunAction": "Esegui macro", + "stepTypes": { + "send": { + "label": "inviare comando", + "help": "Digita il testo nel terminale, proprio come se lo avessi digitato tu." + }, + "wait": { + "label": "Attendi l'output", + "help": "Si mette in pausa finché il server non stampa il testo che hai specificato, ad esempio una richiesta di password." + }, + "delay": { + "label": "Pausa", + "help": "Attende un intervallo di tempo fisso prima di procedere al passo successivo." + }, + "if": { + "label": "Se l'output contiene", + "help": "Controlla tutto ciò che il server ha stampato finora, quindi esegue uno dei due rami." + }, + "repeat": { + "label": "Ripetere", + "help": "Esegue i passaggi al suo interno un numero prestabilito di volte." + } + }, + "send": { + "text": "Comando o testo", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Premi Invio dopo aver inviato", + "pressEnterHelp": "Disattiva questa opzione per digitare del testo senza inviarlo." + }, + "wait": { + "pattern": "Attendi che l'output contenga", + "patternPlaceholder": "password:", + "timeout": "Rinunciare dopo", + "onTimeout": "Se non appare mai", + "stop": "Interrompi la macro", + "continue": "Continua comunque" + }, + "delay": { + "duration": "Attendi" + }, + "ifStep": { + "pattern": "Se l'output contiene", + "patternPlaceholder": "Attivo: attivo (in corsa)", + "then": "Poi fai questo", + "else": "Altrimenti fai così", + "emptyBranch": "Nessun gradino" + }, + "repeat": { + "count": "Numero di volte", + "times": "volte" + }, + "match": { + "useRegex": "Corrispondenza con un'espressione regolare", + "useRegexHelp": "Lasciare vuoto questo elemento per far sì che il testo corrisponda esattamente a come è stato scritto.", + "invalidRegex": "Non è un'espressione regolare valida, verrà interpretata come testo semplice." + }, + "seconds": "secondi", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/ja_JP.json b/plugins/ssh-terminal/locales/translated/ja_JP.json index 29e7a7f48..227500592 100644 --- a/plugins/ssh-terminal/locales/translated/ja_JP.json +++ b/plugins/ssh-terminal/locales/translated/ja_JP.json @@ -307,7 +307,6 @@ "savedThemesLabel": "保存済みテーマ", "scrollbackBufferLabel": "スクロールバッファ", "scrollbackMaxLines": "履歴に保持する最大行数", - "startupSnippetLabel": "起動時スニペット", "sudoPasswordAutoFill": "Sudo パスワードの自動入力", "sudoPasswordAutoFillDesc": "sudo パスワードを保存して、ホストメトリクス、ターミナルプロンプトなどが自動的に特権コマンドを実行できるようにします", "sudoPasswordAutoFillLabel": "sudo パスワードの自動入力", @@ -335,7 +334,8 @@ "history": "履歴", "localTerminal": "ローカル端末", "sshTools": "SSHツール", - "terminal": "ターミナル" + "terminal": "ターミナル", + "macros": "マクロ" }, "sshTools": { "broadcastInputPlaceholder": "ここに入力してキーをブロードキャスト...", @@ -363,5 +363,112 @@ "off": "オフ", "on": "の上", "title": "接続のデフォルト設定" + }, + "keybindings": { + "copy": "選択範囲をコピー", + "paste": "ペースト", + "sendControlCode": "Ctrlキーと文字キーを同時に押して信号を送る", + "sendText": "テキストを送信", + "controlCodeLabel": "手紙", + "textLabel": "送信するテキスト", + "appendEnterLabel": "送信後にEnterキーを押してください。", + "clipboardPermissionNote": "一部のブラウザでは、初回使用時にクリップボードのアクセス許可を求められる場合があります。", + "textRequired": "送信するテキストを入力してください", + "controlCodeRequired": "制御コードとして1文字を入力してください" + }, + "macros": { + "title": "マクロ", + "description": "ターミナルで保存済みのコマンド系列を実行し、サーバーからの応答に応じて処理を行います。", + "target": "ターゲット", + "noTerminal": "端末が開いていません", + "empty": "マクロはまだありません", + "emptyHint": "ログイン、サービスの再起動、プロンプトへの応答など、ターミナル操作の流れを自動化するマクロを作成します。", + "create": "新しいマクロ", + "back": "マクロに戻る", + "name": "名前", + "namePlaceholder": "ウェブサーバーを再起動します", + "nameRequired": "保存する前にマクロに名前を付けてください。", + "descriptionLabel": "説明", + "descriptionPlaceholder": "このマクロの機能(オプション)", + "unsaved": "保存されていない変更", + "save": "保存", + "saved": "マクロが保存されました", + "saveFailed": "マクロを保存できませんでした", + "delete": "消去", + "deleteTitle": "このマクロを削除しますか?", + "deleteBody": "\"{{name}}\" は完全に削除されます。元に戻すことはできません。", + "deleted": "マクロが削除されました", + "cancel": "キャンセル", + "run": "走る", + "stop": "停止", + "running": "実行中...", + "completed": "マクロ撮影終了", + "failed": "マクロが失敗しました", + "loadFailed": "マクロを読み込めませんでした", + "needTerminal": "まずターミナルを開いて接続します。", + "noSteps": "このマクロにはまだ手順がありません。以下に手順を追加してください。", + "addStep": "ステップを追加する", + "stepNumber": "ステップ {{number}}", + "removeStep": "削除手順", + "confirmRunTitle": "このマクロを実行しますか?", + "confirmRunBody": "\"{{name}}\" は {{host}} にコマンドを送信します。", + "confirmRunAction": "マクロを実行する", + "stepTypes": { + "send": { + "label": "コマンドを送信", + "help": "端末にテキストを入力します。まるで手入力したかのように表示されます。" + }, + "wait": { + "label": "出力を待つ", + "help": "サーバーが指定したテキスト(パスワード入力画面など)を表示するまで一時停止します。" + }, + "delay": { + "label": "一時停止", + "help": "次のステップに進む前に、一定時間待機します。" + }, + "if": { + "label": "出力に以下が含まれる場合", + "help": "サーバーがこれまでに出力したすべての内容を確認し、その後、2つの分岐のうちいずれか1つを実行します。" + }, + "repeat": { + "label": "繰り返す", + "help": "指定された回数だけ、その中の手順を実行します。" + } + }, + "send": { + "text": "コマンドまたはテキスト", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "送信後にEnterキーを押してください。", + "pressEnterHelp": "テキストを送信せずに入力する場合は、これをオフにしてください。" + }, + "wait": { + "pattern": "出力に以下の内容が含まれるまで待機してください", + "patternPlaceholder": "パスワード:", + "timeout": "諦めた後", + "onTimeout": "それが決して現れない場合", + "stop": "マクロを停止する", + "continue": "とにかく続けて" + }, + "delay": { + "duration": "待って" + }, + "ifStep": { + "pattern": "出力に以下が含まれる場合", + "patternPlaceholder": "アクティブ:アクティブ(実行中)", + "then": "次にこれを実行してください", + "else": "そうでなければ、これを実行してください。", + "emptyBranch": "階段なし" + }, + "repeat": { + "count": "回数", + "times": "タイムズ" + }, + "match": { + "useRegex": "正規表現で一致させる", + "useRegexHelp": "原文と完全に一致させるには、この部分を削除してください。", + "invalidRegex": "有効な正規表現ではないため、プレーンテキストとしてマッチします。" + }, + "seconds": "秒", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/ko_KR.json b/plugins/ssh-terminal/locales/translated/ko_KR.json index b1e3ec508..480f52609 100644 --- a/plugins/ssh-terminal/locales/translated/ko_KR.json +++ b/plugins/ssh-terminal/locales/translated/ko_KR.json @@ -307,7 +307,6 @@ "savedThemesLabel": "저장된 테마", "scrollbackBufferLabel": "스크롤백 버퍼", "scrollbackMaxLines": "기록에 유지할 최대 라인 수", - "startupSnippetLabel": "시작 스니펫", "sudoPasswordAutoFill": "Sudo 비밀번호 자동 완성", "sudoPasswordAutoFillDesc": "sudo 비밀번호를 저장하여 호스트 메트릭, 터미널 프롬프트 등이 자동으로 권한 있는 명령을 실행할 수 있도록 합니다", "sudoPasswordAutoFillLabel": "Sudo 비밀번호 자동 입력", @@ -335,7 +334,8 @@ "history": "기록", "localTerminal": "로컬 터미널", "sshTools": "SSH 도구", - "terminal": "터미널" + "terminal": "터미널", + "macros": "매크로" }, "sshTools": { "broadcastInputPlaceholder": "키 입력을 브로드캐스트하려면 여기에 입력...", @@ -363,5 +363,112 @@ "off": "끄다", "on": "~에", "title": "연결 기본 설정" + }, + "keybindings": { + "copy": "선택 복사", + "paste": "반죽", + "sendControlCode": "Ctrl+문자 신호를 보내세요", + "sendText": "문자 보내기", + "controlCodeLabel": "편지", + "textLabel": "보낼 텍스트", + "appendEnterLabel": "전송 후 Enter 키를 누르세요.", + "clipboardPermissionNote": "일부 브라우저에서는 처음 사용 시 클립보드 권한을 요청할 수 있습니다.", + "textRequired": "보낼 텍스트를 입력하세요", + "controlCodeRequired": "제어 코드에 한 글자를 입력하세요." + }, + "macros": { + "title": "매크로", + "description": "터미널에서 저장된 일련의 명령어를 실행하고, 서버가 보내는 응답에 반응합니다.", + "target": "목표", + "noTerminal": "터미널이 열려 있지 않습니다", + "empty": "아직 매크로가 없습니다.", + "emptyHint": "터미널 로그인, 서비스 재시작 또는 프롬프트 응답과 같은 터미널 작업 흐름을 자동화하는 매크로를 만드세요.", + "create": "새로운 매크로", + "back": "매크로로 돌아가기", + "name": "이름", + "namePlaceholder": "웹 서버를 재시작하세요", + "nameRequired": "매크로를 저장하기 전에 이름을 지정하세요.", + "descriptionLabel": "설명", + "descriptionPlaceholder": "이 매크로의 기능 (선택 사항)", + "unsaved": "저장되지 않은 변경 사항", + "save": "구하다", + "saved": "매크로 저장됨", + "saveFailed": "매크로를 저장할 수 없습니다.", + "delete": "삭제", + "deleteTitle": "이 매크로를 삭제하시겠습니까?", + "deleteBody": "\"{{name}}\"는 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다.", + "deleted": "매크로 삭제됨", + "cancel": "취소", + "run": "달리다", + "stop": "멈추다", + "running": "달리기...", + "completed": "매크로 완료", + "failed": "매크로 실패", + "loadFailed": "매크로를 불러올 수 없습니다.", + "needTerminal": "먼저 터미널을 열고 연결하세요.", + "noSteps": "이 매크로는 아직 단계가 없습니다. 아래에 단계를 추가하세요.", + "addStep": "단계 추가", + "stepNumber": "단계 {{number}}", + "removeStep": "제거 단계", + "confirmRunTitle": "이 매크로를 실행하시겠습니까?", + "confirmRunBody": "\"{{name}}\"는 {{host}}로 명령을 보냅니다.", + "confirmRunAction": "매크로 실행", + "stepTypes": { + "send": { + "label": "명령을 보내세요", + "help": "터미널에 텍스트를 입력합니다. 마치 직접 타이핑하는 것과 같습니다." + }, + "wait": { + "label": "출력이 나올 때까지 기다리세요", + "help": "서버가 사용자가 입력한 텍스트(예: 비밀번호 입력창)를 출력할 때까지 일시 중지됩니다." + }, + "delay": { + "label": "정지시키다", + "help": "다음 단계로 넘어가기 전에 일정 시간 동안 기다립니다." + }, + "if": { + "label": "출력에 다음 내용이 포함되어 있는 경우", + "help": "서버에서 지금까지 출력한 모든 내용을 확인한 후 두 가지 분기 중 하나를 실행합니다." + }, + "repeat": { + "label": "반복하다", + "help": "그 안에 있는 단계들을 정해진 횟수만큼 실행합니다." + } + }, + "send": { + "text": "명령 또는 텍스트", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "전송 후 Enter 키를 누르세요.", + "pressEnterHelp": "텍스트를 입력하되 제출하지 않으려면 이 기능을 끄세요." + }, + "wait": { + "pattern": "출력 내용이 포함될 때까지 기다리세요", + "patternPlaceholder": "비밀번호:", + "timeout": "포기한 후에", + "onTimeout": "만약 전혀 나타나지 않는다면", + "stop": "매크로를 중지하세요", + "continue": "어쨌든 계속 가세요" + }, + "delay": { + "duration": "기다리세요" + }, + "ifStep": { + "pattern": "출력에 다음 내용이 포함되어 있는 경우", + "patternPlaceholder": "활성: 활성(실행 중)", + "then": "그럼 이렇게 하세요", + "else": "그렇지 않으면 이렇게 하세요.", + "emptyBranch": "단계 없음" + }, + "repeat": { + "count": "횟수", + "times": "타임스" + }, + "match": { + "useRegex": "정규 표현식과 일치", + "useRegexHelp": "원문과 정확히 일치하려면 이 부분을 삭제하세요.", + "invalidRegex": "유효한 정규 표현식이 아니므로 일반 텍스트로 처리됩니다." + }, + "seconds": "초", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/nl_NL.json b/plugins/ssh-terminal/locales/translated/nl_NL.json index 2f743bd32..b83d0c1bf 100644 --- a/plugins/ssh-terminal/locales/translated/nl_NL.json +++ b/plugins/ssh-terminal/locales/translated/nl_NL.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Opgeslagen thema's", "scrollbackBufferLabel": "Terugscrollbuffer", "scrollbackMaxLines": "Maximaal aantal regels dat in de geschiedenis wordt bewaard", - "startupSnippetLabel": "Opstartfragment", "sudoPasswordAutoFill": "Sudo-wachtwoord automatisch invullen", "sudoPasswordAutoFillDesc": "Sla een sudo-wachtwoord op, zodat hoststatistieken, terminalprompts en andere programma's automatisch geprivilegieerde opdrachten kunnen uitvoeren.", "sudoPasswordAutoFillLabel": "Sudo-wachtwoord automatisch invullen", @@ -335,7 +334,8 @@ "history": "Geschiedenis", "localTerminal": "Lokale terminal", "sshTools": "SSH-tools", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macro's" }, "sshTools": { "broadcastInputPlaceholder": "Typ hier om toetsaanslagen te verzenden...", @@ -363,5 +363,112 @@ "off": "Uit", "on": "Op", "title": "Verbindingsstandaarden" + }, + "keybindings": { + "copy": "Selectie kopiëren", + "paste": "Plakken", + "sendControlCode": "Verzend het Ctrl+letter-signaal", + "sendText": "Sms verzenden", + "controlCodeLabel": "Brief", + "textLabel": "Tekst om te verzenden", + "appendEnterLabel": "Druk op Enter na het verzenden.", + "clipboardPermissionNote": "In sommige browsers kan bij het eerste gebruik om toestemming voor het gebruik van het klembord worden gevraagd.", + "textRequired": "Voer de tekst in die u wilt verzenden", + "controlCodeRequired": "Voer één letter in als besturingscode." + }, + "macros": { + "title": "Macro's", + "description": "Voer een opgeslagen reeks commando's uit in een terminal en reageer op wat de server terugstuurt.", + "target": "Doel", + "noTerminal": "Geen terminal geopend", + "empty": "Nog geen macro's", + "emptyHint": "Maak een macro om een terminalproces te automatiseren, zoals inloggen, een service herstarten of prompts beantwoorden.", + "create": "Nieuwe macro", + "back": "Terug naar macro's", + "name": "Naam", + "namePlaceholder": "Webserver opnieuw opstarten", + "nameRequired": "Geef de macro een naam voordat je hem opslaat.", + "descriptionLabel": "Beschrijving", + "descriptionPlaceholder": "Wat deze macro doet (optioneel)", + "unsaved": "Niet-opgeslagen wijzigingen", + "save": "Redden", + "saved": "Macro opgeslagen", + "saveFailed": "Het lukte niet om de macro op te slaan.", + "delete": "Verwijderen", + "deleteTitle": "Deze macro verwijderen?", + "deleteBody": "\"{{name}}\" wordt permanent verwijderd. Dit kan niet ongedaan gemaakt worden.", + "deleted": "Macro verwijderd", + "cancel": "Annuleren", + "run": "Loop", + "stop": "Stop", + "running": "Rennen...", + "completed": "Macro voltooid", + "failed": "Macro mislukt", + "loadFailed": "Macro's konden niet worden geladen.", + "needTerminal": "Open en verbind eerst een terminal.", + "noSteps": "Deze macro bevat nog geen stappen. Voeg er hieronder een toe.", + "addStep": "Voeg stap toe", + "stepNumber": "Stap {{number}}", + "removeStep": "Verwijder stap", + "confirmRunTitle": "Voer deze macro uit?", + "confirmRunBody": "\"{{name}}\" zal commando's naar {{host}} sturen.", + "confirmRunAction": "Macro uitvoeren", + "stepTypes": { + "send": { + "label": "Verzend commando", + "help": "Typt tekst in de terminal, net alsof je die zelf hebt ingetypt." + }, + "wait": { + "label": "Wacht op uitvoer", + "help": "Het programma pauzeert totdat de server de door u opgegeven tekst afdrukt, zoals bij een wachtwoordprompt." + }, + "delay": { + "label": "Pauze", + "help": "Wacht een vaste tijdsduur voordat de volgende stap wordt uitgevoerd." + }, + "if": { + "label": "Als de uitvoer het volgende bevat:", + "help": "Controleert alles wat de server tot nu toe heeft afgedrukt en voert vervolgens een van de twee vertakkingen uit." + }, + "repeat": { + "label": "Herhalen", + "help": "Voert de stappen binnenin een vast aantal keren uit." + } + }, + "send": { + "text": "Opdracht of tekst", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Druk op Enter na het verzenden.", + "pressEnterHelp": "Schakel deze optie uit om tekst te typen zonder deze te verzenden." + }, + "wait": { + "pattern": "Wacht tot de uitvoer het volgende bevat:", + "patternPlaceholder": "wachtwoord:", + "timeout": "Geef het op na", + "onTimeout": "Als het nooit verschijnt", + "stop": "Stop de macro", + "continue": "Ga toch door." + }, + "delay": { + "duration": "Wachten op" + }, + "ifStep": { + "pattern": "Als de uitvoer het volgende bevat:", + "patternPlaceholder": "Actief: actief (lopend)", + "then": "Doe dan dit", + "else": "Doe anders dit", + "emptyBranch": "Geen treden" + }, + "repeat": { + "count": "Aantal keren", + "times": "tijden" + }, + "match": { + "useRegex": "Overeenkomst met een reguliere expressie", + "useRegexHelp": "Laat dit weg zodat de tekst exact overeenkomt met de geschreven tekst.", + "invalidRegex": "Dit is geen geldige reguliere expressie; het wordt als platte tekst herkend." + }, + "seconds": "seconden", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/no_NO.json b/plugins/ssh-terminal/locales/translated/no_NO.json index d4dac7d79..0d5baf002 100644 --- a/plugins/ssh-terminal/locales/translated/no_NO.json +++ b/plugins/ssh-terminal/locales/translated/no_NO.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Lagrede temaer", "scrollbackBufferLabel": "Tilbakespolingsbuffer", "scrollbackMaxLines": "Maksimalt antall linjer lagret i historikken", - "startupSnippetLabel": "Oppstartsutdrag", "sudoPasswordAutoFill": "Sudo-passord automatisk utfylling", "sudoPasswordAutoFillDesc": "Lagre et sudo-passord slik at vertsmålinger, terminalledetekster og andre kan kjøre privilegerte kommandoer automatisk.", "sudoPasswordAutoFillLabel": "Sudo-passord automatisk utfylling", @@ -335,7 +334,8 @@ "history": "Historie", "localTerminal": "Lokal terminal", "sshTools": "SSH-verktøy", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Makroer" }, "sshTools": { "broadcastInputPlaceholder": "Skriv her for å kringkaste tastetrykk...", @@ -363,5 +363,112 @@ "off": "Av", "on": "På", "title": "Standardinnstillinger for tilkobling" + }, + "keybindings": { + "copy": "Kopier valg", + "paste": "Lim inn", + "sendControlCode": "Send Ctrl+bokstavsignal", + "sendText": "Send tekstmelding", + "controlCodeLabel": "Brev", + "textLabel": "Tekst som skal sendes", + "appendEnterLabel": "Trykk Enter etter sending", + "clipboardPermissionNote": "Kan be om tillatelse til utklippstavlen ved første gangs bruk i noen nettlesere.", + "textRequired": "Skriv inn teksten som skal sendes", + "controlCodeRequired": "Skriv inn én bokstav for kontrollkoden" + }, + "macros": { + "title": "Makroer", + "description": "Kjør en lagret serie med kommandoer i en terminal, og reager på det serveren sender tilbake.", + "target": "Mål", + "noTerminal": "Ingen terminal åpen", + "empty": "Ingen makroer ennå", + "emptyHint": "Opprett en makro for å automatisere en terminalflyt, som å logge inn, starte en tjeneste på nytt eller svare på forespørsler.", + "create": "Ny makro", + "back": "Tilbake til makroer", + "name": "Navn", + "namePlaceholder": "Start nettserveren på nytt", + "nameRequired": "Gi makroen et navn før du lagrer", + "descriptionLabel": "Beskrivelse", + "descriptionPlaceholder": "Hva denne makroen gjør (valgfritt)", + "unsaved": "Ulagrede endringer", + "save": "Spare", + "saved": "Makro lagret", + "saveFailed": "Kunne ikke lagre makroen", + "delete": "Slett", + "deleteTitle": "Slette denne makroen?", + "deleteBody": "«{{name}}» vil bli slettet permanent. Dette kan ikke angres.", + "deleted": "Makro slettet", + "cancel": "Kansellere", + "run": "Løp", + "stop": "Stoppe", + "running": "Løping...", + "completed": "Makro ferdig", + "failed": "Makroen mislyktes", + "loadFailed": "Kunne ikke laste inn makroer", + "needTerminal": "Åpne og koble til en terminal først", + "noSteps": "Denne makroen har ingen trinn ennå. Legg til et nedenfor.", + "addStep": "Legg til trinn", + "stepNumber": "Trinn {{number}}", + "removeStep": "Fjern trinn", + "confirmRunTitle": "Kjøre denne makroen?", + "confirmRunBody": "\"{{name}}\" vil sende kommandoer til {{host}}.", + "confirmRunAction": "Kjør makro", + "stepTypes": { + "send": { + "label": "Send kommando", + "help": "Skriver tekst inn i terminalen, på samme måte som om du hadde skrevet den." + }, + "wait": { + "label": "Vent på utdata", + "help": "Pauserer til serveren skriver ut teksten du oppgir, som en passordforespørsel." + }, + "delay": { + "label": "Pause", + "help": "Venter en fast tid før neste trinn." + }, + "if": { + "label": "Hvis utdataene inneholder", + "help": "Sjekker alt serveren har skrevet ut så langt, og kjører deretter en av to grener." + }, + "repeat": { + "label": "Gjenta", + "help": "Kjører trinnene inni den et angitt antall ganger." + } + }, + "send": { + "text": "Kommando eller tekst", + "textPlaceholder": "systemctl omstart nginx", + "pressEnter": "Trykk Enter etter sending", + "pressEnterHelp": "Slå av dette for å skrive tekst uten å sende den inn." + }, + "wait": { + "pattern": "Vent til utdataene inneholder", + "patternPlaceholder": "passord:", + "timeout": "Gi opp etterpå", + "onTimeout": "Hvis den aldri dukker opp", + "stop": "Stopp makroen", + "continue": "Fortsett uansett" + }, + "delay": { + "duration": "Vent på" + }, + "ifStep": { + "pattern": "Hvis utdataene inneholder", + "patternPlaceholder": "Aktiv: aktiv (løper)", + "then": "Gjør så dette", + "else": "Ellers gjør dette", + "emptyBranch": "Ingen trinn" + }, + "repeat": { + "count": "Antall ganger", + "times": "ganger" + }, + "match": { + "useRegex": "Samsvar med et regulært uttrykk", + "useRegexHelp": "La dette være av for å få teksten til å se nøyaktig slik den er skrevet.", + "invalidRegex": "Ikke et gyldig regulært uttrykk, det vil bli matchet som ren tekst" + }, + "seconds": "sekunder", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/pl_PL.json b/plugins/ssh-terminal/locales/translated/pl_PL.json index 687f8a70c..e2637385d 100644 --- a/plugins/ssh-terminal/locales/translated/pl_PL.json +++ b/plugins/ssh-terminal/locales/translated/pl_PL.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Zapisane motywy", "scrollbackBufferLabel": "Bufor przewijania", "scrollbackMaxLines": "Maksymalna liczba wierszy przechowywanych w historii", - "startupSnippetLabel": "Snippet startowy", "sudoPasswordAutoFill": "Automatyczne uzupełnianie hasła sudo", "sudoPasswordAutoFillDesc": "Przechowuj hasło sudo, aby metryki hosta, monity terminala i inne narzędzia mogły automatycznie uruchamiać polecenia uprzywilejowane.", "sudoPasswordAutoFillLabel": "Automatyczne uzupełnianie hasła sudo", @@ -335,7 +334,8 @@ "history": "Historia", "localTerminal": "Terminal lokalny", "sshTools": "Narzędzia SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Makra" }, "sshTools": { "broadcastInputPlaceholder": "Wpisz tutaj, aby rozesłać naciśnięcia klawiszy...", @@ -363,5 +363,112 @@ "off": "Wył.", "on": "Wł.", "title": "Domyślne ustawienia połączenia" + }, + "keybindings": { + "copy": "Kopiuj zaznaczenie", + "paste": "Wklej", + "sendControlCode": "Wyślij sygnał Ctrl+litera", + "sendText": "Wyślij tekst", + "controlCodeLabel": "Litera", + "textLabel": "Tekst do wysłania", + "appendEnterLabel": "Naciśnij Enter po wysłaniu", + "clipboardPermissionNote": "Przy pierwszym użyciu w niektórych przeglądarkach może zostać wyświetlony monit o zezwolenie na dostęp do schowka.", + "textRequired": "Wprowadź tekst do wysłania", + "controlCodeRequired": "Wprowadź pojedynczą literę kodu kontrolnego" + }, + "macros": { + "title": "Makra", + "description": "Uruchom zapisaną serię poleceń w terminalu i zareaguj na to, co odeśle serwer.", + "target": "Cel", + "noTerminal": "Brak otwartego terminala", + "empty": "Brak makr", + "emptyHint": "Utwórz makro, aby zautomatyzować pracę w terminalu: logowanie, restart usługi czy odpowiadanie na monity.", + "create": "Nowe makro", + "back": "Powrót do makr", + "name": "Nazwa", + "namePlaceholder": "Uruchom ponownie serwer WWW", + "nameRequired": "Przed zapisaniem nadaj makrze nazwę", + "descriptionLabel": "Opis", + "descriptionPlaceholder": "Co robi to makro (opcjonalnie)", + "unsaved": "Niezapisane zmiany", + "save": "Zapisz", + "saved": "Zapisano makro", + "saveFailed": "Nie udało się zapisać makra", + "delete": "Usuń", + "deleteTitle": "Usunąć to makro?", + "deleteBody": "„{{name}}” zostanie trwale usunięte. Nie można tego cofnąć.", + "deleted": "Usunięto makro", + "cancel": "Anuluj", + "run": "Uruchom", + "stop": "Zatrzymaj", + "running": "Działa...", + "completed": "Makro zakończone", + "failed": "Makro nie powiodło się", + "loadFailed": "Nie można załadować makr", + "needTerminal": "Najpierw otwórz i podłącz terminal", + "noSteps": "To makro nie ma jeszcze żadnych kroków. Dodaj krok poniżej.", + "addStep": "Dodaj krok", + "stepNumber": "Krok {{number}}", + "removeStep": "Usuń krok", + "confirmRunTitle": "Uruchomić to makro?", + "confirmRunBody": "„{{name}}” wyśle polecenia do {{host}}.", + "confirmRunAction": "Uruchom makro", + "stepTypes": { + "send": { + "label": "Wyślij polecenie", + "help": "Wpisuje tekst do terminala, tak samo, jakby wpisano go ręcznie." + }, + "wait": { + "label": "Czekaj na wyjście", + "help": "Wstrzymuje wykonanie, aż serwer wypisze podany tekst, np. monit o hasło." + }, + "delay": { + "label": "Pauza", + "help": "Czeka przez ustalony czas przed kolejnym krokiem." + }, + "if": { + "label": "Jeśli wyjście zawiera", + "help": "Sprawdza wszystko, co serwer wypisał do tej pory, a potem uruchamia jedną z dwóch gałęzi." + }, + "repeat": { + "label": "Powtórz", + "help": "Uruchamia zawarte w nim kroki ustaloną liczbę razy." + } + }, + "send": { + "text": "Polecenie lub tekst", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Naciśnij Enter po wysłaniu", + "pressEnterHelp": "Wyłącz tę opcję, aby wpisać tekst bez przesyłania go." + }, + "wait": { + "pattern": "Czekaj, aż wyjście będzie zawierać", + "patternPlaceholder": "password:", + "timeout": "Przerwij po", + "onTimeout": "Jeśli nigdy się nie pojawi", + "stop": "Zatrzymaj makro", + "continue": "Kontynuuj mimo wszystko" + }, + "delay": { + "duration": "Czekaj" + }, + "ifStep": { + "pattern": "Jeśli wyjście zawiera", + "patternPlaceholder": "Active: active (running)", + "then": "Następnie zrób to", + "else": "W przeciwnym razie zrób to", + "emptyBranch": "Brak kroków" + }, + "repeat": { + "count": "Liczba razy", + "times": "razy" + }, + "match": { + "useRegex": "Dopasuj do wyrażenia regularnego", + "useRegexHelp": "Zostaw wyłączone, aby dopasować tekst dokładnie tak, jak został wpisany.", + "invalidRegex": "Nie jest to prawidłowe wyrażenie regularne, zostanie dopasowane jako zwykły tekst" + }, + "seconds": "sekundy", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/pt_BR.json b/plugins/ssh-terminal/locales/translated/pt_BR.json index e000fcf85..6f090bbd9 100644 --- a/plugins/ssh-terminal/locales/translated/pt_BR.json +++ b/plugins/ssh-terminal/locales/translated/pt_BR.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Temas salvos", "scrollbackBufferLabel": "Buffer de rolagem", "scrollbackMaxLines": "Número máximo de linhas mantidas no histórico", - "startupSnippetLabel": "Trecho de startup", "sudoPasswordAutoFill": "Preenchimento automático de senhas Sudo", "sudoPasswordAutoFillDesc": "Armazene uma senha de sudo para que as métricas do host, os prompts do terminal e outros possam executar comandos privilegiados automaticamente.", "sudoPasswordAutoFillLabel": "Preenchimento automático de senha Sudo", @@ -335,7 +334,8 @@ "history": "História", "localTerminal": "Terminal local", "sshTools": "Ferramentas SSH", - "terminal": "terminal" + "terminal": "terminal", + "macros": "Macros" }, "sshTools": { "broadcastInputPlaceholder": "Digite aqui para transmitir as teclas pressionadas...", @@ -363,5 +363,112 @@ "off": "Desligado", "on": "Sobre", "title": "Configurações de conexão padrão" + }, + "keybindings": { + "copy": "Copiar seleção", + "paste": "Colar", + "sendControlCode": "Enviar sinal Ctrl+letra", + "sendText": "Enviar mensagem de texto", + "controlCodeLabel": "Carta", + "textLabel": "Texto a enviar", + "appendEnterLabel": "Pressione Enter após enviar", + "clipboardPermissionNote": "Pode solicitar permissão para acessar a área de transferência no primeiro uso em alguns navegadores.", + "textRequired": "Digite o texto que deseja enviar.", + "controlCodeRequired": "Digite uma única letra para o código de controle." + }, + "macros": { + "title": "Macros", + "description": "Execute uma série de comandos salvos em um terminal e reaja ao que o servidor envia de volta.", + "target": "Alvo", + "noTerminal": "Nenhum terminal aberto", + "empty": "Ainda não há macros.", + "emptyHint": "Crie uma macro para automatizar um fluxo de trabalho no terminal, como fazer login, reiniciar um serviço ou responder a solicitações.", + "create": "Nova macro", + "back": "Voltar às macros", + "name": "Nome", + "namePlaceholder": "Reinicie o servidor web.", + "nameRequired": "Dê um nome à macro antes de salvar.", + "descriptionLabel": "Descrição", + "descriptionPlaceholder": "O que esta macro faz (opcional)", + "unsaved": "Alterações não salvas", + "save": "Salvar", + "saved": "Macro salvo", + "saveFailed": "Não foi possível salvar a macro.", + "delete": "Excluir", + "deleteTitle": "Excluir esta macro?", + "deleteBody": "\"{{name}}\" será excluído permanentemente. Esta ação não pode ser desfeita.", + "deleted": "Macro excluída", + "cancel": "Cancelar", + "run": "Correr", + "stop": "Parar", + "running": "Correndo...", + "completed": "Macro finalizado", + "failed": "A macro falhou", + "loadFailed": "Não foi possível carregar as macros", + "needTerminal": "Primeiro, abra e conecte um terminal.", + "noSteps": "Esta macro ainda não possui etapas. Adicione uma abaixo.", + "addStep": "Adicionar etapa", + "stepNumber": "Passo {{number}}", + "removeStep": "Remover etapa", + "confirmRunTitle": "Executar esta macro?", + "confirmRunBody": "\"{{name}}\" enviará comandos para {{host}}.", + "confirmRunAction": "Executar macro", + "stepTypes": { + "send": { + "label": "Enviar comando", + "help": "Digita o texto no terminal, da mesma forma que se você o digitasse." + }, + "wait": { + "label": "Aguarde a saída", + "help": "Aguarda até que o servidor imprima o texto que você digitou, como uma solicitação de senha." + }, + "delay": { + "label": "Pausa", + "help": "Aguarda um período de tempo fixo antes da próxima etapa." + }, + "if": { + "label": "Se a saída contiver", + "help": "Verifica tudo o que o servidor imprimiu até o momento e, em seguida, executa uma das duas ramificações." + }, + "repeat": { + "label": "Repita", + "help": "Executa os passos internos um número definido de vezes." + } + }, + "send": { + "text": "Comando ou texto", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Pressione Enter após enviar", + "pressEnterHelp": "Desative esta opção para digitar texto sem enviá-lo." + }, + "wait": { + "pattern": "Aguarde até que a saída contenha", + "patternPlaceholder": "senha:", + "timeout": "Desistir depois", + "onTimeout": "Se nunca aparecer", + "stop": "Pare a macro", + "continue": "Continue mesmo assim" + }, + "delay": { + "duration": "Aguarde" + }, + "ifStep": { + "pattern": "Se a saída contiver", + "patternPlaceholder": "Ativo: ativo (em execução)", + "then": "Então faça isso", + "else": "Caso contrário, faça isto", + "emptyBranch": "Sem degraus" + }, + "repeat": { + "count": "Número de vezes", + "times": "vezes" + }, + "match": { + "useRegex": "Combinar com uma expressão regular", + "useRegexHelp": "Deixe este campo em branco para que o texto corresponda exatamente ao que foi escrito.", + "invalidRegex": "Expressão regular inválida; será interpretada como texto simples." + }, + "seconds": "segundos", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/pt_PT.json b/plugins/ssh-terminal/locales/translated/pt_PT.json index 7e82b4ffd..2272d97fe 100644 --- a/plugins/ssh-terminal/locales/translated/pt_PT.json +++ b/plugins/ssh-terminal/locales/translated/pt_PT.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Temas guardados", "scrollbackBufferLabel": "Buffer de Retrocesso", "scrollbackMaxLines": "Número máximo de linhas guardadas no histórico", - "startupSnippetLabel": "Fragmento de inicialização", "sudoPasswordAutoFill": "Preenchimento Automático da Palavra-passe Sudo", "sudoPasswordAutoFillDesc": "Armazenar uma palavra-passe sudo para que as métricas do host, prompts do terminal e outros possam executar comandos com privilégios automaticamente.", "sudoPasswordAutoFillLabel": "Preenchimento automático da palavra-passe sudo", @@ -335,7 +334,8 @@ "history": "Histórico", "localTerminal": "Terminal local", "sshTools": "Ferramentas SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macros" }, "sshTools": { "broadcastInputPlaceholder": "Digite aqui para difundir as teclas premidas...", @@ -363,5 +363,112 @@ "off": "Desligado", "on": "Em", "title": "Configurações de ligação padrão" + }, + "keybindings": { + "copy": "Copiar seleção", + "paste": "Colar", + "sendControlCode": "Enviar sinal Ctrl+letra", + "sendText": "Enviar mensagem de texto", + "controlCodeLabel": "Carta", + "textLabel": "Texto a enviar", + "appendEnterLabel": "Prima Enter após enviar", + "clipboardPermissionNote": "Pode solicitar permissão para aceder à área de transferência na primeira utilização em alguns navegadores.", + "textRequired": "Introduza o texto que pretende enviar.", + "controlCodeRequired": "Introduza uma única letra para o código de controlo." + }, + "macros": { + "title": "Macros", + "description": "Execute uma série de comandos guardados num terminal e reaja ao que o servidor envia de volta.", + "target": "Objetivo", + "noTerminal": "Nenhum terminal aberto", + "empty": "Ainda não existem macros.", + "emptyHint": "Crie uma macro para automatizar um fluxo de trabalho no terminal, como por exemplo, iniciar sessão, reiniciar um serviço ou responder a pedidos.", + "create": "Nova macro", + "back": "Voltar às macros", + "name": "Nome", + "namePlaceholder": "Reinicie o servidor web.", + "nameRequired": "Dê um nome à macro antes de guardar.", + "descriptionLabel": "Descrição", + "descriptionPlaceholder": "O que faz esta macro (opcional)", + "unsaved": "Alterações não guardadas", + "save": "Guardar", + "saved": "Macro salvo", + "saveFailed": "Não foi possível guardar a macro.", + "delete": "Eliminar", + "deleteTitle": "Apagar esta macro?", + "deleteBody": "\"{{name}}\" será eliminado permanentemente. Esta ação não pode ser anulada.", + "deleted": "Macro excluída", + "cancel": "Cancelar", + "run": "Correr", + "stop": "Parar", + "running": "Corrida...", + "completed": "Macro finalizado", + "failed": "A macro falhou", + "loadFailed": "Não foi possível carregar as macros", + "needTerminal": "Primeiro, abra e ligue um terminal.", + "noSteps": "Esta macro ainda não tem etapas. Adicione uma abaixo.", + "addStep": "Adicionar etapa", + "stepNumber": "Passo {{number}}", + "removeStep": "Remover etapa", + "confirmRunTitle": "Executar esta macro?", + "confirmRunBody": "\"{{name}}\" enviará comandos para {{host}}.", + "confirmRunAction": "Executar macro", + "stepTypes": { + "send": { + "label": "Enviar comando", + "help": "Digita o texto no terminal, da mesma forma que se o digitasse." + }, + "wait": { + "label": "Aguarde a saída", + "help": "Aguarda até que o servidor imprima o texto que introduziu, como por exemplo um pedido de palavra-passe." + }, + "delay": { + "label": "Pausa", + "help": "Aguarda um período de tempo fixo antes da etapa seguinte." + }, + "if": { + "label": "Se a saída contiver", + "help": "Verifica tudo o que o servidor imprimiu até à data e, em seguida, executa uma das duas ramificações." + }, + "repeat": { + "label": "Repetir", + "help": "Executa os passos internos um número definido de vezes." + } + }, + "send": { + "text": "Comando ou texto", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Prima Enter após enviar", + "pressEnterHelp": "Desative esta opção para digitar texto sem o enviar." + }, + "wait": { + "pattern": "Aguarde até que a saída contenha", + "patternPlaceholder": "palavra-passe:", + "timeout": "Desistir depois", + "onTimeout": "Se nunca aparecer", + "stop": "Pare a macro", + "continue": "Continue mesmo assim" + }, + "delay": { + "duration": "Aguarde" + }, + "ifStep": { + "pattern": "Se a saída contiver", + "patternPlaceholder": "Ativo: ativo (em execução)", + "then": "Então faça isso", + "else": "Caso contrário, faça-o", + "emptyBranch": "Sem degraus" + }, + "repeat": { + "count": "Número de vezes", + "times": "vezes" + }, + "match": { + "useRegex": "Combinar com uma expressão regular", + "useRegexHelp": "Deixe este campo em branco para que o texto corresponda exatamente ao que foi escrito.", + "invalidRegex": "Expressão regular inválida; será interpretada como texto simples." + }, + "seconds": "segundos", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/ro_RO.json b/plugins/ssh-terminal/locales/translated/ro_RO.json index e56932077..8033eed19 100644 --- a/plugins/ssh-terminal/locales/translated/ro_RO.json +++ b/plugins/ssh-terminal/locales/translated/ro_RO.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Teme salvate", "scrollbackBufferLabel": "Buffer de derulare înapoi", "scrollbackMaxLines": "Numărul maxim de linii păstrate în istoric", - "startupSnippetLabel": "Fragment de pornire", "sudoPasswordAutoFill": "Completare automată parolă Sudo", "sudoPasswordAutoFillDesc": "Stocați o parolă sudo, astfel încât metricile gazdei, prompturile terminalului și alte funcții să poată executa automat comenzi privilegiate.", "sudoPasswordAutoFillLabel": "Completare automată parolă Sudo", @@ -335,7 +334,8 @@ "history": "Istorie", "localTerminal": "Terminal local", "sshTools": "Instrumente SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macrocomenzi" }, "sshTools": { "broadcastInputPlaceholder": "Tastați aici pentru a difuza apăsările de taste...", @@ -363,5 +363,112 @@ "off": "Dezactivat", "on": "Pe", "title": "Setări implicite ale conexiunii" + }, + "keybindings": { + "copy": "Copiați selecția", + "paste": "Pastă", + "sendControlCode": "Trimite semnal Ctrl+literă", + "sendText": "Trimiteți mesaj text", + "controlCodeLabel": "Scrisoare", + "textLabel": "Text de trimis", + "appendEnterLabel": "Apăsați Enter după trimitere", + "clipboardPermissionNote": "În unele browsere, este posibil să solicite permisiunea pentru clipboard la prima utilizare.", + "textRequired": "Introduceți textul de trimis", + "controlCodeRequired": "Introduceți o singură literă pentru codul de control" + }, + "macros": { + "title": "Macrocomenzi", + "description": "Executați o serie de comenzi salvate într-un terminal și reacționați la ceea ce serverul trimite înapoi.", + "target": "Ţintă", + "noTerminal": "Niciun terminal deschis", + "empty": "Încă nu există macrocomenzi", + "emptyHint": "Creați o macrocomandă pentru a automatiza un flux de lucru în terminal, cum ar fi conectarea, repornirea unui serviciu sau răspunsul la solicitări.", + "create": "Macrocomandă nouă", + "back": "Înapoi la macrocomenzi", + "name": "Nume", + "namePlaceholder": "Reporniți serverul web", + "nameRequired": "Dați un nume macrocomenzii înainte de salvare", + "descriptionLabel": "Descriere", + "descriptionPlaceholder": "Ce face această macrocomandă (opțional)", + "unsaved": "Modificări nesalvate", + "save": "Salva", + "saved": "Macrocomandă salvată", + "saveFailed": "Nu s-a putut salva macrocomanda", + "delete": "Şterge", + "deleteTitle": "Ștergeți această macrocomandă?", + "deleteBody": "„{{name}}” va fi șters definitiv. Această acțiune nu poate fi anulată.", + "deleted": "Macrocomandă ștearsă", + "cancel": "Anula", + "run": "Aleargă", + "stop": "Stop", + "running": "Funcţionare...", + "completed": "Macrocomandă finalizată", + "failed": "Macrocomanda a eșuat", + "loadFailed": "Nu s-au putut încărca macrocomenzile", + "needTerminal": "Deschideți și conectați mai întâi un terminal", + "noSteps": "Această macrocomandă nu are încă pași. Adăugați unul mai jos.", + "addStep": "Adăugați un pas", + "stepNumber": "Pasul {{number}}", + "removeStep": "Eliminați pasul", + "confirmRunTitle": "Rulați această macrocomandă?", + "confirmRunBody": "„{{name}}” va trimite comenzi către {{host}}.", + "confirmRunAction": "Executare macrocomandă", + "stepTypes": { + "send": { + "label": "Trimiteți comanda", + "help": "Introduce text în terminal, la fel ca și cum l-ai fi tastat." + }, + "wait": { + "label": "Așteptați ieșirea", + "help": "Se întrerupe până când serverul afișează textul pe care îl denumiți, ca o solicitare de parolă." + }, + "delay": { + "label": "Pauză", + "help": "Așteaptă o perioadă fixă de timp înainte de pasul următor." + }, + "if": { + "label": "Dacă ieșirea conține", + "help": "Verifică tot ce a imprimat serverul până acum, apoi rulează una dintre cele două ramificații." + }, + "repeat": { + "label": "Repeta", + "help": "Execută pașii din interiorul său de un număr stabilit de ori." + } + }, + "send": { + "text": "Comandă sau text", + "textPlaceholder": "repornirea systemctl a nginx", + "pressEnter": "Apăsați Enter după trimitere", + "pressEnterHelp": "Dezactivați această opțiune pentru a introduce text fără a-l trimite." + }, + "wait": { + "pattern": "Așteptați până când ieșirea conține", + "patternPlaceholder": "parolă:", + "timeout": "Renunță după", + "onTimeout": "Dacă nu apare niciodată", + "stop": "Opriți macrocomanda", + "continue": "Continuă oricum" + }, + "delay": { + "duration": "Așteptați" + }, + "ifStep": { + "pattern": "Dacă ieșirea conține", + "patternPlaceholder": "Activ: activ (alergând)", + "then": "Apoi fă asta", + "else": "Altfel, fă asta", + "emptyBranch": "Fără trepte" + }, + "repeat": { + "count": "Numărul de ori", + "times": "ori" + }, + "match": { + "useRegex": "Potrivire cu o expresie regulată", + "useRegexHelp": "Omiteți această opțiune pentru a corespunde exact textului așa cum este scris.", + "invalidRegex": "Nu este o expresie regulată validă, va fi găsită ca text simplu." + }, + "seconds": "secunde", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/ru_RU.json b/plugins/ssh-terminal/locales/translated/ru_RU.json index f0ac86396..052eb34fd 100644 --- a/plugins/ssh-terminal/locales/translated/ru_RU.json +++ b/plugins/ssh-terminal/locales/translated/ru_RU.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Сохраненные темы", "scrollbackBufferLabel": "Буфер прокрутки", "scrollbackMaxLines": "Максимальное количество строк, сохраняемых в истории", - "startupSnippetLabel": "Фрагмент запуска", "sudoPasswordAutoFill": "Автозаполнение пароля sudo", "sudoPasswordAutoFillDesc": "Хранить пароль sudo, чтобы метрики хоста, запросы терминала и другие могли автоматически выполнять привилегированные команды.", "sudoPasswordAutoFillLabel": "Автозаполнение пароля sudo", @@ -335,7 +334,8 @@ "history": "История", "localTerminal": "Локальный терминал", "sshTools": "Инструменты SSH", - "terminal": "Терминал" + "terminal": "Терминал", + "macros": "Макросы" }, "sshTools": { "broadcastInputPlaceholder": "Введите текст для трансляции нажатий...", @@ -363,5 +363,112 @@ "off": "Выключенный", "on": "На", "title": "Настройки подключения по умолчанию" + }, + "keybindings": { + "copy": "Скопировать выделенный фрагмент", + "paste": "Вставить", + "sendControlCode": "Отправить сигнал Ctrl+буква", + "sendText": "Отправить текст", + "controlCodeLabel": "Письмо", + "textLabel": "Текст для отправки", + "appendEnterLabel": "Нажмите Enter после отправки", + "clipboardPermissionNote": "В некоторых браузерах при первом использовании может запрашиваться разрешение на использование буфера обмена.", + "textRequired": "Введите текст для отправки", + "controlCodeRequired": "Введите одну букву в качестве управляющего кода." + }, + "macros": { + "title": "Макросы", + "description": "Выполните сохраненную последовательность команд в терминале и отреагируйте на то, что сервер отправит вам в ответ.", + "target": "Цель", + "noTerminal": "Терминал не открыт", + "empty": "Макросы пока отсутствуют", + "emptyHint": "Создайте макрос для автоматизации действий в терминале, таких как вход в систему, перезапуск службы или ответ на запросы.", + "create": "Новый макрос", + "back": "Вернуться к макроэлементам", + "name": "Имя", + "namePlaceholder": "Перезапустите веб-сервер", + "nameRequired": "Перед сохранением дайте макросу имя.", + "descriptionLabel": "Описание", + "descriptionPlaceholder": "Что делает этот макрос (необязательно)", + "unsaved": "Несохраненные изменения", + "save": "Сохранять", + "saved": "Макрос сохранен", + "saveFailed": "Не удалось сохранить макрос.", + "delete": "Удалить", + "deleteTitle": "Удалить этот макрос?", + "deleteBody": "\"{{name}}\" будет удалено навсегда. Этого нельзя будет отменить.", + "deleted": "Макрос удален", + "cancel": "Отмена", + "run": "Бегать", + "stop": "Останавливаться", + "running": "Бег...", + "completed": "Макро завершен", + "failed": "Макрос не сработал", + "loadFailed": "Не удалось загрузить макросы.", + "needTerminal": "Сначала откройте и подключите терминал.", + "noSteps": "В этом макросе пока нет шагов. Добавьте один ниже.", + "addStep": "Добавить шаг", + "stepNumber": "Шаг {{number}}", + "removeStep": "Удалить шаг", + "confirmRunTitle": "Запустить этот макрос?", + "confirmRunBody": "\"{{name}}\" будет отправлять команды {{host}}.", + "confirmRunAction": "Запустить макрос", + "stepTypes": { + "send": { + "label": "Отправить команду", + "help": "Вводит текст в терминал так же, как если бы вы его ввели вручную." + }, + "wait": { + "label": "Дождитесь вывода", + "help": "Приостанавливается до тех пор, пока сервер не выведет указанный вами текст, как запрос пароля." + }, + "delay": { + "label": "Пауза", + "help": "Перед следующим шагом выполняется ожидание в течение заданного промежутка времени." + }, + "if": { + "label": "Если вывод содержит", + "help": "Проверяет все данные, выведенные сервером на экран, а затем запускает одну из двух ветвей выполнения." + }, + "repeat": { + "label": "Повторить", + "help": "Выполняет все шаги, входящие в его состав, заданное количество раз." + } + }, + "send": { + "text": "Команда или текст", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Нажмите Enter после отправки", + "pressEnterHelp": "Отключите эту функцию, чтобы вводить текст без отправки." + }, + "wait": { + "pattern": "Дождитесь, пока на выходе не появится", + "patternPlaceholder": "пароль:", + "timeout": "Сдаться после", + "onTimeout": "Если оно никогда не появится", + "stop": "Остановите макрос", + "continue": "Продолжайте в любом случае." + }, + "delay": { + "duration": "Подождите" + }, + "ifStep": { + "pattern": "Если в выходных данных содержится", + "patternPlaceholder": "Активный: активен (работает)", + "then": "Затем сделайте это.", + "else": "В противном случае сделайте это.", + "emptyBranch": "Без ступенек" + }, + "repeat": { + "count": "Количество раз", + "times": "времена" + }, + "match": { + "useRegex": "Сопоставление с использованием регулярного выражения", + "useRegexHelp": "Оставьте этот параметр отключенным, чтобы текст точно соответствовал написанному.", + "invalidRegex": "Не является допустимым регулярным выражением, будет сопоставлено как обычный текст." + }, + "seconds": "секунд", + "secondsShort": "{{count}}с" } } diff --git a/plugins/ssh-terminal/locales/translated/sr_SP.json b/plugins/ssh-terminal/locales/translated/sr_SP.json index 3a9dfcea6..4bd320e6b 100644 --- a/plugins/ssh-terminal/locales/translated/sr_SP.json +++ b/plugins/ssh-terminal/locales/translated/sr_SP.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Сачуване теме", "scrollbackBufferLabel": "Бафер за померање уназад", "scrollbackMaxLines": "Максималан број линија сачуваних у историји", - "startupSnippetLabel": "Исечак за покретање", "sudoPasswordAutoFill": "Аутоматско попуњавање лозинке за Судо", "sudoPasswordAutoFillDesc": "Сачувајте лозинку за sudo како би метрике хоста, упити терминала и други могли аутоматски да покрећу привилеговане команде.", "sudoPasswordAutoFillLabel": "Аутоматско попуњавање лозинке за Sudo", @@ -335,7 +334,8 @@ "history": "Историја", "localTerminal": "Локални терминал", "sshTools": "SSH алати", - "terminal": "Терминал" + "terminal": "Терминал", + "macros": "Макрои" }, "sshTools": { "broadcastInputPlaceholder": "Куцајте овде да бисте емитовали притиске тастера...", @@ -363,5 +363,112 @@ "off": "Искључено", "on": "Укључено", "title": "Подразумеване вредности везе" + }, + "keybindings": { + "copy": "Копирај избор", + "paste": "Залепи", + "sendControlCode": "Пошаљи сигнал Ctrl+слово", + "sendText": "Пошаљи текст", + "controlCodeLabel": "Писмо", + "textLabel": "Текст за слање", + "appendEnterLabel": "Притисните Ентер након слања", + "clipboardPermissionNote": "Може затражити дозволу за међуспремник при првој употреби у неким прегледачима.", + "textRequired": "Унесите текст за слање", + "controlCodeRequired": "Унесите једно слово за контролни код" + }, + "macros": { + "title": "Макрои", + "description": "Покрените сачувану серију команди у терминалу и реагујте на оно што сервер пошаље назад.", + "target": "Циљ", + "noTerminal": "Ниједан терминал није отворен", + "empty": "Још нема макроа", + "emptyHint": "Направите макро за аутоматизацију тока рада терминала, као што је пријављивање, поновно покретање сервиса или одговарање на упите.", + "create": "Нови макро", + "back": "Назад на макрое", + "name": "Име", + "namePlaceholder": "Поново покрените веб сервер", + "nameRequired": "Дајте макроу име пре чувања", + "descriptionLabel": "Опис", + "descriptionPlaceholder": "Шта овај макро ради (опционо)", + "unsaved": "Несачуване промене", + "save": "Сачувај", + "saved": "Макро је сачуван", + "saveFailed": "Није могуће сачувати макро", + "delete": "Обриши", + "deleteTitle": "Да ли желите да обришете овај макро?", + "deleteBody": "„{{name}}“ ће бити трајно обрисано. Ова радња се не може поништити.", + "deleted": "Макро је обрисан", + "cancel": "Откажи", + "run": "Трчи", + "stop": "Заустави", + "running": "Трчи се...", + "completed": "Макро је завршен", + "failed": "Макро није успео", + "loadFailed": "Није могуће учитати макрое", + "needTerminal": "Прво отворите и повежите терминал", + "noSteps": "Овај макро још нема кораке. Додајте један испод.", + "addStep": "Додај корак", + "stepNumber": "Корак {{number}}", + "removeStep": "Уклони корак", + "confirmRunTitle": "Покрени овај макро?", + "confirmRunBody": "„{{name}}“ ће послати команде на {{host}}.", + "confirmRunAction": "Покрени макро", + "stepTypes": { + "send": { + "label": "Пошаљи команду", + "help": "Уноси текст у терминал, исто као да сте га откуцали." + }, + "wait": { + "label": "Сачекајте излаз", + "help": "Паузира док сервер не одштампа текст који наведете, као што је захтев за лозинку." + }, + "delay": { + "label": "Пауза", + "help": "Чека одређено време пре следећег корака." + }, + "if": { + "label": "Ако излаз садржи", + "help": "Проверава све што је сервер до сада одштампао, а затим покреће једну од две гране." + }, + "repeat": { + "label": "Понови", + "help": "Покреће кораке унутар њега одређени број пута." + } + }, + "send": { + "text": "Команда или текст", + "textPlaceholder": "системцтл рестартовање нгинкса", + "pressEnter": "Притисните Ентер након слања", + "pressEnterHelp": "Искључите ово да бисте куцали текст без његовог слања." + }, + "wait": { + "pattern": "Сачекајте док излаз не садржи", + "patternPlaceholder": "лозинка:", + "timeout": "Одустани после", + "onTimeout": "Ако се никада не појави", + "stop": "Зауставите макро", + "continue": "Настави ипак" + }, + "delay": { + "duration": "Сачекајте" + }, + "ifStep": { + "pattern": "Ако излаз садржи", + "patternPlaceholder": "Активно: активно (покреће се)", + "then": "Онда уради ово", + "else": "У супротном, урадите ово", + "emptyBranch": "Без степеница" + }, + "repeat": { + "count": "Број пута", + "times": "пута" + }, + "match": { + "useRegex": "Упари се са регуларним изразом", + "useRegexHelp": "Оставите ово искључено да би текст тачно одговарао написаном тексту.", + "invalidRegex": "Није важећи регуларни израз, биће упарен као обичан текст" + }, + "seconds": "секунде", + "secondsShort": "{{count}}с" } } diff --git a/plugins/ssh-terminal/locales/translated/sv_SE.json b/plugins/ssh-terminal/locales/translated/sv_SE.json index 672d790b3..b43e55ac7 100644 --- a/plugins/ssh-terminal/locales/translated/sv_SE.json +++ b/plugins/ssh-terminal/locales/translated/sv_SE.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Sparade teman", "scrollbackBufferLabel": "Scrollback-buffert", "scrollbackMaxLines": "Maximalt antal rader som sparas i historiken", - "startupSnippetLabel": "Startkodavsnitt", "sudoPasswordAutoFill": "Sudo-lösenordsautomatik", "sudoPasswordAutoFillDesc": "Lagra ett sudo-lösenord så att värdmätvärden, terminalprompter och annat kan köra privilegierade kommandon automatiskt.", "sudoPasswordAutoFillLabel": "Sudo-lösenordsautomatik", @@ -335,7 +334,8 @@ "history": "Historia", "localTerminal": "Lokal terminal", "sshTools": "SSH-verktyg", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Makron" }, "sshTools": { "broadcastInputPlaceholder": "Skriv här för att sända tangenttryckningar...", @@ -363,5 +363,112 @@ "off": "Av", "on": "På", "title": "Standardinställningar för anslutning" + }, + "keybindings": { + "copy": "Kopiera val", + "paste": "Klistra", + "sendControlCode": "Skicka Ctrl+bokstavssignal", + "sendText": "Skicka sms", + "controlCodeLabel": "Brev", + "textLabel": "SMS att skicka", + "appendEnterLabel": "Tryck Enter efter att du skickat", + "clipboardPermissionNote": "Kan be om behörighet för urklipp vid första användningen i vissa webbläsare.", + "textRequired": "Ange texten som ska skickas", + "controlCodeRequired": "Ange en enda bokstav för kontrollkoden" + }, + "macros": { + "title": "Makron", + "description": "Kör en sparad serie kommandon i en terminal och reagera på vad servern skickar tillbaka.", + "target": "Mål", + "noTerminal": "Ingen terminal öppen", + "empty": "Inga makron ännu", + "emptyHint": "Skapa ett makro för att automatisera ett terminalflöde, som att logga in, starta om en tjänst eller svara på frågor.", + "create": "Nytt makro", + "back": "Tillbaka till makron", + "name": "Namn", + "namePlaceholder": "Starta om webbservern", + "nameRequired": "Ge makrot ett namn innan du sparar", + "descriptionLabel": "Beskrivning", + "descriptionPlaceholder": "Vad detta makro gör (valfritt)", + "unsaved": "Osparade ändringar", + "save": "Spara", + "saved": "Makrot sparat", + "saveFailed": "Kunde inte spara makrot", + "delete": "Radera", + "deleteTitle": "Ta bort det här makrot?", + "deleteBody": "\"{{name}}\" kommer att raderas permanent. Detta kan inte ångras.", + "deleted": "Makro borttaget", + "cancel": "Avboka", + "run": "Sikt", + "stop": "Stopp", + "running": "Spring...", + "completed": "Makro klart", + "failed": "Makrot misslyckades", + "loadFailed": "Kunde inte läsa in makron", + "needTerminal": "Öppna och anslut en terminal först", + "noSteps": "Det här makrot har inga steg ännu. Lägg till ett nedan.", + "addStep": "Lägg till steg", + "stepNumber": "Steg {{number}}", + "removeStep": "Ta bort steg", + "confirmRunTitle": "Köra det här makrot?", + "confirmRunBody": "\"{{name}}\" skickar kommandon till {{host}}.", + "confirmRunAction": "Kör makro", + "stepTypes": { + "send": { + "label": "Skicka kommando", + "help": "Skriver text i terminalen, på samma sätt som om du hade skrivit den." + }, + "wait": { + "label": "Vänta på utdata", + "help": "Pausar tills servern skriver ut texten du anger, som en lösenordsfråga." + }, + "delay": { + "label": "Paus", + "help": "Väntar en fastställd tid innan nästa steg." + }, + "if": { + "label": "Om utdata innehåller", + "help": "Kontrollerar allt som servern har skrivit ut hittills och kör sedan en av två grenar." + }, + "repeat": { + "label": "Upprepa", + "help": "Kör stegen inuti ett visst antal gånger." + } + }, + "send": { + "text": "Kommando eller text", + "textPlaceholder": "systemctl starta om nginx", + "pressEnter": "Tryck Enter efter att du skickat", + "pressEnterHelp": "Stäng av detta för att skriva text utan att skicka in den." + }, + "wait": { + "pattern": "Vänta tills utdata innehåller", + "patternPlaceholder": "lösenord:", + "timeout": "Ge upp efteråt", + "onTimeout": "Om det aldrig dyker upp", + "stop": "Stoppa makrot", + "continue": "Fortsätt ändå" + }, + "delay": { + "duration": "Vänta på" + }, + "ifStep": { + "pattern": "Om utdata innehåller", + "patternPlaceholder": "Aktiv: aktiv (löper)", + "then": "Gör sedan detta", + "else": "Annars gör så här", + "emptyBranch": "Inga steg" + }, + "repeat": { + "count": "Antal gånger", + "times": "gånger" + }, + "match": { + "useRegex": "Matcha med ett reguljärt uttryck", + "useRegexHelp": "Lämna detta av för att texten ska se exakt ut som den är skriven.", + "invalidRegex": "Inte ett giltigt reguljärt uttryck, det kommer att matchas som vanlig text" + }, + "seconds": "sekunder", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/th_TH.json b/plugins/ssh-terminal/locales/translated/th_TH.json index 8b64cea61..0c7f44f81 100644 --- a/plugins/ssh-terminal/locales/translated/th_TH.json +++ b/plugins/ssh-terminal/locales/translated/th_TH.json @@ -307,7 +307,6 @@ "savedThemesLabel": "ธีมที่บันทึกไว้", "scrollbackBufferLabel": "บัฟเฟอร์การเลื่อนกลับ", "scrollbackMaxLines": "จำนวนบรรทัดสูงสุดที่เก็บไว้ในประวัติ", - "startupSnippetLabel": "ตัวอย่างโค้ดสำหรับเริ่มต้น", "sudoPasswordAutoFill": "การกรอกรหัสผ่านอัตโนมัติของ Sudo", "sudoPasswordAutoFillDesc": "บันทึกรหัสผ่าน sudo เพื่อให้โปรแกรมตรวจสอบข้อมูลโฮสต์ ข้อความแจ้งเตือนในเทอร์มินัล และโปรแกรมอื่นๆ สามารถเรียกใช้คำสั่งที่มีสิทธิ์พิเศษได้โดยอัตโนมัติ", "sudoPasswordAutoFillLabel": "การกรอกรหัสผ่านอัตโนมัติของ Sudo", @@ -335,7 +334,8 @@ "history": "ประวัติศาสตร์", "localTerminal": "สถานีปลายทางท้องถิ่น", "sshTools": "เครื่องมือ SSH", - "terminal": "เทอร์มินัล" + "terminal": "เทอร์มินัล", + "macros": "มาโคร" }, "sshTools": { "broadcastInputPlaceholder": "พิมพ์ที่นี่เพื่อถ่ายทอดการกดแป้นพิมพ์...", @@ -363,5 +363,112 @@ "off": "ปิด", "on": "บน", "title": "ค่าเริ่มต้นการเชื่อมต่อ" + }, + "keybindings": { + "copy": "คัดลอกการเลือก", + "paste": "แปะ", + "sendControlCode": "ส่งสัญญาณ Ctrl+ตัวอักษร", + "sendText": "ส่งข้อความ", + "controlCodeLabel": "จดหมาย", + "textLabel": "ข้อความที่จะส่ง", + "appendEnterLabel": "กด Enter หลังจากส่งแล้ว", + "clipboardPermissionNote": "ในบางเบราว์เซอร์ อาจขออนุญาตคัดลอกและวางข้อมูลลงในคลิปบอร์ดเมื่อใช้งานครั้งแรก", + "textRequired": "ป้อนข้อความที่จะส่ง", + "controlCodeRequired": "ป้อนตัวอักษรตัวเดียวสำหรับรหัสควบคุม" + }, + "macros": { + "title": "มาโคร", + "description": "เรียกใช้ชุดคำสั่งที่บันทึกไว้ในเทอร์มินัล แล้วตอบสนองต่อสิ่งที่เซิร์ฟเวอร์ส่งกลับมา", + "target": "เป้า", + "noTerminal": "ไม่มีเทอร์มินัลเปิดอยู่", + "empty": "ยังไม่มีมาโคร", + "emptyHint": "สร้างมาโครเพื่อทำให้กระบวนการทำงานบนเทอร์มินัลเป็นไปโดยอัตโนมัติ เช่น การล็อกอิน การรีสตาร์ทบริการ หรือการตอบคำถามต่างๆ", + "create": "มาโครใหม่", + "back": "กลับไปที่มาโคร", + "name": "ชื่อ", + "namePlaceholder": "รีสตาร์ทเว็บเซิร์ฟเวอร์", + "nameRequired": "ตั้งชื่อมาโครก่อนบันทึก", + "descriptionLabel": "คำอธิบาย", + "descriptionPlaceholder": "มาโครนี้ทำอะไร (ไม่บังคับ)", + "unsaved": "การเปลี่ยนแปลงที่ยังไม่ได้บันทึก", + "save": "บันทึก", + "saved": "บันทึกมาโครแล้ว", + "saveFailed": "ไม่สามารถบันทึกมาโครได้", + "delete": "ลบ", + "deleteTitle": "ลบมาโครนี้?", + "deleteBody": "\"{{name}}\" จะถูกลบอย่างถาวร ไม่สามารถกู้คืนได้", + "deleted": "มาโครถูกลบแล้ว", + "cancel": "ยกเลิก", + "run": "วิ่ง", + "stop": "หยุด", + "running": "วิ่ง...", + "completed": "มาโครเสร็จสิ้น", + "failed": "มาโครล้มเหลว", + "loadFailed": "ไม่สามารถโหลดมาโครได้", + "needTerminal": "เปิดและเชื่อมต่อเทอร์มินัลก่อน", + "noSteps": "มาโครนี้ยังไม่มีขั้นตอนใดๆ เพิ่มขั้นตอนด้านล่าง", + "addStep": "เพิ่มขั้นตอน", + "stepNumber": "ขั้นตอน {{number}}", + "removeStep": "ลบขั้นตอน", + "confirmRunTitle": "เรียกใช้มาโครนี้หรือไม่?", + "confirmRunBody": "\"{{name}}\" จะส่งคำสั่งไปยัง {{host}}", + "confirmRunAction": "เรียกใช้มาโคร", + "stepTypes": { + "send": { + "label": "ส่งคำสั่ง", + "help": "พิมพ์ข้อความลงในเทอร์มินัล เหมือนกับที่คุณพิมพ์ข้อความนั้นด้วยตนเอง" + }, + "wait": { + "label": "รอผลลัพธ์", + "help": "หยุดรอจนกว่าเซิร์ฟเวอร์จะแสดงข้อความที่คุณระบุ เช่นเดียวกับการขอให้ป้อนรหัสผ่าน" + }, + "delay": { + "label": "หยุดชั่วคราว", + "help": "รอเป็นระยะเวลาที่กำหนดก่อนดำเนินการขั้นตอนต่อไป" + }, + "if": { + "label": "หากผลลัพธ์ประกอบด้วย", + "help": "ตรวจสอบทุกอย่างที่เซิร์ฟเวอร์ได้พิมพ์ออกมาแล้ว จากนั้นจึงเลือกดำเนินการตามหนึ่งในสองทางเลือก" + }, + "repeat": { + "label": "ทำซ้ำ", + "help": "ดำเนินการตามขั้นตอนภายในตามจำนวนครั้งที่กำหนด" + } + }, + "send": { + "text": "คำสั่งหรือข้อความ", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "กด Enter หลังจากส่งแล้ว", + "pressEnterHelp": "ปิดตัวเลือกนี้เพื่อพิมพ์ข้อความโดยไม่ต้องกดส่ง" + }, + "wait": { + "pattern": "รอจนกว่าผลลัพธ์จะประกอบด้วย", + "patternPlaceholder": "รหัสผ่าน:", + "timeout": "ยอมแพ้หลังจาก", + "onTimeout": "หากมันไม่ปรากฏเลย", + "stop": "หยุดมาโคร", + "continue": "อย่าหยุดแค่นี้" + }, + "delay": { + "duration": "รอสักครู่" + }, + "ifStep": { + "pattern": "หากผลลัพธ์ประกอบด้วย", + "patternPlaceholder": "สถานะ: กำลังทำกิจกรรม (วิ่ง)", + "then": "จากนั้นให้ทำแบบนี้", + "else": "หรือทำแบบนี้ก็ได้", + "emptyBranch": "ไม่มีขั้นตอน" + }, + "repeat": { + "count": "จำนวนครั้ง", + "times": "ครั้ง" + }, + "match": { + "useRegex": "จับคู่ด้วยนิพจน์ปกติ", + "useRegexHelp": "ไม่ต้องใส่ส่วนนี้เพื่อให้ตรงกับข้อความที่เขียนไว้ทุกประการ", + "invalidRegex": "ไม่ใช่รูปแบบนิพจน์ปกติที่ถูกต้อง ระบบจะถือว่าเป็นข้อความธรรมดา" + }, + "seconds": "วินาที", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/tr_TR.json b/plugins/ssh-terminal/locales/translated/tr_TR.json index eba01fbbb..df200a3fa 100644 --- a/plugins/ssh-terminal/locales/translated/tr_TR.json +++ b/plugins/ssh-terminal/locales/translated/tr_TR.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Kaydedilen Temalar", "scrollbackBufferLabel": "Kaydırma Arabelleği", "scrollbackMaxLines": "Geçmişte saklanan maksimum satır sayısı", - "startupSnippetLabel": "Başlangıç Betiği", "sudoPasswordAutoFill": "Sudo Parolası Otomatik Doldurma", "sudoPasswordAutoFillDesc": "Sudo parolasını saklayın; böylece ana bilgisayar metrikleri, terminal istemleri ve diğer bileşenler ayrıcalıklı komutları otomatik olarak çalıştırabilir.", "sudoPasswordAutoFillLabel": "Sudo Parolası Otomatik Doldurma", @@ -335,7 +334,8 @@ "history": "Geçmiş", "localTerminal": "Yerel Terminal", "sshTools": "SSH Araçları", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Makrolar" }, "sshTools": { "broadcastInputPlaceholder": "Tuş vuruşlarını yayınlamak için buraya yazın...", @@ -363,5 +363,112 @@ "off": "Kapalı", "on": "Açık", "title": "Bağlantı Varsayılanları" + }, + "keybindings": { + "copy": "Seçimi kopyala", + "paste": "Yapıştır", + "sendControlCode": "Ctrl+harf sinyali gönder", + "sendText": "Mesaj gönder", + "controlCodeLabel": "Mektup", + "textLabel": "Gönderilecek metin", + "appendEnterLabel": "Gönderdikten sonra Enter tuşuna basın.", + "clipboardPermissionNote": "Bazı tarayıcılarda ilk kullanımda panoya kopyalama izni isteyebilir.", + "textRequired": "Göndermek istediğiniz metni girin.", + "controlCodeRequired": "Kontrol kodu için tek bir harf girin." + }, + "macros": { + "title": "Makrolar", + "description": "Önceden kaydedilmiş bir komut dizisini terminalde çalıştırın ve sunucunun geri gönderdiği yanıtlara göre hareket edin.", + "target": "Hedef", + "noTerminal": "Açık terminal yok.", + "empty": "Henüz makro yok", + "emptyHint": "Giriş yapma, bir hizmeti yeniden başlatma veya komut istemlerine yanıt verme gibi terminal işlemlerini otomatikleştirmek için bir makro oluşturun.", + "create": "Yeni makro", + "back": "Makrolara geri dön", + "name": "İsim", + "namePlaceholder": "Web sunucusunu yeniden başlatın.", + "nameRequired": "Kaydetmeden önce makroya bir ad verin.", + "descriptionLabel": "Tanım", + "descriptionPlaceholder": "Bu makronun ne yaptığı (isteğe bağlı)", + "unsaved": "Kaydedilmemiş değişiklikler", + "save": "Kaydetmek", + "saved": "Makro kaydedildi.", + "saveFailed": "Makro kaydedilemedi.", + "delete": "Silmek", + "deleteTitle": "Bu makroyu silmek ister misiniz?", + "deleteBody": "\"{{name}}\" kalıcı olarak silinecektir. Bu işlem geri alınamaz.", + "deleted": "Makro silindi", + "cancel": "İptal etmek", + "run": "Koşmak", + "stop": "Durmak", + "running": "Koşma...", + "completed": "Makro tamamlandı", + "failed": "Makro başarısız oldu", + "loadFailed": "Makrolar yüklenemedi.", + "needTerminal": "Öncelikle bir terminal açın ve bağlayın.", + "noSteps": "Bu makronun henüz hiçbir adımı yok. Aşağıya bir adım ekleyin.", + "addStep": "Adım ekle", + "stepNumber": "Adım {{number}}", + "removeStep": "Adımı kaldır", + "confirmRunTitle": "Bu makroyu çalıştırayım mı?", + "confirmRunBody": "\"{{name}}\" {{host}} adresine komutlar gönderecektir.", + "confirmRunAction": "Makroyu çalıştır", + "stepTypes": { + "send": { + "label": "Komut gönder", + "help": "Terminale, sanki siz yazmışsınız gibi metin yazar." + }, + "wait": { + "label": "Çıktıyı bekleyin", + "help": "Sunucu, belirttiğiniz metni (örneğin parola istemi gibi) yazdırana kadar bekler." + }, + "delay": { + "label": "Duraklat", + "help": "Bir sonraki adıma geçmeden önce belirli bir süre bekler." + }, + "if": { + "label": "Çıktı içeriyorsa", + "help": "Sunucunun şu ana kadar yazdırdığı her şeyi kontrol eder, ardından iki daldan birini çalıştırır." + }, + "repeat": { + "label": "Tekrarlamak", + "help": "İçindeki adımları belirtilen sayıda tekrarlar." + } + }, + "send": { + "text": "Komut veya metin", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Gönderdikten sonra Enter tuşuna basın.", + "pressEnterHelp": "Göndermeden metin yazmak için bunu kapatın." + }, + "wait": { + "pattern": "Çıktıyı oluşturana kadar bekleyin.", + "patternPlaceholder": "şifre:", + "timeout": "Vazgeçtikten sonra", + "onTimeout": "Eğer hiç ortaya çıkmazsa", + "stop": "Makroyu durdur", + "continue": "Yine de devam edin." + }, + "delay": { + "duration": "Bekleyin" + }, + "ifStep": { + "pattern": "Çıktı şunları içeriyorsa:", + "patternPlaceholder": "Aktif: aktif (koşmak)", + "then": "O halde şunu yapın", + "else": "Aksi takdirde şunu yapın", + "emptyBranch": "Hiçbir adım yok" + }, + "repeat": { + "count": "Sayısı", + "times": "kez" + }, + "match": { + "useRegex": "Düzenli ifadeyle eşleştirin", + "useRegexHelp": "Metnin aynen yazıldığı gibi kalması için bunu atlayın.", + "invalidRegex": "Geçerli bir normal ifade değil, düz metin olarak eşleştirilecektir." + }, + "seconds": "saniyeler", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/uk_UA.json b/plugins/ssh-terminal/locales/translated/uk_UA.json index 2da6b1e3e..a827e523b 100644 --- a/plugins/ssh-terminal/locales/translated/uk_UA.json +++ b/plugins/ssh-terminal/locales/translated/uk_UA.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Збережені теми", "scrollbackBufferLabel": "Буфер прокручування", "scrollbackMaxLines": "Максимальна кількість рядків, що зберігаються в історії", - "startupSnippetLabel": "Фрагмент запуску", "sudoPasswordAutoFill": "Автозаповнення пароля Sudo", "sudoPasswordAutoFillDesc": "Зберігайте пароль sudo, щоб метрики хоста, запити терміналу та інші могли автоматично запускати привілейовані команди.", "sudoPasswordAutoFillLabel": "Автозаповнення пароля Sudo", @@ -335,7 +334,8 @@ "history": "Історія", "localTerminal": "Місцевий термінал", "sshTools": "SSH-інструменти", - "terminal": "Термінал" + "terminal": "Термінал", + "macros": "Макроси" }, "sshTools": { "broadcastInputPlaceholder": "Введіть тут, щоб транслювати натискання клавіш...", @@ -363,5 +363,112 @@ "off": "Вимкнено", "on": "Увімкнено", "title": "Налаштування підключення за замовчуванням" + }, + "keybindings": { + "copy": "Копіювати виділення", + "paste": "Вставити", + "sendControlCode": "Надіслати сигнал Ctrl+літера", + "sendText": "Надіслати текст", + "controlCodeLabel": "Лист", + "textLabel": "Текст для надсилання", + "appendEnterLabel": "Натисніть Enter після відправлення", + "clipboardPermissionNote": "У деяких браузерах може запитуватися дозвіл на використання буфера обміну під час першого використання.", + "textRequired": "Введіть текст для надсилання", + "controlCodeRequired": "Введіть одну літеру для контрольного коду" + }, + "macros": { + "title": "Макроси", + "description": "Виконайте збережену серію команд у терміналі та відреагуйте на те, що надсилає сервер.", + "target": "Ціль", + "noTerminal": "Термінал не відкритий", + "empty": "Поки що немає макросів", + "emptyHint": "Створіть макрос для автоматизації процесу роботи термінала, такого як вхід у систему, перезапуск служби або відповідь на запити.", + "create": "Новий макрос", + "back": "Повернутися до макросів", + "name": "Ім'я", + "namePlaceholder": "Перезапустіть веб-сервер", + "nameRequired": "Введіть ім'я макросу перед збереженням", + "descriptionLabel": "Опис", + "descriptionPlaceholder": "Що робить цей макрос (необов'язково)", + "unsaved": "Незбережені зміни", + "save": "Зберегти", + "saved": "Макрос збережено", + "saveFailed": "Не вдалося зберегти макрос", + "delete": "Видалити", + "deleteTitle": "Видалити цей макрос?", + "deleteBody": "«{{name}}» буде видалено назавжди. Цю дію неможливо скасувати.", + "deleted": "Макрос видалено", + "cancel": "Скасувати", + "run": "Бігти", + "stop": "СТІЙ", + "running": "Біг...", + "completed": "Макрос завершено", + "failed": "Помилка макросу", + "loadFailed": "Не вдалося завантажити макроси", + "needTerminal": "Спочатку відкрийте та підключіть термінал", + "noSteps": "Цей макрос ще не має кроків. Додайте один нижче.", + "addStep": "Додати крок", + "stepNumber": "Крок {{number}}", + "removeStep": "Видалити крок", + "confirmRunTitle": "Запустити цей макрос?", + "confirmRunBody": "\"{{name}}\" надсилатиме команди до {{host}}.", + "confirmRunAction": "Запустити макрос", + "stepTypes": { + "send": { + "label": "Надіслати команду", + "help": "Вводить текст у термінал так само, як і надруковано." + }, + "wait": { + "label": "Зачекайте на вивід", + "help": "Призупиняється, доки сервер не надрукує вказаний вами текст, наприклад, запит на пароль." + }, + "delay": { + "label": "Пауза", + "help": "Очікує фіксований проміжок часу перед наступним кроком." + }, + "if": { + "label": "Якщо вихід містить", + "help": "Перевіряє все, що сервер надрукував до цього часу, а потім запускає одну з двох гілок." + }, + "repeat": { + "label": "Повторити", + "help": "Виконує кроки всередині нього задану кількість разів." + } + }, + "send": { + "text": "Команда або текст", + "textPlaceholder": "перезапуск systemctl nginx", + "pressEnter": "Натисніть Enter після відправлення", + "pressEnterHelp": "Вимкніть цю функцію, щоб вводити текст, не надсилаючи його." + }, + "wait": { + "pattern": "Зачекайте, поки вихідні дані міститимуть", + "patternPlaceholder": "пароль:", + "timeout": "Здайся після", + "onTimeout": "Якщо воно ніколи не з'явиться", + "stop": "Зупинити макрос", + "continue": "Продовжуй у будь-якому разі" + }, + "delay": { + "duration": "Зачекайте" + }, + "ifStep": { + "pattern": "Якщо вихід містить", + "patternPlaceholder": "Активний: активний (працює)", + "then": "Тоді зробіть це", + "else": "Інакше зробіть це", + "emptyBranch": "Без сходинок" + }, + "repeat": { + "count": "Кількість разів", + "times": "рази" + }, + "match": { + "useRegex": "Зіставлення з регулярним виразом", + "useRegexHelp": "Не виключайте це, щоб текст точно відповідав написаному.", + "invalidRegex": "Недійсний регулярний вираз, буде знайдено звичайний текст" + }, + "seconds": "секунди", + "secondsShort": "{{count}}с" } } diff --git a/plugins/ssh-terminal/locales/translated/vi_VN.json b/plugins/ssh-terminal/locales/translated/vi_VN.json index 68ee2e742..6ee7139c4 100644 --- a/plugins/ssh-terminal/locales/translated/vi_VN.json +++ b/plugins/ssh-terminal/locales/translated/vi_VN.json @@ -307,7 +307,6 @@ "savedThemesLabel": "Chủ đề đã lưu", "scrollbackBufferLabel": "Bộ đệm cuộn ngược", "scrollbackMaxLines": "Số dòng tối đa được giữ trong lịch sử", - "startupSnippetLabel": "Đoạn mã khởi động", "sudoPasswordAutoFill": "Tự động điền mật khẩu Sudo", "sudoPasswordAutoFillDesc": "Lưu trữ mật khẩu sudo để các số liệu máy chủ, lời nhắc terminal và các thành phần khác có thể chạy lệnh đặc quyền tự động.", "sudoPasswordAutoFillLabel": "Tự động điền mật khẩu sudo", @@ -335,7 +334,8 @@ "history": "Lịch sử", "localTerminal": "Ga địa phương", "sshTools": "Công cụ SSH", - "terminal": "Terminal" + "terminal": "Terminal", + "macros": "Macro" }, "sshTools": { "broadcastInputPlaceholder": "Nhập vào đây để phát tán thao tác phím...", @@ -363,5 +363,112 @@ "off": "Tắt", "on": "TRÊN", "title": "Cài đặt mặc định kết nối" + }, + "keybindings": { + "copy": "Sao chép lựa chọn", + "paste": "Dán", + "sendControlCode": "Gửi tín hiệu Ctrl+chữ cái", + "sendText": "Gửi tin nhắn", + "controlCodeLabel": "Thư", + "textLabel": "Văn bản để gửi", + "appendEnterLabel": "Nhấn Enter sau khi gửi", + "clipboardPermissionNote": "Một số trình duyệt có thể yêu cầu quyền truy cập clipboard khi sử dụng lần đầu.", + "textRequired": "Nhập văn bản cần gửi", + "controlCodeRequired": "Nhập một chữ cái duy nhất cho mã điều khiển." + }, + "macros": { + "title": "Macro", + "description": "Chạy một chuỗi lệnh đã lưu trong cửa sổ dòng lệnh và phản hồi lại những gì máy chủ gửi về.", + "target": "Mục tiêu", + "noTerminal": "Không có thiết bị đầu cuối nào đang mở", + "empty": "Chưa có macro nào.", + "emptyHint": "Tạo macro để tự động hóa quy trình trên thiết bị đầu cuối, chẳng hạn như đăng nhập, khởi động lại dịch vụ hoặc trả lời các lời nhắc.", + "create": "Macro mới", + "back": "Quay lại phần macro", + "name": "Tên", + "namePlaceholder": "Khởi động lại máy chủ web", + "nameRequired": "Đặt tên cho macro trước khi lưu.", + "descriptionLabel": "Sự miêu tả", + "descriptionPlaceholder": "Chức năng của macro này (tùy chọn)", + "unsaved": "Thay đổi chưa được lưu", + "save": "Cứu", + "saved": "Macro đã được lưu", + "saveFailed": "Không thể lưu macro", + "delete": "Xóa bỏ", + "deleteTitle": "Xóa macro này?", + "deleteBody": "\"{{name}}\" sẽ bị xóa vĩnh viễn. Không thể hoàn tác.", + "deleted": "Macro đã bị xóa", + "cancel": "Hủy bỏ", + "run": "Chạy", + "stop": "Dừng lại", + "running": "Đang chạy...", + "completed": "Macro đã hoàn thành", + "failed": "Macro bị lỗi", + "loadFailed": "Không thể tải macro", + "needTerminal": "Trước tiên hãy mở và kết nối thiết bị đầu cuối.", + "noSteps": "Macro này chưa có bước nào. Hãy thêm một bước bên dưới.", + "addStep": "Thêm bước", + "stepNumber": "Bước {{number}}", + "removeStep": "Xóa bước", + "confirmRunTitle": "Chạy macro này?", + "confirmRunBody": "\"{{name}}\" sẽ gửi lệnh đến {{host}}.", + "confirmRunAction": "Chạy macro", + "stepTypes": { + "send": { + "label": "Gửi lệnh", + "help": "Nhập văn bản vào cửa sổ dòng lệnh, giống như khi bạn tự nhập văn bản." + }, + "wait": { + "label": "Chờ kết quả đầu ra", + "help": "Tạm dừng cho đến khi máy chủ in ra đoạn văn bản bạn đã đặt tên, ví dụ như lời nhắc nhập mật khẩu." + }, + "delay": { + "label": "Tạm dừng", + "help": "Chờ một khoảng thời gian cố định trước khi thực hiện bước tiếp theo." + }, + "if": { + "label": "Nếu đầu ra chứa", + "help": "Kiểm tra tất cả những gì máy chủ đã in ra cho đến nay, sau đó chạy một trong hai nhánh." + }, + "repeat": { + "label": "Lặp lại", + "help": "Thực thi các bước bên trong nó một số lần nhất định." + } + }, + "send": { + "text": "Lệnh hoặc văn bản", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "Nhấn Enter sau khi gửi", + "pressEnterHelp": "Tắt tùy chọn này để nhập văn bản mà không cần gửi đi." + }, + "wait": { + "pattern": "Chờ cho đến khi đầu ra chứa", + "patternPlaceholder": "mật khẩu:", + "timeout": "Bỏ cuộc sau", + "onTimeout": "Nếu nó không bao giờ xuất hiện", + "stop": "Dừng macro", + "continue": "Cứ tiếp tục đi" + }, + "delay": { + "duration": "Chờ đợi" + }, + "ifStep": { + "pattern": "Nếu đầu ra chứa", + "patternPlaceholder": "Đang hoạt động: đang hoạt động (đang chạy)", + "then": "Sau đó hãy làm như thế này", + "else": "Nếu không thì hãy làm như thế này", + "emptyBranch": "Không có bước nào" + }, + "repeat": { + "count": "Số lần", + "times": "lần" + }, + "match": { + "useRegex": "So khớp với biểu thức chính quy", + "useRegexHelp": "Bỏ phần này đi để văn bản khớp chính xác với nội dung đã viết.", + "invalidRegex": "Đây không phải là biểu thức chính quy hợp lệ, nó sẽ được so khớp như văn bản thuần túy." + }, + "seconds": "giây", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/zh_CN.json b/plugins/ssh-terminal/locales/translated/zh_CN.json index 40f0e367a..7a25aefab 100644 --- a/plugins/ssh-terminal/locales/translated/zh_CN.json +++ b/plugins/ssh-terminal/locales/translated/zh_CN.json @@ -307,7 +307,6 @@ "savedThemesLabel": "已保存的主题", "scrollbackBufferLabel": "回滚缓冲区", "scrollbackMaxLines": "保留在历史记录中的最大行数", - "startupSnippetLabel": "启动代码片段", "sudoPasswordAutoFill": "Sudo 密码自动填充", "sudoPasswordAutoFillDesc": "存储 sudo 密码,以便主机指标、终端提示和其他操作可以自动运行特权命令。", "sudoPasswordAutoFillLabel": "Sudo 密码自动填充", @@ -335,7 +334,8 @@ "history": "历史记录", "localTerminal": "本地终端", "sshTools": "SSH 工具", - "terminal": "终端" + "terminal": "终端", + "macros": "宏" }, "sshTools": { "broadcastInputPlaceholder": "在此输入以广播按键...", @@ -363,5 +363,112 @@ "off": "关闭", "on": "开启", "title": "连接默认值" + }, + "keybindings": { + "copy": "复制选择", + "paste": "粘贴", + "sendControlCode": "发送 Ctrl+字母信号", + "sendText": "发送文本", + "controlCodeLabel": "字母", + "textLabel": "发送的文本", + "appendEnterLabel": "发送后按回车键", + "clipboardPermissionNote": "某些浏览器在首次使用时可能会提示授予剪贴板权限。", + "textRequired": "请输入要发送的文本", + "controlCodeRequired": "请输入单个字母作为控制码" + }, + "macros": { + "title": "宏", + "description": "在终端中运行已保存的一系列命令,并对服务器返回的内容做出反应。", + "target": "目标", + "noTerminal": "没有终端打开", + "empty": "暂无宏", + "emptyHint": "创建一个宏来自动化终端流程,例如登录、重启服务或回答提示。", + "create": "新宏", + "back": "返回宏", + "name": "名称", + "namePlaceholder": "重启 Web 服务器", + "nameRequired": "保存之前,请给宏命名。", + "descriptionLabel": "描述", + "descriptionPlaceholder": "此宏的功能(可选)", + "unsaved": "未保存的更改", + "save": "保存", + "saved": "宏已保存", + "saveFailed": "无法保存宏", + "delete": "删除", + "deleteTitle": "删除这个宏?", + "deleteBody": "“{{name}}”将被永久删除。此操作无法撤销。", + "deleted": "宏已删除", + "cancel": "取消", + "run": "运行", + "stop": "停止", + "running": "运行中...", + "completed": "宏已完成", + "failed": "宏失败", + "loadFailed": "无法加载宏。", + "needTerminal": "首先打开并连接终端", + "noSteps": "此宏目前尚无步骤。请在下方添加步骤。", + "addStep": "添加步骤", + "stepNumber": "步骤 {{number}}", + "removeStep": "移除步骤", + "confirmRunTitle": "运行这个宏?", + "confirmRunBody": "\"{{name}}\" 将向 {{host}} 发送命令。", + "confirmRunAction": "运行宏", + "stepTypes": { + "send": { + "label": "发送命令", + "help": "在终端中输入文本,就像你手动输入一样。" + }, + "wait": { + "label": "等待输出", + "help": "暂停,直到服务器打印出您指定的文本,例如密码提示。" + }, + "delay": { + "label": "暂停", + "help": "等待一段固定时间后再执行下一步操作。" + }, + "if": { + "label": "如果输出包含", + "help": "检查服务器目前为止打印的所有内容,然后运行两个分支中的一个。" + }, + "repeat": { + "label": "重复", + "help": "将其中的步骤执行设定的次数。" + } + }, + "send": { + "text": "命令或文本", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "发送后按回车键", + "pressEnterHelp": "关闭此功能即可输入文本而不提交。" + }, + "wait": { + "pattern": "等待输出包含", + "patternPlaceholder": "密码:", + "timeout": "放弃", + "onTimeout": "如果它从未出现", + "stop": "停止宏", + "continue": "无论如何都继续" + }, + "delay": { + "duration": "等待" + }, + "ifStep": { + "pattern": "如果输出包含", + "patternPlaceholder": "活跃:正在运行", + "then": "然后这样做", + "else": "否则就这样做", + "emptyBranch": "没有步骤" + }, + "repeat": { + "count": "次数", + "times": "次" + }, + "match": { + "useRegex": "使用正则表达式进行匹配", + "useRegexHelp": "关闭此选项以完全按原文匹配文本。", + "invalidRegex": "这不是有效的正则表达式,它将被匹配为纯文本。" + }, + "seconds": "秒", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/locales/translated/zh_TW.json b/plugins/ssh-terminal/locales/translated/zh_TW.json index 55974432d..f3c8af95a 100644 --- a/plugins/ssh-terminal/locales/translated/zh_TW.json +++ b/plugins/ssh-terminal/locales/translated/zh_TW.json @@ -307,7 +307,6 @@ "savedThemesLabel": "已儲存的主題", "scrollbackBufferLabel": "回滾緩衝區", "scrollbackMaxLines": "歷史記錄保留的最大行數", - "startupSnippetLabel": "啟動程式碼片段", "sudoPasswordAutoFill": "Sudo 密碼自動填寫", "sudoPasswordAutoFillDesc": "儲存 sudo 密碼,以便主機指標、終端機提示及其他功能能自動執行特權命令。", "sudoPasswordAutoFillLabel": "Sudo 密碼自動填入", @@ -335,7 +334,8 @@ "history": "歷史記錄", "localTerminal": "本地端", "sshTools": "SSH 工具", - "terminal": "終端機" + "terminal": "終端機", + "macros": "宏" }, "sshTools": { "broadcastInputPlaceholder": "在此輸入以廣播按鍵輸入...", @@ -363,5 +363,112 @@ "off": "離開", "on": "在", "title": "連接預設值" + }, + "keybindings": { + "copy": "複製選擇", + "paste": "貼上", + "sendControlCode": "發送 Ctrl+字母訊號", + "sendText": "傳簡訊", + "controlCodeLabel": "信", + "textLabel": "發送的文字", + "appendEnterLabel": "發送後按回車鍵", + "clipboardPermissionNote": "某些瀏覽器在首次使用時可能會提示授予剪貼簿權限。", + "textRequired": "請輸入要發送的文字", + "controlCodeRequired": "請輸入一個字母作為控制碼" + }, + "macros": { + "title": "宏", + "description": "在終端機中執行已儲存的一系列命令,並對伺服器傳回的內容做出反應。", + "target": "目標", + "noTerminal": "沒有終端機打開", + "empty": "暫無宏", + "emptyHint": "建立一個巨集來自動化終端流程,例如登入、重新啟動服務或回答提示。", + "create": "新宏", + "back": "回傳宏", + "name": "姓名", + "namePlaceholder": "重啟Web伺服器", + "nameRequired": "儲存之前,請先為巨集命名。", + "descriptionLabel": "描述", + "descriptionPlaceholder": "此巨集的功能(選用)", + "unsaved": "未儲存的更改", + "save": "節省", + "saved": "巨集已儲存", + "saveFailed": "無法保存巨集", + "delete": "刪除", + "deleteTitle": "刪除這個巨集?", + "deleteBody": "“{{name}}”將永久刪除。此操作無法撤銷。", + "deleted": "巨集已刪除", + "cancel": "取消", + "run": "跑步", + "stop": "停止", + "running": "跑步...", + "completed": "巨集已完成", + "failed": "巨集失敗", + "loadFailed": "無法載入巨集。", + "needTerminal": "首先打開並連接終端。", + "noSteps": "此巨集目前尚無步驟。請在下方新增步驟。", + "addStep": "新增步驟", + "stepNumber": "步驟 {{number}}", + "removeStep": "移除步驟", + "confirmRunTitle": "運行這個巨集?", + "confirmRunBody": "\"{{name}}\" 將向 {{host}} 傳送指令。", + "confirmRunAction": "運行巨集", + "stepTypes": { + "send": { + "label": "發送命令", + "help": "在終端機中輸入文本,就像你手動輸入一樣。" + }, + "wait": { + "label": "等待輸出", + "help": "暫停,直到伺服器列印您指定的文本,例如密碼提示。" + }, + "delay": { + "label": "暫停", + "help": "等待一段固定時間後再執行下一步操作。" + }, + "if": { + "label": "如果輸出包含", + "help": "檢查伺服器目前為止列印的所有內容,然後執行兩個分支中的一個。" + }, + "repeat": { + "label": "重複", + "help": "將其中的步驟執行設定的次數。" + } + }, + "send": { + "text": "命令或文字", + "textPlaceholder": "systemctl restart nginx", + "pressEnter": "發送後按回車鍵", + "pressEnterHelp": "關閉此功能即可輸入文字而不提交。" + }, + "wait": { + "pattern": "等待輸出包含", + "patternPlaceholder": "密碼:", + "timeout": "放棄", + "onTimeout": "如果它從未出現", + "stop": "停止宏", + "continue": "無論如何,繼續前進。" + }, + "delay": { + "duration": "等待" + }, + "ifStep": { + "pattern": "如果輸出包含", + "patternPlaceholder": "活躍:正在運行", + "then": "然後這樣做", + "else": "否則就這樣做", + "emptyBranch": "沒有步驟" + }, + "repeat": { + "count": "次數", + "times": "時代" + }, + "match": { + "useRegex": "使用正規表示式進行匹配", + "useRegexHelp": "請刪除此行以完全按照原文填寫。", + "invalidRegex": "這不是有效的正規表示式,它將被匹配為純文字。" + }, + "seconds": "秒", + "secondsShort": "{{count}}s" } } diff --git a/plugins/ssh-terminal/manifest.json b/plugins/ssh-terminal/manifest.json index 8093822e2..d66b2dbb0 100644 --- a/plugins/ssh-terminal/manifest.json +++ b/plugins/ssh-terminal/manifest.json @@ -79,6 +79,11 @@ "id": "ssh-tools", "titleKey": "nav.sshTools", "icon": "Hammer" + }, + { + "id": "macros", + "titleKey": "nav.macros", + "icon": "Braces" } ], "actionSlots": [ @@ -752,9 +757,47 @@ "labelKey": "hosts.linkClickBehaviorDirect" } ] + }, + { + "key": "macros", + "type": "json", + "labelKey": "macros.title", + "hidden": true, + "default": [] } ] - } + }, + "keybindingActions": [ + { + "id": "copy" + }, + { + "id": "paste" + }, + { + "id": "sendControlCode", + "params": { + "controlCode": { + "type": "string", + "required": true, + "pattern": "^[a-zA-Z]$" + } + } + }, + { + "id": "sendText", + "params": { + "text": { + "type": "string", + "maxLength": 65536, + "required": true + }, + "appendEnter": { + "type": "boolean" + } + } + } + ] }, "locales": "locales", "platforms": ["linux", "win32", "darwin"] diff --git a/plugins/ssh-terminal/src/backend/index.ts b/plugins/ssh-terminal/src/backend/index.ts index 9d1c12392..18aa7f48b 100644 --- a/plugins/ssh-terminal/src/backend/index.ts +++ b/plugins/ssh-terminal/src/backend/index.ts @@ -22,7 +22,10 @@ import { import { ADMIN_KEYS } from "./settings.js"; import { commandHistory } from "./tables.js"; import { createTerminalSocket } from "./terminal-socket.js"; -import { validateAdminSettings } from "./settings-validation.js"; +import { + validateAdminSettings, + validateUserSettings, +} from "./settings-validation.js"; function toInfo(session: TerminalSession): LiveSessionInfo { return { @@ -57,6 +60,7 @@ function optionalService( export async function activate(ctx: PluginContext) { const log = createTerminalLogger(ctx.log); ctx.settings.onValidate("admin", validateAdminSettings); + ctx.settings.onValidate("user", validateUserSettings); const table = await ctx.db.define(commandHistory); const history = createHistoryRepository(ctx.db, table); diff --git a/plugins/ssh-terminal/src/backend/settings-validation.ts b/plugins/ssh-terminal/src/backend/settings-validation.ts index 7a591baa0..65363b77c 100644 --- a/plugins/ssh-terminal/src/backend/settings-validation.ts +++ b/plugins/ssh-terminal/src/backend/settings-validation.ts @@ -30,3 +30,38 @@ export function validateAdminSettings( } return errors; } + +const MAX_MACROS = 100; +const MAX_MACROS_BYTES = 512 * 1024; + +/** Save-time checks for the user settings: macros stay bounded. */ +export function validateUserSettings( + values: Record, +): Record { + const errors: Record = {}; + if (values.macros === undefined || values.macros === null) return errors; + let macros = values.macros; + if (typeof macros === "string") { + try { + macros = JSON.parse(macros); + } catch { + macros = null; + } + } + const valid = + Array.isArray(macros) && + macros.length <= MAX_MACROS && + JSON.stringify(macros).length <= MAX_MACROS_BYTES && + macros.every( + (macro) => + !!macro && + typeof macro === "object" && + typeof (macro as { id?: unknown }).id === "string" && + typeof (macro as { name?: unknown }).name === "string" && + Array.isArray((macro as { steps?: unknown }).steps), + ); + if (!valid) { + errors.macros = `Macros must be a list of at most ${MAX_MACROS} valid macros`; + } + return errors; +} diff --git a/plugins/ssh-terminal/src/frontend/index.tsx b/plugins/ssh-terminal/src/frontend/index.tsx index 75ba3e419..1ef546537 100644 --- a/plugins/ssh-terminal/src/frontend/index.tsx +++ b/plugins/ssh-terminal/src/frontend/index.tsx @@ -1,5 +1,6 @@ import { lazy, Suspense, type ComponentType } from "react"; import { + Braces, Hammer, History, Laptop, @@ -33,6 +34,17 @@ import { HostTerminalSection } from "./settings/HostTerminalSection"; import { resetTouchInputSettingsCache } from "./terminal/touch-input-settings-store"; import { hostSetting } from "./terminal-api"; import { SshToolsPanel } from "./ssh-tools/SshToolsPanel"; +import { MacrosPanel } from "./macros/MacrosPanel"; +import { + TERMINAL_KEYBINDING_DEFAULTS, + validateSendControlCode, + validateSendText, +} from "./lib/keybinding-dispatch"; +import { + PasteNote, + SendControlCodeEditor, + SendTextEditor, +} from "./lib/keybinding-editors"; import { TerminalDefaultsSettings } from "./settings/TerminalDefaultsSettings"; import { NewHostFontSetting, @@ -191,6 +203,45 @@ export function activate(app: TermixApp): void { separatorAfter: true, }); + app.registerPanel( + "macros", + MacrosPanel as unknown as ComponentType, + ); + app.registerRailItem({ + id: "macros", + icon: Braces, + titleKey: "nav.macros", + after: "ssh-tools", + hideable: true, + promotable: true, + rightDockable: true, + separatorAfter: true, + }); + + // The keys a user can bind to the terminal's own actions, and its + // built-in keys they can rebind. The terminal runs these itself. + app.registerKeybindingAction({ id: "copy", titleKey: "keybindings.copy" }); + app.registerKeybindingAction({ + id: "paste", + titleKey: "keybindings.paste", + editor: PasteNote, + }); + app.registerKeybindingAction({ + id: "sendControlCode", + titleKey: "keybindings.sendControlCode", + editor: SendControlCodeEditor, + validate: validateSendControlCode, + }); + app.registerKeybindingAction({ + id: "sendText", + titleKey: "keybindings.sendText", + editor: SendTextEditor, + validate: validateSendText, + }); + for (const binding of TERMINAL_KEYBINDING_DEFAULTS) { + app.registerKeybindingDefault(binding); + } + // Places other plugins can fill: toolbar buttons and readouts, a side // panel and overlays that follow the session's connection flow. app.declareActionSlot({ id: TERMINAL_TOOLBAR_SLOT, accepts: ["button"] }); diff --git a/plugins/ssh-terminal/src/frontend/lib/keybinding-dispatch.ts b/plugins/ssh-terminal/src/frontend/lib/keybinding-dispatch.ts index 794a14361..7e9aa79cb 100644 --- a/plugins/ssh-terminal/src/frontend/lib/keybinding-dispatch.ts +++ b/plugins/ssh-terminal/src/frontend/lib/keybinding-dispatch.ts @@ -1,27 +1,26 @@ import type { Terminal } from "@xterm/xterm"; -import { type KeybindingAction } from "@termix/plugin-sdk/ui"; -import { invokeAction } from "@termix/plugin-sdk/frontend"; - -export interface SnippetHostContext { - ip?: string; - username?: string; - port?: number | string; - name?: string; -} +import type { + KeybindingAction, + KeybindingDefaultContribution, +} from "@termix/plugin-sdk/frontend"; export interface KeybindingDispatchContext { terminal: Terminal; webSocketRef: React.MutableRefObject; writeTextToClipboard: (text: string) => Promise; readTextFromClipboard: () => Promise; - /** Host context used to resolve $HOST/$USER/$PORT/$NAME in runSnippet actions. */ - hostContext?: SnippetHostContext | null; - /** - * Called instead of sending the snippet directly when its content still has - * unresolved $INPUT_n placeholders after host-variable substitution -- the - * caller is expected to collect values (e.g. via a dialog) and send itself. - */ - onSnippetNeedsInputs?: (snippet: { id: string; content: string }) => void; +} + +/** The keybinding actions the terminal runs itself. */ +export const TERMINAL_KEYBINDING_ACTIONS = [ + "copy", + "paste", + "sendControlCode", + "sendText", +] as const; + +export function isTerminalKeybindingAction(type: string): boolean { + return (TERMINAL_KEYBINDING_ACTIONS as readonly string[]).includes(type); } export function sendRawToSocket( @@ -33,10 +32,19 @@ export function sendRawToSocket( } } +function text(action: KeybindingAction, key: string): string { + const value = action[key]; + return typeof value === "string" ? value : ""; +} + +/** + * Runs one of the terminal's own bound actions. False for any other type, + * which the terminal hands on to whoever registered it. + */ export function dispatchKeybindingAction( action: KeybindingAction, ctx: KeybindingDispatchContext, -): void { +): boolean { const sendRaw = (data: string) => sendRawToSocket(ctx.webSocketRef, data); switch (action.type) { @@ -46,58 +54,96 @@ export function dispatchKeybindingAction( ctx.writeTextToClipboard(selection); ctx.terminal.clearSelection(); } - return; + return true; } case "paste": { - ctx.readTextFromClipboard().then((text) => { - if (text) ctx.terminal.paste(text); + ctx.readTextFromClipboard().then((value) => { + if (value) ctx.terminal.paste(value); }); - return; + return true; } case "sendControlCode": { - if (!action.controlCode) return; - const code = action.controlCode.toLowerCase().charCodeAt(0) - 96; + const letter = text(action, "controlCode"); + if (!letter) return true; + const code = letter.toLowerCase().charCodeAt(0) - 96; if (code >= 1 && code <= 26) sendRaw(String.fromCharCode(code)); - return; + return true; } case "sendText": { - sendRaw((action.text ?? "") + (action.appendEnter ? "\r" : "")); - return; - } - case "runSnippet": { - if (!action.snippetId) return; - const snippetId = Number(action.snippetId); - if (!Number.isFinite(snippetId)) return; - // Resolving with no inputValues first tells us whether the snippet - // still needs them; the plugin decides and reports back. - void invokeAction( - "snippets.resolveForTerminal", - snippetId, - ctx.hostContext ?? null, - ).then((result) => { - const resolved = result as - { needsInputs: boolean; content: string } | null | undefined; - if (!resolved) return; - if (resolved.needsInputs) { - ctx.onSnippetNeedsInputs?.({ - id: action.snippetId!, - content: resolved.content, - }); - return; - } - sendRaw(resolved.content + (action.appendEnter !== false ? "\r" : "")); - }); - return; - } - case "nextTab": - case "previousTab": - case "openCommandPalette": { - window.dispatchEvent( - new CustomEvent("termix:global-keybinding", { - detail: { type: action.type }, - }), - ); - return; + sendRaw(text(action, "text") + (action.appendEnter === true ? "\r" : "")); + return true; } + default: + return false; } } + +export function validateSendText(action: KeybindingAction): string | null { + return text(action, "text").trim() ? null : "keybindings.textRequired"; +} + +export function validateSendControlCode( + action: KeybindingAction, +): string | null { + return /^[a-zA-Z]$/.test(text(action, "controlCode")) + ? null + : "keybindings.controlCodeRequired"; +} + +function combo( + key: string, + modifiers: { ctrl?: boolean; alt?: boolean; shift?: boolean; meta?: boolean }, +) { + return { + key, + isCode: false, + ctrl: !!modifiers.ctrl, + alt: !!modifiers.alt, + shift: !!modifiers.shift, + meta: !!modifiers.meta, + }; +} + +/** The terminal's built-in keys, listed so a user can rebind them. */ +export const TERMINAL_KEYBINDING_DEFAULTS: KeybindingDefaultContribution[] = [ + { + id: "default-copy-ctrlc", + combo: combo("c", { ctrl: true }), + descriptionKey: "keybindings.builtIn.copyCtrlC", + }, + { + id: "default-copy-ctrlshiftc", + combo: combo("c", { ctrl: true, shift: true }), + descriptionKey: "keybindings.builtIn.copyCtrlShiftC", + }, + { + id: "default-copy-cmdc", + combo: combo("c", { meta: true }), + descriptionKey: "keybindings.builtIn.copyCmdC", + }, + { + id: "default-paste-ctrlshiftv", + combo: combo("v", { ctrl: true, shift: true }), + descriptionKey: "keybindings.builtIn.pasteCtrlShiftV", + }, + { + id: "default-ctrlaltw", + combo: combo("w", { ctrl: true, alt: true }), + descriptionKey: "keybindings.builtIn.ctrlW", + }, + { + id: "default-ctrlaltt", + combo: combo("t", { ctrl: true, alt: true }), + descriptionKey: "keybindings.builtIn.ctrlT", + }, + { + id: "default-ctrlaltn", + combo: combo("n", { ctrl: true, alt: true }), + descriptionKey: "keybindings.builtIn.ctrlN", + }, + { + id: "default-ctrlaltq", + combo: combo("q", { ctrl: true, alt: true }), + descriptionKey: "keybindings.builtIn.ctrlQ", + }, +]; diff --git a/plugins/ssh-terminal/src/frontend/lib/keybinding-editors.tsx b/plugins/ssh-terminal/src/frontend/lib/keybinding-editors.tsx new file mode 100644 index 000000000..52d614176 --- /dev/null +++ b/plugins/ssh-terminal/src/frontend/lib/keybinding-editors.tsx @@ -0,0 +1,81 @@ +import { + useTranslation, + type KeybindingActionEditorProps, +} from "@termix/plugin-sdk/frontend"; +import { Input } from "@termix/plugin-sdk/ui"; + +export function PasteNote() { + const { t } = useTranslation(); + return ( + + {t("keybindings.clipboardPermissionNote")} + + ); +} + +export function SendControlCodeEditor({ + action, + onChange, +}: KeybindingActionEditorProps) { + const { t } = useTranslation(); + const value = + typeof action.controlCode === "string" ? action.controlCode : ""; + return ( +
+ + + onChange({ + type: action.type, + controlCode: e.target.value.toLowerCase() || undefined, + }) + } + placeholder="w" + className="w-16 font-mono" + /> +
+ ); +} + +export function SendTextEditor({ + action, + onChange, +}: KeybindingActionEditorProps) { + const { t } = useTranslation(); + const text = typeof action.text === "string" ? action.text : ""; + const appendEnter = action.appendEnter === true; + return ( +
+ +