mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 15:45:23 +00:00
feat: move snippets, keybinding actions and macros out of core into plugins
This commit is contained in:
1 parent
d9bba10612
commit
e0c30a4e6f
262 files changed
+8979
-10632
No files matched your search
@@ -279,6 +279,51 @@
|
|||||||
"guest": {
|
"guest": {
|
||||||
"type": "boolean"
|
"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": {
|
"syncEntities": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
@@ -840,6 +885,10 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"description": "HTTP options for the plugin's /plugin-api/<id>/ router.",
|
"description": "HTTP options for the plugin's /plugin-api/<id>/ router.",
|
||||||
"properties": {
|
"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": {
|
"legacyPaths": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Old URLs under /<plugin id>/ served by the plugin's router with that prefix removed.",
|
"description": "Old URLs under /<plugin id>/ served by the plugin's router with that prefix removed.",
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export interface SyncEntityRegistration {
|
|||||||
singleton?: boolean;
|
singleton?: boolean;
|
||||||
/**
|
/**
|
||||||
* Names core uses when its own rows point at this entity without knowing
|
* 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[];
|
answersTo?: readonly string[];
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -301,7 +301,8 @@ export interface HostEditorSectionProps {
|
|||||||
) => void;
|
) => void;
|
||||||
host?: PluginHostRecord;
|
host?: PluginHostRecord;
|
||||||
credentials?: unknown[];
|
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. */
|
/** Which connection protocols are switched on for this host. */
|
||||||
protocols: Record<string, boolean>;
|
protocols: Record<string, boolean>;
|
||||||
}
|
}
|
||||||
@@ -408,6 +409,97 @@ export interface PaletteEntryContribution {
|
|||||||
run: (shell: ShellApi, host?: PluginHostRecord) => void;
|
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<PaletteItem[]>;
|
||||||
|
/** 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<KeybindingActionEditorProps>;
|
||||||
|
/** Drawn after the action's name in the binding list, e.g. a warning. */
|
||||||
|
summary?: ComponentType<KeybindingActionEditorProps>;
|
||||||
|
/** 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 {
|
export interface DashboardCardProps {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
shell: ShellApi;
|
shell: ShellApi;
|
||||||
@@ -627,6 +719,11 @@ export interface TermixApp extends TermixAppInfo {
|
|||||||
item: HostContextMenuItemContribution,
|
item: HostContextMenuItemContribution,
|
||||||
) => Disposer;
|
) => Disposer;
|
||||||
registerPaletteEntry: (entry: PaletteEntryContribution) => Disposer;
|
registerPaletteEntry: (entry: PaletteEntryContribution) => Disposer;
|
||||||
|
registerPaletteGroup: (group: PaletteGroupContribution) => Disposer;
|
||||||
|
registerKeybindingAction: (action: KeybindingActionContribution) => Disposer;
|
||||||
|
registerKeybindingDefault: (
|
||||||
|
binding: KeybindingDefaultContribution,
|
||||||
|
) => Disposer;
|
||||||
registerDashboardCard: (card: DashboardCardContribution) => Disposer;
|
registerDashboardCard: (card: DashboardCardContribution) => Disposer;
|
||||||
registerHomepageWidget: (widget: HomepageWidgetContribution) => Disposer;
|
registerHomepageWidget: (widget: HomepageWidgetContribution) => Disposer;
|
||||||
registerSettingsComponent: (
|
registerSettingsComponent: (
|
||||||
@@ -815,23 +912,16 @@ export interface KeyCombo {
|
|||||||
meta: boolean;
|
meta: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KeybindingActionType =
|
/**
|
||||||
| "copy"
|
* The shell's own actions (nextTab, previousTab, openCommandPalette,
|
||||||
| "paste"
|
* reconnectSession) or one a plugin declared in contributes.keybindingActions.
|
||||||
| "sendControlCode"
|
*/
|
||||||
| "sendText"
|
export type KeybindingActionType = string;
|
||||||
| "runSnippet"
|
|
||||||
| "nextTab"
|
|
||||||
| "previousTab"
|
|
||||||
| "openCommandPalette"
|
|
||||||
| "reconnectSession";
|
|
||||||
|
|
||||||
|
/** A bound action: its type plus the parameters its declaration lists. */
|
||||||
export interface KeybindingAction {
|
export interface KeybindingAction {
|
||||||
type: KeybindingActionType;
|
type: KeybindingActionType;
|
||||||
text?: string;
|
[param: string]: unknown;
|
||||||
controlCode?: string;
|
|
||||||
snippetId?: string;
|
|
||||||
appendEnter?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomKeybinding {
|
export interface CustomKeybinding {
|
||||||
@@ -869,6 +959,15 @@ export interface PluginCoreApi {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
/** The user's custom keybindings, enabled or not. */
|
/** The user's custom keybindings, enabled or not. */
|
||||||
getCustomKeybindings: () => Promise<CustomKeybinding[]>;
|
getCustomKeybindings: () => Promise<CustomKeybinding[]>;
|
||||||
|
/**
|
||||||
|
* 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). */
|
/** A browser-side UI preference (a cookie, or the desktop app's store). */
|
||||||
getClientPreference: (name: string) => string | undefined;
|
getClientPreference: (name: string) => string | undefined;
|
||||||
/** Saves a browser-side UI preference where getClientPreference reads it. */
|
/** Saves a browser-side UI preference where getClientPreference reads it. */
|
||||||
@@ -1021,6 +1120,13 @@ export function getCustomKeybindings(): Promise<CustomKeybinding[]> {
|
|||||||
return requireHost().core.getCustomKeybindings();
|
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 {
|
export function getClientPreference(name: string): string | undefined {
|
||||||
return requireHost().core.getClientPreference(name);
|
return requireHost().core.getClientPreference(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,8 +344,46 @@ export interface PluginContributions {
|
|||||||
* off and a desktop can list them in its sync settings.
|
* off and a desktop can list them in its sync settings.
|
||||||
*/
|
*/
|
||||||
syncEntities?: string[];
|
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
|
||||||
|
* "<plugin id>.<name>"; the bundled ones keep their 2.8 names.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
params?: Record<string, PluginKeybindingParam>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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.
|
* How a desktop linked to a server treats this plugin.
|
||||||
* - "mirror": follows the server, installed and switched on or off with it.
|
* - "mirror": follows the server, installed and switched on or off with it.
|
||||||
@@ -376,6 +414,12 @@ export interface PluginHttpContribution {
|
|||||||
* Core routes win a clash.
|
* Core routes win a clash.
|
||||||
*/
|
*/
|
||||||
legacyRedirects?: PluginLegacyRedirect[];
|
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/<id>/.
|
||||||
|
*/
|
||||||
|
adminImpersonation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginLegacyRedirect {
|
export interface PluginLegacyRedirect {
|
||||||
@@ -478,6 +522,7 @@ const ALLOWED_CONTRIBUTES = new Set([
|
|||||||
"uiPresets",
|
"uiPresets",
|
||||||
"auth",
|
"auth",
|
||||||
"syncEntities",
|
"syncEntities",
|
||||||
|
"keybindingActions",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ALLOWED_SETTINGS_FIELD = [
|
const ALLOWED_SETTINGS_FIELD = [
|
||||||
@@ -947,6 +992,132 @@ function validateContributes(
|
|||||||
validateAuthContribution(contributes.auth, errors);
|
validateAuthContribution(contributes.auth, errors);
|
||||||
validateHttpContribution(contributes.http, pluginId, errors);
|
validateHttpContribution(contributes.http, pluginId, errors);
|
||||||
validateUiPresets(contributes.uiPresets, 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<string>();
|
||||||
|
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<string, unknown>,
|
||||||
|
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 {
|
function isPresetValue(value: unknown): boolean {
|
||||||
@@ -1041,10 +1212,18 @@ function validateHttpContribution(
|
|||||||
}
|
}
|
||||||
rejectUnknown(
|
rejectUnknown(
|
||||||
value,
|
value,
|
||||||
["legacyPaths", "legacyRedirects"],
|
["legacyPaths", "legacyRedirects", "adminImpersonation"],
|
||||||
"contributes.http",
|
"contributes.http",
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
|
if (
|
||||||
|
value.adminImpersonation !== undefined &&
|
||||||
|
typeof value.adminImpersonation !== "boolean"
|
||||||
|
) {
|
||||||
|
errors.push(
|
||||||
|
'Field "contributes.http.adminImpersonation" must be a boolean',
|
||||||
|
);
|
||||||
|
}
|
||||||
validateLegacyRedirects(value.legacyRedirects, errors);
|
validateLegacyRedirects(value.legacyRedirects, errors);
|
||||||
const paths = value.legacyPaths;
|
const paths = value.legacyPaths;
|
||||||
if (paths === undefined) return;
|
if (paths === undefined) return;
|
||||||
|
|||||||
@@ -2000,7 +2000,14 @@ export interface RenderedPluginApp {
|
|||||||
actions: () => string[];
|
actions: () => string[];
|
||||||
loginMethods: () => string[];
|
loginMethods: () => string[];
|
||||||
secondFactors: () => 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<import("./frontend.js").PaletteItem[]>;
|
||||||
/** Shell calls made by the plugin's code, applyLayout included. */
|
/** Shell calls made by the plugin's code, applyLayout included. */
|
||||||
shellCalls: ShellCall[];
|
shellCalls: ShellCall[];
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ const offline = () =>
|
|||||||
getHostPassword: async () => null,
|
getHostPassword: async () => null,
|
||||||
patchOpenTab: async () => {},
|
patchOpenTab: async () => {},
|
||||||
getCustomKeybindings: async () => [],
|
getCustomKeybindings: async () => [],
|
||||||
|
runKeybindingAction: () => false,
|
||||||
getClientPreference: () => undefined,
|
getClientPreference: () => undefined,
|
||||||
setClientPreference: () => {},
|
setClientPreference: () => {},
|
||||||
listHosts: async () => [],
|
listHosts: async () => [],
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
"network:serve",
|
"network:serve",
|
||||||
"ui:surface"
|
"ui:surface"
|
||||||
],
|
],
|
||||||
|
"optionalDependencies": {
|
||||||
|
"snippets": "^1.0.0"
|
||||||
|
},
|
||||||
"provides": [
|
"provides": [
|
||||||
{
|
{
|
||||||
"service": "fleets.access",
|
"service": "fleets.access",
|
||||||
|
|||||||
@@ -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, string> = {},
|
|
||||||
): 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;
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
ElevationError,
|
ElevationError,
|
||||||
} from "@termix/plugin-sdk/host-commands";
|
} from "@termix/plugin-sdk/host-commands";
|
||||||
import type { FleetRepository } from "./repository.js";
|
import type { FleetRepository } from "./repository.js";
|
||||||
import { resolveCommandVariables } from "./command-variables.js";
|
|
||||||
|
|
||||||
const FLEET_TRANSFER_MAX_BYTES = 200 * 1024 * 1024;
|
const FLEET_TRANSFER_MAX_BYTES = 200 * 1024 * 1024;
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
@@ -74,6 +73,35 @@ function parseShareTargets(
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CommandVariables {
|
||||||
|
resolve: (
|
||||||
|
content: string,
|
||||||
|
host: {
|
||||||
|
ip?: string;
|
||||||
|
username?: string;
|
||||||
|
port?: number | string;
|
||||||
|
name?: string;
|
||||||
|
} | null,
|
||||||
|
inputValues?: Record<string, string>,
|
||||||
|
) => 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<PluginContext, "registry">,
|
||||||
|
command: string,
|
||||||
|
host: Parameters<CommandVariables["resolve"]>[1],
|
||||||
|
inputValues: Record<string, string> = {},
|
||||||
|
): string {
|
||||||
|
const variables =
|
||||||
|
ctx.registry.consume<CommandVariables>("snippets.variables");
|
||||||
|
return variables ? variables.resolve(command, host, inputValues) : command;
|
||||||
|
}
|
||||||
|
|
||||||
function actor(ctx: PluginContext): string {
|
function actor(ctx: PluginContext): string {
|
||||||
return ctx.currentActor() as string;
|
return ctx.currentActor() as string;
|
||||||
}
|
}
|
||||||
@@ -884,6 +912,7 @@ export function registerFleetRoutes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolvedCommand = resolveCommandVariables(
|
const resolvedCommand = resolveCommandVariables(
|
||||||
|
ctx,
|
||||||
command,
|
command,
|
||||||
{
|
{
|
||||||
ip: fullHost.ip,
|
ip: fullHost.ip,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import {
|
import { getErrorMessage, type SnippetInput } from "./helpers.js";
|
||||||
getErrorMessage,
|
|
||||||
extractSnippetInputs,
|
|
||||||
type SnippetInput,
|
|
||||||
} from "./helpers.js";
|
|
||||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
|
invokeAction,
|
||||||
useTranslation,
|
useTranslation,
|
||||||
useHosts,
|
useHosts,
|
||||||
type PluginHostRecord,
|
type PluginHostRecord,
|
||||||
@@ -792,8 +789,16 @@ function RunCommandTab({ api, fleetId }: { api: FleetsApi; fleetId: number }) {
|
|||||||
const [running, setRunning] = useState(false);
|
const [running, setRunning] = useState(false);
|
||||||
const [results, setResults] = useState<FleetHostResult[] | null>(null);
|
const [results, setResults] = useState<FleetHostResult[] | null>(null);
|
||||||
|
|
||||||
|
// The snippets plugin owns $INPUT_n parsing; without it the command
|
||||||
|
// runs as typed.
|
||||||
useEffect(() => {
|
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]);
|
}, [command]);
|
||||||
|
|
||||||
async function handleRun() {
|
async function handleRun() {
|
||||||
|
|||||||
@@ -21,24 +21,3 @@ export interface SnippetInput {
|
|||||||
key: string;
|
key: string;
|
||||||
label: 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<string, SnippetInput>();
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveCommandVariables } from "../../src/backend/routes.js";
|
||||||
|
|
||||||
|
function registry(value?: unknown) {
|
||||||
|
return {
|
||||||
|
registry: {
|
||||||
|
provide: () => {},
|
||||||
|
revoke: () => false,
|
||||||
|
consume: <T>() => value as T | undefined,
|
||||||
|
},
|
||||||
|
} as never;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("resolveCommandVariables", () => {
|
||||||
|
it("fills variables through the snippets plugin's resolver", () => {
|
||||||
|
const ctx = registry({
|
||||||
|
resolve: (content: string, host: { ip?: string } | null) =>
|
||||||
|
content.replace("$HOST", host?.ip ?? ""),
|
||||||
|
});
|
||||||
|
expect(resolveCommandVariables(ctx, "ping $HOST", { ip: "10.0.0.1" })).toBe(
|
||||||
|
"ping 10.0.0.1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("runs the command as typed while snippets is off", () => {
|
||||||
|
expect(
|
||||||
|
resolveCommandVariables(registry(), "ping $HOST", { ip: "10.0.0.1" }),
|
||||||
|
).toBe("ping $HOST");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -54,10 +54,6 @@
|
|||||||
"processesRunning": "running",
|
"processesRunning": "running",
|
||||||
"loginStats": "SSH Login Statistics",
|
"loginStats": "SSH Login Statistics",
|
||||||
"noRecentLoginData": "No recent login data",
|
"noRecentLoginData": "No recent login data",
|
||||||
"executingQuickAction": "Executing {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} completed successfully",
|
|
||||||
"quickActionFailed": "{{name}} failed",
|
|
||||||
"quickActionError": "Failed to execute {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Listening Ports",
|
"title": "Listening Ports",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
@@ -182,8 +178,7 @@
|
|||||||
"wgLastHandshake": "Last handshake",
|
"wgLastHandshake": "Last handshake",
|
||||||
"wgHandshakeNever": "Never",
|
"wgHandshakeNever": "Never",
|
||||||
"wgTransfer": "Transfer"
|
"wgTransfer": "Transfer"
|
||||||
},
|
}
|
||||||
"snippetsUnavailable": "Snippets are turned off, so quick actions cannot run."
|
|
||||||
},
|
},
|
||||||
"metricsHistory": {
|
"metricsHistory": {
|
||||||
"title": "Metrics History",
|
"title": "Metrics History",
|
||||||
@@ -219,12 +214,6 @@
|
|||||||
"addExcludedMount": "Add",
|
"addExcludedMount": "Add",
|
||||||
"visibleWidgets": "Visible Widgets",
|
"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.",
|
"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",
|
"hostNotFound": "Host not found",
|
||||||
"loadingHost": "Loading host..."
|
"loadingHost": "Loading host..."
|
||||||
},
|
},
|
||||||
@@ -269,15 +258,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disk"
|
"disk": "Disk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Cancel",
|
|
||||||
"confirmRunButton": "Run",
|
|
||||||
"confirmRunMessage": "Run \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Connected"
|
"connected": "Connected"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "hardloop",
|
"processesRunning": "hardloop",
|
||||||
"loginStats": "SSH-aanmeldingstatistieke",
|
"loginStats": "SSH-aanmeldingstatistieke",
|
||||||
"noRecentLoginData": "Geen onlangse aanmelddata nie",
|
"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": {
|
"ports": {
|
||||||
"title": "Luisterpoorte",
|
"title": "Luisterpoorte",
|
||||||
"protocol": "Protokol",
|
"protocol": "Protokol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Voeg by",
|
"addExcludedMount": "Voeg by",
|
||||||
"visibleWidgets": "Sigbare Widgets",
|
"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.",
|
"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",
|
"hostNotFound": "Gasheer nie gevind nie",
|
||||||
"loadingHost": "Laai gasheer..."
|
"loadingHost": "Laai gasheer..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Skyf"
|
"disk": "Skyf"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Kanselleer",
|
|
||||||
"confirmRunButton": "Hardloop",
|
|
||||||
"confirmRunMessage": "Begin \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Verbonde"
|
"connected": "Verbonde"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "قيد التشغيل",
|
"processesRunning": "قيد التشغيل",
|
||||||
"loginStats": "إحصائيات تسجيل الدخول عبر SSH",
|
"loginStats": "إحصائيات تسجيل الدخول عبر SSH",
|
||||||
"noRecentLoginData": "لا توجد بيانات تسجيل دخول حديثة",
|
"noRecentLoginData": "لا توجد بيانات تسجيل دخول حديثة",
|
||||||
"executingQuickAction": "جارٍ تنفيذ {{name}}...",
|
|
||||||
"quickActionSuccess": "تم إكمال {{name}} بنجاح",
|
|
||||||
"quickActionFailed": "فشل {{name}}",
|
|
||||||
"quickActionError": "فشل تنفيذ {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "المنافذ المستمعة",
|
"title": "المنافذ المستمعة",
|
||||||
"protocol": "البروتوكول",
|
"protocol": "البروتوكول",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "يضيف",
|
"addExcludedMount": "يضيف",
|
||||||
"visibleWidgets": "الأدوات المرئية",
|
"visibleWidgets": "الأدوات المرئية",
|
||||||
"widgetsMovedToHostMetrics": "تتم الآن إضافة البطاقات وترتيبها وتغيير حجمها مباشرةً في علامة تبويب مقاييس المضيف. افتح مقاييس المضيف لهذا المضيف واستخدم تخصيص لاختيار البطاقات التي ستظهر.",
|
"widgetsMovedToHostMetrics": "تتم الآن إضافة البطاقات وترتيبها وتغيير حجمها مباشرةً في علامة تبويب مقاييس المضيف. افتح مقاييس المضيف لهذا المضيف واستخدم تخصيص لاختيار البطاقات التي ستظهر.",
|
||||||
"quickActionsLabel": "إجراءات سريعة",
|
|
||||||
"quickActionsToolbar": "تظهر الإجراءات السريعة كأزرار في شريط أدوات مقاييس المضيف لتنفيذ الأوامر بنقرة واحدة.",
|
|
||||||
"noQuickActions": "لا توجد إجراءات سريعة بعد.",
|
|
||||||
"buttonLabel": "تسمية الزر",
|
|
||||||
"selectSnippetPlaceholder": "اختر مقتطفًا...",
|
|
||||||
"addActionBtn": "إضافة إجراء",
|
|
||||||
"hostNotFound": "المضيف غير موجود",
|
"hostNotFound": "المضيف غير موجود",
|
||||||
"loadingHost": "جارٍ تحميل المضيف..."
|
"loadingHost": "جارٍ تحميل المضيف..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "القرص"
|
"disk": "القرص"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "إلغاء",
|
|
||||||
"confirmRunButton": "تشغيل",
|
|
||||||
"confirmRunMessage": "تشغيل \"{{name}}\"؟"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "متصل"
|
"connected": "متصل"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "бягане",
|
"processesRunning": "бягане",
|
||||||
"loginStats": "Статистика за SSH вход",
|
"loginStats": "Статистика за SSH вход",
|
||||||
"noRecentLoginData": "Няма скорошни данни за вход",
|
"noRecentLoginData": "Няма скорошни данни за вход",
|
||||||
"executingQuickAction": "Изпълнява се {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} завършено успешно",
|
|
||||||
"quickActionFailed": "{{name}} неуспешно",
|
|
||||||
"quickActionError": "Неуспешно изпълнение на {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Портове за слушане",
|
"title": "Портове за слушане",
|
||||||
"protocol": "Протокол",
|
"protocol": "Протокол",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Добавяне",
|
"addExcludedMount": "Добавяне",
|
||||||
"visibleWidgets": "Видими джаджи",
|
"visibleWidgets": "Видими джаджи",
|
||||||
"widgetsMovedToHostMetrics": "Картите вече се добавят, подреждат и преоразмеряват директно в раздела „Показатели за хоста“. Отворете „Показатели за хоста“ за този хост и използвайте „Персонализиране“, за да изберете кои карти да се показват.",
|
"widgetsMovedToHostMetrics": "Картите вече се добавят, подреждат и преоразмеряват директно в раздела „Показатели за хоста“. Отворете „Показатели за хоста“ за този хост и използвайте „Персонализиране“, за да изберете кои карти да се показват.",
|
||||||
"quickActionsLabel": "Бързи действия",
|
|
||||||
"quickActionsToolbar": "Бързите действия се показват като бутони в лентата с инструменти Host Metrics за изпълнение на команди с едно щракване.",
|
|
||||||
"noQuickActions": "Все още няма бързи действия.",
|
|
||||||
"buttonLabel": "Етикет на бутона",
|
|
||||||
"selectSnippetPlaceholder": "Изберете фрагмент...",
|
|
||||||
"addActionBtn": "Добавяне на действие",
|
|
||||||
"hostNotFound": "Хостът не е намерен",
|
"hostNotFound": "Хостът не е намерен",
|
||||||
"loadingHost": "Зареждане на хоста..."
|
"loadingHost": "Зареждане на хоста..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Диск"
|
"disk": "Диск"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Отказ",
|
|
||||||
"confirmRunButton": "Бягане",
|
|
||||||
"confirmRunMessage": "Изпълни „{{name}}“?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Свързан"
|
"connected": "Свързан"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "দৌড়ানো",
|
"processesRunning": "দৌড়ানো",
|
||||||
"loginStats": "SSH লগইন পরিসংখ্যান",
|
"loginStats": "SSH লগইন পরিসংখ্যান",
|
||||||
"noRecentLoginData": "সাম্প্রতিক কোনো লগইন ডেটা নেই",
|
"noRecentLoginData": "সাম্প্রতিক কোনো লগইন ডেটা নেই",
|
||||||
"executingQuickAction": "{{name}} কার্যকর করা হচ্ছে ...",
|
|
||||||
"quickActionSuccess": "{{name}} সফলভাবে সম্পন্ন হয়েছে",
|
|
||||||
"quickActionFailed": "{{name}} ব্যর্থ হয়েছে",
|
|
||||||
"quickActionError": "{{name}} কার্যকর করতে ব্যর্থ হয়েছে",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "লিসেনিং পোর্ট",
|
"title": "লিসেনিং পোর্ট",
|
||||||
"protocol": "প্রোটোকল",
|
"protocol": "প্রোটোকল",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "যোগ করুন",
|
"addExcludedMount": "যোগ করুন",
|
||||||
"visibleWidgets": "দৃশ্যমান উইজেট",
|
"visibleWidgets": "দৃশ্যমান উইজেট",
|
||||||
"widgetsMovedToHostMetrics": "এখন কার্ডগুলি সরাসরি হোস্ট মেট্রিক্স ট্যাবে যোগ, সাজানো এবং আকার পরিবর্তন করা যায়। এই হোস্টের জন্য হোস্ট মেট্রিক্স খুলুন এবং কোন কার্ডগুলি দেখানো হবে তা বেছে নিতে কাস্টমাইজ ব্যবহার করুন।",
|
"widgetsMovedToHostMetrics": "এখন কার্ডগুলি সরাসরি হোস্ট মেট্রিক্স ট্যাবে যোগ, সাজানো এবং আকার পরিবর্তন করা যায়। এই হোস্টের জন্য হোস্ট মেট্রিক্স খুলুন এবং কোন কার্ডগুলি দেখানো হবে তা বেছে নিতে কাস্টমাইজ ব্যবহার করুন।",
|
||||||
"quickActionsLabel": "দ্রুত পদক্ষেপ",
|
|
||||||
"quickActionsToolbar": "এক ক্লিকে কমান্ড কার্যকর করার জন্য কুইক অ্যাকশনগুলো হোস্ট মেট্রিক্স টুলবারে বাটন হিসেবে প্রদর্শিত হয়।",
|
|
||||||
"noQuickActions": "এখনো কোনো দ্রুত পদক্ষেপ নেওয়া হয়নি।",
|
|
||||||
"buttonLabel": "বোতাম লেবেল",
|
|
||||||
"selectSnippetPlaceholder": "স্নিপেট নির্বাচন করুন...",
|
|
||||||
"addActionBtn": "অ্যাকশন যোগ করুন",
|
|
||||||
"hostNotFound": "হোস্ট খুঁজে পাওয়া যায়নি",
|
"hostNotFound": "হোস্ট খুঁজে পাওয়া যায়নি",
|
||||||
"loadingHost": "হোস্ট লোড হচ্ছে..."
|
"loadingHost": "হোস্ট লোড হচ্ছে..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "ডিস্ক"
|
"disk": "ডিস্ক"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "বাতিল করুন",
|
|
||||||
"confirmRunButton": "দৌড়",
|
|
||||||
"confirmRunMessage": "\"{{name}} \" চালান?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "সংযুক্ত"
|
"connected": "সংযুক্ত"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "corrent",
|
"processesRunning": "corrent",
|
||||||
"loginStats": "Estadístiques d'inici de sessió SSH",
|
"loginStats": "Estadístiques d'inici de sessió SSH",
|
||||||
"noRecentLoginData": "Sense dades d'inici de sessió recents",
|
"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": {
|
"ports": {
|
||||||
"title": "Ports d'escolta",
|
"title": "Ports d'escolta",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Afegeix",
|
"addExcludedMount": "Afegeix",
|
||||||
"visibleWidgets": "Widgets visibles",
|
"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.",
|
"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ó",
|
"hostNotFound": "No s'ha trobat l'amfitrió",
|
||||||
"loadingHost": "S'està carregant l'amfitrió..."
|
"loadingHost": "S'està carregant l'amfitrió..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disc"
|
"disk": "Disc"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Cancel·la",
|
|
||||||
"confirmRunButton": "Corre",
|
|
||||||
"confirmRunMessage": "Executar \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Connectat"
|
"connected": "Connectat"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "běh",
|
"processesRunning": "běh",
|
||||||
"loginStats": "Statistiky přihlášení SSH",
|
"loginStats": "Statistiky přihlášení SSH",
|
||||||
"noRecentLoginData": "Žádné nedávné přihlašovací údaje",
|
"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": {
|
"ports": {
|
||||||
"title": "Poslouchající porty",
|
"title": "Poslouchající porty",
|
||||||
"protocol": "Protokol",
|
"protocol": "Protokol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Přidat",
|
"addExcludedMount": "Přidat",
|
||||||
"visibleWidgets": "Viditelné widgety",
|
"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í.",
|
"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",
|
"hostNotFound": "Hostitel nenalezen",
|
||||||
"loadingHost": "Načítání hostitele..."
|
"loadingHost": "Načítání hostitele..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disk"
|
"disk": "Disk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Zrušit",
|
|
||||||
"confirmRunButton": "Běh",
|
|
||||||
"confirmRunMessage": "Spustit „{{name}}“?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Připojeno"
|
"connected": "Připojeno"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "løb",
|
"processesRunning": "løb",
|
||||||
"loginStats": "SSH-loginstatistik",
|
"loginStats": "SSH-loginstatistik",
|
||||||
"noRecentLoginData": "Ingen nylige logindata",
|
"noRecentLoginData": "Ingen nylige logindata",
|
||||||
"executingQuickAction": "Udfører {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} gennemført",
|
|
||||||
"quickActionFailed": "{{name}} mislykkedes",
|
|
||||||
"quickActionError": "Kunne ikke udføre {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Lytteporte",
|
"title": "Lytteporte",
|
||||||
"protocol": "Protokol",
|
"protocol": "Protokol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Tilføje",
|
"addExcludedMount": "Tilføje",
|
||||||
"visibleWidgets": "Synlige widgets",
|
"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.",
|
"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",
|
"hostNotFound": "Værten blev ikke fundet",
|
||||||
"loadingHost": "Indlæser vært..."
|
"loadingHost": "Indlæser vært..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disk"
|
"disk": "Disk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Ophæve",
|
|
||||||
"confirmRunButton": "Løbe",
|
|
||||||
"confirmRunMessage": "Kør \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Forbundet"
|
"connected": "Forbundet"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "laufend",
|
"processesRunning": "laufend",
|
||||||
"loginStats": "SSH-Anmeldestatistiken",
|
"loginStats": "SSH-Anmeldestatistiken",
|
||||||
"noRecentLoginData": "Keine aktuellen Anmeldedaten",
|
"noRecentLoginData": "Keine aktuellen Anmeldedaten",
|
||||||
"executingQuickAction": "{{name}} wird ausgeführt...",
|
|
||||||
"quickActionSuccess": "{{name}} erfolgreich abgeschlossen",
|
|
||||||
"quickActionFailed": "{{name}} fehlgeschlagen",
|
|
||||||
"quickActionError": "Fehler beim Ausführen von {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Lauschende Ports",
|
"title": "Lauschende Ports",
|
||||||
"protocol": "Protokoll",
|
"protocol": "Protokoll",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Hinzufügen",
|
"addExcludedMount": "Hinzufügen",
|
||||||
"visibleWidgets": "Sichtbare Widgets",
|
"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.",
|
"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",
|
"hostNotFound": "Host nicht gefunden",
|
||||||
"loadingHost": "Host wird geladen..."
|
"loadingHost": "Host wird geladen..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Datenträger"
|
"disk": "Datenträger"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Abbrechen",
|
|
||||||
"confirmRunButton": "Ausführen",
|
|
||||||
"confirmRunMessage": "„{{name}}“ ausführen?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Verbunden"
|
"connected": "Verbunden"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "τρέξιμο",
|
"processesRunning": "τρέξιμο",
|
||||||
"loginStats": "Στατιστικά στοιχεία σύνδεσης SSH",
|
"loginStats": "Στατιστικά στοιχεία σύνδεσης SSH",
|
||||||
"noRecentLoginData": "Δεν υπάρχουν πρόσφατα δεδομένα σύνδεσης",
|
"noRecentLoginData": "Δεν υπάρχουν πρόσφατα δεδομένα σύνδεσης",
|
||||||
"executingQuickAction": "Εκτέλεση {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} ολοκληρώθηκε με επιτυχία",
|
|
||||||
"quickActionFailed": "{{name}} απέτυχε",
|
|
||||||
"quickActionError": "Αποτυχία εκτέλεσης {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Θύρες ακρόασης",
|
"title": "Θύρες ακρόασης",
|
||||||
"protocol": "Πρωτόκολλο",
|
"protocol": "Πρωτόκολλο",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Προσθέτω",
|
"addExcludedMount": "Προσθέτω",
|
||||||
"visibleWidgets": "Ορατά γραφικά στοιχεία",
|
"visibleWidgets": "Ορατά γραφικά στοιχεία",
|
||||||
"widgetsMovedToHostMetrics": "Οι κάρτες προστίθενται, ταξινομούνται και αλλάζουν μέγεθος απευθείας στην καρτέλα Μετρήσεις κεντρικού υπολογιστή. Ανοίξτε τις Μετρήσεις κεντρικού υπολογιστή για αυτόν τον κεντρικό υπολογιστή και χρησιμοποιήστε την Προσαρμογή για να επιλέξετε ποιες κάρτες θα εμφανίζονται.",
|
"widgetsMovedToHostMetrics": "Οι κάρτες προστίθενται, ταξινομούνται και αλλάζουν μέγεθος απευθείας στην καρτέλα Μετρήσεις κεντρικού υπολογιστή. Ανοίξτε τις Μετρήσεις κεντρικού υπολογιστή για αυτόν τον κεντρικό υπολογιστή και χρησιμοποιήστε την Προσαρμογή για να επιλέξετε ποιες κάρτες θα εμφανίζονται.",
|
||||||
"quickActionsLabel": "Γρήγορες ενέργειες",
|
|
||||||
"quickActionsToolbar": "Οι γρήγορες ενέργειες εμφανίζονται ως κουμπιά στη γραμμή εργαλείων Μετρήσεων κεντρικού υπολογιστή για εκτέλεση εντολών με ένα κλικ.",
|
|
||||||
"noQuickActions": "Δεν υπάρχουν ακόμη γρήγορες ενέργειες.",
|
|
||||||
"buttonLabel": "Ετικέτα κουμπιού",
|
|
||||||
"selectSnippetPlaceholder": "Επιλογή αποσπάσματος...",
|
|
||||||
"addActionBtn": "Προσθήκη ενέργειας",
|
|
||||||
"hostNotFound": "Δεν βρέθηκε κεντρικός υπολογιστής",
|
"hostNotFound": "Δεν βρέθηκε κεντρικός υπολογιστής",
|
||||||
"loadingHost": "Φόρτωση κεντρικού υπολογιστή..."
|
"loadingHost": "Φόρτωση κεντρικού υπολογιστή..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Δίσκος"
|
"disk": "Δίσκος"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Ματαίωση",
|
|
||||||
"confirmRunButton": "Τρέξιμο",
|
|
||||||
"confirmRunMessage": "Εκτέλεση \"{{name}}\";"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Συνδεδεμένος"
|
"connected": "Συνδεδεμένος"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "en ejecución",
|
"processesRunning": "en ejecución",
|
||||||
"loginStats": "Estadísticas de inicio de sesión SSH",
|
"loginStats": "Estadísticas de inicio de sesión SSH",
|
||||||
"noRecentLoginData": "No hay datos de inicio de sesión recientes",
|
"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": {
|
"ports": {
|
||||||
"title": "Puertos en escucha",
|
"title": "Puertos en escucha",
|
||||||
"protocol": "Protocolo",
|
"protocol": "Protocolo",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Agregar",
|
"addExcludedMount": "Agregar",
|
||||||
"visibleWidgets": "Widgets visibles",
|
"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.",
|
"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",
|
"hostNotFound": "Host no encontrado",
|
||||||
"loadingHost": "Cargando host..."
|
"loadingHost": "Cargando host..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disco"
|
"disk": "Disco"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"confirmRunButton": "Ejecutar",
|
|
||||||
"confirmRunMessage": "¿Ejecutar \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Conectado"
|
"connected": "Conectado"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "juokseminen",
|
"processesRunning": "juokseminen",
|
||||||
"loginStats": "SSH-kirjautumistilastot",
|
"loginStats": "SSH-kirjautumistilastot",
|
||||||
"noRecentLoginData": "Ei viimeaikaisia kirjautumistietoja",
|
"noRecentLoginData": "Ei viimeaikaisia kirjautumistietoja",
|
||||||
"executingQuickAction": "Suoritetaan {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} suoritettu onnistuneesti",
|
|
||||||
"quickActionFailed": "{{name}} epäonnistui",
|
|
||||||
"quickActionError": "Suoritus epäonnistui {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Kuunteluportit",
|
"title": "Kuunteluportit",
|
||||||
"protocol": "Protokolla",
|
"protocol": "Protokolla",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Lisätä",
|
"addExcludedMount": "Lisätä",
|
||||||
"visibleWidgets": "Näkyvät widgetit",
|
"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.",
|
"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",
|
"hostNotFound": "Isäntää ei löydy",
|
||||||
"loadingHost": "Ladataan isäntää..."
|
"loadingHost": "Ladataan isäntää..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Levy"
|
"disk": "Levy"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Peruuttaa",
|
|
||||||
"confirmRunButton": "Juokse",
|
|
||||||
"confirmRunMessage": "Suorittaa \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Yhdistetty"
|
"connected": "Yhdistetty"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "en cours",
|
"processesRunning": "en cours",
|
||||||
"loginStats": "Statistiques de connexion SSH",
|
"loginStats": "Statistiques de connexion SSH",
|
||||||
"noRecentLoginData": "Aucune donnée de connexion récente",
|
"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": {
|
"ports": {
|
||||||
"title": "Ports en écoute",
|
"title": "Ports en écoute",
|
||||||
"protocol": "Protocole",
|
"protocol": "Protocole",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Ajouter",
|
"addExcludedMount": "Ajouter",
|
||||||
"visibleWidgets": "Widgets visibles",
|
"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.",
|
"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",
|
"hostNotFound": "Hôte introuvable",
|
||||||
"loadingHost": "Chargement de l'hôte..."
|
"loadingHost": "Chargement de l'hôte..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disque"
|
"disk": "Disque"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Annuler",
|
|
||||||
"confirmRunButton": "Exécuter",
|
|
||||||
"confirmRunMessage": "Exécuter « {{name}} » ?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Connecté"
|
"connected": "Connecté"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "רִיצָה",
|
"processesRunning": "רִיצָה",
|
||||||
"loginStats": "סטטיסטיקות כניסה ל-SSH",
|
"loginStats": "סטטיסטיקות כניסה ל-SSH",
|
||||||
"noRecentLoginData": "אין נתוני התחברות אחרונים",
|
"noRecentLoginData": "אין נתוני התחברות אחרונים",
|
||||||
"executingQuickAction": "מבצע {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} הושלם בהצלחה",
|
|
||||||
"quickActionFailed": "{{name}} נכשל",
|
|
||||||
"quickActionError": "נכשל בביצוע {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "יציאות האזנה",
|
"title": "יציאות האזנה",
|
||||||
"protocol": "פּרוֹטוֹקוֹל",
|
"protocol": "פּרוֹטוֹקוֹל",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "לְהוֹסִיף",
|
"addExcludedMount": "לְהוֹסִיף",
|
||||||
"visibleWidgets": "ווידג'טים גלויים",
|
"visibleWidgets": "ווידג'טים גלויים",
|
||||||
"widgetsMovedToHostMetrics": "כעת ניתן להוסיף, לסידור ולשנות את גודל הכרטיסים ישירות בכרטיסייה 'מדדי מארח'. פתחו את 'מדדי מארח' עבור מארח זה והשתמשו באפשרות 'התאמה אישית' כדי לבחור אילו כרטיסים יוצגו.",
|
"widgetsMovedToHostMetrics": "כעת ניתן להוסיף, לסידור ולשנות את גודל הכרטיסים ישירות בכרטיסייה 'מדדי מארח'. פתחו את 'מדדי מארח' עבור מארח זה והשתמשו באפשרות 'התאמה אישית' כדי לבחור אילו כרטיסים יוצגו.",
|
||||||
"quickActionsLabel": "פעולות מהירות",
|
|
||||||
"quickActionsToolbar": "פעולות מהירות מופיעות ככפתורים בסרגל הכלים של Host Metrics לביצוע פקודה בלחיצה אחת.",
|
|
||||||
"noQuickActions": "אין עדיין פעולות מהירות.",
|
|
||||||
"buttonLabel": "תווית כפתור",
|
|
||||||
"selectSnippetPlaceholder": "בחר קטע...",
|
|
||||||
"addActionBtn": "הוסף פעולה",
|
|
||||||
"hostNotFound": "המארח לא נמצא",
|
"hostNotFound": "המארח לא נמצא",
|
||||||
"loadingHost": "טוען מארח..."
|
"loadingHost": "טוען מארח..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "דִיסק"
|
"disk": "דִיסק"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "לְבַטֵל",
|
|
||||||
"confirmRunButton": "לָרוּץ",
|
|
||||||
"confirmRunMessage": "להפעיל את \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "מְחוּבָּר"
|
"connected": "מְחוּבָּר"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "चल रहे",
|
"processesRunning": "चल रहे",
|
||||||
"loginStats": "SSH लॉगिन आँकड़े",
|
"loginStats": "SSH लॉगिन आँकड़े",
|
||||||
"noRecentLoginData": "कोई हालिया लॉगिन डेटा नहीं",
|
"noRecentLoginData": "कोई हालिया लॉगिन डेटा नहीं",
|
||||||
"executingQuickAction": "{{name}} निष्पादित हो रहा है...",
|
|
||||||
"quickActionSuccess": "{{name}} सफलतापूर्वक पूर्ण हुआ",
|
|
||||||
"quickActionFailed": "{{name}} विफल रहा",
|
|
||||||
"quickActionError": "{{name}} निष्पादित करने में विफल",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "लिसनिंग पोर्ट",
|
"title": "लिसनिंग पोर्ट",
|
||||||
"protocol": "प्रोटोकॉल",
|
"protocol": "प्रोटोकॉल",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "जोड़ना",
|
"addExcludedMount": "जोड़ना",
|
||||||
"visibleWidgets": "दृश्यमान विजेट्स",
|
"visibleWidgets": "दृश्यमान विजेट्स",
|
||||||
"widgetsMovedToHostMetrics": "कार्ड अब सीधे होस्ट मेट्रिक्स टैब में जोड़े, व्यवस्थित और आकार बदले जा सकते हैं। इस होस्ट के लिए होस्ट मेट्रिक्स खोलें और यह चुनने के लिए कस्टमाइज़ का उपयोग करें कि कौन से कार्ड दिखाए जाएँ।",
|
"widgetsMovedToHostMetrics": "कार्ड अब सीधे होस्ट मेट्रिक्स टैब में जोड़े, व्यवस्थित और आकार बदले जा सकते हैं। इस होस्ट के लिए होस्ट मेट्रिक्स खोलें और यह चुनने के लिए कस्टमाइज़ का उपयोग करें कि कौन से कार्ड दिखाए जाएँ।",
|
||||||
"quickActionsLabel": "त्वरित कार्रवाइयाँ",
|
|
||||||
"quickActionsToolbar": "त्वरित कार्रवाइयाँ एक-क्लिक कमांड निष्पादन के लिए होस्ट मेट्रिक्स टूलबार में बटन के रूप में दिखाई देती हैं।",
|
|
||||||
"noQuickActions": "अभी तक कोई त्वरित कार्रवाई नहीं।",
|
|
||||||
"buttonLabel": "बटन लेबल",
|
|
||||||
"selectSnippetPlaceholder": "स्निपेट चुनें...",
|
|
||||||
"addActionBtn": "कार्रवाई जोड़ें",
|
|
||||||
"hostNotFound": "होस्ट नहीं मिला",
|
"hostNotFound": "होस्ट नहीं मिला",
|
||||||
"loadingHost": "होस्ट लोड हो रहा है..."
|
"loadingHost": "होस्ट लोड हो रहा है..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "डिस्क"
|
"disk": "डिस्क"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "रद्द करें",
|
|
||||||
"confirmRunButton": "चलाएँ",
|
|
||||||
"confirmRunMessage": "\"{{name}}\" चलाएँ?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "कनेक्टेड"
|
"connected": "कनेक्टेड"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "futás",
|
"processesRunning": "futás",
|
||||||
"loginStats": "SSH bejelentkezési statisztikák",
|
"loginStats": "SSH bejelentkezési statisztikák",
|
||||||
"noRecentLoginData": "Nincsenek friss bejelentkezési adatok",
|
"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": {
|
"ports": {
|
||||||
"title": "Figyelő portok",
|
"title": "Figyelő portok",
|
||||||
"protocol": "Jegyzőkönyv",
|
"protocol": "Jegyzőkönyv",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Hozzáadás",
|
"addExcludedMount": "Hozzáadás",
|
||||||
"visibleWidgets": "Látható widgetek",
|
"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.",
|
"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",
|
"hostNotFound": "Nem található gazdagép",
|
||||||
"loadingHost": "Gazdagép betöltése..."
|
"loadingHost": "Gazdagép betöltése..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Korong"
|
"disk": "Korong"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Mégsem",
|
|
||||||
"confirmRunButton": "Fut",
|
|
||||||
"confirmRunMessage": "Futtassa a \"{{name}} \" programot?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Csatlakoztatva"
|
"connected": "Csatlakoztatva"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "berlari",
|
"processesRunning": "berlari",
|
||||||
"loginStats": "Statistik Login SSH",
|
"loginStats": "Statistik Login SSH",
|
||||||
"noRecentLoginData": "Tidak ada data login terbaru.",
|
"noRecentLoginData": "Tidak ada data login terbaru.",
|
||||||
"executingQuickAction": "Menjalankan {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} berhasil diselesaikan",
|
|
||||||
"quickActionFailed": "{{name}} gagal",
|
|
||||||
"quickActionError": "Gagal mengeksekusi {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Port Pendengaran",
|
"title": "Port Pendengaran",
|
||||||
"protocol": "Protokol",
|
"protocol": "Protokol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Menambahkan",
|
"addExcludedMount": "Menambahkan",
|
||||||
"visibleWidgets": "Widget yang Terlihat",
|
"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.",
|
"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",
|
"hostNotFound": "Host tidak ditemukan",
|
||||||
"loadingHost": "Memuat host..."
|
"loadingHost": "Memuat host..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disk"
|
"disk": "Disk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Membatalkan",
|
|
||||||
"confirmRunButton": "Berlari",
|
|
||||||
"confirmRunMessage": "Jalankan \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Terhubung"
|
"connected": "Terhubung"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "in esecuzione",
|
"processesRunning": "in esecuzione",
|
||||||
"loginStats": "Statistiche di accesso SSH",
|
"loginStats": "Statistiche di accesso SSH",
|
||||||
"noRecentLoginData": "Nessun dato di accesso recente",
|
"noRecentLoginData": "Nessun dato di accesso recente",
|
||||||
"executingQuickAction": "Esecuzione di {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} completato con successo",
|
|
||||||
"quickActionFailed": "{{name}} non riuscito",
|
|
||||||
"quickActionError": "Impossibile eseguire {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Porte in ascolto",
|
"title": "Porte in ascolto",
|
||||||
"protocol": "Protocollo",
|
"protocol": "Protocollo",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Aggiungere",
|
"addExcludedMount": "Aggiungere",
|
||||||
"visibleWidgets": "Widget visibili",
|
"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.",
|
"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",
|
"hostNotFound": "Host non trovato",
|
||||||
"loadingHost": "Caricamento host..."
|
"loadingHost": "Caricamento host..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disco"
|
"disk": "Disco"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Annulla",
|
|
||||||
"confirmRunButton": "Esegui",
|
|
||||||
"confirmRunMessage": "Eseguire \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Connesso"
|
"connected": "Connesso"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "実行中",
|
"processesRunning": "実行中",
|
||||||
"loginStats": "SSHログイン統計",
|
"loginStats": "SSHログイン統計",
|
||||||
"noRecentLoginData": "最近のログインデータがありません",
|
"noRecentLoginData": "最近のログインデータがありません",
|
||||||
"executingQuickAction": "{{name}} を実行中...",
|
|
||||||
"quickActionSuccess": "{{name}} が正常に完了しました",
|
|
||||||
"quickActionFailed": "{{name}} が失敗しました",
|
|
||||||
"quickActionError": "{{name}} の実行に失敗しました",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "リスニングポート",
|
"title": "リスニングポート",
|
||||||
"protocol": "プロトコル",
|
"protocol": "プロトコル",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "追加",
|
"addExcludedMount": "追加",
|
||||||
"visibleWidgets": "表示するウィジェット",
|
"visibleWidgets": "表示するウィジェット",
|
||||||
"widgetsMovedToHostMetrics": "カードは、ホストメトリクスタブで直接追加、配置、サイズ変更できるようになりました。このホストのホストメトリクスを開き、カスタマイズを使用して表示するカードを選択してください。",
|
"widgetsMovedToHostMetrics": "カードは、ホストメトリクスタブで直接追加、配置、サイズ変更できるようになりました。このホストのホストメトリクスを開き、カスタマイズを使用して表示するカードを選択してください。",
|
||||||
"quickActionsLabel": "クイックアクション",
|
|
||||||
"quickActionsToolbar": "クイックアクションはホストメトリクスツールバーにボタンとして表示され、ワンクリックでコマンドを実行できます。",
|
|
||||||
"noQuickActions": "クイックアクションはまだありません",
|
|
||||||
"buttonLabel": "ボタンラベル",
|
|
||||||
"selectSnippetPlaceholder": "スニペットを選択...",
|
|
||||||
"addActionBtn": "アクションを追加",
|
|
||||||
"hostNotFound": "ホストが見つかりません",
|
"hostNotFound": "ホストが見つかりません",
|
||||||
"loadingHost": "ホストを読み込み中..."
|
"loadingHost": "ホストを読み込み中..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "ディスク"
|
"disk": "ディスク"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "キャンセル",
|
|
||||||
"confirmRunButton": "実行",
|
|
||||||
"confirmRunMessage": "「{{name}}」を実行しますか?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "接続中"
|
"connected": "接続中"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "실행 중",
|
"processesRunning": "실행 중",
|
||||||
"loginStats": "SSH 로그인 통계",
|
"loginStats": "SSH 로그인 통계",
|
||||||
"noRecentLoginData": "최근 로그인 데이터가 없습니다.",
|
"noRecentLoginData": "최근 로그인 데이터가 없습니다.",
|
||||||
"executingQuickAction": "{{name}} 실행 중...",
|
|
||||||
"quickActionSuccess": "{{name}}이(가) 성공적으로 완료되었습니다.",
|
|
||||||
"quickActionFailed": "{{name}} 실패",
|
|
||||||
"quickActionError": "{{name}} 실행에 실패했습니다.",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "수신 대기 포트",
|
"title": "수신 대기 포트",
|
||||||
"protocol": "프로토콜",
|
"protocol": "프로토콜",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "추가하다",
|
"addExcludedMount": "추가하다",
|
||||||
"visibleWidgets": "표시 위젯",
|
"visibleWidgets": "표시 위젯",
|
||||||
"widgetsMovedToHostMetrics": "카드는 이제 호스트 메트릭 탭에서 직접 추가, 정렬 및 크기 조정됩니다. 이 호스트의 호스트 메트릭을 열고 사용자 정의를 사용하여 표시할 카드를 선택하세요.",
|
"widgetsMovedToHostMetrics": "카드는 이제 호스트 메트릭 탭에서 직접 추가, 정렬 및 크기 조정됩니다. 이 호스트의 호스트 메트릭을 열고 사용자 정의를 사용하여 표시할 카드를 선택하세요.",
|
||||||
"quickActionsLabel": "빠른 작업",
|
|
||||||
"quickActionsToolbar": "빠른 작업은 한 번의 클릭으로 명령을 실행할 수 있도록 호스트 메트릭 도구 모음에 버튼으로 표시됩니다.",
|
|
||||||
"noQuickActions": "아직 빠른 작업이 없습니다.",
|
|
||||||
"buttonLabel": "버튼 레이블",
|
|
||||||
"selectSnippetPlaceholder": "스니펫 선택...",
|
|
||||||
"addActionBtn": "작업 추가",
|
|
||||||
"hostNotFound": "호스트를 찾을 수 없습니다",
|
"hostNotFound": "호스트를 찾을 수 없습니다",
|
||||||
"loadingHost": "호스트 로드 중..."
|
"loadingHost": "호스트 로드 중..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "디스크"
|
"disk": "디스크"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "취소",
|
|
||||||
"confirmRunButton": "실행",
|
|
||||||
"confirmRunMessage": "\"{{name}}\"을(를) 실행하시겠습니까?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "연결됨"
|
"connected": "연결됨"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "rennen",
|
"processesRunning": "rennen",
|
||||||
"loginStats": "SSH-aanmeldingsstatistieken",
|
"loginStats": "SSH-aanmeldingsstatistieken",
|
||||||
"noRecentLoginData": "Geen recente inloggegevens",
|
"noRecentLoginData": "Geen recente inloggegevens",
|
||||||
"executingQuickAction": "Het uitvoeren van {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} succesvol voltooid",
|
|
||||||
"quickActionFailed": "{{name}} mislukt",
|
|
||||||
"quickActionError": "Het uitvoeren van {{name}} is mislukt.",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Luisterpoorten",
|
"title": "Luisterpoorten",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Toevoegen",
|
"addExcludedMount": "Toevoegen",
|
||||||
"visibleWidgets": "Zichtbare widgets",
|
"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.",
|
"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",
|
"hostNotFound": "Host niet gevonden",
|
||||||
"loadingHost": "Host wordt geladen..."
|
"loadingHost": "Host wordt geladen..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Schijf"
|
"disk": "Schijf"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Annuleren",
|
|
||||||
"confirmRunButton": "Loop",
|
|
||||||
"confirmRunMessage": "Run \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Aangesloten"
|
"connected": "Aangesloten"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "løping",
|
"processesRunning": "løping",
|
||||||
"loginStats": "SSH-påloggingsstatistikk",
|
"loginStats": "SSH-påloggingsstatistikk",
|
||||||
"noRecentLoginData": "Ingen nylige påloggingsdata",
|
"noRecentLoginData": "Ingen nylige påloggingsdata",
|
||||||
"executingQuickAction": "Utfører {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} fullført",
|
|
||||||
"quickActionFailed": "{{name}} mislyktes",
|
|
||||||
"quickActionError": "Kunne ikke utføre {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Lytteporter",
|
"title": "Lytteporter",
|
||||||
"protocol": "Protokoll",
|
"protocol": "Protokoll",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Legge til",
|
"addExcludedMount": "Legge til",
|
||||||
"visibleWidgets": "Synlige widgeter",
|
"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.",
|
"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",
|
"hostNotFound": "Verten ble ikke funnet",
|
||||||
"loadingHost": "Laster inn vert ..."
|
"loadingHost": "Laster inn vert ..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disk"
|
"disk": "Disk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Kansellere",
|
|
||||||
"confirmRunButton": "Løp",
|
|
||||||
"confirmRunMessage": "Kjør \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Tilkoblet"
|
"connected": "Tilkoblet"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "działa",
|
"processesRunning": "działa",
|
||||||
"loginStats": "Statystyki logowania SSH",
|
"loginStats": "Statystyki logowania SSH",
|
||||||
"noRecentLoginData": "Brak ostatnich danych logowania",
|
"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": {
|
"ports": {
|
||||||
"title": "Porty nasłuchujące",
|
"title": "Porty nasłuchujące",
|
||||||
"protocol": "Protokół",
|
"protocol": "Protokół",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Dodaj",
|
"addExcludedMount": "Dodaj",
|
||||||
"visibleWidgets": "Widoczne widżety",
|
"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.",
|
"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",
|
"hostNotFound": "Nie znaleziono hosta",
|
||||||
"loadingHost": "Ładowanie hosta..."
|
"loadingHost": "Ładowanie hosta..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Dysk"
|
"disk": "Dysk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Anuluj",
|
|
||||||
"confirmRunButton": "Uruchom",
|
|
||||||
"confirmRunMessage": "Uruchomić „{{name}}”?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Połączono"
|
"connected": "Połączono"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "correndo",
|
"processesRunning": "correndo",
|
||||||
"loginStats": "Estatísticas de login SSH",
|
"loginStats": "Estatísticas de login SSH",
|
||||||
"noRecentLoginData": "Nenhum dado de login recente",
|
"noRecentLoginData": "Nenhum dado de login recente",
|
||||||
"executingQuickAction": "Executando {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} concluído com sucesso",
|
|
||||||
"quickActionFailed": "{{name}} falhou",
|
|
||||||
"quickActionError": "Falha ao executar {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Portas de escuta",
|
"title": "Portas de escuta",
|
||||||
"protocol": "Protocolo",
|
"protocol": "Protocolo",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Adicionar",
|
"addExcludedMount": "Adicionar",
|
||||||
"visibleWidgets": "Widgets Visíveis",
|
"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.",
|
"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",
|
"hostNotFound": "Host não encontrado",
|
||||||
"loadingHost": "Carregando host..."
|
"loadingHost": "Carregando host..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disco"
|
"disk": "Disco"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"confirmRunButton": "Correr",
|
|
||||||
"confirmRunMessage": "Executar \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Conectado"
|
"connected": "Conectado"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "em execução",
|
"processesRunning": "em execução",
|
||||||
"loginStats": "Estatísticas de início de sessão SSH",
|
"loginStats": "Estatísticas de início de sessão SSH",
|
||||||
"noRecentLoginData": "Sem dados de início de sessão recentes",
|
"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": {
|
"ports": {
|
||||||
"title": "Portas em escuta",
|
"title": "Portas em escuta",
|
||||||
"protocol": "Protocolo",
|
"protocol": "Protocolo",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Adicionar",
|
"addExcludedMount": "Adicionar",
|
||||||
"visibleWidgets": "Widgets Visíveis",
|
"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.",
|
"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",
|
"hostNotFound": "Host não encontrado",
|
||||||
"loadingHost": "A carregar o host..."
|
"loadingHost": "A carregar o host..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disco"
|
"disk": "Disco"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"confirmRunButton": "Executar",
|
|
||||||
"confirmRunMessage": "Executar \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Ligado"
|
"connected": "Ligado"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "funcţionare",
|
"processesRunning": "funcţionare",
|
||||||
"loginStats": "Statistici de conectare SSH",
|
"loginStats": "Statistici de conectare SSH",
|
||||||
"noRecentLoginData": "Nu există date de conectare recente",
|
"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": {
|
"ports": {
|
||||||
"title": "Porturi de ascultare",
|
"title": "Porturi de ascultare",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Adăuga",
|
"addExcludedMount": "Adăuga",
|
||||||
"visibleWidgets": "Widgeturi vizibile",
|
"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.",
|
"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ă",
|
"hostNotFound": "Gazdă negăsită",
|
||||||
"loadingHost": "Se încarcă gazda..."
|
"loadingHost": "Se încarcă gazda..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disc"
|
"disk": "Disc"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Anula",
|
|
||||||
"confirmRunButton": "Aleargă",
|
|
||||||
"confirmRunMessage": "Executați „{{name}}”?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Conectat"
|
"connected": "Conectat"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "выполняется",
|
"processesRunning": "выполняется",
|
||||||
"loginStats": "Статистика входов SSH",
|
"loginStats": "Статистика входов SSH",
|
||||||
"noRecentLoginData": "Нет данных о недавних входах",
|
"noRecentLoginData": "Нет данных о недавних входах",
|
||||||
"executingQuickAction": "Выполняется {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} успешно завершено",
|
|
||||||
"quickActionFailed": "{{name}} не удалось",
|
|
||||||
"quickActionError": "Не удалось выполнить {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Прослушиваемые порты",
|
"title": "Прослушиваемые порты",
|
||||||
"protocol": "Протокол",
|
"protocol": "Протокол",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Добавлять",
|
"addExcludedMount": "Добавлять",
|
||||||
"visibleWidgets": "Видимые виджеты",
|
"visibleWidgets": "Видимые виджеты",
|
||||||
"widgetsMovedToHostMetrics": "Карточки теперь добавляются, упорядочиваются и изменяются в размере непосредственно на вкладке «Метрики хоста». Откройте метрики для этого хоста и используйте «Настроить», чтобы выбрать отображаемые карточки.",
|
"widgetsMovedToHostMetrics": "Карточки теперь добавляются, упорядочиваются и изменяются в размере непосредственно на вкладке «Метрики хоста». Откройте метрики для этого хоста и используйте «Настроить», чтобы выбрать отображаемые карточки.",
|
||||||
"quickActionsLabel": "Быстрые действия",
|
|
||||||
"quickActionsToolbar": "Быстрые действия отображаются в виде кнопок на панели инструментов метрик хоста для выполнения команд в один клик.",
|
|
||||||
"noQuickActions": "Пока нет быстрых действий.",
|
|
||||||
"buttonLabel": "Надпись кнопки",
|
|
||||||
"selectSnippetPlaceholder": "Выбрать сниппет...",
|
|
||||||
"addActionBtn": "Добавить действие",
|
|
||||||
"hostNotFound": "Хост не найден",
|
"hostNotFound": "Хост не найден",
|
||||||
"loadingHost": "Загрузка хоста..."
|
"loadingHost": "Загрузка хоста..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Диск"
|
"disk": "Диск"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Отмена",
|
|
||||||
"confirmRunButton": "Запустить",
|
|
||||||
"confirmRunMessage": "Запустить \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Подключено"
|
"connected": "Подключено"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "трчање",
|
"processesRunning": "трчање",
|
||||||
"loginStats": "Статистика SSH пријава",
|
"loginStats": "Статистика SSH пријава",
|
||||||
"noRecentLoginData": "Нема скорашњих података за пријављивање",
|
"noRecentLoginData": "Нема скорашњих података за пријављивање",
|
||||||
"executingQuickAction": "Извршава се {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} успешно завршено",
|
|
||||||
"quickActionFailed": "{{name}} није успело",
|
|
||||||
"quickActionError": "Није успело извршавање {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Портови за слушање",
|
"title": "Портови за слушање",
|
||||||
"protocol": "Протокол",
|
"protocol": "Протокол",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Додај",
|
"addExcludedMount": "Додај",
|
||||||
"visibleWidgets": "Видљиви виџети",
|
"visibleWidgets": "Видљиви виџети",
|
||||||
"widgetsMovedToHostMetrics": "Картице се сада додају, распоређују и мењају им се величина директно на картици „Host Metrics“. Отворите „Host Metrics“ за овог хоста и користите „Customize“ да бисте изабрали које ће картице бити приказане.",
|
"widgetsMovedToHostMetrics": "Картице се сада додају, распоређују и мењају им се величина директно на картици „Host Metrics“. Отворите „Host Metrics“ за овог хоста и користите „Customize“ да бисте изабрали које ће картице бити приказане.",
|
||||||
"quickActionsLabel": "Брзе акције",
|
|
||||||
"quickActionsToolbar": "Брзе акције се појављују као дугмад у траци са алаткама Host Metrics за извршавање команди једним кликом.",
|
|
||||||
"noQuickActions": "Још нема брзих акција.",
|
|
||||||
"buttonLabel": "Ознака дугмета",
|
|
||||||
"selectSnippetPlaceholder": "Изаберите исечак...",
|
|
||||||
"addActionBtn": "Додај акцију",
|
|
||||||
"hostNotFound": "Хост није пронађен",
|
"hostNotFound": "Хост није пронађен",
|
||||||
"loadingHost": "Учитавање хоста..."
|
"loadingHost": "Учитавање хоста..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Диск"
|
"disk": "Диск"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Откажи",
|
|
||||||
"confirmRunButton": "Трчи",
|
|
||||||
"confirmRunMessage": "Покрени „{{name}}“?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Повезано"
|
"connected": "Повезано"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "spring",
|
"processesRunning": "spring",
|
||||||
"loginStats": "SSH-inloggningsstatistik",
|
"loginStats": "SSH-inloggningsstatistik",
|
||||||
"noRecentLoginData": "Inga aktuella inloggningsuppgifter",
|
"noRecentLoginData": "Inga aktuella inloggningsuppgifter",
|
||||||
"executingQuickAction": "Kör {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} slutfört",
|
|
||||||
"quickActionFailed": "{{name}} misslyckades",
|
|
||||||
"quickActionError": "Misslyckades med att köra {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Lyssningsportar",
|
"title": "Lyssningsportar",
|
||||||
"protocol": "Protokoll",
|
"protocol": "Protokoll",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Tillägga",
|
"addExcludedMount": "Tillägga",
|
||||||
"visibleWidgets": "Synliga widgetar",
|
"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.",
|
"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",
|
"hostNotFound": "Värden hittades inte",
|
||||||
"loadingHost": "Laddar värd..."
|
"loadingHost": "Laddar värd..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disk"
|
"disk": "Disk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Avboka",
|
|
||||||
"confirmRunButton": "Sikt",
|
|
||||||
"confirmRunMessage": "Kör \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Ansluten"
|
"connected": "Ansluten"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "วิ่ง",
|
"processesRunning": "วิ่ง",
|
||||||
"loginStats": "สถิติการเข้าสู่ระบบ SSH",
|
"loginStats": "สถิติการเข้าสู่ระบบ SSH",
|
||||||
"noRecentLoginData": "ไม่มีข้อมูลการเข้าสู่ระบบล่าสุด",
|
"noRecentLoginData": "ไม่มีข้อมูลการเข้าสู่ระบบล่าสุด",
|
||||||
"executingQuickAction": "กำลังดำเนินการ {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} เสร็จสมบูรณ์เรียบร้อยแล้ว",
|
|
||||||
"quickActionFailed": "{{name}} ล้มเหลว",
|
|
||||||
"quickActionError": "ไม่สามารถดำเนินการ {{name}} ได้",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "พอร์ตรับฟัง",
|
"title": "พอร์ตรับฟัง",
|
||||||
"protocol": "โปรโตคอล",
|
"protocol": "โปรโตคอล",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "เพิ่ม",
|
"addExcludedMount": "เพิ่ม",
|
||||||
"visibleWidgets": "วิดเจ็ตที่มองเห็นได้",
|
"visibleWidgets": "วิดเจ็ตที่มองเห็นได้",
|
||||||
"widgetsMovedToHostMetrics": "ขณะนี้สามารถเพิ่ม จัดเรียง และปรับขนาดการ์ดได้โดยตรงในแท็บเมตริกโฮสต์ เปิดเมตริกโฮสต์สำหรับโฮสต์นี้แล้วใช้ ปรับแต่ง เพื่อเลือกการ์ดที่จะแสดง",
|
"widgetsMovedToHostMetrics": "ขณะนี้สามารถเพิ่ม จัดเรียง และปรับขนาดการ์ดได้โดยตรงในแท็บเมตริกโฮสต์ เปิดเมตริกโฮสต์สำหรับโฮสต์นี้แล้วใช้ ปรับแต่ง เพื่อเลือกการ์ดที่จะแสดง",
|
||||||
"quickActionsLabel": "การดำเนินการด่วน",
|
|
||||||
"quickActionsToolbar": "การดำเนินการด่วนจะปรากฏเป็นปุ่มในแถบเครื่องมือ Host Metrics เพื่อให้สามารถเรียกใช้คำสั่งได้ด้วยการคลิกเพียงครั้งเดียว",
|
|
||||||
"noQuickActions": "ยังไม่มีการดำเนินการใดๆ อย่างเร่งด่วนในขณะนี้",
|
|
||||||
"buttonLabel": "ป้ายกำกับปุ่ม",
|
|
||||||
"selectSnippetPlaceholder": "เลือกส่วนย่อย...",
|
|
||||||
"addActionBtn": "เพิ่มการดำเนินการ",
|
|
||||||
"hostNotFound": "ไม่พบโฮสต์",
|
"hostNotFound": "ไม่พบโฮสต์",
|
||||||
"loadingHost": "กำลังโหลดโฮสต์..."
|
"loadingHost": "กำลังโหลดโฮสต์..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "ดิสก์"
|
"disk": "ดิสก์"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "ยกเลิก",
|
|
||||||
"confirmRunButton": "วิ่ง",
|
|
||||||
"confirmRunMessage": "เรียกใช้ \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "เชื่อมต่อแล้ว"
|
"connected": "เชื่อมต่อแล้ว"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "çalışan",
|
"processesRunning": "çalışan",
|
||||||
"loginStats": "SSH Giriş İstatistikleri",
|
"loginStats": "SSH Giriş İstatistikleri",
|
||||||
"noRecentLoginData": "Son giriş verisi yok",
|
"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": {
|
"ports": {
|
||||||
"title": "Dinlenen Portlar",
|
"title": "Dinlenen Portlar",
|
||||||
"protocol": "Protokol",
|
"protocol": "Protokol",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Eklemek",
|
"addExcludedMount": "Eklemek",
|
||||||
"visibleWidgets": "Görünür Widget'lar",
|
"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.",
|
"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ı",
|
"hostNotFound": "Host bulunamadı",
|
||||||
"loadingHost": "Host yükleniyor..."
|
"loadingHost": "Host yükleniyor..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Disk"
|
"disk": "Disk"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "İptal",
|
|
||||||
"confirmRunButton": "Çalıştır",
|
|
||||||
"confirmRunMessage": "\"{{name}}\" çalıştırılsın mı?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Bağlandı"
|
"connected": "Bağlandı"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "біг",
|
"processesRunning": "біг",
|
||||||
"loginStats": "Статистика входу SSH",
|
"loginStats": "Статистика входу SSH",
|
||||||
"noRecentLoginData": "Немає останніх даних для входу",
|
"noRecentLoginData": "Немає останніх даних для входу",
|
||||||
"executingQuickAction": "Виконання {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} успішно завершено",
|
|
||||||
"quickActionFailed": "{{name}} не вдалося",
|
|
||||||
"quickActionError": "Не вдалося виконати {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "Порти прослуховування",
|
"title": "Порти прослуховування",
|
||||||
"protocol": "Протокол",
|
"protocol": "Протокол",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Додати",
|
"addExcludedMount": "Додати",
|
||||||
"visibleWidgets": "Видимі віджети",
|
"visibleWidgets": "Видимі віджети",
|
||||||
"widgetsMovedToHostMetrics": "Тепер картки додаються, упорядковуються та змінюються за розміром безпосередньо на вкладці «Метрики хоста». Відкрийте «Метрики хоста» для цього хоста та скористайтеся функцією «Налаштувати», щоб вибрати, які картки відображатимуться.",
|
"widgetsMovedToHostMetrics": "Тепер картки додаються, упорядковуються та змінюються за розміром безпосередньо на вкладці «Метрики хоста». Відкрийте «Метрики хоста» для цього хоста та скористайтеся функцією «Налаштувати», щоб вибрати, які картки відображатимуться.",
|
||||||
"quickActionsLabel": "Швидкі дії",
|
|
||||||
"quickActionsToolbar": "Швидкі дії відображаються як кнопки на панелі інструментів «Метрики хоста» для виконання команд одним клацанням миші.",
|
|
||||||
"noQuickActions": "Поки що жодних швидких дій.",
|
|
||||||
"buttonLabel": "Мітка кнопки",
|
|
||||||
"selectSnippetPlaceholder": "Вибрати фрагмент...",
|
|
||||||
"addActionBtn": "Додати дію",
|
|
||||||
"hostNotFound": "Хост не знайдено",
|
"hostNotFound": "Хост не знайдено",
|
||||||
"loadingHost": "Завантаження хоста..."
|
"loadingHost": "Завантаження хоста..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Диск"
|
"disk": "Диск"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Скасувати",
|
|
||||||
"confirmRunButton": "Бігти",
|
|
||||||
"confirmRunMessage": "Запустити \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Підключено"
|
"connected": "Підключено"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "đang chạy",
|
"processesRunning": "đang chạy",
|
||||||
"loginStats": "Thống kê đăng nhập SSH",
|
"loginStats": "Thống kê đăng nhập SSH",
|
||||||
"noRecentLoginData": "Không có dữ liệu đăng nhập gần đây",
|
"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": {
|
"ports": {
|
||||||
"title": "Cổng đang lắng nghe",
|
"title": "Cổng đang lắng nghe",
|
||||||
"protocol": "Giao thức",
|
"protocol": "Giao thức",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "Thêm vào",
|
"addExcludedMount": "Thêm vào",
|
||||||
"visibleWidgets": "Widget hiển thị",
|
"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ị.",
|
"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ủ",
|
"hostNotFound": "Không tìm thấy máy chủ",
|
||||||
"loadingHost": "Đang tải máy chủ..."
|
"loadingHost": "Đang tải máy chủ..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "Ổ đĩa"
|
"disk": "Ổ đĩa"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "Hủy",
|
|
||||||
"confirmRunButton": "Chạy",
|
|
||||||
"confirmRunMessage": "Chạy \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "Đã kết nối"
|
"connected": "Đã kết nối"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "运行中",
|
"processesRunning": "运行中",
|
||||||
"loginStats": "SSH 登录统计",
|
"loginStats": "SSH 登录统计",
|
||||||
"noRecentLoginData": "无最近登录数据",
|
"noRecentLoginData": "无最近登录数据",
|
||||||
"executingQuickAction": "正在执行 {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} 执行成功",
|
|
||||||
"quickActionFailed": "{{name}} 执行失败",
|
|
||||||
"quickActionError": "执行 {{name}} 失败",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "监听端口",
|
"title": "监听端口",
|
||||||
"protocol": "协议",
|
"protocol": "协议",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "添加",
|
"addExcludedMount": "添加",
|
||||||
"visibleWidgets": "可见小部件",
|
"visibleWidgets": "可见小部件",
|
||||||
"widgetsMovedToHostMetrics": "卡片现在可在“主机指标”选项卡中直接添加、排列和调整大小。打开此主机的“主机指标”,使用“自定义”选择要显示的卡片。",
|
"widgetsMovedToHostMetrics": "卡片现在可在“主机指标”选项卡中直接添加、排列和调整大小。打开此主机的“主机指标”,使用“自定义”选择要显示的卡片。",
|
||||||
"quickActionsLabel": "快速操作",
|
|
||||||
"quickActionsToolbar": "快速操作以按钮形式显示在主机指标工具栏中,以便一键执行命令。",
|
|
||||||
"noQuickActions": "尚无快速操作。",
|
|
||||||
"buttonLabel": "按钮标签",
|
|
||||||
"selectSnippetPlaceholder": "选择代码片段...",
|
|
||||||
"addActionBtn": "添加操作",
|
|
||||||
"hostNotFound": "未找到主机",
|
"hostNotFound": "未找到主机",
|
||||||
"loadingHost": "正在加载主机..."
|
"loadingHost": "正在加载主机..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "磁盘"
|
"disk": "磁盘"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "取消",
|
|
||||||
"confirmRunButton": "运行",
|
|
||||||
"confirmRunMessage": "运行“{{name}}”?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "已连接"
|
"connected": "已连接"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,6 @@
|
|||||||
"processesRunning": "執行中",
|
"processesRunning": "執行中",
|
||||||
"loginStats": "SSH 登入統計資訊",
|
"loginStats": "SSH 登入統計資訊",
|
||||||
"noRecentLoginData": "沒有最近的登入資料",
|
"noRecentLoginData": "沒有最近的登入資料",
|
||||||
"executingQuickAction": "正在執行 {{name}}...",
|
|
||||||
"quickActionSuccess": "{{name}} 已成功完成",
|
|
||||||
"quickActionFailed": "{{name}} 失敗",
|
|
||||||
"quickActionError": "無法執行 {{name}}",
|
|
||||||
"ports": {
|
"ports": {
|
||||||
"title": "監聽中的連接埠",
|
"title": "監聽中的連接埠",
|
||||||
"protocol": "通訊協定",
|
"protocol": "通訊協定",
|
||||||
@@ -235,12 +231,6 @@
|
|||||||
"addExcludedMount": "添加",
|
"addExcludedMount": "添加",
|
||||||
"visibleWidgets": "可見的小工具",
|
"visibleWidgets": "可見的小工具",
|
||||||
"widgetsMovedToHostMetrics": "現在卡片可直接在主機指標分頁中新增、排列和調整大小。開啟此主機的主機指標,然後使用自訂功能選擇要顯示的卡片。",
|
"widgetsMovedToHostMetrics": "現在卡片可直接在主機指標分頁中新增、排列和調整大小。開啟此主機的主機指標,然後使用自訂功能選擇要顯示的卡片。",
|
||||||
"quickActionsLabel": "快速動作",
|
|
||||||
"quickActionsToolbar": "快速動作會以按鈕形式顯示在主機計量工具列上,可一鍵執行指令。",
|
|
||||||
"noQuickActions": "尚無快速動作。",
|
|
||||||
"buttonLabel": "按鈕標籤",
|
|
||||||
"selectSnippetPlaceholder": "選取程式碼片段...",
|
|
||||||
"addActionBtn": "新增動作",
|
|
||||||
"hostNotFound": "找不到主機",
|
"hostNotFound": "找不到主機",
|
||||||
"loadingHost": "正在載入主機..."
|
"loadingHost": "正在載入主機..."
|
||||||
},
|
},
|
||||||
@@ -282,15 +272,6 @@
|
|||||||
"dashboardTab": {
|
"dashboardTab": {
|
||||||
"disk": "磁碟"
|
"disk": "磁碟"
|
||||||
},
|
},
|
||||||
"newUi": {
|
|
||||||
"sidebar": {
|
|
||||||
"snippets": {
|
|
||||||
"cancel": "取消",
|
|
||||||
"confirmRunButton": "執行",
|
|
||||||
"confirmRunMessage": "執行 \"{{name}}\"?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"terminal": {
|
"terminal": {
|
||||||
"connected": "已連線"
|
"connected": "已連線"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,10 @@
|
|||||||
{
|
{
|
||||||
"id": "host-metrics.managers",
|
"id": "host-metrics.managers",
|
||||||
"accepts": ["component"]
|
"accepts": ["component"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "host-metrics.toolbar",
|
||||||
|
"accepts": ["component"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uiPresets": {
|
"uiPresets": {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
SectionCard,
|
SectionCard,
|
||||||
SettingRow,
|
SettingRow,
|
||||||
FakeSwitch,
|
FakeSwitch,
|
||||||
Select2,
|
|
||||||
} from "@termix/plugin-sdk/ui";
|
} from "@termix/plugin-sdk/ui";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -12,33 +11,24 @@ import {
|
|||||||
useTranslation,
|
useTranslation,
|
||||||
type HostEditorSectionProps,
|
type HostEditorSectionProps,
|
||||||
} from "@termix/plugin-sdk/frontend";
|
} from "@termix/plugin-sdk/frontend";
|
||||||
import {
|
import { HardDrive, LayoutDashboard, Plus, Server, Trash2 } from "lucide-react";
|
||||||
HardDrive,
|
|
||||||
LayoutDashboard,
|
|
||||||
Plus,
|
|
||||||
Server,
|
|
||||||
Trash2,
|
|
||||||
Zap,
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
import { readHostMetricsSettings } from "../shared/stats-widgets.js";
|
import { readHostMetricsSettings } from "../shared/stats-widgets.js";
|
||||||
|
|
||||||
const PLUGIN_ID = "host-metrics";
|
const PLUGIN_ID = "host-metrics";
|
||||||
|
|
||||||
type PluginSettingsBag = Record<string, Record<string, unknown>>;
|
type PluginSettingsBag = Record<string, Record<string, unknown>>;
|
||||||
type QuickActionRow = { name: string; snippetId: string };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Host Metrics section of the host editor. Its values are this plugin's
|
* 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
|
* 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({
|
export function HostStatsTab({
|
||||||
form,
|
form,
|
||||||
setField,
|
setField,
|
||||||
updateForm,
|
updateForm,
|
||||||
host,
|
host,
|
||||||
snippets,
|
|
||||||
}: HostEditorSectionProps) {
|
}: HostEditorSectionProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [newMount, setNewMount] = useState("");
|
const [newMount, setNewMount] = useState("");
|
||||||
@@ -49,8 +39,6 @@ export function HostStatsTab({
|
|||||||
];
|
];
|
||||||
const settings = readHostMetricsSettings(bag);
|
const settings = readHostMetricsSettings(bag);
|
||||||
const { excludedMounts, monitoredMounts } = settings;
|
const { excludedMounts, monitoredMounts } = settings;
|
||||||
const quickActions = (form.quickActions ?? []) as QuickActionRow[];
|
|
||||||
const snippetOptions = (snippets ?? []) as { id: number; name: string }[];
|
|
||||||
const api = usePluginApi();
|
const api = usePluginApi();
|
||||||
|
|
||||||
const patch = (values: Record<string, unknown>) =>
|
const patch = (values: Record<string, unknown>) =>
|
||||||
@@ -288,84 +276,6 @@ export function HostStatsTab({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
<SectionCard
|
|
||||||
title={t("hosts.quickActionsLabel")}
|
|
||||||
icon={<Zap className="size-3.5" />}
|
|
||||||
action={
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-6 text-[10px] px-2 border-accent-brand/40 text-accent-brand"
|
|
||||||
onClick={() =>
|
|
||||||
setField("quickActions", [
|
|
||||||
...quickActions,
|
|
||||||
{ name: "", snippetId: "" },
|
|
||||||
])
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Plus className="size-3 mr-1" /> {t("hosts.addActionBtn")}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-3 py-3">
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t("hosts.quickActionsToolbar")}
|
|
||||||
</p>
|
|
||||||
{quickActions.length === 0 && (
|
|
||||||
<div className="flex flex-col items-center justify-center py-4 text-muted-foreground/40 gap-1.5">
|
|
||||||
<Zap className="size-6" />
|
|
||||||
<span className="text-xs">{t("hosts.noQuickActions")}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{quickActions.map((a, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="flex items-center gap-2 p-2 bg-muted/20 border border-border group"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
className="h-7 text-xs flex-1"
|
|
||||||
placeholder={t("hosts.buttonLabel")}
|
|
||||||
value={a.name}
|
|
||||||
onChange={(e) => {
|
|
||||||
const updated = [...quickActions];
|
|
||||||
updated[i] = { ...updated[i], name: e.target.value };
|
|
||||||
setField("quickActions", updated);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Select2
|
|
||||||
className="h-7 text-xs flex-1 border border-border bg-background px-2 outline-none focus:ring-1 focus:ring-ring"
|
|
||||||
value={a.snippetId}
|
|
||||||
onChange={(e) => {
|
|
||||||
const updated = [...quickActions];
|
|
||||||
updated[i] = {
|
|
||||||
...updated[i],
|
|
||||||
snippetId: e.target.value,
|
|
||||||
};
|
|
||||||
setField("quickActions", updated);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="">{t("hosts.selectSnippetPlaceholder")}</option>
|
|
||||||
{snippetOptions.map((s) => (
|
|
||||||
<option key={s.id} value={String(s.id)}>
|
|
||||||
{s.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select2>
|
|
||||||
<button
|
|
||||||
className="text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
onClick={() =>
|
|
||||||
setField(
|
|
||||||
"quickActions",
|
|
||||||
quickActions.filter((_, idx) => idx !== i),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Trash2 className="size-3.5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</SectionCard>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Separator,
|
|
||||||
Button,
|
Button,
|
||||||
useConfirmation,
|
ComponentSlot,
|
||||||
TOTPDialog,
|
TOTPDialog,
|
||||||
useTabsSafe,
|
useTabsSafe,
|
||||||
ConnectionLogProvider,
|
ConnectionLogProvider,
|
||||||
@@ -11,7 +10,6 @@ import {
|
|||||||
CardGridCanvas,
|
CardGridCanvas,
|
||||||
ColumnCountStepper,
|
ColumnCountStepper,
|
||||||
type GridCardCatalogEntry,
|
type GridCardCatalogEntry,
|
||||||
SnippetVariablesDialog,
|
|
||||||
} from "@termix/plugin-sdk/ui";
|
} from "@termix/plugin-sdk/ui";
|
||||||
import { readHostMetricsSettings } from "../shared/stats-widgets.js";
|
import { readHostMetricsSettings } from "../shared/stats-widgets.js";
|
||||||
import {
|
import {
|
||||||
@@ -23,33 +21,12 @@ import type { ServerMetrics } from "../shared/metrics.js";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useHostMetricsApi } from "./host-metrics-api";
|
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 {
|
import {
|
||||||
useTranslation,
|
useTranslation,
|
||||||
useSlotContributions,
|
useSlotContributions,
|
||||||
useConnectionRetry,
|
useConnectionRetry,
|
||||||
useHost,
|
useHost,
|
||||||
useHostStatus,
|
useHostStatus,
|
||||||
invokeAction,
|
|
||||||
logActivity,
|
logActivity,
|
||||||
usePluginUiPreferences,
|
usePluginUiPreferences,
|
||||||
} from "@termix/plugin-sdk/frontend";
|
} from "@termix/plugin-sdk/frontend";
|
||||||
@@ -70,11 +47,6 @@ import { metricsChangeKey } from "./metrics-change-key";
|
|||||||
|
|
||||||
const HISTORY_LEN = 30;
|
const HISTORY_LEN = 30;
|
||||||
|
|
||||||
interface QuickAction {
|
|
||||||
name: string;
|
|
||||||
snippetId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionLogPayload = Parameters<
|
type ConnectionLogPayload = Parameters<
|
||||||
ReturnType<typeof useConnectionLog>["addLog"]
|
ReturnType<typeof useConnectionLog>["addLog"]
|
||||||
>[0];
|
>[0];
|
||||||
@@ -87,7 +59,6 @@ interface HostConfig {
|
|||||||
name: string;
|
name: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
username: string;
|
username: string;
|
||||||
quickActions?: QuickAction[];
|
|
||||||
statusCheckEnabled?: boolean;
|
statusCheckEnabled?: boolean;
|
||||||
pluginSettings?: Record<string, Record<string, unknown>>;
|
pluginSettings?: Record<string, Record<string, unknown>>;
|
||||||
authType?: string;
|
authType?: string;
|
||||||
@@ -127,9 +98,6 @@ function HostMetricsInner({
|
|||||||
});
|
});
|
||||||
const [currentHostConfig, setCurrentHostConfig] = React.useState(hostConfig);
|
const [currentHostConfig, setCurrentHostConfig] = React.useState(hostConfig);
|
||||||
const [isRefreshing, setIsRefreshing] = React.useState(false);
|
const [isRefreshing, setIsRefreshing] = React.useState(false);
|
||||||
const [executingActions, setExecutingActions] = React.useState<Set<number>>(
|
|
||||||
new Set(),
|
|
||||||
);
|
|
||||||
const [totpRequired, setTotpRequired] = React.useState(false);
|
const [totpRequired, setTotpRequired] = React.useState(false);
|
||||||
const [totpSessionId, setTotpSessionId] = React.useState<string | null>(null);
|
const [totpSessionId, setTotpSessionId] = React.useState<string | null>(null);
|
||||||
const [totpPrompt, setTotpPrompt] = React.useState<string>("");
|
const [totpPrompt, setTotpPrompt] = React.useState<string>("");
|
||||||
@@ -139,15 +107,9 @@ function HostMetricsInner({
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [editMode, setEditMode] = React.useState(false);
|
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 activityLoggedRef = React.useRef(false);
|
||||||
const activityLoggingRef = React.useRef(false);
|
const activityLoggingRef = React.useRef(false);
|
||||||
const snippetsCacheRef = React.useRef(new Map<number, Snippet>());
|
|
||||||
|
|
||||||
const statsConfig = React.useMemo(() => {
|
const statsConfig = React.useMemo(() => {
|
||||||
const settings = readHostMetricsSettings(
|
const settings = readHostMetricsSettings(
|
||||||
@@ -532,102 +494,6 @@ function HostMetricsInner({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function executeQuickAction(
|
|
||||||
action: QuickAction,
|
|
||||||
inputValues: Record<string, string>,
|
|
||||||
) {
|
|
||||||
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<string, string>,
|
|
||||||
) {
|
|
||||||
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 =
|
const showCards =
|
||||||
metricsEnabled && metricsRetry.status === "connected" && metrics;
|
metricsEnabled && metricsRetry.status === "connected" && metrics;
|
||||||
const showOffline =
|
const showOffline =
|
||||||
@@ -653,43 +519,13 @@ function HostMetricsInner({
|
|||||||
<h1 className="text-lg font-bold md:text-2xl">{title}</h1>
|
<h1 className="text-lg font-bold md:text-2xl">{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-0">
|
<div className="flex items-center gap-0">
|
||||||
{currentHostConfig?.quickActions &&
|
<ComponentSlot
|
||||||
currentHostConfig.quickActions.length > 0 && (
|
slotId="host-metrics.toolbar"
|
||||||
<>
|
props={{
|
||||||
<div className="mr-3 flex flex-wrap gap-2">
|
hostId: currentHostConfig?.id,
|
||||||
{currentHostConfig.quickActions.map(
|
host: currentHostConfig,
|
||||||
(action, index) => {
|
}}
|
||||||
const isExecuting = executingActions.has(
|
|
||||||
action.snippetId,
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={index}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 text-xs font-semibold"
|
|
||||||
disabled={isExecuting}
|
|
||||||
onClick={() => void runQuickAction(action)}
|
|
||||||
>
|
|
||||||
{isExecuting ? (
|
|
||||||
<>
|
|
||||||
<RefreshCw className="mr-1 size-3 animate-spin" />
|
|
||||||
{action.name}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
action.name
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Separator
|
|
||||||
orientation="vertical"
|
|
||||||
className="mx-3 h-8"
|
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{editMode && (
|
{editMode && (
|
||||||
<>
|
<>
|
||||||
<ColumnCountStepper
|
<ColumnCountStepper
|
||||||
@@ -806,32 +642,6 @@ function HostMetricsInner({
|
|||||||
onCancel={handleTOTPCancel}
|
onCancel={handleTOTPCancel}
|
||||||
backgroundColor="var(--bg-canvas)"
|
backgroundColor="var(--bg-canvas)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{runningAction && (
|
|
||||||
<SnippetVariablesDialog
|
|
||||||
snippet={
|
|
||||||
runningAction.snippet as Parameters<
|
|
||||||
typeof SnippetVariablesDialog
|
|
||||||
>[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);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,4 +140,10 @@ export async function activate(app: TermixApp): Promise<void> {
|
|||||||
id: "host-metrics.managers",
|
id: "host-metrics.managers",
|
||||||
accepts: ["component"],
|
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"],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ afterEach(cleanup);
|
|||||||
|
|
||||||
function renderSection(pluginSettings: Record<string, unknown> = {}) {
|
function renderSection(pluginSettings: Record<string, unknown> = {}) {
|
||||||
let form: Record<string, unknown> = {
|
let form: Record<string, unknown> = {
|
||||||
quickActions: [],
|
|
||||||
pluginSettings: { "host-metrics": pluginSettings, other: { keep: 1 } },
|
pluginSettings: { "host-metrics": pluginSettings, other: { keep: 1 } },
|
||||||
};
|
};
|
||||||
const updateForm = vi.fn(
|
const updateForm = vi.fn(
|
||||||
@@ -21,7 +20,6 @@ function renderSection(pluginSettings: Record<string, unknown> = {}) {
|
|||||||
setField={vi.fn()}
|
setField={vi.fn()}
|
||||||
updateForm={updateForm as never}
|
updateForm={updateForm as never}
|
||||||
host={{ id: "7", name: "h", ip: "10.0.0.7", port: 22 }}
|
host={{ id: "7", name: "h", ip: "10.0.0.7", port: 22 }}
|
||||||
snippets={[]}
|
|
||||||
protocols={{ ssh: true }}
|
protocols={{ ssh: true }}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -195,5 +195,37 @@
|
|||||||
"shareFolderHint": "Shares every snippet in this folder. Snippets added later are not shared automatically.",
|
"shareFolderHint": "Shares every snippet in this folder. Snippets added later are not shared automatically.",
|
||||||
"shareFolderSummary": "Shared {{count}} snippet(s) in this folder",
|
"shareFolderSummary": "Shared {{count}} snippet(s) in this folder",
|
||||||
"grantedBy": "Granted by",
|
"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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}} কার্যকর করতে ব্যর্থ হয়েছে"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}} निष्पादित करने में विफल"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}} の実行に失敗しました"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}} 실행에 실패했습니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}} ได้"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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ı"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}} 失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,8 +95,51 @@
|
|||||||
"descriptionKey": "settings.confirmExecution.description",
|
"descriptionKey": "settings.confirmExecution.description",
|
||||||
"default": false
|
"default": false
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"host": {
|
||||||
|
"editorGroup": "ssh",
|
||||||
|
"editorOrder": 15,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"key": "startupSnippetId",
|
||||||
|
"type": "number",
|
||||||
|
"labelKey": "host.startupTitle",
|
||||||
|
"descriptionKey": "host.startupDescription",
|
||||||
|
"hidden": true,
|
||||||
|
"min": 1,
|
||||||
|
"shareRead": "view"
|
||||||
},
|
},
|
||||||
"syncEntities": ["snippetFolders", "snippets"]
|
{
|
||||||
|
"key": "quickActions",
|
||||||
|
"type": "json",
|
||||||
|
"labelKey": "host.quickActionsTitle",
|
||||||
|
"descriptionKey": "host.quickActionsDescription",
|
||||||
|
"hidden": true,
|
||||||
|
"default": [],
|
||||||
|
"shareRead": "view"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syncEntities": ["snippetFolders", "snippets"],
|
||||||
|
"keybindingActions": [
|
||||||
|
{
|
||||||
|
"id": "runSnippet",
|
||||||
|
"params": {
|
||||||
|
"snippetId": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+$",
|
||||||
|
"maxLength": 20,
|
||||||
|
"syncEntity": "snippets"
|
||||||
|
},
|
||||||
|
"appendEnter": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"http": {
|
||||||
|
"adminImpersonation": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,58 +4,8 @@ export interface SnippetExecutionResult {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnippetHostVars {
|
export { resolveSnippetContent as resolveSnippetCommand } from "../shared/variables.js";
|
||||||
ip?: string;
|
export type { SnippetHostContext as SnippetHostVars } from "../shared/variables.js";
|
||||||
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, string> = {},
|
|
||||||
): 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 function getSnippetExecutionTimeoutMs(
|
export function getSnippetExecutionTimeoutMs(
|
||||||
value = process.env.SNIPPET_EXECUTION_TIMEOUT_SECONDS,
|
value = process.env.SNIPPET_EXECUTION_TIMEOUT_SECONDS,
|
||||||
|
|||||||
@@ -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<string, unknown>,
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
const carried = (raw.pluginSettings as Record<string, unknown> | undefined)?.[
|
||||||
|
PLUGIN_ID
|
||||||
|
];
|
||||||
|
if (
|
||||||
|
carried &&
|
||||||
|
typeof carried === "object" &&
|
||||||
|
Object.keys(carried).length > 0
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values: Record<string, unknown> = {};
|
||||||
|
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<string, unknown>,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const out: Record<string, unknown> = {
|
||||||
|
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<string | null>;
|
||||||
|
findIdBySyncId: (syncId: string) => Promise<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,7 +3,15 @@ import type { PluginContext } from "@termix/plugin-sdk/backend";
|
|||||||
import { snippetAccess, snippetFolders, snippets } from "./tables.js";
|
import { snippetAccess, snippetFolders, snippets } from "./tables.js";
|
||||||
import { createSnippetRepository } from "./repository.js";
|
import { createSnippetRepository } from "./repository.js";
|
||||||
import { registerSnippetRoutes } from "./routes.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}). */
|
/** What other plugins get from ctx.services.get("snippets.access", {userId}). */
|
||||||
export interface SnippetsService {
|
export interface SnippetsService {
|
||||||
@@ -74,8 +82,24 @@ export async function activate(ctx: PluginContext) {
|
|||||||
type: "snippets",
|
type: "snippets",
|
||||||
table: snippetsTable,
|
table: snippetsTable,
|
||||||
order: 60,
|
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
|
// 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;
|
if (!userId) return null;
|
||||||
const snippet = await findAccessible(userId, id);
|
const snippet = await findAccessible(userId, id);
|
||||||
if (!snippet) return null;
|
if (!snippet) return null;
|
||||||
return resolveSnippetCommand(snippet.content, vars, inputValues);
|
return resolveSnippetContent(snippet.content, vars, inputValues);
|
||||||
},
|
},
|
||||||
create: async (input) => {
|
create: async (input) => {
|
||||||
const userId = ctx.currentActor();
|
const userId = ctx.currentActor();
|
||||||
|
|||||||
@@ -882,6 +882,27 @@ export function createSnippetRepository(
|
|||||||
return rows[0] ?? null;
|
return rows[0] ?? null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** A snippet's sync id, for a host setting that names it over sync. */
|
||||||
|
async findSyncIdById(snippetId: number): Promise<string | null> {
|
||||||
|
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<number | null> {
|
||||||
|
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. */
|
/** A user's role ids, for the sharing filters above. */
|
||||||
async listUserRoleIds(userId: string): Promise<number[]> {
|
async listUserRoleIds(userId: string): Promise<number[]> {
|
||||||
const drizzle = await client();
|
const drizzle = await client();
|
||||||
|
|||||||
@@ -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<string, Record<string, unknown>>;
|
||||||
|
|
||||||
|
/** 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<string, unknown>) =>
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<SectionCard
|
||||||
|
title={t("host.startupTitle")}
|
||||||
|
icon={<Play className="size-3.5" />}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-1.5 py-3">
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t("host.startupDescription")}
|
||||||
|
</p>
|
||||||
|
<Select2
|
||||||
|
aria-label={t("host.startupTitle")}
|
||||||
|
value={startupSnippetId ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<option value="">{t("host.none")}</option>
|
||||||
|
{options.map((snippet) => (
|
||||||
|
<option key={snippet.id} value={snippet.id}>
|
||||||
|
{snippet.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select2>
|
||||||
|
</div>
|
||||||
|
</SectionCard>
|
||||||
|
<SectionCard
|
||||||
|
title={t("host.quickActionsTitle")}
|
||||||
|
icon={<Zap className="size-3.5" />}
|
||||||
|
action={
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-6 text-[10px] px-2 border-accent-brand/40 text-accent-brand"
|
||||||
|
onClick={() =>
|
||||||
|
setQuickActions([...quickActions, { name: "", snippetId: null }])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Plus className="size-3 mr-1" /> {t("host.addQuickAction")}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-3 py-3">
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t("host.quickActionsDescription")}
|
||||||
|
</p>
|
||||||
|
{quickActions.length === 0 && (
|
||||||
|
<div className="flex flex-col items-center justify-center py-4 text-muted-foreground/40 gap-1.5">
|
||||||
|
<Zap className="size-6" />
|
||||||
|
<span className="text-xs">{t("host.noQuickActions")}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{quickActions.map((row, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-center gap-2 p-2 bg-muted/20 border border-border group"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
className="h-7 text-xs flex-1"
|
||||||
|
placeholder={t("host.buttonLabel")}
|
||||||
|
value={row.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = [...quickActions];
|
||||||
|
next[index] = { ...row, name: e.target.value };
|
||||||
|
setQuickActions(next);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Select2
|
||||||
|
aria-label={t("host.selectSnippet")}
|
||||||
|
className="h-7 text-xs flex-1 border border-border bg-background px-2 outline-none focus:ring-1 focus:ring-ring"
|
||||||
|
value={row.snippetId ?? ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = [...quickActions];
|
||||||
|
next[index] = {
|
||||||
|
...row,
|
||||||
|
snippetId: e.target.value ? Number(e.target.value) : null,
|
||||||
|
};
|
||||||
|
setQuickActions(next);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">{t("host.selectSnippet")}</option>
|
||||||
|
{options.map((snippet) => (
|
||||||
|
<option key={snippet.id} value={snippet.id}>
|
||||||
|
{snippet.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select2>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={t("host.removeQuickAction")}
|
||||||
|
className="text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
|
onClick={() =>
|
||||||
|
setQuickActions(quickActions.filter((_, i) => i !== index))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Trash2 className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SectionCard>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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<string, Record<string, unknown>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string, unknown>) {
|
||||||
|
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<Set<number>>(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<string, string>,
|
||||||
|
) {
|
||||||
|
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<string, string> = {};
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div className="mr-3 flex flex-wrap gap-2">
|
||||||
|
{actions.map((action, index) => {
|
||||||
|
const busy = running.has(action.snippetId);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 text-xs font-semibold"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => void run(action)}
|
||||||
|
>
|
||||||
|
{busy && <RefreshCw className="mr-1 size-3 animate-spin" />}
|
||||||
|
{action.name}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Separator orientation="vertical" className="mx-3 h-8" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<SnippetVariablesDialog
|
||||||
|
snippet={prompt.snippet}
|
||||||
|
host={prompt.host}
|
||||||
|
onCancel={() => prompt.settle(null)}
|
||||||
|
onConfirm={(_resolved, values) => prompt.settle(values)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
+19
-16
@@ -1,25 +1,30 @@
|
|||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "@termix/plugin-sdk/frontend";
|
||||||
import { Button } from "@/components/button";
|
|
||||||
import { Input } from "@/components/input";
|
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
} from "@/components/dialog";
|
} from "@termix/plugin-sdk/ui";
|
||||||
import {
|
import {
|
||||||
extractSnippetInputs,
|
extractSnippetInputs,
|
||||||
resolveSnippetContent,
|
resolveSnippetContent,
|
||||||
type SnippetHostContext,
|
type SnippetHostContext,
|
||||||
} from "@/lib/snippet-variables";
|
} from "../shared/variables.js";
|
||||||
import type { Snippet } from "@/types/ui-types";
|
|
||||||
|
/** 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
|
* 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({
|
export function SnippetVariablesDialog({
|
||||||
snippet,
|
snippet,
|
||||||
@@ -27,7 +32,7 @@ export function SnippetVariablesDialog({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
}: {
|
}: {
|
||||||
snippet: Snippet;
|
snippet: VariablesDialogSnippet;
|
||||||
host: SnippetHostContext | null;
|
host: SnippetHostContext | null;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onConfirm: (
|
onConfirm: (
|
||||||
@@ -53,12 +58,10 @@ export function SnippetVariablesDialog({
|
|||||||
<DialogContent className="sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-lg font-bold">
|
<DialogTitle className="text-lg font-bold">
|
||||||
{t("newUi.sidebar.snippets.variablesDialogTitle", {
|
{t("variablesDialogTitle", { name: snippet.name })}
|
||||||
name: snippet.name,
|
|
||||||
})}
|
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-xs text-muted-foreground">
|
<DialogDescription className="text-xs text-muted-foreground">
|
||||||
{t("newUi.sidebar.snippets.variablesDialogDescription")}
|
{t("variablesDialogDescription")}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex flex-col gap-4 mt-1">
|
<div className="flex flex-col gap-4 mt-1">
|
||||||
@@ -79,7 +82,7 @@ export function SnippetVariablesDialog({
|
|||||||
))}
|
))}
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<label className="text-xs font-semibold text-muted-foreground">
|
<label className="text-xs font-semibold text-muted-foreground">
|
||||||
{t("newUi.sidebar.snippets.variablesPreviewLabel")}
|
{t("variablesPreviewLabel")}
|
||||||
</label>
|
</label>
|
||||||
<span className="text-xs text-muted-foreground font-mono px-2.5 py-2 border border-border bg-muted/20 min-w-0 break-all whitespace-pre-wrap">
|
<span className="text-xs text-muted-foreground font-mono px-2.5 py-2 border border-border bg-muted/20 min-w-0 break-all whitespace-pre-wrap">
|
||||||
{preview}
|
{preview}
|
||||||
@@ -88,14 +91,14 @@ export function SnippetVariablesDialog({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2 mt-2">
|
<div className="flex items-center justify-end gap-2 mt-2">
|
||||||
<Button variant="ghost" onClick={onCancel}>
|
<Button variant="ghost" onClick={onCancel}>
|
||||||
{t("newUi.sidebar.snippets.cancel")}
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
|
className="border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
|
||||||
onClick={() => onConfirm(preview, values)}
|
onClick={() => onConfirm(preview, values)}
|
||||||
>
|
>
|
||||||
{t("newUi.sidebar.snippets.variablesConfirmButton")}
|
{t("variablesConfirmButton")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -1,15 +1,53 @@
|
|||||||
import { Play } from "lucide-react";
|
import { Clipboard, Play } from "lucide-react";
|
||||||
import type { PanelProps, TermixApp } from "@termix/plugin-sdk/frontend";
|
import type {
|
||||||
|
PaletteItem,
|
||||||
|
PanelProps,
|
||||||
|
PluginHostRecord,
|
||||||
|
TermixApp,
|
||||||
|
} from "@termix/plugin-sdk/frontend";
|
||||||
|
import type { Snippet } from "./types";
|
||||||
import { SnippetsPanel } from "./SnippetsPanel";
|
import { SnippetsPanel } from "./SnippetsPanel";
|
||||||
import { createSnippetsApi } from "./snippets-api";
|
import { createSnippetsApi } from "./snippets-api";
|
||||||
import { resolveSnippetContent, hasSnippetInputs } from "./snippet-variables";
|
|
||||||
import {
|
import {
|
||||||
AdminUserSnippets,
|
extractSnippetInputs,
|
||||||
adminOptions,
|
resolveSnippetContent,
|
||||||
mapSnippets,
|
type SnippetHostContext,
|
||||||
} from "./AdminUserSnippets";
|
} 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<string, unknown>;
|
||||||
|
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 {
|
export function activate(app: TermixApp): void {
|
||||||
|
app.onDispose(resetPrompts);
|
||||||
|
|
||||||
app.registerRailItem({
|
app.registerRailItem({
|
||||||
id: "snippets",
|
id: "snippets",
|
||||||
icon: Play,
|
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<PaletteItem[]> => {
|
||||||
|
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", {
|
app.registerSlotContribution("onboarding.features", {
|
||||||
actionId: "snippets.feature",
|
actionId: "snippets.feature",
|
||||||
titleKey: "onboarding.feature_snippets",
|
titleKey: "onboarding.feature_snippets",
|
||||||
@@ -49,71 +154,17 @@ export function activate(app: TermixApp): void {
|
|||||||
order: 10,
|
order: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Core's snippet pickers (the host editor's startup snippet, keybindings,
|
// The command a terminal types when it connects to a host: the host's
|
||||||
// the command palette) read the list through this. An admin editing
|
// startup snippet with its variables filled in, or null.
|
||||||
// another user's host passes that user's id.
|
app.registerAction("snippets.startupCommand", (async (
|
||||||
app.registerAction("snippet.list", (async (options?: {
|
host: PluginHostRecord | null,
|
||||||
targetUserId?: string;
|
vars?: SnippetHostContext,
|
||||||
}) => {
|
|
||||||
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<string, unknown>[]).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<string, string>,
|
|
||||||
) => {
|
) => {
|
||||||
const api = createSnippetsApi(app.api);
|
const id = readStartupSnippetId(snippetHostSettings(host).startupSnippetId);
|
||||||
const snippet = await api.get(snippetId).catch(() => null);
|
if (id === null) return null;
|
||||||
|
const snippet = await fetchSnippet(app, id);
|
||||||
if (!snippet) return null;
|
if (!snippet) return null;
|
||||||
if (
|
return resolveSnippetContent(snippet.content, vars ?? hostContext(host));
|
||||||
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;
|
|
||||||
}) as never);
|
}) as never);
|
||||||
|
|
||||||
// For pickers in other plugins (the AI assistant's @-mentions): id and name.
|
// 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 }));
|
return snippets.map((snippet) => ({ id: snippet.id, name: snippet.name }));
|
||||||
}) as never);
|
}) as never);
|
||||||
|
|
||||||
app.registerAction("snippets.execute", (async (
|
// Variable handling for commands other plugins run (fleets' run box).
|
||||||
snippetId: number,
|
app.registerAction("snippets.extractInputs", ((content: string) =>
|
||||||
hostId: number,
|
extractSnippetInputs(typeof content === "string" ? content : "")) as never);
|
||||||
inputValues?: Record<string, string>,
|
|
||||||
) =>
|
|
||||||
createSnippetsApi(app.api).execute(
|
|
||||||
snippetId,
|
|
||||||
hostId,
|
|
||||||
inputValues,
|
|
||||||
)) as never);
|
|
||||||
}
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-xs font-semibold">
|
||||||
|
{t("keybindings.snippetLabel")}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
aria-label={t("keybindings.snippetLabel")}
|
||||||
|
value={snippetIdOf(action)}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChange({
|
||||||
|
type: RUN_SNIPPET_ACTION,
|
||||||
|
snippetId: e.target.value || undefined,
|
||||||
|
appendEnter: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="h-8 border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
|
||||||
|
>
|
||||||
|
<option value="">{t("keybindings.selectSnippet")}</option>
|
||||||
|
{options.map((snippet) => (
|
||||||
|
<option key={snippet.id} value={String(snippet.id)}>
|
||||||
|
{snippet.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 <span>({snippet.name})</span>;
|
||||||
|
return (
|
||||||
|
<span className="text-destructive">
|
||||||
|
({t("keybindings.snippetMissing")})
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<TermixApp, "api" | "t">,
|
||||||
|
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 },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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<string, string> | 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<Record<string, string> | 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();
|
||||||
|
}
|
||||||
@@ -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<string | null> {
|
||||||
|
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<TermixApp, "t">,
|
||||||
|
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<TermixApp, "t">,
|
||||||
|
snippet: RunnableSnippet,
|
||||||
|
target: RunTarget,
|
||||||
|
host: SnippetHostContext | null,
|
||||||
|
options: { pressEnter?: boolean; announce?: boolean } = {},
|
||||||
|
): Promise<void> {
|
||||||
|
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<TermixApp, "api">,
|
||||||
|
id: number,
|
||||||
|
): Promise<RunnableSnippet | null> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
Loaded 100 of 262 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user