mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 08:32:48 +00:00
Compare commits
2 Commits
ibuler-pat
...
pr@dev@fea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecdc1a056a | ||
|
|
5001ca6960 |
@@ -105,6 +105,10 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
|
||||
h['account']['mode'] = 'sysdba' if account.privileged else None
|
||||
return h
|
||||
|
||||
def add_extra_params(self, host, **kwargs):
|
||||
host['ssh_params'] = {}
|
||||
return host
|
||||
|
||||
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
||||
host = super().host_callback(
|
||||
host, asset=asset, account=account, automation=automation,
|
||||
@@ -113,8 +117,7 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
|
||||
if host.get('error'):
|
||||
return host
|
||||
|
||||
host['ssh_params'] = {}
|
||||
|
||||
host = self.add_extra_params(host, automation=automation)
|
||||
accounts = self.get_accounts(account)
|
||||
existing_ids = set(map(str, accounts.values_list('id', flat=True)))
|
||||
missing_ids = set(map(str, self.account_ids)) - existing_ids
|
||||
|
||||
@@ -5,6 +5,9 @@ from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from xlsxwriter import Workbook
|
||||
|
||||
from assets.automations.methods import platform_automation_methods as asset_methods
|
||||
from assets.const import AutomationTypes as AssetAutomationTypes
|
||||
from accounts.automations.methods import platform_automation_methods as account_methods
|
||||
from accounts.const import (
|
||||
AutomationTypes, SecretStrategy, ChangeSecretRecordStatusChoice
|
||||
)
|
||||
@@ -22,6 +25,22 @@ logger = get_logger(__name__)
|
||||
class ChangeSecretManager(BaseChangeSecretPushManager):
|
||||
ansible_account_prefer = ''
|
||||
|
||||
def get_method_id_meta_mapper(self):
|
||||
return {
|
||||
method["id"]: method for method in self.platform_automation_methods
|
||||
}
|
||||
|
||||
@property
|
||||
def platform_automation_methods(self):
|
||||
return asset_methods + account_methods
|
||||
|
||||
def add_extra_params(self, host, **kwargs):
|
||||
host = super().add_extra_params(host, **kwargs)
|
||||
automation = kwargs.get('automation')
|
||||
for extra_type in [AssetAutomationTypes.ping, AutomationTypes.verify_account]:
|
||||
host[f"{extra_type}_params"] = self.get_params(automation, extra_type)
|
||||
return host
|
||||
|
||||
@classmethod
|
||||
def method_type(cls):
|
||||
return AutomationTypes.change_secret
|
||||
|
||||
36
apps/accounts/automations/change_secret/web/website/main.yml
Normal file
36
apps/accounts/automations/change_secret/web/website/main.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
- hosts: website
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ local_python_interpreter }}"
|
||||
|
||||
tasks:
|
||||
- name: Test privileged account
|
||||
website_ping:
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
steps: "{{ ping_params.steps }}"
|
||||
load_state: "{{ ping_params.load_state }}"
|
||||
|
||||
- name: "Change {{ account.username }} password"
|
||||
website_user:
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
steps: "{{ params.steps }}"
|
||||
load_state: "{{ params.load_state }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
ignore_errors: true
|
||||
register: change_secret_result
|
||||
|
||||
- name: "Verify {{ account.username }} password"
|
||||
website_ping:
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
steps: "{{ verify_account_params.steps }}"
|
||||
load_state: "{{ verify_account_params.load_state }}"
|
||||
when:
|
||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||
delegate_to: localhost
|
||||
@@ -0,0 +1,51 @@
|
||||
id: change_account_website
|
||||
name: "{{ 'Website account change secret' | trans }}"
|
||||
category: web
|
||||
type:
|
||||
- website
|
||||
method: change_secret
|
||||
priority: 50
|
||||
params:
|
||||
- name: load_state
|
||||
type: choice
|
||||
label: "{{ 'Load state' | trans }}"
|
||||
choices:
|
||||
- [ networkidle, "{{ 'Network idle' | trans }}" ]
|
||||
- [ domcontentloaded, "{{ 'Dom content loaded' | trans }}" ]
|
||||
- [ load, "{{ 'Load completed' | trans }}" ]
|
||||
default: 'load'
|
||||
- name: steps
|
||||
type: list
|
||||
default: [ ]
|
||||
label: "{{ 'Steps' | trans }}"
|
||||
help_text: "{{ 'Params step help text' | trans }}"
|
||||
|
||||
i18n:
|
||||
Website account change secret:
|
||||
zh: 使用 Playwright 模拟浏览器变更账号密码
|
||||
ja: Playwright を使用してブラウザをシミュレートし、アカウントのパスワードを変更します
|
||||
en: Use Playwright to simulate a browser for account password change.
|
||||
Load state:
|
||||
zh: 加载状态检测
|
||||
en: Load state detection
|
||||
ja: ロード状態の検出
|
||||
Steps:
|
||||
zh: 步骤
|
||||
en: Steps
|
||||
ja: 手順
|
||||
Network idle:
|
||||
zh: 网络空闲
|
||||
en: Network idle
|
||||
ja: ネットワークが空いた状態
|
||||
Dom content loaded:
|
||||
zh: 文档内容加载完成
|
||||
en: Dom content loaded
|
||||
ja: ドキュメントの内容がロードされた状態
|
||||
Load completed:
|
||||
zh: 全部加载完成
|
||||
en: All load completed
|
||||
ja: すべてのロードが完了した状態
|
||||
Params step help text:
|
||||
zh: 根据配置决定任务执行步骤
|
||||
ja: 設定に基づいてタスクの実行ステップを決定する
|
||||
en: Determine task execution steps based on configuration
|
||||
@@ -0,0 +1,13 @@
|
||||
- hosts: website
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ local_python_interpreter }}"
|
||||
|
||||
tasks:
|
||||
- name: Verify account
|
||||
website_ping:
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
steps: "{{ params.steps }}"
|
||||
load_state: "{{ params.load_state }}"
|
||||
@@ -0,0 +1,50 @@
|
||||
id: verify_account_website
|
||||
name: "{{ 'Website account verify' | trans }}"
|
||||
category: web
|
||||
type:
|
||||
- website
|
||||
method: verify_account
|
||||
priority: 50
|
||||
params:
|
||||
- name: load_state
|
||||
type: choice
|
||||
label: "{{ 'Load state' | trans }}"
|
||||
choices:
|
||||
- [ networkidle, "{{ 'Network idle' | trans }}" ]
|
||||
- [ domcontentloaded, "{{ 'Dom content loaded' | trans }}" ]
|
||||
- [ load, "{{ 'Load completed' | trans }}" ]
|
||||
default: 'load'
|
||||
- name: steps
|
||||
type: list
|
||||
label: "{{ 'Steps' | trans }}"
|
||||
help_text: "{{ 'Params step help text' | trans }}"
|
||||
default: []
|
||||
i18n:
|
||||
Website account verify:
|
||||
zh: 使用 Playwright 模拟浏览器验证账号
|
||||
ja: Playwright を使用してブラウザをシミュレートし、アカウントの検証を行います
|
||||
en: Use Playwright to simulate a browser for account verification.
|
||||
Load state:
|
||||
zh: 加载状态检测
|
||||
en: Load state detection
|
||||
ja: ロード状態の検出
|
||||
Steps:
|
||||
zh: 步骤
|
||||
en: Steps
|
||||
ja: 手順
|
||||
Network idle:
|
||||
zh: 网络空闲
|
||||
en: Network idle
|
||||
ja: ネットワークが空いた状態
|
||||
Dom content loaded:
|
||||
zh: 文档内容加载完成
|
||||
en: Dom content loaded
|
||||
ja: ドキュメントの内容がロードされた状態
|
||||
Load completed:
|
||||
zh: 全部加载完成
|
||||
en: All load completed
|
||||
ja: すべてのロードが完了した状態
|
||||
Params step help text:
|
||||
zh: 配置步骤,根据配置决定任务执行步骤
|
||||
ja: パラメータを設定し、設定に基づいてタスクの実行手順を決定します
|
||||
en: Configure steps, and determine the task execution steps based on the configuration.
|
||||
@@ -201,14 +201,17 @@ class PlaybookPrepareMixin:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# example: {'gather_fact_windows': {'id': 'gather_fact_windows', 'name': '', 'method': 'gather_fact', ...} }
|
||||
self.method_id_meta_mapper = {
|
||||
self.method_id_meta_mapper = self.get_method_id_meta_mapper()
|
||||
# 根据执行方式就行分组, 不同资产的改密、推送等操作可能会使用不同的执行方式
|
||||
# 然后根据执行方式分组, 再根据 bulk_size 分组, 生成不同的 playbook
|
||||
self.playbooks = []
|
||||
|
||||
def get_method_id_meta_mapper(self):
|
||||
return {
|
||||
method["id"]: method
|
||||
for method in self.platform_automation_methods
|
||||
if method["method"] == self.__class__.method_type()
|
||||
}
|
||||
# 根据执行方式就行分组, 不同资产的改密、推送等操作可能会使用不同的执行方式
|
||||
# 然后根据执行方式分组, 再根据 bulk_size 分组, 生成不同的 playbook
|
||||
self.playbooks = []
|
||||
|
||||
@classmethod
|
||||
def method_type(cls):
|
||||
|
||||
13
apps/assets/automations/ping/web/website/main.yml
Normal file
13
apps/assets/automations/ping/web/website/main.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
- hosts: website
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ local_python_interpreter }}"
|
||||
|
||||
tasks:
|
||||
- name: Test Website connection
|
||||
website_ping:
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
steps: "{{ params.steps }}"
|
||||
load_state: "{{ params.load_state }}"
|
||||
50
apps/assets/automations/ping/web/website/manifest.yml
Normal file
50
apps/assets/automations/ping/web/website/manifest.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
id: website_ping
|
||||
name: "{{ 'Website ping' | trans }}"
|
||||
method: ping
|
||||
category:
|
||||
- web
|
||||
type:
|
||||
- website
|
||||
params:
|
||||
- name: load_state
|
||||
type: choice
|
||||
label: "{{ 'Load state' | trans }}"
|
||||
choices:
|
||||
- [ networkidle, "{{ 'Network idle' | trans }}" ]
|
||||
- [ domcontentloaded, "{{ 'Dom content loaded' | trans }}" ]
|
||||
- [ load, "{{ 'Load completed' | trans }}" ]
|
||||
default: 'load'
|
||||
- name: steps
|
||||
type: list
|
||||
default: []
|
||||
label: "{{ 'Steps' | trans }}"
|
||||
help_text: "{{ 'Params step help text' | trans }}"
|
||||
i18n:
|
||||
Website ping:
|
||||
zh: 使用 Playwright 模拟浏览器测试可连接性
|
||||
en: Use Playwright to simulate a browser for connectivity testing
|
||||
ja: Playwright を使用してブラウザをシミュレートし、接続性テストを実行する
|
||||
Load state:
|
||||
zh: 加载状态检测
|
||||
en: Load state detection
|
||||
ja: ロード状態の検出
|
||||
Steps:
|
||||
zh: 步骤
|
||||
en: Steps
|
||||
ja: 手順
|
||||
Network idle:
|
||||
zh: 网络空闲
|
||||
en: Network idle
|
||||
ja: ネットワークが空いた状態
|
||||
Dom content loaded:
|
||||
zh: 文档内容加载完成
|
||||
en: Dom content loaded
|
||||
ja: ドキュメントの内容がロードされた状態
|
||||
Load completed:
|
||||
zh: 全部加载完成
|
||||
en: All load completed
|
||||
ja: すべてのロードが完了した状態
|
||||
Params step help text:
|
||||
zh: 配置步骤,根据配置决定任务执行步骤
|
||||
ja: パラメータを設定し、設定に基づいてタスクの実行手順を決定します
|
||||
en: Configure steps, and determine the task execution steps based on the configuration.
|
||||
@@ -20,13 +20,17 @@ class WebTypes(BaseType):
|
||||
def _get_automation_constrains(cls) -> dict:
|
||||
constrains = {
|
||||
'*': {
|
||||
'ansible_enabled': False,
|
||||
'ping_enabled': False,
|
||||
'ansible_enabled': True,
|
||||
'ansible_config': {
|
||||
'ansible_connection': 'local',
|
||||
},
|
||||
'ping_enabled': True,
|
||||
'gather_facts_enabled': False,
|
||||
'verify_account_enabled': False,
|
||||
'change_secret_enabled': False,
|
||||
'verify_account_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'push_account_enabled': False,
|
||||
'gather_accounts_enabled': False,
|
||||
'remove_account_enabled': False,
|
||||
}
|
||||
}
|
||||
return constrains
|
||||
|
||||
@@ -310,7 +310,9 @@
|
||||
"ChatAI": "Chat AI",
|
||||
"ChatHello": "Hello, can I help you?",
|
||||
"ChdirHelpText": "By default, the execution directory is the user's home directory",
|
||||
"Check": "Check",
|
||||
"CheckAssetsAmount": "Check asset quantity",
|
||||
"CheckResponse": "Check the response",
|
||||
"CheckViewAcceptor": "Click to view the acceptance person",
|
||||
"CleanHelpText": "A scheduled cleanup task will be carried out every day at 2 a.m. the data cleaned up will not be recoverable",
|
||||
"Cleaning": "Regular clean-up",
|
||||
@@ -320,6 +322,7 @@
|
||||
"ClearSecret": "Clear secret",
|
||||
"ClearSelection": "Clear selection",
|
||||
"ClearSuccessMsg": "Clear successful",
|
||||
"Click": "Click",
|
||||
"ClickCopy": "Click to copy",
|
||||
"ClientCertificate": "Client certificate",
|
||||
"Clipboard": "Clipboard",
|
||||
@@ -812,6 +815,7 @@
|
||||
"LoginTitleTip": "Note: it will be displayed on the enterprise edition user ssh login koko login page (e.g.: welcome to use jumpserver open source PAM)",
|
||||
"LoginUserRanking": "Login user ranking",
|
||||
"LoginUserToday": "Users logged today",
|
||||
"LoginUsername": "Login username",
|
||||
"LoginUsers": "Active account",
|
||||
"LogoIndexTip": "Tip: it will be displayed in the upper left corner of the page (recommended image size: 185px*55px)",
|
||||
"LogoLogoutTip": "Tip: it will be displayed on the web terminal page of enterprise edition users (recommended image size: 82px*82px)",
|
||||
@@ -1151,6 +1155,9 @@
|
||||
"ResolveSelected": "Resolve selected",
|
||||
"Resource": "Resources",
|
||||
"ResourceType": "Resource type",
|
||||
"Response": "Response",
|
||||
"ResponseExpression": "Response body expression",
|
||||
"ResponseExpressionTip": "For the response body {count: 1}, the expression can be written as count=1 to match\nFor the response body {data: [a, b]}, the expression can be written as data.length=2 to match\nFor the response body {data: [{username: admin}]}, the expression can be written as data.0.username=admin to match",
|
||||
"RestoreButton": "Restore",
|
||||
"RestoreDefault": "Reset to default",
|
||||
"RestoreDialogMessage": "Are you sure you want to restore to default initialization?",
|
||||
@@ -1251,6 +1258,7 @@
|
||||
"Selected": "Selected",
|
||||
"Selection": "Selection",
|
||||
"Selector": "Selector",
|
||||
"SelectorPlaceholder": "Enter the element locator (supports CSS/XPath)",
|
||||
"Send": "Send",
|
||||
"SendVerificationCode": "Send verification code",
|
||||
"SerialNumber": "Serial number",
|
||||
@@ -1300,6 +1308,7 @@
|
||||
"Skipped": "Skipped",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Slack OAuth",
|
||||
"Sleep": "Sleep",
|
||||
"Source": "Source",
|
||||
"SourceIP": "Source address",
|
||||
"SourcePort": "Source port",
|
||||
@@ -1313,6 +1322,7 @@
|
||||
"State": "Status",
|
||||
"StateClosed": "Is closed",
|
||||
"Status": "Status",
|
||||
"StatusCode": "Status code",
|
||||
"StatusGreen": "Recently in good condition",
|
||||
"StatusRed": "Last task execution failed",
|
||||
"StatusYellow": "There have been recent failures",
|
||||
@@ -1551,6 +1561,7 @@
|
||||
"UsersAndUserGroups": "Users/groups",
|
||||
"UsersTotal": "Total users",
|
||||
"Valid": "Valid",
|
||||
"Value": "Value",
|
||||
"Variable": "Variable",
|
||||
"VariableHelpText": "You can use {{ key }} to read built-in variables in commands",
|
||||
"VariableName": "Variable name",
|
||||
@@ -1575,6 +1586,8 @@
|
||||
"VisitTimeDistribution": "Visit time distribution",
|
||||
"Visits": "Visits",
|
||||
"Volcengine": "Volcengine",
|
||||
"WaitForSelector": "Wait for the element",
|
||||
"WaitForURL": "Wait for the URL",
|
||||
"Warning": "Warning",
|
||||
"WatermarkVariableHelpText": "You can use ${key} to read built-in variables in watermark content",
|
||||
"WeChat": "WeChat",
|
||||
|
||||
@@ -309,7 +309,9 @@
|
||||
"ChatAI": "Consulta Inteligente",
|
||||
"ChatHello": "¡Hola! ¿En qué puedo ayudarte?",
|
||||
"ChdirHelpText": "El directorio de ejecución predeterminado es el directorio home del usuario ejecutivo",
|
||||
"Check": "Verificar",
|
||||
"CheckAssetsAmount": "Verificar la cantidad de activos",
|
||||
"CheckResponse": "Verificar la respuesta",
|
||||
"CheckViewAcceptor": "Haz clic para ver al receptor",
|
||||
"CleanHelpText": "La tarea de limpieza programada se ejecutará todos los días a las 2 de la mañana; los datos eliminados no podrán ser recuperados.",
|
||||
"Cleaning": "Limpiar regularmente",
|
||||
@@ -319,6 +321,7 @@
|
||||
"ClearSecret": "Limpiar texto cifrado",
|
||||
"ClearSelection": "Limpiar selección",
|
||||
"ClearSuccessMsg": "Limpieza exitosa",
|
||||
"Click": "Hacer clic",
|
||||
"ClickCopy": "Hacer clic para copiar",
|
||||
"ClientCertificate": "Certificado de cliente",
|
||||
"Clipboard": "Portapapeles",
|
||||
@@ -812,6 +815,7 @@
|
||||
"LoginTitleTip": "Nota: Se mostrará en la página de inicio de sesión de SSH de usuarios de la versión empresarial de KoKo (por ejemplo: Bienvenido a JumpServer, la puerta de enlace de código abierto)",
|
||||
"LoginUserRanking": "Clasificación de cuentas",
|
||||
"LoginUserToday": "Número de usuarios que han iniciado sesión hoy",
|
||||
"LoginUsername": "Nombre de usuario de inicio de sesión",
|
||||
"LoginUsers": "Cuentas activas",
|
||||
"LogoIndexTip": "Sugerencia: se mostrará en la parte superior izquierda de la página de gestión (se recomienda un tamaño de imagen de: 185px*55px)",
|
||||
"LogoLogoutTip": "Nota: Se mostrará en la página del terminal web del usuario de la versión empresarial (se recomienda un tamaño de imagen de: 82px*82px)",
|
||||
@@ -1155,6 +1159,9 @@
|
||||
"ResolveSelected": "Resolver lo establecido",
|
||||
"Resource": "Recursos",
|
||||
"ResourceType": "Tipo de recurso",
|
||||
"Response": "Cuerpo de la respuesta",
|
||||
"ResponseExpression": "Expresión del cuerpo de la respuesta",
|
||||
"ResponseExpressionTip": "Para el cuerpo de la respuesta {count: 1}, la expresión se puede escribir como count=1 para hacer coincidencia\nPara el cuerpo de la respuesta {data: [a, b]}, la expresión se puede escribir como data.length=2 para hacer coincidencia\nPara el cuerpo de la respuesta {data: [{username: admin}]}, la expresión se puede escribir como data.0.username=admin para hacer coincidencia",
|
||||
"RestoreButton": "Restaurar predeterminados",
|
||||
"RestoreDefault": "Restablecer a predeterminado",
|
||||
"RestoreDialogMessage": "¿Está seguro de que desea restablecer la inicialización predeterminada?",
|
||||
@@ -1257,6 +1264,7 @@
|
||||
"Selected": "Seleccionado",
|
||||
"Selection": "Seleccionar",
|
||||
"Selector": "Selector",
|
||||
"SelectorPlaceholder": "Ingrese el localizador de elemento (admite CSS/XPath)",
|
||||
"Send": "Enviar",
|
||||
"SendVerificationCode": "Enviar código de verificación",
|
||||
"SerialNumber": "Número de serie",
|
||||
@@ -1306,6 +1314,7 @@
|
||||
"Skipped": "Saltado",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Autenticación de Slack",
|
||||
"Sleep": "Esperar",
|
||||
"Source": "Fuente",
|
||||
"SourceIP": "Dirección de origen",
|
||||
"SourcePort": "Puerto de origen",
|
||||
@@ -1319,6 +1328,7 @@
|
||||
"State": "Estado",
|
||||
"StateClosed": "ha cerrado",
|
||||
"Status": "Estado",
|
||||
"StatusCode": "Código de estado",
|
||||
"StatusGreen": "Estado reciente bueno",
|
||||
"StatusRed": "La última tarea se ejecutó con errores",
|
||||
"StatusYellow": "Recientemente se han producido fallos en la ejecución",
|
||||
@@ -1557,6 +1567,7 @@
|
||||
"UsersAndUserGroups": "Usuario/Grupo de usuarios",
|
||||
"UsersTotal": "número total de usuarios",
|
||||
"Valid": "válido",
|
||||
"Value": "Valor",
|
||||
"Variable": "Variable",
|
||||
"VariableHelpText": "Puede usar {{ key }} en el comando para leer variables integradas",
|
||||
"VariableName": "Nombre de variable",
|
||||
@@ -1581,6 +1592,8 @@
|
||||
"VisitTimeDistribution": "Distribución de períodos de acceso",
|
||||
"Visits": "Visitas",
|
||||
"Volcengine": "Motor de volcán",
|
||||
"WaitForSelector": "Esperar el elemento",
|
||||
"WaitForURL": "Esperar la URL",
|
||||
"Warning": "Advertencia",
|
||||
"Watermark": "Marca de agua",
|
||||
"WatermarkVariableHelpText": "Puede utilizar ${key} en el contenido del sello personalizado para leer las variables integradas como la lista de contraseñas débiles, la hora actual, la disponibilidad de la página de gestión, el ID de usuario, el nombre de usuario, la dirección de activos, el nombre de usuario, el sello, el ID de activos y el nombre de activos.",
|
||||
@@ -1625,4 +1638,4 @@
|
||||
"setVariable": "configurar parámetros",
|
||||
"userId": "ID de usuario",
|
||||
"userName": "Nombre de usuario"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +313,9 @@
|
||||
"ChatAI": "スマートアンサー",
|
||||
"ChatHello": "こんにちは!お手伝いできることがあれば何でもお申し付けください。",
|
||||
"ChdirHelpText": "デフォルトの実行ディレクトリは実行ユーザーのホームディレクトリです",
|
||||
"Check": "確認",
|
||||
"CheckAssetsAmount": "資産の数量を確認",
|
||||
"CheckResponse": "レスポンスを確認する",
|
||||
"CheckViewAcceptor": "受付人を見るにはクリック",
|
||||
"CleanHelpText": "定期的なクリーンアップタスクは毎日午前2時に実行され、クリーンアップ後のデータは回復できません",
|
||||
"Cleaning": "定期的にクリーニング",
|
||||
@@ -323,6 +325,7 @@
|
||||
"ClearSecret": "暗号文のクリア",
|
||||
"ClearSelection": "選択をクリア",
|
||||
"ClearSuccessMsg": "クリアに成功",
|
||||
"Click": "クリックする",
|
||||
"ClickCopy": "クリックでコピー",
|
||||
"ClientCertificate": "クライアント証明書",
|
||||
"Clipboard": "クリップボード",
|
||||
@@ -817,6 +820,7 @@
|
||||
"LoginTitleTip": "ヒント:企業版ユーザーのSSHログイン KoKo ログインページに表示されます(例:JumpServerオープンソースバスチオンへようこそ)",
|
||||
"LoginUserRanking": "ログインアカウントランキング",
|
||||
"LoginUserToday": "今日のログインユーザー数",
|
||||
"LoginUsername": "ログインユーザー名",
|
||||
"LoginUsers": "アクティブなアカウント",
|
||||
"LogoIndexTip": "ヒント:管理ページの左上に表示されます(推奨画像サイズ:185px*55px)",
|
||||
"LogoLogoutTip": "ヒント:これは、エンタープライズ版ユーザーのWebターミナルページに表示されます(推奨画像サイズ:82px*82px)",
|
||||
@@ -1160,6 +1164,9 @@
|
||||
"ResolveSelected": "解決選択",
|
||||
"Resource": "リソース",
|
||||
"ResourceType": "リソースタイプ",
|
||||
"Response": "レスポンスボディ",
|
||||
"ResponseExpression": "レスポンスボディ式",
|
||||
"ResponseExpressionTip": "レスポンスボディ {count: 1} の場合、式は count=1 と記述して一致させることができます\n レスポンスボディ {data: [a, b]} の場合、式は data.length=2 と記述して一致させることができます\n レスポンスボディ {data: [{username: admin}]} の場合、式は data.0.username=admin と記述して一致させることができます",
|
||||
"RestoreButton": "デフォルトに戻す",
|
||||
"RestoreDefault": "デフォルトの復元",
|
||||
"RestoreDialogMessage": "デフォルトの初期化を復元してもよろしいですか?",
|
||||
@@ -1262,6 +1269,7 @@
|
||||
"Selected": "選択済み",
|
||||
"Selection": "選択可能",
|
||||
"Selector": "セレクタ",
|
||||
"SelectorPlaceholder": "要素ロケーターを入力します(CSS/XPath に対応)",
|
||||
"Send": "送信",
|
||||
"SendVerificationCode": "認証コードを送信",
|
||||
"SerialNumber": "シリアルナンバー",
|
||||
@@ -1311,6 +1319,7 @@
|
||||
"Skipped": "スキップ済み",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Slack認証",
|
||||
"Sleep": "待つ",
|
||||
"Source": "源泉",
|
||||
"SourceIP": "ソースアドレス",
|
||||
"SourcePort": "Source Port",
|
||||
@@ -1324,6 +1333,7 @@
|
||||
"State": "状態",
|
||||
"StateClosed": "閉じられた",
|
||||
"Status": "状況",
|
||||
"StatusCode": "ステータスコード",
|
||||
"StatusGreen": "最近の状態は良好",
|
||||
"StatusRed": "前回のタスク実行は失敗しました",
|
||||
"StatusYellow": "最近、実行に失敗があり。",
|
||||
@@ -1562,6 +1572,7 @@
|
||||
"UsersAndUserGroups": "ユーザー/ユーザーグループ",
|
||||
"UsersTotal": "総ユーザー数",
|
||||
"Valid": "有効",
|
||||
"Value": "値",
|
||||
"Variable": "変数",
|
||||
"VariableHelpText": "コマンド中で {{ key }} を使用して内蔵変数を読み取ることができます",
|
||||
"VariableName": "変数名",
|
||||
@@ -1586,6 +1597,8 @@
|
||||
"VisitTimeDistribution": "訪問時間帯の分布",
|
||||
"Visits": "アクセス数",
|
||||
"Volcengine": "ヴォルケーノエンジン",
|
||||
"WaitForSelector": "要素を待つ",
|
||||
"WaitForURL": "URL を待つ",
|
||||
"Warning": "警告",
|
||||
"Watermark": "透かし",
|
||||
"WatermarkVariableHelpText": "カスタム透かし内容には、${key}を使用して組み込み変数を読み込むことができます。",
|
||||
@@ -1630,4 +1643,4 @@
|
||||
"setVariable": "パラメータ設定",
|
||||
"userId": "ユーザーID",
|
||||
"userName": "ユーザー名"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +309,9 @@
|
||||
"ChatAI": "지능형 질문 응답",
|
||||
"ChatHello": "안녕하세요! 무엇을 도와드릴까요?",
|
||||
"ChdirHelpText": "기본 실행 디렉토리는 실행 사용자의 홈 디렉토리입니다.",
|
||||
"Check": "확인하다",
|
||||
"CheckAssetsAmount": "데이터베이스 프로토콜 포트",
|
||||
"CheckResponse": "응답을 확인하다",
|
||||
"CheckViewAcceptor": "수신자 클릭 확인",
|
||||
"CleanHelpText": "정기 청소 작업은 매일 오전 2시에 실행되며, 청소된 데이터는 복원할 수 없습니다",
|
||||
"Cleaning": "정기 청소",
|
||||
@@ -319,6 +321,7 @@
|
||||
"ClearSecret": "암호 해제",
|
||||
"ClearSelection": "선택 지우기",
|
||||
"ClearSuccessMsg": "성공적으로 삭제되었습니다",
|
||||
"Click": "클릭하다",
|
||||
"ClickCopy": "클릭하여 복사",
|
||||
"ClientCertificate": "클라이언트 인증서",
|
||||
"Clipboard": "클립보드",
|
||||
@@ -812,6 +815,7 @@
|
||||
"LoginTitleTip": "提示:기업 버전 사용자 SSH 로그인 KoKo 로그인 페이지에 표시됩니다 (예: JumpServer 오픈 소스 방화벽 기계에 오신 것을 환영합니다)",
|
||||
"LoginUserRanking": "로그인 계정 순위",
|
||||
"LoginUserToday": "오늘 로그인 사용자 수",
|
||||
"LoginUsername": "로그인 사용자 이름",
|
||||
"LoginUsers": "활성 계정",
|
||||
"LogoIndexTip": "알림: 관리 페이지 왼쪽 상단에 표시됩니다 (추천 이미지 크기: 185px*55px)",
|
||||
"LogoLogoutTip": "提示:기업용 사용자 웹 터미널 페이지에 표시됩니다. (이미지 크기는 82px*82px로 설정하는 것이 좋습니다.)",
|
||||
@@ -1155,6 +1159,9 @@
|
||||
"ResolveSelected": "선택한 사항 해결",
|
||||
"Resource": "리소스",
|
||||
"ResourceType": "자원 유형",
|
||||
"Response": "응답 본문",
|
||||
"ResponseExpression": "응답 본문 표현식",
|
||||
"ResponseExpressionTip": "응답 본문 {count: 1}의 경우 표현식은 count=1로 작성하여 매칭할 수 있습니다\n응답 본문 {data: [a, b]}의 경우 표현식은 data.length=2로 작성하여 매칭할 수 있습니다\n응답 본문 {data: [{username: admin}]}의 경우 표현식은 data.0.username=admin로 작성하여 매칭할 수 있습니다",
|
||||
"RestoreButton": "기본 설정 복원",
|
||||
"RestoreDefault": "기본값 복원",
|
||||
"RestoreDialogMessage": "기본 초기화를 복원하시겠습니까?",
|
||||
@@ -1257,6 +1264,7 @@
|
||||
"Selected": "선택됨",
|
||||
"Selection": "선택 가능",
|
||||
"Selector": "선택기",
|
||||
"SelectorPlaceholder": "요소 로케이터를 입력하세요(CSS/XPath 지원)",
|
||||
"Send": "전송",
|
||||
"SendVerificationCode": "인증 코드 발송",
|
||||
"SerialNumber": "시리얼 번호",
|
||||
@@ -1306,6 +1314,7 @@
|
||||
"Skipped": "건너뛰기",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Slack 인증",
|
||||
"Sleep": "기다리다",
|
||||
"Source": "출처",
|
||||
"SourceIP": "출발지 주소",
|
||||
"SourcePort": "소스 포트",
|
||||
@@ -1319,6 +1328,7 @@
|
||||
"State": "상태",
|
||||
"StateClosed": "닫힘",
|
||||
"Status": "상태",
|
||||
"StatusCode": "상태 코드",
|
||||
"StatusGreen": "최근 상태 양호",
|
||||
"StatusRed": "지난 작업 실행 실패",
|
||||
"StatusYellow": "최근 실행 실패가 있었습니다",
|
||||
@@ -1557,6 +1567,7 @@
|
||||
"UsersAndUserGroups": "사용자/사용자 그룹",
|
||||
"UsersTotal": "사용자 총 수",
|
||||
"Valid": "유효",
|
||||
"Value": "값",
|
||||
"Variable": "변수",
|
||||
"VariableHelpText": "명령어에서 {{ key }}를 사용하여 내장 변수를 읽을 수 있습니다",
|
||||
"VariableName": "변수명",
|
||||
@@ -1581,6 +1592,8 @@
|
||||
"VisitTimeDistribution": "방문 시간대 분포",
|
||||
"Visits": "방문량",
|
||||
"Volcengine": "화산 엔진",
|
||||
"WaitForSelector": "요소를 기다리다",
|
||||
"WaitForURL": "URL을 기다리다",
|
||||
"Warning": "경고",
|
||||
"Watermark": "워터마크",
|
||||
"WatermarkVariableHelpText": "사용자가 지정한 워터마크 내용에서 ${key}를 사용하여 내장 변수를 읽을 수 있습니다. \n- 약한 비밀번호 목록 \n- 현재 시간 \n- 관리 페이지 사용 가능 여부 \n- 사용자 ID \n- 사용자 이름 \n- 자산 주소 \n- 사용자명 \n- 워터마크 \n- 자산 ID \n- 자산 이름",
|
||||
@@ -1625,4 +1638,4 @@
|
||||
"setVariable": "설정 매개변수",
|
||||
"userId": "사용자 ID",
|
||||
"userName": "사용자명"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +310,9 @@
|
||||
"ChatAI": "Resposta inteligente",
|
||||
"ChatHello": "Olá! Como posso ajudá-lo? ",
|
||||
"ChdirHelpText": "O diretório de execução padrão é o diretório home do usuário de execução",
|
||||
"Check": "Verificar",
|
||||
"CheckAssetsAmount": "Verificar Quantidade de Ativos",
|
||||
"CheckResponse": "Verificar a resposta",
|
||||
"CheckViewAcceptor": " Clique para ver o receptor do pedido ",
|
||||
"CleanHelpText": "Tarefas de limpeza regulares serão executadas às 2 da manhã todos os dias, os dados após a limpeza não poderão ser restaurados",
|
||||
"Cleaning": "Limpeza regular",
|
||||
@@ -320,6 +322,7 @@
|
||||
"ClearSecret": "Limpar texto cifrado",
|
||||
"ClearSelection": "Limpar seleção",
|
||||
"ClearSuccessMsg": "Limpeza bem-sucedida",
|
||||
"Click": "Clicar",
|
||||
"ClickCopy": "Clicar para copiar",
|
||||
"ClientCertificate": "Certificado do cliente",
|
||||
"Clipboard": "Área de transferência",
|
||||
@@ -813,6 +816,7 @@
|
||||
"LoginTitleTip": "Dica: será exibido na página de login SSH do usuário da versão corporativa do KoKo (por exemplo: Bem-vindo ao uso do JumpServer Open Source Bastion)",
|
||||
"LoginUserRanking": "Classificação de contas de login",
|
||||
"LoginUserToday": "Número de usuários que fizeram login hoje",
|
||||
"LoginUsername": "Nome de usuário de login",
|
||||
"LoginUsers": "Conta ativa",
|
||||
"LogoIndexTip": "Nota: será exibido no canto superior esquerdo da página de administração (tamanho da imagem recomendado: 185px*55px)",
|
||||
"LogoLogoutTip": "Dica: Será exibido na página do terminal da Web do usuário da versão corporativa (tamanho sugerido da imagem: 82px*82px)",
|
||||
@@ -1156,6 +1160,9 @@
|
||||
"ResolveSelected": "Resolver seleção",
|
||||
"Resource": "Recursos",
|
||||
"ResourceType": "Tipo de recurso",
|
||||
"Response": "Corpo da resposta",
|
||||
"ResponseExpression": "Expressão do corpo da resposta",
|
||||
"ResponseExpressionTip": "Para o corpo da resposta {count: 1}, a expressão pode ser escrita como count=1 para fazer correspondência\nPara o corpo da resposta {data: [a, b]}, a expressão pode ser escrita como data.length=2 para fazer correspondência\nPara o corpo da resposta {data: [{username: admin}]}, a expressão pode ser escrita como data.0.username=admin para fazer correspondência",
|
||||
"RestoreButton": "Restaurar Padrões",
|
||||
"RestoreDefault": "Restaurar ao padrão",
|
||||
"RestoreDialogMessage": "Tem certeza de que deseja restaurar para as configurações padrão?",
|
||||
@@ -1258,6 +1265,7 @@
|
||||
"Selected": "Selecionado",
|
||||
"Selection": "Selecionável",
|
||||
"Selector": "Seletor",
|
||||
"SelectorPlaceholder": "Insira o localizador de elemento (suporta CSS/XPath)",
|
||||
"Send": "Enviar",
|
||||
"SendVerificationCode": "Enviar código de verificação",
|
||||
"SerialNumber": "Número de Série",
|
||||
@@ -1307,6 +1315,7 @@
|
||||
"Skipped": "Ignorado",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Autenticação Slack",
|
||||
"Sleep": "Esperar",
|
||||
"Source": "Origem",
|
||||
"SourceIP": "Endereço de origem",
|
||||
"SourcePort": "Porta de Origem",
|
||||
@@ -1320,6 +1329,7 @@
|
||||
"State": "Status",
|
||||
"StateClosed": "Desativado",
|
||||
"Status": "Status",
|
||||
"StatusCode": "Código de status",
|
||||
"StatusGreen": "Estado recente é bom",
|
||||
"StatusRed": "A última tarefa falhou",
|
||||
"StatusYellow": "Recentemente houve falhas na execução",
|
||||
@@ -1558,6 +1568,7 @@
|
||||
"UsersAndUserGroups": "Usuário/Grupo de usuários",
|
||||
"UsersTotal": " Total de usuários ",
|
||||
"Valid": "Válido",
|
||||
"Value": "Valor",
|
||||
"Variable": "Variáveis",
|
||||
"VariableHelpText": "Você pode usar {{ key }} no comando para ler a variável integrada",
|
||||
"VariableName": " Nome da variável ",
|
||||
@@ -1582,6 +1593,8 @@
|
||||
"VisitTimeDistribution": "Distribuição dos períodos de acesso",
|
||||
"Visits": "Visualizações",
|
||||
"Volcengine": "Motor Volcano",
|
||||
"WaitForSelector": "Esperar o elemento",
|
||||
"WaitForURL": "Esperar a URL",
|
||||
"Warning": "Aviso",
|
||||
"Watermark": "Marca d'água",
|
||||
"WatermarkVariableHelpText": "Você pode usar ${key} no conteúdo da marca d'água personalizada para ler variáveis internas como: lista de senhas fracas, hora atual, se a página de gestão está disponível, ID do usuário, nome do usuário, endereço do ativo, nome de usuário, marca d'água, ID do ativo, nome do ativo.",
|
||||
@@ -1626,4 +1639,4 @@
|
||||
"setVariable": "Parâmetros de configuração",
|
||||
"userId": "ID do usuário",
|
||||
"userName": "Usuário"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +310,9 @@
|
||||
"ChatAI": "Chat AI",
|
||||
"ChatHello": "Здравствуйте! Чем я могу вам помочь?",
|
||||
"ChdirHelpText": "По умолчанию каталогом выполнения является домашний каталог пользователя",
|
||||
"Check": "Проверять",
|
||||
"CheckAssetsAmount": "Проверка количества активов",
|
||||
"CheckResponse": "Проверить ответ",
|
||||
"CheckViewAcceptor": "Нажмите, чтобы просмотреть одобряющего",
|
||||
"CleanHelpText": "Периодическая очистка задач будет выполняться каждый день в 2 часа ночи, данные после очистки невозможно восстановить.",
|
||||
"Cleaning": "Регулярная очистка",
|
||||
@@ -320,6 +322,7 @@
|
||||
"ClearSecret": "Очистить секрет",
|
||||
"ClearSelection": "Очистить выбор",
|
||||
"ClearSuccessMsg": "Очистка выполнена успешно",
|
||||
"Click": "Кликнуть",
|
||||
"ClickCopy": "Нажмите для копирования",
|
||||
"ClientCertificate": "Сертификат клиента",
|
||||
"Clipboard": "Буфер обмена",
|
||||
@@ -814,6 +817,7 @@
|
||||
"LoginTitleTip": "Примечание: будет отображаться на странице входа SSH пользователей корпоративной версии KoKo (например: Добро пожаловать в JumpServer)",
|
||||
"LoginUserRanking": "Рейтинг входящих пользователей",
|
||||
"LoginUserToday": "Входившие сегодня пользователи",
|
||||
"LoginUsername": "Имя пользователя для входа",
|
||||
"LoginUsers": "Активные УЗ",
|
||||
"LogoIndexTip": "Примечание: будет отображена в верхнем левом углу страницы управления (рекомендуемый размер изображения: 185px*55px)",
|
||||
"LogoLogoutTip": "Примечание: будет отображаться на веб-терминале пользователей корпоративной версии (рекомендуемый размер изображения: 82px*82px)",
|
||||
@@ -1157,6 +1161,9 @@
|
||||
"ResolveSelected": "Решение установлено",
|
||||
"Resource": "Ресурсы",
|
||||
"ResourceType": "Тип ресурса",
|
||||
"Response": "Тело ответа",
|
||||
"ResponseExpression": "Выражение тела ответа",
|
||||
"ResponseExpressionTip": "Для тела ответа {count: 1} выражение можно записать как count=1 для совпадения\nДля тела ответа {data: [a, b]} выражение можно записать как data.length=2 для совпадения\nДля тела ответа {data: [{username: admin}]} выражение можно записать как data.0.username=admin для совпадения",
|
||||
"RestoreButton": "Восстановить",
|
||||
"RestoreDefault": "Сброс на настройки по умолчанию",
|
||||
"RestoreDialogMessage": "Вы уверены, что хотите восстановить настройки по умолчанию?",
|
||||
@@ -1259,6 +1266,7 @@
|
||||
"Selected": "Выбрано",
|
||||
"Selection": "Выбор",
|
||||
"Selector": "Селектор",
|
||||
"SelectorPlaceholder": "Введите локатор элемента (поддерживает CSS/XPath)",
|
||||
"Send": "Отправить",
|
||||
"SendVerificationCode": "Отправить код подтверждения",
|
||||
"SerialNumber": "Серийный номер",
|
||||
@@ -1308,6 +1316,7 @@
|
||||
"Skipped": "Пропущено",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Slack OAuth",
|
||||
"Sleep": "Ждать",
|
||||
"Source": "Источник",
|
||||
"SourceIP": "Исходный адрес",
|
||||
"SourcePort": "Исходный порт",
|
||||
@@ -1321,6 +1330,7 @@
|
||||
"State": "Состояние",
|
||||
"StateClosed": "Закрыто",
|
||||
"Status": "Статус",
|
||||
"StatusCode": "Код статуса",
|
||||
"StatusGreen": "Недавнее состояние - хорошее",
|
||||
"StatusRed": "Не удалось выполнить последнюю задачу",
|
||||
"StatusYellow": "Недавние неудачные выполнения",
|
||||
@@ -1559,6 +1569,7 @@
|
||||
"UsersAndUserGroups": "Пользователи/группы",
|
||||
"UsersTotal": "Всего пользователей",
|
||||
"Valid": "Действительно",
|
||||
"Value": "Значение",
|
||||
"Variable": "Переменная",
|
||||
"VariableHelpText": "Вы можете использовать {{ key }} в команде для чтения встроенных переменных",
|
||||
"VariableName": "Имя переменной",
|
||||
@@ -1583,6 +1594,8 @@
|
||||
"VisitTimeDistribution": "Посещение во временные интервалы",
|
||||
"Visits": "Посещения",
|
||||
"Volcengine": "Volcengine",
|
||||
"WaitForSelector": "Ждать элемента",
|
||||
"WaitForURL": "Ждать URL",
|
||||
"Warning": "Предупреждение",
|
||||
"Watermark": "Водяной знак",
|
||||
"WatermarkVariableHelpText": "Вы можете использовать ${key} для чтения встроенных переменных в содержимом водяного знака",
|
||||
@@ -1627,4 +1640,4 @@
|
||||
"setVariable": "Задать переменную",
|
||||
"userId": "ID пользователя",
|
||||
"userName": "Имя пользовател"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +309,9 @@
|
||||
"ChatAI": "智能问答",
|
||||
"ChatHello": "你好!我能为你提供什么帮助?",
|
||||
"ChdirHelpText": "默认执行目录为执行用户的 home 目录",
|
||||
"Check": "检查",
|
||||
"CheckAssetsAmount": "校对资产数量",
|
||||
"CheckResponse": "检查响应体",
|
||||
"CheckViewAcceptor": "点击查看受理人",
|
||||
"CleanHelpText": "定期清理任务会在 每天凌晨 2 点执行, 清理后的数据将无法恢复",
|
||||
"Cleaning": "定期清理",
|
||||
@@ -319,6 +321,7 @@
|
||||
"ClearSecret": "清除密文",
|
||||
"ClearSelection": "清空选择",
|
||||
"ClearSuccessMsg": "清除成功",
|
||||
"Click": "点击",
|
||||
"ClickCopy": "点击复制",
|
||||
"ClientCertificate": "客户端证书",
|
||||
"Clipboard": "剪贴板",
|
||||
@@ -813,6 +816,7 @@
|
||||
"LoginTitleTip": "提示:将会显示在企业版用户 SSH 登录 KoKo 登录页面(eg: 欢迎使用JumpServer开源堡垒机)",
|
||||
"LoginUserRanking": "登录账号排名",
|
||||
"LoginUserToday": "今日登录用户数",
|
||||
"LoginUsername": "登录用户名",
|
||||
"LoginUsers": "活跃账号",
|
||||
"LogoIndexTip": "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)",
|
||||
"LogoLogoutTip": "提示:将会显示在企业版用户的 Web 终端页面(建议图片大小为:82px*82px)",
|
||||
@@ -1156,6 +1160,9 @@
|
||||
"ResolveSelected": "解决所选",
|
||||
"Resource": "资源",
|
||||
"ResourceType": "资源类型",
|
||||
"Response": "响应体",
|
||||
"ResponseExpression": "响应体表达式",
|
||||
"ResponseExpressionTip": "响应体 {count: 1} 表达式可写为 count=1 来匹配\n响应体 {data: [a, b]} 表达式可写为 data.length=2 来匹配\n响应体 {data: [{username: admin}]} 表达式可写为 data.0.username=admin 来匹配",
|
||||
"RestoreButton": "恢复默认",
|
||||
"RestoreDefault": "恢复默认",
|
||||
"RestoreDialogMessage": "您确定要恢复默认初始化吗?",
|
||||
@@ -1258,6 +1265,7 @@
|
||||
"Selected": "已选择",
|
||||
"Selection": "可选择",
|
||||
"Selector": "选择器",
|
||||
"SelectorPlaceholder": "输入元素定位符(支持 CSS/XPath)",
|
||||
"Send": "发送",
|
||||
"SendVerificationCode": "发送验证码",
|
||||
"SerialNumber": "序列号",
|
||||
@@ -1307,6 +1315,7 @@
|
||||
"Skipped": "已跳过",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Slack 认证",
|
||||
"Sleep": "等待",
|
||||
"Source": "来源",
|
||||
"SourceIP": "源地址",
|
||||
"SourcePort": "源端口",
|
||||
@@ -1320,6 +1329,7 @@
|
||||
"State": "状态",
|
||||
"StateClosed": "已关闭",
|
||||
"Status": "状态",
|
||||
"StatusCode": "状态码",
|
||||
"StatusGreen": "近期状态良好",
|
||||
"StatusRed": "上一次任务执行失败",
|
||||
"StatusYellow": "近期存在在执行失败",
|
||||
@@ -1558,6 +1568,7 @@
|
||||
"UsersAndUserGroups": "用户/用户组",
|
||||
"UsersTotal": "用户总数",
|
||||
"Valid": "有效",
|
||||
"Value": "值",
|
||||
"Variable": "变量",
|
||||
"VariableHelpText": "您可以在命令中使用 {{ key }} 读取内置变量",
|
||||
"VariableName": "变量名",
|
||||
@@ -1582,6 +1593,8 @@
|
||||
"VisitTimeDistribution": "访问时段分布",
|
||||
"Visits": "访问量",
|
||||
"Volcengine": "火山引擎",
|
||||
"WaitForSelector": "等待元素",
|
||||
"WaitForURL": "等待 URL",
|
||||
"Warning": "警告",
|
||||
"Watermark": "水印",
|
||||
"WatermarkVariableHelpText": "您可以在自定义水印内容中使用 ${key} 读取内置变量",
|
||||
|
||||
@@ -313,7 +313,9 @@
|
||||
"ChatAI": "智慧問答",
|
||||
"ChatHello": "你好!我能為你提供什麼幫助?",
|
||||
"ChdirHelpText": "默認執行目錄為執行用戶的 home 目錄",
|
||||
"Check": "檢查",
|
||||
"CheckAssetsAmount": "校對資產數量",
|
||||
"CheckResponse": "檢查回應體",
|
||||
"CheckViewAcceptor": "點擊查看受理人",
|
||||
"CleanHelpText": "定期清理任務會在 每天凌晨 2 點執行, 清理後的數據將無法恢復",
|
||||
"Cleaning": "定期清理",
|
||||
@@ -323,6 +325,7 @@
|
||||
"ClearSecret": "清除密文",
|
||||
"ClearSelection": "清空選擇",
|
||||
"ClearSuccessMsg": "清除成功",
|
||||
"Click": "點擊",
|
||||
"ClickCopy": "點擊複製",
|
||||
"ClientCertificate": "用戶端證書",
|
||||
"Clipboard": "剪貼簿",
|
||||
@@ -817,6 +820,7 @@
|
||||
"LoginTitleTip": "提示:將會顯示在企業版使用者 SSH 登入 KoKo 登入頁面(eg: 歡迎使用JumpServer開源堡壘機)",
|
||||
"LoginUserRanking": "會話用戶排名",
|
||||
"LoginUserToday": "今日登入用戶數",
|
||||
"LoginUsername": "登入使用者名稱",
|
||||
"LoginUsers": "活躍帳號",
|
||||
"LogoIndexTip": "提示:將會顯示在管理頁面左上方(建議圖片大小為: 185px*55px)",
|
||||
"LogoLogoutTip": "提示:將會顯示在企業版使用者的 Web 終端頁面(建議圖片大小為:82px*82px)",
|
||||
@@ -1160,6 +1164,9 @@
|
||||
"ResolveSelected": "解決選定",
|
||||
"Resource": "資源",
|
||||
"ResourceType": "資源類型",
|
||||
"Response": "回應體",
|
||||
"ResponseExpression": "回應體運算式",
|
||||
"ResponseExpressionTip": "回應體 {count: 1} 運算式可寫為 count=1 來比對\n 回應體 {data: [a, b]} 運算式可寫為 data.length=2 來比對\n 回應體 {data: [{username: admin}]} 運算式可寫為 data.0.username=admin 來比對",
|
||||
"RestoreButton": "恢復默認",
|
||||
"RestoreDefault": "恢復默認",
|
||||
"RestoreDialogMessage": "您確定要恢復成預設初始化嗎?",
|
||||
@@ -1262,6 +1269,7 @@
|
||||
"Selected": "已選擇",
|
||||
"Selection": "可選擇",
|
||||
"Selector": "選擇器",
|
||||
"SelectorPlaceholder": "輸入元素定位器(支援 CSS/XPath)",
|
||||
"Send": "發送",
|
||||
"SendVerificationCode": "發送驗證碼",
|
||||
"SerialNumber": "序號",
|
||||
@@ -1311,6 +1319,7 @@
|
||||
"Skipped": "已跳過",
|
||||
"Slack": "Slack",
|
||||
"SlackOAuth": "Slack 認證",
|
||||
"Sleep": "等待",
|
||||
"Source": "來源",
|
||||
"SourceIP": "源地址",
|
||||
"SourcePort": "源埠",
|
||||
@@ -1324,6 +1333,7 @@
|
||||
"State": "狀態",
|
||||
"StateClosed": "已關閉",
|
||||
"Status": "狀態",
|
||||
"StatusCode": "狀態碼",
|
||||
"StatusGreen": "近期狀態良好",
|
||||
"StatusRed": "上一次任務執行失敗",
|
||||
"StatusYellow": "近期存在在執行失敗",
|
||||
@@ -1562,6 +1572,7 @@
|
||||
"UsersAndUserGroups": "User/User Group",
|
||||
"UsersTotal": "用戶總數",
|
||||
"Valid": "有效",
|
||||
"Value": "值",
|
||||
"Variable": "變數",
|
||||
"VariableHelpText": "您可以在命令中使用 {{ key }} 讀取內建變數",
|
||||
"VariableName": "變數名",
|
||||
@@ -1586,6 +1597,8 @@
|
||||
"VisitTimeDistribution": "訪問時段分布",
|
||||
"Visits": "訪問量",
|
||||
"Volcengine": "火山引擎",
|
||||
"WaitForSelector": "等待元素",
|
||||
"WaitForURL": "等待 URL",
|
||||
"Warning": "警告",
|
||||
"Watermark": "水印",
|
||||
"WatermarkVariableHelpText": "您可以在自訂水印內容中使用 ${key} 來讀取內建變數",
|
||||
@@ -1630,4 +1643,4 @@
|
||||
"setVariable": "設置參數",
|
||||
"userId": "用戶ID",
|
||||
"userName": "用戶名"
|
||||
}
|
||||
}
|
||||
|
||||
78
apps/libs/ansible/modules/website_ping.py
Normal file
78
apps/libs/ansible/modules/website_ping.py
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: website_ping
|
||||
short_description: Use Playwright to simulate a browser for connectivity testing
|
||||
description:
|
||||
- Use Playwright to simulate a browser for connectivity testing
|
||||
options:
|
||||
login_host:
|
||||
description: The target host to connect.
|
||||
type: str
|
||||
required: True
|
||||
login_user:
|
||||
description: The username for the website connection.
|
||||
type: str
|
||||
required: True
|
||||
login_password:
|
||||
description: The password for the website connection.
|
||||
type: str
|
||||
required: True
|
||||
no_log: True
|
||||
timeout:
|
||||
description: Timeout period for step execution.
|
||||
type: int
|
||||
required: False
|
||||
steps:
|
||||
description: Meta-information for browser-emulated actions.
|
||||
type: list
|
||||
required: False
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Ping asset server using Playwright.
|
||||
website_ping:
|
||||
login_host: 127.0.0.1
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
is_available:
|
||||
description: Indicate whether the target server is reachable via Playwright.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from libs.ansible.modules_utils.web_common import WebAutomationHandler, common_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
options = common_argument_spec()
|
||||
module = AnsibleModule(argument_spec=options, supports_check_mode=False)
|
||||
extra_infos = {
|
||||
'login_username': module.params['login_user'],
|
||||
'login_password': module.params['login_password'],
|
||||
}
|
||||
handler = WebAutomationHandler(
|
||||
address=module.params['login_host'],
|
||||
timeout=module.params['timeout'],
|
||||
load_state=module.params['load_state'],
|
||||
extra_infos=extra_infos,
|
||||
)
|
||||
try:
|
||||
handler.execute(steps=module.params['steps'])
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
result = {'changed': False, 'is_available': True}
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
94
apps/libs/ansible/modules/website_user.py
Normal file
94
apps/libs/ansible/modules/website_user.py
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: website_user
|
||||
short_description: Use Playwright to simulate browser operations for users.
|
||||
description:
|
||||
- Use Playwright to simulate browser operations for users, such as password change.
|
||||
options:
|
||||
login_host:
|
||||
description: The target host to connect.
|
||||
type: str
|
||||
required: True
|
||||
login_user:
|
||||
description: The username for the website connection.
|
||||
type: str
|
||||
required: True
|
||||
login_password:
|
||||
description: The password for the website connection.
|
||||
type: str
|
||||
required: True
|
||||
no_log: True
|
||||
name:
|
||||
description: The name of the user to change password.
|
||||
required: true
|
||||
aliases: [user]
|
||||
type: str
|
||||
password:
|
||||
description: The password to use for the user.
|
||||
type: str
|
||||
aliases: [pass]
|
||||
timeout:
|
||||
description: Timeout period for step execution.
|
||||
type: int
|
||||
required: False
|
||||
steps:
|
||||
description: Meta-information for browser-emulated actions.
|
||||
type: list
|
||||
required: False
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Change password using Playwright.
|
||||
website_user:
|
||||
login_host: 127.0.0.1
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
failed:
|
||||
description: Verify whether the task simulated and operated via Playwright has succeeded.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from libs.ansible.modules_utils.web_common import WebAutomationHandler, common_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = common_argument_spec()
|
||||
argument_spec.update(
|
||||
name=dict(required=True, aliases=['user']),
|
||||
password=dict(aliases=['pass'], no_log=True),
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
|
||||
|
||||
extra_infos = {
|
||||
'login_username': module.params['login_user'],
|
||||
'login_password': module.params['login_password'],
|
||||
'username': module.params['name'],
|
||||
'password': module.params['password'],
|
||||
}
|
||||
handler = WebAutomationHandler(
|
||||
address=module.params['login_host'],
|
||||
timeout=module.params['timeout'],
|
||||
load_state=module.params['load_state'],
|
||||
extra_infos=extra_infos,
|
||||
)
|
||||
try:
|
||||
handler.execute(steps=module.params['steps'])
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
result = {'changed': True}
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
245
apps/libs/ansible/modules_utils/web_common.py
Normal file
245
apps/libs/ansible/modules_utils/web_common.py
Normal file
@@ -0,0 +1,245 @@
|
||||
import time
|
||||
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
from playwright.sync_api import sync_playwright, TimeoutError
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def common_argument_spec():
|
||||
options = dict(
|
||||
login_host=dict(type='str', required=False, default='localhost'),
|
||||
login_user=dict(type='str', required=False),
|
||||
login_password=dict(type='str', required=False, no_log=True),
|
||||
steps=dict(type='list', required=False, default=[]),
|
||||
load_state=dict(type='str', required=False, default='load'),
|
||||
timeout=dict(type='int', required=False, default=10000),
|
||||
)
|
||||
return options
|
||||
|
||||
|
||||
class WebAutomationHandler(object):
|
||||
def __init__(self, address, load_state=None, timeout=10000, extra_infos=None):
|
||||
self._response_mapping = {}
|
||||
self._context = None
|
||||
self._extra_infos = extra_infos or {}
|
||||
|
||||
self.address = address
|
||||
self.load_state = load_state or 'load'
|
||||
self.timeout = timeout
|
||||
|
||||
def _iframe(self, selector):
|
||||
if not selector:
|
||||
return
|
||||
|
||||
if selector.startswith(('id=', 'name=')):
|
||||
key, value = selector.split('=', 1)
|
||||
frame = self._context.frame(name=value)
|
||||
else:
|
||||
iframe_elem = self._context.wait_for_selector(selector, timeout=self.timeout)
|
||||
frame = iframe_elem.content_frame()
|
||||
|
||||
if not frame:
|
||||
raise RuntimeError(f"Iframe not found: {selector}")
|
||||
frame.wait_for_selector("body", timeout=self.timeout)
|
||||
self._context = frame
|
||||
|
||||
def _input(self, selector, value):
|
||||
if not selector:
|
||||
return
|
||||
|
||||
elem = self._context.wait_for_selector(selector, timeout=self.timeout)
|
||||
elem.fill(value)
|
||||
|
||||
def _click(self, selector):
|
||||
if not selector:
|
||||
return
|
||||
|
||||
elem = self._context.wait_for_selector(selector, timeout=self.timeout)
|
||||
elem.scroll_into_view_if_needed()
|
||||
elem.click(timeout=self.timeout)
|
||||
|
||||
def __combine_url(self, url_path):
|
||||
if not url_path:
|
||||
return self.address
|
||||
|
||||
parsed = urlparse(url_path)
|
||||
if parsed.netloc:
|
||||
return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
|
||||
else:
|
||||
re_parsed = urlparse(urljoin(self.address, url_path))
|
||||
return f"{re_parsed.scheme}://{re_parsed.netloc}{re_parsed.path}"
|
||||
|
||||
def _check(self, config: dict):
|
||||
method = config.get('type')
|
||||
if not method:
|
||||
return
|
||||
|
||||
if method == 'wait_for_url':
|
||||
url = config.get('url', '')
|
||||
url = self.__combine_url(url)
|
||||
self._context.wait_for_url(url, timeout=self.timeout)
|
||||
elif method == 'wait_for_selector':
|
||||
selector = config.get('selector', '')
|
||||
self._context.wait_for_selector(selector, timeout=self.timeout)
|
||||
elif method == 'check_response':
|
||||
self._check_response(
|
||||
method=config.get('method'),
|
||||
url=config.get('url'),
|
||||
status_code=config.get('status_code'),
|
||||
body_expr=config.get('body_expr'),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def __get_nested_value(data, key_path):
|
||||
keys = key_path.split('.')
|
||||
current = data
|
||||
for key in keys:
|
||||
if isinstance(current, dict):
|
||||
current = current.get(key)
|
||||
elif isinstance(current, list) and key.isdigit():
|
||||
index = int(key)
|
||||
current = current[index] if 0 <= index < len(current) else None
|
||||
else:
|
||||
return None
|
||||
if current is None:
|
||||
return None
|
||||
return current
|
||||
|
||||
@staticmethod
|
||||
def __compare_values(actual, expected):
|
||||
if actual is None:
|
||||
return expected.lower() in ['none', 'null', '']
|
||||
|
||||
if isinstance(actual, str):
|
||||
return str(actual) == str(expected)
|
||||
|
||||
if isinstance(actual, (int, float)):
|
||||
try:
|
||||
return float(actual) == float(expected)
|
||||
except ValueError:
|
||||
return str(actual) == str(expected)
|
||||
|
||||
if isinstance(actual, bool):
|
||||
expected_lower = expected.lower()
|
||||
if expected_lower in ['true', '1', 'yes']:
|
||||
return actual is True
|
||||
elif expected_lower in ['false', '0', 'no']:
|
||||
return actual is False
|
||||
|
||||
if isinstance(actual, list) and expected.startswith('length='):
|
||||
expected_len = int(expected[7:])
|
||||
return len(actual) == expected_len
|
||||
|
||||
return str(actual) == str(expected)
|
||||
|
||||
def __validate_response(self, resp_data, body_expr):
|
||||
try:
|
||||
if '=' in body_expr:
|
||||
key, expected_value = body_expr.split('=', 1)
|
||||
elif ':' in body_expr:
|
||||
key, expected_value = body_expr.split(':', 1)
|
||||
else:
|
||||
return False
|
||||
|
||||
key = key.strip()
|
||||
expected_value = expected_value.strip()
|
||||
actual_value = self.__get_nested_value(resp_data, key)
|
||||
return self.__compare_values(actual_value, expected_value)
|
||||
except Exception: # noqa
|
||||
return False
|
||||
|
||||
def _check_response(self, method, url, status_code, body_expr):
|
||||
if not method:
|
||||
return False
|
||||
|
||||
timeout_seconds = self.timeout / 1000
|
||||
start_time = time.time()
|
||||
check_url = self.__combine_url(url)
|
||||
expected_status = None
|
||||
if status_code:
|
||||
try:
|
||||
expected_status = int(status_code)
|
||||
except ValueError:
|
||||
expected_status = None
|
||||
|
||||
while time.time() - start_time < timeout_seconds:
|
||||
self._context.wait_for_timeout(50)
|
||||
method = method.upper()
|
||||
|
||||
if method not in self._response_mapping:
|
||||
continue
|
||||
|
||||
url_mapping = self._response_mapping[method]
|
||||
if len(url_mapping) == 0:
|
||||
continue
|
||||
|
||||
url, response = url_mapping.popitem()
|
||||
if expected_status and response['status'] != expected_status:
|
||||
continue
|
||||
|
||||
if check_url and url != check_url:
|
||||
continue
|
||||
|
||||
if body_expr and not self.__validate_response(response['data'], body_expr):
|
||||
continue
|
||||
return
|
||||
|
||||
raise TimeoutError(
|
||||
f'Check response failed: method={method}, url={url}, status={status_code}'
|
||||
)
|
||||
|
||||
def _handle_response(self, response):
|
||||
if response.url.endswith(('.js', '.css')):
|
||||
return
|
||||
|
||||
method = response.request.method.upper()
|
||||
url = self.__combine_url(response.url)
|
||||
self._response_mapping.setdefault(method, {})
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
except Exception: # noqa
|
||||
data = {}
|
||||
response_data = {'status': response.status, 'data': data}
|
||||
self._response_mapping[method][url] = response_data
|
||||
|
||||
def execute(self, steps: list):
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
self._context = browser.new_page()
|
||||
|
||||
try:
|
||||
self._context.on('response', self._handle_response)
|
||||
self._context.goto(self.address, wait_until=self.load_state)
|
||||
|
||||
for step in steps:
|
||||
action_type = step.get('action')
|
||||
if not action_type:
|
||||
continue
|
||||
|
||||
config = step.get('config', {})
|
||||
if action_type == 'iframe':
|
||||
self._iframe(config['selector'])
|
||||
elif action_type == 'input':
|
||||
value = self._extra_infos.get(config['value'], config['value'])
|
||||
self._input(config['selector'], value)
|
||||
elif action_type == 'click':
|
||||
self._click(config['selector'])
|
||||
elif action_type == 'check':
|
||||
self._check(config)
|
||||
elif action_type == 'sleep':
|
||||
try:
|
||||
sleep_time = float(config['value'])
|
||||
except ValueError:
|
||||
sleep_time = 0
|
||||
self._context.wait_for_timeout(sleep_time * 1000)
|
||||
else:
|
||||
raise ValueError(f"Unsupported action type: {action_type}")
|
||||
return True
|
||||
except TimeoutError:
|
||||
raise TimeoutError(f'Task execution timeout when execute step: {step}')
|
||||
except Exception as e:
|
||||
raise Exception(f'Execute steps failed: {to_native(e)}')
|
||||
finally:
|
||||
browser.close()
|
||||
Reference in New Issue
Block a user