diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml
index 1ad366a98..df0e667aa 100644
--- a/.github/workflows/build-base-image.yml
+++ b/.github/workflows/build-base-image.yml
@@ -1,21 +1,33 @@
name: Build and Push Base Image
on:
- push:
+ pull_request:
branches:
- - 'pr*'
+ - 'dev'
+ - 'v*'
paths:
- - 'poetry.lock'
- - 'pyproject.toml'
- - 'Dockerfile-base'
+ - poetry.lock
+ - pyproject.toml
+ - Dockerfile-base
+ - package.json
+ - go.mod
+ - yarn.lock
+ - pom.xml
+ - install_deps.sh
+ - utils/clean_site_packages.sh
+ types:
+ - opened
+ - synchronize
+ - reopened
jobs:
build-and-push:
runs-on: ubuntu-latest
-
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.ref }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -55,6 +67,6 @@ jobs:
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add Dockerfile
git commit -m "perf: Update Dockerfile with new base image tag"
- git push
+ git push origin ${{ github.event.pull_request.head.ref }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/check-compilemessages.yml b/.github/workflows/check-compilemessages.yml
new file mode 100644
index 000000000..fd032b99b
--- /dev/null
+++ b/.github/workflows/check-compilemessages.yml
@@ -0,0 +1,31 @@
+name: Check I18n files CompileMessages
+
+on:
+ pull_request:
+ branches:
+ - 'dev'
+ paths:
+ - 'apps/i18n/core/**/*.po'
+ types:
+ - opened
+ - synchronize
+ - reopened
+jobs:
+ compile-messages-check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build and check compilemessages
+ uses: docker/build-push-action@v6
+ with:
+ platforms: linux/amd64
+ push: false
+ file: Dockerfile
+ target: stage-build
+ tags: jumpserver/core:stage-build
diff --git a/Dockerfile b/Dockerfile
index e487f250e..b8ffe29e4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM jumpserver/core-base:20240808_054051 AS stage-build
+FROM jumpserver/core-base:20240919_024156 AS stage-build
ARG VERSION
diff --git a/Dockerfile-base b/Dockerfile-base
index 067cad40e..3574cadb1 100644
--- a/Dockerfile-base
+++ b/Dockerfile-base
@@ -43,12 +43,18 @@ RUN set -ex \
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.org/simple
+ENV ANSIBLE_COLLECTIONS_PATHS=/opt/py3/lib/python3.11/site-packages/ansible_collections
+
RUN --mount=type=cache,target=/root/.cache,sharing=locked,id=core \
--mount=type=bind,source=poetry.lock,target=poetry.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
+ --mount=type=bind,source=utils/clean_site_packages.sh,target=clean_site_packages.sh \
+ --mount=type=bind,source=requirements/collections.yml,target=collections.yml \
set -ex \
&& python3 -m venv /opt/py3 \
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& . /opt/py3/bin/activate \
- && poetry install --only main
+ && poetry install --only main \
+ && ansible-galaxy collection install -r collections.yml --force --ignore-certs \
+ && bash clean_site_packages.sh
diff --git a/README.md b/README.md
index 9c10316ed..ba092cb21 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,8 @@ Access JumpServer in your browser at `http://your-jumpserver-ip/`
- Username: `admin`
- Password: `ChangeMe`
+[![JumpServer Quickstart](https://github.com/user-attachments/assets/0f32f52b-9935-485e-8534-336c63389612)](https://www.youtube.com/watch?v=UlGYRbKrpgY "JumpServer Quickstart")
+
## Screenshots
diff --git a/apps/accounts/automations/change_secret/database/mysql/main.yml b/apps/accounts/automations/change_secret/database/mysql/main.yml
index f36eff171..0d8452a4a 100644
--- a/apps/accounts/automations/change_secret/database/mysql/main.yml
+++ b/apps/accounts/automations/change_secret/database/mysql/main.yml
@@ -4,6 +4,9 @@
ansible_python_interpreter: /opt/py3/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}"
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test MySQL connection
@@ -13,9 +16,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
register: db_info
@@ -30,9 +33,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
@@ -47,7 +50,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
diff --git a/apps/accounts/automations/change_secret/database/postgresql/main.yml b/apps/accounts/automations/change_secret/database/postgresql/main.yml
index 9d55a898e..34dea98b7 100644
--- a/apps/accounts/automations/change_secret/database/postgresql/main.yml
+++ b/apps/accounts/automations/change_secret/database/postgresql/main.yml
@@ -2,6 +2,10 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
+ check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test PostgreSQL connection
@@ -11,6 +15,10 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available
@@ -28,6 +36,10 @@
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
@@ -39,3 +51,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
diff --git a/apps/accounts/automations/change_secret/host/aix/main.yml b/apps/accounts/automations/change_secret/host/aix/main.yml
index 761a9c2c1..c61029d74 100644
--- a/apps/accounts/automations/change_secret/host/aix/main.yml
+++ b/apps/accounts/automations/change_secret/host/aix/main.yml
@@ -14,27 +14,15 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
- shell: "{{ params.shell }}"
- home: "{{ params.home | default('/home/' + account.username, true) }}"
- groups: "{{ params.groups }}"
+ uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
+ shell: "{{ params.shell if params.shell | length > 0 else omit }}"
+ home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
+ groups: "{{ params.groups if params.groups | length > 0 else omit }}"
+ append: yes
expires: -1
state: present
when: user_info.failed
- - name: "Add {{ account.username }} group"
- ansible.builtin.group:
- name: "{{ account.username }}"
- state: present
- when: user_info.failed
-
- - name: "Add {{ account.username }} user to group"
- ansible.builtin.user:
- name: "{{ account.username }}"
- groups: "{{ params.groups }}"
- when:
- - user_info.failed
- - params.groups
-
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
@@ -54,14 +42,40 @@
ignore_errors: true
when: account.secret_type == "password"
- - name: remove jumpserver ssh key
+ - name: "Get home directory for {{ account.username }}"
+ ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
+ register: home_dir
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Check if home directory exists for {{ account.username }}"
+ ansible.builtin.stat:
+ path: "{{ home_dir.stdout.strip() }}"
+ register: home_dir_stat
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Ensure {{ account.username }} home directory exists"
+ ansible.builtin.file:
+ path: "{{ home_dir.stdout.strip() }}"
+ state: directory
+ owner: "{{ account.username }}"
+ group: "{{ account.username }}"
+ mode: '0750'
+ when:
+ - account.secret_type == "ssh_key"
+ - home_dir_stat.stat.exists == false
+ ignore_errors: yes
+
+ - name: Remove jumpserver ssh key
ansible.builtin.lineinfile:
- dest: "{{ ssh_params.dest }}"
+ dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
+ ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -79,7 +93,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
@@ -95,7 +109,7 @@
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost
diff --git a/apps/accounts/automations/change_secret/host/aix/manifest.yml b/apps/accounts/automations/change_secret/host/aix/manifest.yml
index 145a4bf26..0d9e6e83a 100644
--- a/apps/accounts/automations/change_secret/host/aix/manifest.yml
+++ b/apps/accounts/automations/change_secret/host/aix/manifest.yml
@@ -34,6 +34,12 @@ params:
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
+ - name: uid
+ type: str
+ label: "{{ 'Params uid label' | trans }}"
+ default: ''
+ help_text: "{{ 'Params uid help text' | trans }}"
+
i18n:
AIX account change secret:
zh: '使用 Ansible 模块 user 执行账号改密 (DES)'
@@ -60,6 +66,11 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
+ Params uid help text:
+ zh: '请输入用户ID'
+ ja: 'ユーザーIDを入力してください'
+ en: 'Please enter the user ID'
+
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
@@ -75,3 +86,7 @@ i18n:
ja: 'グループ'
en: 'Groups'
+ Params uid label:
+ zh: '用户ID'
+ ja: 'ユーザーID'
+ en: 'User ID'
diff --git a/apps/accounts/automations/change_secret/host/posix/main.yml b/apps/accounts/automations/change_secret/host/posix/main.yml
index d166de81f..e36ecdd33 100644
--- a/apps/accounts/automations/change_secret/host/posix/main.yml
+++ b/apps/accounts/automations/change_secret/host/posix/main.yml
@@ -14,27 +14,15 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
- shell: "{{ params.shell }}"
- home: "{{ params.home | default('/home/' + account.username, true) }}"
- groups: "{{ params.groups }}"
+ uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
+ shell: "{{ params.shell if params.shell | length > 0 else omit }}"
+ home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
+ groups: "{{ params.groups if params.groups | length > 0 else omit }}"
+ append: yes
expires: -1
state: present
when: user_info.failed
- - name: "Add {{ account.username }} group"
- ansible.builtin.group:
- name: "{{ account.username }}"
- state: present
- when: user_info.failed
-
- - name: "Add {{ account.username }} user to group"
- ansible.builtin.user:
- name: "{{ account.username }}"
- groups: "{{ params.groups }}"
- when:
- - user_info.failed
- - params.groups
-
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
@@ -54,14 +42,40 @@
ignore_errors: true
when: account.secret_type == "password"
- - name: remove jumpserver ssh key
+ - name: "Get home directory for {{ account.username }}"
+ ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
+ register: home_dir
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Check if home directory exists for {{ account.username }}"
+ ansible.builtin.stat:
+ path: "{{ home_dir.stdout.strip() }}"
+ register: home_dir_stat
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Ensure {{ account.username }} home directory exists"
+ ansible.builtin.file:
+ path: "{{ home_dir.stdout.strip() }}"
+ state: directory
+ owner: "{{ account.username }}"
+ group: "{{ account.username }}"
+ mode: '0750'
+ when:
+ - account.secret_type == "ssh_key"
+ - home_dir_stat.stat.exists == false
+ ignore_errors: yes
+
+ - name: Remove jumpserver ssh key
ansible.builtin.lineinfile:
- dest: "{{ ssh_params.dest }}"
+ dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
+ ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -79,7 +93,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
@@ -95,7 +109,7 @@
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost
diff --git a/apps/accounts/automations/change_secret/host/posix/manifest.yml b/apps/accounts/automations/change_secret/host/posix/manifest.yml
index 9ab86cdb9..618f509b8 100644
--- a/apps/accounts/automations/change_secret/host/posix/manifest.yml
+++ b/apps/accounts/automations/change_secret/host/posix/manifest.yml
@@ -36,6 +36,12 @@ params:
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
+ - name: uid
+ type: str
+ label: "{{ 'Params uid label' | trans }}"
+ default: ''
+ help_text: "{{ 'Params uid help text' | trans }}"
+
i18n:
Posix account change secret:
zh: '使用 Ansible 模块 user 执行账号改密 (SHA512)'
@@ -62,6 +68,11 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
+ Params uid help text:
+ zh: '请输入用户ID'
+ ja: 'ユーザーIDを入力してください'
+ en: 'Please enter the user ID'
+
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
@@ -77,3 +88,7 @@ i18n:
ja: 'グループ'
en: 'Groups'
+ Params uid label:
+ zh: '用户ID'
+ ja: 'ユーザーID'
+ en: 'User ID'
diff --git a/apps/accounts/automations/change_secret/host/windows_rdp_verify/main.yml b/apps/accounts/automations/change_secret/host/windows_rdp_verify/main.yml
index e1ced1359..31da190ef 100644
--- a/apps/accounts/automations/change_secret/host/windows_rdp_verify/main.yml
+++ b/apps/accounts/automations/change_secret/host/windows_rdp_verify/main.yml
@@ -25,11 +25,11 @@
- name: Verify password (pyfreerdp)
rdp_ping:
- login_host: "{{ jms_asset.address }}"
+ login_host: "{{ jms_asset.origin_address }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
- login_private_key_path: "{{ account.private_key_path }}"
+ gateway_args: "{{ jms_gateway | default(None) }}"
when: account.secret_type == "password"
delegate_to: localhost
diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py
index 024282d82..0b56cc5c7 100644
--- a/apps/accounts/automations/change_secret/manager.py
+++ b/apps/accounts/automations/change_secret/manager.py
@@ -50,9 +50,6 @@ class ChangeSecretManager(AccountBasePlaybookManager):
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
- username = account.username
- path = f'/{username}' if username == "root" else f'/home/{username}'
- kwargs['dest'] = f'{path}/.ssh/authorized_keys'
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
return kwargs
@@ -130,6 +127,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
+ comment=f'{account.username}@{asset.address}'
)
records.append(recorder)
else:
diff --git a/apps/accounts/automations/gather_accounts/database/mysql/main.yml b/apps/accounts/automations/gather_accounts/database/mysql/main.yml
index e36925209..37e446502 100644
--- a/apps/accounts/automations/gather_accounts/database/mysql/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/mysql/main.yml
@@ -3,6 +3,9 @@
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -12,9 +15,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: users
register: db_info
diff --git a/apps/accounts/automations/gather_accounts/database/postgresql/main.yml b/apps/accounts/automations/gather_accounts/database/postgresql/main.yml
index ce5ed181c..0d2093aab 100644
--- a/apps/accounts/automations/gather_accounts/database/postgresql/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/postgresql/main.yml
@@ -2,6 +2,10 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
+ check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -11,6 +15,10 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
filter: "roles"
register: db_info
diff --git a/apps/accounts/automations/gather_accounts/manager.py b/apps/accounts/automations/gather_accounts/manager.py
index f0610e331..e97d78cd0 100644
--- a/apps/accounts/automations/gather_accounts/manager.py
+++ b/apps/accounts/automations/gather_accounts/manager.py
@@ -95,12 +95,14 @@ class GatherAccountsManager(AccountBasePlaybookManager):
return None, None
users = User.objects.filter(id__in=recipients)
- if not users:
+ if not users.exists():
return users, None
asset_ids = self.asset_username_mapper.keys()
- assets = Asset.objects.filter(id__in=asset_ids)
+
+ assets = Asset.objects.filter(id__in=asset_ids).prefetch_related('accounts')
gather_accounts = GatheredAccount.objects.filter(asset_id__in=asset_ids, present=True)
+
asset_id_map = {str(asset.id): asset for asset in assets}
asset_id_username = list(assets.values_list('id', 'accounts__username'))
asset_id_username.extend(list(gather_accounts.values_list('asset_id', 'username')))
@@ -109,26 +111,24 @@ class GatherAccountsManager(AccountBasePlaybookManager):
for asset_id, username in asset_id_username:
system_asset_username_mapper[str(asset_id)].add(username)
- change_info = {}
+ change_info = defaultdict(dict)
for asset_id, usernames in self.asset_username_mapper.items():
system_usernames = system_asset_username_mapper.get(asset_id)
-
if not system_usernames:
continue
add_usernames = usernames - system_usernames
remove_usernames = system_usernames - usernames
- k = f'{asset_id_map[asset_id]}[{asset_id}]'
if not add_usernames and not remove_usernames:
continue
- change_info[k] = {
- 'add_usernames': ', '.join(add_usernames),
- 'remove_usernames': ', '.join(remove_usernames),
+ change_info[str(asset_id_map[asset_id])] = {
+ 'add_usernames': add_usernames,
+ 'remove_usernames': remove_usernames
}
- return users, change_info
+ return users, dict(change_info)
@staticmethod
def send_email_if_need(users, change_info):
diff --git a/apps/accounts/automations/push_account/database/mysql/main.yml b/apps/accounts/automations/push_account/database/mysql/main.yml
index f36eff171..0d8452a4a 100644
--- a/apps/accounts/automations/push_account/database/mysql/main.yml
+++ b/apps/accounts/automations/push_account/database/mysql/main.yml
@@ -4,6 +4,9 @@
ansible_python_interpreter: /opt/py3/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}"
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test MySQL connection
@@ -13,9 +16,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
register: db_info
@@ -30,9 +33,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
@@ -47,7 +50,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
diff --git a/apps/accounts/automations/push_account/database/postgresql/main.yml b/apps/accounts/automations/push_account/database/postgresql/main.yml
index 265401fbd..1a74d0cb0 100644
--- a/apps/accounts/automations/push_account/database/postgresql/main.yml
+++ b/apps/accounts/automations/push_account/database/postgresql/main.yml
@@ -2,6 +2,10 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
+ check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test PostgreSQL connection
@@ -11,6 +15,10 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available
@@ -28,6 +36,10 @@
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
@@ -40,6 +52,10 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
when:
- result is succeeded
- change_info is succeeded
diff --git a/apps/accounts/automations/push_account/host/aix/main.yml b/apps/accounts/automations/push_account/host/aix/main.yml
index 8c03c07f3..8e451fb83 100644
--- a/apps/accounts/automations/push_account/host/aix/main.yml
+++ b/apps/accounts/automations/push_account/host/aix/main.yml
@@ -14,27 +14,15 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
- shell: "{{ params.shell }}"
- home: "{{ params.home | default('/home/' + account.username, true) }}"
- groups: "{{ params.groups }}"
+ uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
+ shell: "{{ params.shell if params.shell | length > 0 else omit }}"
+ home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
+ groups: "{{ params.groups if params.groups | length > 0 else omit }}"
+ append: yes
expires: -1
state: present
when: user_info.failed
- - name: "Add {{ account.username }} group"
- ansible.builtin.group:
- name: "{{ account.username }}"
- state: present
- when: user_info.failed
-
- - name: "Add {{ account.username }} user to group"
- ansible.builtin.user:
- name: "{{ account.username }}"
- groups: "{{ params.groups }}"
- when:
- - user_info.failed
- - params.groups
-
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
@@ -54,14 +42,40 @@
ignore_errors: true
when: account.secret_type == "password"
- - name: remove jumpserver ssh key
+ - name: "Get home directory for {{ account.username }}"
+ ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
+ register: home_dir
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Check if home directory exists for {{ account.username }}"
+ ansible.builtin.stat:
+ path: "{{ home_dir.stdout.strip() }}"
+ register: home_dir_stat
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Ensure {{ account.username }} home directory exists"
+ ansible.builtin.file:
+ path: "{{ home_dir.stdout.strip() }}"
+ state: directory
+ owner: "{{ account.username }}"
+ group: "{{ account.username }}"
+ mode: '0750'
+ when:
+ - account.secret_type == "ssh_key"
+ - home_dir_stat.stat.exists == false
+ ignore_errors: yes
+
+ - name: Remove jumpserver ssh key
ansible.builtin.lineinfile:
- dest: "{{ ssh_params.dest }}"
+ dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
+ ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -79,7 +93,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
@@ -95,7 +109,7 @@
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost
diff --git a/apps/accounts/automations/push_account/host/aix/manifest.yml b/apps/accounts/automations/push_account/host/aix/manifest.yml
index f00a7435d..d4ba0924c 100644
--- a/apps/accounts/automations/push_account/host/aix/manifest.yml
+++ b/apps/accounts/automations/push_account/host/aix/manifest.yml
@@ -34,6 +34,12 @@ params:
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
+ - name: uid
+ type: str
+ label: "{{ 'Params uid label' | trans }}"
+ default: ''
+ help_text: "{{ 'Params uid help text' | trans }}"
+
i18n:
Aix account push:
zh: '使用 Ansible 模块 user 执行 Aix 账号推送 (DES)'
@@ -60,6 +66,11 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
+ Params uid help text:
+ zh: '请输入用户ID'
+ ja: 'ユーザーIDを入力してください'
+ en: 'Please enter the user ID'
+
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
@@ -75,3 +86,7 @@ i18n:
ja: 'グループ'
en: 'Groups'
+ Params uid label:
+ zh: '用户ID'
+ ja: 'ユーザーID'
+ en: 'User ID'
diff --git a/apps/accounts/automations/push_account/host/posix/main.yml b/apps/accounts/automations/push_account/host/posix/main.yml
index 8bc433f5a..537256a3d 100644
--- a/apps/accounts/automations/push_account/host/posix/main.yml
+++ b/apps/accounts/automations/push_account/host/posix/main.yml
@@ -14,27 +14,15 @@
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
- shell: "{{ params.shell }}"
- home: "{{ params.home | default('/home/' + account.username, true) }}"
- groups: "{{ params.groups }}"
+ uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
+ shell: "{{ params.shell if params.shell | length > 0 else omit }}"
+ home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
+ groups: "{{ params.groups if params.groups | length > 0 else omit }}"
+ append: yes
expires: -1
state: present
when: user_info.failed
- - name: "Add {{ account.username }} group"
- ansible.builtin.group:
- name: "{{ account.username }}"
- state: present
- when: user_info.failed
-
- - name: "Add {{ account.username }} user to group"
- ansible.builtin.user:
- name: "{{ account.username }}"
- groups: "{{ params.groups }}"
- when:
- - user_info.failed
- - params.groups
-
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
@@ -54,14 +42,40 @@
ignore_errors: true
when: account.secret_type == "password"
- - name: remove jumpserver ssh key
+ - name: "Get home directory for {{ account.username }}"
+ ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
+ register: home_dir
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Check if home directory exists for {{ account.username }}"
+ ansible.builtin.stat:
+ path: "{{ home_dir.stdout.strip() }}"
+ register: home_dir_stat
+ when: account.secret_type == "ssh_key"
+ ignore_errors: yes
+
+ - name: "Ensure {{ account.username }} home directory exists"
+ ansible.builtin.file:
+ path: "{{ home_dir.stdout.strip() }}"
+ state: directory
+ owner: "{{ account.username }}"
+ group: "{{ account.username }}"
+ mode: '0750'
+ when:
+ - account.secret_type == "ssh_key"
+ - home_dir_stat.stat.exists == false
+ ignore_errors: yes
+
+ - name: Remove jumpserver ssh key
ansible.builtin.lineinfile:
- dest: "{{ ssh_params.dest }}"
+ dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
+ ignore_errors: yes
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
@@ -79,7 +93,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
@@ -95,7 +109,7 @@
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
- gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
+ gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost
diff --git a/apps/accounts/automations/push_account/host/posix/manifest.yml b/apps/accounts/automations/push_account/host/posix/manifest.yml
index 86342b48a..9db17bd28 100644
--- a/apps/accounts/automations/push_account/host/posix/manifest.yml
+++ b/apps/accounts/automations/push_account/host/posix/manifest.yml
@@ -36,6 +36,12 @@ params:
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
+ - name: uid
+ type: str
+ label: "{{ 'Params uid label' | trans }}"
+ default: ''
+ help_text: "{{ 'Params uid help text' | trans }}"
+
i18n:
Posix account push:
zh: '使用 Ansible 模块 user 执行账号推送 (sha512)'
@@ -62,6 +68,11 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
+ Params uid help text:
+ zh: '请输入用户ID'
+ ja: 'ユーザーIDを入力してください'
+ en: 'Please enter the user ID'
+
Modify sudo label:
zh: '修改 sudo 权限'
ja: 'sudo 権限を変更'
@@ -75,4 +86,9 @@ i18n:
Params groups label:
zh: '用户组'
ja: 'グループ'
- en: 'Groups'
\ No newline at end of file
+ en: 'Groups'
+
+ Params uid label:
+ zh: '用户ID'
+ ja: 'ユーザーID'
+ en: 'User ID'
\ No newline at end of file
diff --git a/apps/accounts/automations/push_account/host/windows_rdp_verify/main.yml b/apps/accounts/automations/push_account/host/windows_rdp_verify/main.yml
index 29f4fb022..e15b5889e 100644
--- a/apps/accounts/automations/push_account/host/windows_rdp_verify/main.yml
+++ b/apps/accounts/automations/push_account/host/windows_rdp_verify/main.yml
@@ -25,11 +25,11 @@
- name: Verify password (pyfreerdp)
rdp_ping:
- login_host: "{{ jms_asset.address }}"
+ login_host: "{{ jms_asset.origin_address }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
- login_private_key_path: "{{ account.private_key_path }}"
+ gateway_args: "{{ jms_gateway | default(None) }}"
when: account.secret_type == "password"
delegate_to: localhost
diff --git a/apps/accounts/automations/remove_account/database/mysql/main.yml b/apps/accounts/automations/remove_account/database/mysql/main.yml
index a8700850f..f877dfe18 100644
--- a/apps/accounts/automations/remove_account/database/mysql/main.yml
+++ b/apps/accounts/automations/remove_account/database/mysql/main.yml
@@ -3,6 +3,9 @@
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: "Remove account"
@@ -12,8 +15,8 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
name: "{{ account.username }}"
state: absent
diff --git a/apps/accounts/automations/remove_account/database/postgresql/main.yml b/apps/accounts/automations/remove_account/database/postgresql/main.yml
index 7004dc945..3aad331f2 100644
--- a/apps/accounts/automations/remove_account/database/postgresql/main.yml
+++ b/apps/accounts/automations/remove_account/database/postgresql/main.yml
@@ -2,6 +2,10 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
+ check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: "Remove account"
@@ -12,4 +16,8 @@
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
state: absent
diff --git a/apps/accounts/automations/verify_account/custom/rdp/main.yml b/apps/accounts/automations/verify_account/custom/rdp/main.yml
index 2d8bcb883..33ee8282b 100644
--- a/apps/accounts/automations/verify_account/custom/rdp/main.yml
+++ b/apps/accounts/automations/verify_account/custom/rdp/main.yml
@@ -13,4 +13,3 @@
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
- login_private_key_path: "{{ account.private_key_path }}"
diff --git a/apps/accounts/automations/verify_account/database/mysql/main.yml b/apps/accounts/automations/verify_account/database/mysql/main.yml
index e2768d2c2..2c4ae5c0b 100644
--- a/apps/accounts/automations/verify_account/database/mysql/main.yml
+++ b/apps/accounts/automations/verify_account/database/mysql/main.yml
@@ -3,6 +3,9 @@
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Verify account
@@ -12,7 +15,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
diff --git a/apps/accounts/automations/verify_account/database/postgresql/main.yml b/apps/accounts/automations/verify_account/database/postgresql/main.yml
index 564749425..9667d335b 100644
--- a/apps/accounts/automations/verify_account/database/postgresql/main.yml
+++ b/apps/accounts/automations/verify_account/database/postgresql/main.yml
@@ -2,6 +2,10 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
+ check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Verify account
@@ -11,5 +15,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available
diff --git a/apps/accounts/migrations/0004_alter_changesecretrecord_account_and_more.py b/apps/accounts/migrations/0004_alter_changesecretrecord_account_and_more.py
new file mode 100644
index 000000000..c2df97445
--- /dev/null
+++ b/apps/accounts/migrations/0004_alter_changesecretrecord_account_and_more.py
@@ -0,0 +1,30 @@
+# Generated by Django 4.1.13 on 2024-08-26 09:05
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0005_myasset'),
+ ('accounts', '0003_automation'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='changesecretrecord',
+ name='account',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.account'),
+ ),
+ migrations.AlterField(
+ model_name='changesecretrecord',
+ name='asset',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.asset'),
+ ),
+ migrations.AlterField(
+ model_name='changesecretrecord',
+ name='execution',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.automationexecution'),
+ ),
+ ]
diff --git a/apps/accounts/models/automations/change_secret.py b/apps/accounts/models/automations/change_secret.py
index 48c0a45e1..6d1c22715 100644
--- a/apps/accounts/models/automations/change_secret.py
+++ b/apps/accounts/models/automations/change_secret.py
@@ -33,16 +33,15 @@ class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
class ChangeSecretRecord(JMSBaseModel):
- execution = models.ForeignKey('accounts.AutomationExecution', on_delete=models.CASCADE)
- asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
- account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, null=True)
+ execution = models.ForeignKey('accounts.AutomationExecution', on_delete=models.SET_NULL, null=True)
+ asset = models.ForeignKey('assets.Asset', on_delete=models.SET_NULL, null=True)
+ account = models.ForeignKey('accounts.Account', on_delete=models.SET_NULL, null=True)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
status = models.CharField(
- max_length=16, verbose_name=_('Status'),
- default=ChangeSecretRecordStatusChoice.pending.value
+ max_length=16, verbose_name=_('Status'), default=ChangeSecretRecordStatusChoice.pending.value
)
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
@@ -51,4 +50,4 @@ class ChangeSecretRecord(JMSBaseModel):
verbose_name = _("Change secret record")
def __str__(self):
- return self.account.__str__()
+ return f'{self.account.username}@{self.asset}'
diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py
index 691961f3e..a6d31671b 100644
--- a/apps/accounts/serializers/account/account.py
+++ b/apps/accounts/serializers/account/account.py
@@ -178,7 +178,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
instance.save()
return instance, 'updated'
else:
- raise serializers.ValidationError('Account already exists')
+ raise serializers.ValidationError(_('Account already exists'))
def create(self, validated_data):
push_now = validated_data.pop('push_now', None)
@@ -247,6 +247,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
'name': {'required': False},
'source_id': {'required': False, 'allow_null': True},
}
+ fields_unimport_template = ['params']
@classmethod
def setup_eager_loading(cls, queryset):
diff --git a/apps/accounts/serializers/account/template.py b/apps/accounts/serializers/account/template.py
index fc6ccefeb..5a0529b2d 100644
--- a/apps/accounts/serializers/account/template.py
+++ b/apps/accounts/serializers/account/template.py
@@ -19,6 +19,16 @@ class PasswordRulesSerializer(serializers.Serializer):
default='', allow_blank=True, max_length=16, label=_('Exclude symbol')
)
+ @staticmethod
+ def get_render_help_text():
+ return _("""length is the length of the password, and the range is 8 to 30.
+lowercase indicates whether the password contains lowercase letters,
+uppercase indicates whether it contains uppercase letters,
+digit indicates whether it contains numbers, and symbol indicates whether it contains special symbols.
+exclude_symbols is used to exclude specific symbols. You can fill in the symbol characters to be excluded (up to 16).
+If you do not need to exclude symbols, you can leave it blank.
+default: {"length": 16, "lowercase": true, "uppercase": true, "digit": true, "symbol": true, "exclude_symbols": ""}""")
+
class AccountTemplateSerializer(BaseAccountSerializer):
password_rules = PasswordRulesSerializer(required=False, label=_('Password rules'))
@@ -46,6 +56,7 @@ class AccountTemplateSerializer(BaseAccountSerializer):
'required': False
},
}
+ fields_unimport_template = ['push_params']
@staticmethod
def generate_secret(attrs):
diff --git a/apps/accounts/tasks/automation.py b/apps/accounts/tasks/automation.py
index f691825ef..3d1eb0883 100644
--- a/apps/accounts/tasks/automation.py
+++ b/apps/accounts/tasks/automation.py
@@ -1,9 +1,15 @@
+import datetime
+
from celery import shared_task
+from django.db.models import Q
+from django.utils import timezone
from django.utils.translation import gettext_lazy as _, gettext_noop
from accounts.const import AutomationTypes
from accounts.tasks.common import quickstart_automation_by_snapshot
-from common.utils import get_logger, get_object_or_none
+from common.const.crontab import CRONTAB_AT_AM_THREE
+from common.utils import get_logger, get_object_or_none, get_log_keep_day
+from ops.celery.decorator import register_as_period_task
from orgs.utils import tmp_to_org, tmp_to_root_org
logger = get_logger(__file__)
@@ -22,8 +28,14 @@ def task_activity_callback(self, pid, trigger, tp, *args, **kwargs):
@shared_task(
- queue='ansible', verbose_name=_('Account execute automation'),
- activity_callback=task_activity_callback
+ queue='ansible',
+ verbose_name=_('Account execute automation'),
+ activity_callback=task_activity_callback,
+ description=_(
+ """Unified execution entry for account automation tasks: when the system performs tasks
+ such as account push, password change, account verification, account collection,
+ and gateway account verification, all tasks are executed through this unified entry"""
+ )
)
def execute_account_automation_task(pid, trigger, tp):
model = AutomationTypes.get_type_model(tp)
@@ -48,8 +60,12 @@ def record_task_activity_callback(self, record_ids, *args, **kwargs):
@shared_task(
- queue='ansible', verbose_name=_('Execute automation record'),
- activity_callback=record_task_activity_callback
+ queue='ansible',
+ verbose_name=_('Execute automation record'),
+ activity_callback=record_task_activity_callback,
+ description=_(
+ """When manually executing password change records, this task is used"""
+ )
)
def execute_automation_record_task(record_ids, tp):
from accounts.models import ChangeSecretRecord
@@ -74,3 +90,33 @@ def execute_automation_record_task(record_ids, tp):
}
with tmp_to_org(record.execution.org_id):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
+
+
+@shared_task(
+ verbose_name=_('Clean change secret and push record period'),
+ description=_(
+ """The system will periodically clean up unnecessary password change and push records,
+ including their associated change tasks, execution logs, assets, and accounts. When any
+ of these associated items are deleted, the corresponding password change and push records
+ become invalid. Therefore, to maintain a clean and efficient database, the system will
+ clean up expired records at 2 a.m daily, based on the interval specified by
+ PERM_EXPIRED_CHECK_PERIODIC in the config.txt configuration file. This periodic cleanup
+ mechanism helps free up storage space and enhances the security and overall performance
+ of data management"""
+ )
+)
+@register_as_period_task(crontab=CRONTAB_AT_AM_THREE)
+def clean_change_secret_and_push_record_period():
+ from accounts.models import ChangeSecretRecord
+ print('Start clean change secret and push record period')
+ with tmp_to_root_org():
+ now = timezone.now()
+ days = get_log_keep_day('ACCOUNT_CHANGE_SECRET_RECORD_KEEP_DAYS')
+ expired_day = now - datetime.timedelta(days=days)
+ records = ChangeSecretRecord.objects.filter(
+ date_updated__lt=expired_day
+ ).filter(
+ Q(execution__isnull=True) | Q(asset__isnull=True) | Q(account__isnull=True)
+ )
+
+ records.delete()
diff --git a/apps/accounts/tasks/backup_account.py b/apps/accounts/tasks/backup_account.py
index c9b881b4d..d7d708c86 100644
--- a/apps/accounts/tasks/backup_account.py
+++ b/apps/accounts/tasks/backup_account.py
@@ -22,7 +22,13 @@ def task_activity_callback(self, pid, trigger, *args, **kwargs):
return resource_ids, org_id
-@shared_task(verbose_name=_('Execute account backup plan'), activity_callback=task_activity_callback)
+@shared_task(
+ verbose_name=_('Execute account backup plan'),
+ activity_callback=task_activity_callback,
+ description=_(
+ "When performing scheduled or manual account backups, this task is used"
+ )
+)
def execute_account_backup_task(pid, trigger, **kwargs):
from accounts.models import AccountBackupAutomation
with tmp_to_root_org():
diff --git a/apps/accounts/tasks/gather_accounts.py b/apps/accounts/tasks/gather_accounts.py
index 42f8641bb..831a9fdf6 100644
--- a/apps/accounts/tasks/gather_accounts.py
+++ b/apps/accounts/tasks/gather_accounts.py
@@ -26,8 +26,10 @@ def gather_asset_accounts_util(nodes, task_name):
@shared_task(
- queue="ansible", verbose_name=_('Gather asset accounts'),
- activity_callback=lambda self, node_ids, task_name=None, *args, **kwargs: (node_ids, None)
+ queue="ansible",
+ verbose_name=_('Gather asset accounts'),
+ activity_callback=lambda self, node_ids, task_name=None, *args, **kwargs: (node_ids, None),
+ description=_("Unused")
)
def gather_asset_accounts_task(node_ids, task_name=None):
if task_name is None:
diff --git a/apps/accounts/tasks/push_account.py b/apps/accounts/tasks/push_account.py
index 0e0608999..479b88c72 100644
--- a/apps/accounts/tasks/push_account.py
+++ b/apps/accounts/tasks/push_account.py
@@ -12,8 +12,12 @@ __all__ = [
@shared_task(
- queue="ansible", verbose_name=_('Push accounts to assets'),
- activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None)
+ queue="ansible",
+ verbose_name=_('Push accounts to assets'),
+ activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None),
+ description=_(
+ "When creating or modifying an account requires account push, this task is executed"
+ )
)
def push_accounts_to_assets_task(account_ids, params=None):
from accounts.models import PushAccountAutomation
diff --git a/apps/accounts/tasks/remove_account.py b/apps/accounts/tasks/remove_account.py
index 44bb9a840..f5f1936f5 100644
--- a/apps/accounts/tasks/remove_account.py
+++ b/apps/accounts/tasks/remove_account.py
@@ -21,8 +21,13 @@ __all__ = ['remove_accounts_task']
@shared_task(
- queue="ansible", verbose_name=_('Remove account'),
- activity_callback=lambda self, gather_account_ids, *args, **kwargs: (gather_account_ids, None)
+ queue="ansible",
+ verbose_name=_('Remove account'),
+ activity_callback=lambda self, gather_account_ids, *args, **kwargs: (gather_account_ids, None),
+ description=_(
+ """When clicking "Sync deletion" in 'Console - Gather Account - Gathered accounts' this
+ task will be executed"""
+ )
)
def remove_accounts_task(gather_account_ids):
from accounts.models import GatheredAccount
@@ -41,7 +46,15 @@ def remove_accounts_task(gather_account_ids):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
-@shared_task(verbose_name=_('Clean historical accounts'))
+@shared_task(
+ verbose_name=_('Clean historical accounts'),
+ description=_(
+ """Each time an asset account is updated, a historical account is generated, so it is
+ necessary to clean up the asset account history. The system will clean up excess account
+ records at 2 a.m. daily based on the configuration in the "System settings - Features -
+ Account storage - Record limit"""
+ )
+)
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
@tmp_to_root_org()
def clean_historical_accounts():
diff --git a/apps/accounts/tasks/template.py b/apps/accounts/tasks/template.py
index bc5416b9f..578911c83 100644
--- a/apps/accounts/tasks/template.py
+++ b/apps/accounts/tasks/template.py
@@ -9,7 +9,11 @@ from orgs.utils import tmp_to_root_org, tmp_to_org
@shared_task(
verbose_name=_('Template sync info to related accounts'),
- activity_callback=lambda self, template_id, *args, **kwargs: (template_id, None)
+ activity_callback=lambda self, template_id, *args, **kwargs: (template_id, None),
+ description=_(
+ """When clicking 'Sync new secret to accounts' in 'Console - Account - Templates -
+ Accounts' this task will be executed"""
+ )
)
def template_sync_related_accounts(template_id, user_id=None):
from accounts.models import Account, AccountTemplate
diff --git a/apps/accounts/tasks/vault.py b/apps/accounts/tasks/vault.py
index 6429b7133..128543ed3 100644
--- a/apps/accounts/tasks/vault.py
+++ b/apps/accounts/tasks/vault.py
@@ -28,7 +28,12 @@ def sync_instance(instance):
return "succeeded", msg
-@shared_task(verbose_name=_('Sync secret to vault'))
+@shared_task(
+ verbose_name=_('Sync secret to vault'),
+ description=_(
+ "When clicking 'Sync' in 'System Settings - Features - Account Storage' this task will be executed"
+ )
+)
def sync_secret_to_vault():
if not vault_client.enabled:
# 这里不能判断 settings.VAULT_ENABLED, 必须判断当前 vault_client 的类型
diff --git a/apps/accounts/tasks/verify_account.py b/apps/accounts/tasks/verify_account.py
index 523e7f3d2..d6526ec12 100644
--- a/apps/accounts/tasks/verify_account.py
+++ b/apps/accounts/tasks/verify_account.py
@@ -4,7 +4,6 @@ from django.utils.translation import gettext_noop
from accounts.const import AutomationTypes
from accounts.tasks.common import quickstart_automation_by_snapshot
-from assets.const import GATEWAY_NAME
from common.utils import get_logger
from orgs.utils import org_aware_func
@@ -32,13 +31,13 @@ def verify_accounts_connectivity_util(accounts, task_name):
asset_ids = [a.asset_id for a in accounts]
assets = Asset.objects.filter(id__in=asset_ids)
- gateways = assets.filter(platform__name=GATEWAY_NAME)
+ gateways = assets.gateways()
verify_connectivity_util(
gateways, AutomationTypes.verify_gateway_account,
accounts, task_name
)
- common_assets = assets.exclude(platform__name=GATEWAY_NAME)
+ common_assets = assets.gateways(0)
verify_connectivity_util(
common_assets, AutomationTypes.verify_account,
accounts, task_name
@@ -46,8 +45,12 @@ def verify_accounts_connectivity_util(accounts, task_name):
@shared_task(
- queue="ansible", verbose_name=_('Verify asset account availability'),
- activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None)
+ queue="ansible",
+ verbose_name=_('Verify asset account availability'),
+ activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None),
+ description=_(
+ "When clicking 'Test' in 'Console - Asset details - Accounts' this task will be executed"
+ )
)
def verify_accounts_connectivity_task(account_ids):
from accounts.models import Account, VerifyAccountAutomation
diff --git a/apps/accounts/templates/accounts/asset_account_change_info.html b/apps/accounts/templates/accounts/asset_account_change_info.html
index b778b3cd3..c537dffd4 100644
--- a/apps/accounts/templates/accounts/asset_account_change_info.html
+++ b/apps/accounts/templates/accounts/asset_account_change_info.html
@@ -1,18 +1,29 @@
{% load i18n %}
-
-{% trans 'Gather account change information' %}
-
+
+
- {% trans 'Asset' %} |
- {% trans 'Added account' %} |
- {% trans 'Deleted account' %} |
+
+ {% trans 'Asset' %}
+ |
+
+ {% trans 'Added account' %}
+ |
+
+ {% trans 'Deleted account' %}
+ |
{% for name, change in change_info.items %}
- {{ name }} |
- {{ change.add_usernames }} |
- {{ change.remove_usernames }} |
+
+ {{ name | safe }}
+ |
+
+ {{ change.add_usernames | join:" " | safe }}
+ |
+
+ {{ change.remove_usernames | join:" " | safe }}
+ |
{% endfor %}
diff --git a/apps/acls/const.py b/apps/acls/const.py
index 3c03d8e2d..f36bfc21e 100644
--- a/apps/acls/const.py
+++ b/apps/acls/const.py
@@ -8,3 +8,4 @@ class ActionChoices(models.TextChoices):
review = 'review', _('Review')
warning = 'warning', _('Warn')
notice = 'notice', _('Notify')
+ notify_and_warn = 'notify_and_warn', _('Notify and warn')
diff --git a/apps/acls/serializers/base.py b/apps/acls/serializers/base.py
index 09f75bf42..4dfa56b9f 100644
--- a/apps/acls/serializers/base.py
+++ b/apps/acls/serializers/base.py
@@ -62,7 +62,7 @@ class ActionAclSerializer(serializers.Serializer):
self.set_action_choices()
class Meta:
- action_choices_exclude = [ActionChoices.warning]
+ action_choices_exclude = [ActionChoices.warning, ActionChoices.notify_and_warn]
def set_action_choices(self):
field_action = self.fields.get("action")
diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py
index 09160b9e6..849b5f3ad 100644
--- a/apps/assets/api/asset/asset.py
+++ b/apps/assets/api/asset/asset.py
@@ -2,10 +2,10 @@
#
from collections import defaultdict
-import django_filters
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
+from django_filters import rest_framework as drf_filters
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
@@ -22,6 +22,7 @@ from common.drf.filters import BaseFilterSet, AttrRulesFilterBackend
from common.utils import get_logger, is_uuid
from orgs.mixins import generics
from orgs.mixins.api import OrgBulkModelViewSet
+from ...const import GATEWAY_NAME
from ...notifications import BulkUpdatePlatformSkipAssetUserMsg
logger = get_logger(__file__)
@@ -32,31 +33,32 @@ __all__ = [
class AssetFilterSet(BaseFilterSet):
- platform = django_filters.CharFilter(method='filter_platform')
- exclude_platform = django_filters.CharFilter(field_name="platform__name", lookup_expr='exact', exclude=True)
- domain = django_filters.CharFilter(method='filter_domain')
- type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
- category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
- protocols = django_filters.CharFilter(method='filter_protocols')
- domain_enabled = django_filters.BooleanFilter(
+ platform = drf_filters.CharFilter(method='filter_platform')
+ is_gateway = drf_filters.BooleanFilter(method='filter_is_gateway')
+ exclude_platform = drf_filters.CharFilter(field_name="platform__name", lookup_expr='exact', exclude=True)
+ domain = drf_filters.CharFilter(method='filter_domain')
+ type = drf_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
+ category = drf_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
+ protocols = drf_filters.CharFilter(method='filter_protocols')
+ domain_enabled = drf_filters.BooleanFilter(
field_name="platform__domain_enabled", lookup_expr="exact"
)
- ping_enabled = django_filters.BooleanFilter(
+ ping_enabled = drf_filters.BooleanFilter(
field_name="platform__automation__ping_enabled", lookup_expr="exact"
)
- gather_facts_enabled = django_filters.BooleanFilter(
+ gather_facts_enabled = drf_filters.BooleanFilter(
field_name="platform__automation__gather_facts_enabled", lookup_expr="exact"
)
- change_secret_enabled = django_filters.BooleanFilter(
+ change_secret_enabled = drf_filters.BooleanFilter(
field_name="platform__automation__change_secret_enabled", lookup_expr="exact"
)
- push_account_enabled = django_filters.BooleanFilter(
+ push_account_enabled = drf_filters.BooleanFilter(
field_name="platform__automation__push_account_enabled", lookup_expr="exact"
)
- verify_account_enabled = django_filters.BooleanFilter(
+ verify_account_enabled = drf_filters.BooleanFilter(
field_name="platform__automation__verify_account_enabled", lookup_expr="exact"
)
- gather_accounts_enabled = django_filters.BooleanFilter(
+ gather_accounts_enabled = drf_filters.BooleanFilter(
field_name="platform__automation__gather_accounts_enabled", lookup_expr="exact"
)
@@ -71,9 +73,16 @@ class AssetFilterSet(BaseFilterSet):
def filter_platform(queryset, name, value):
if value.isdigit():
return queryset.filter(platform_id=value)
+ elif value == GATEWAY_NAME:
+ return queryset.filter(platform__name__istartswith=GATEWAY_NAME)
else:
return queryset.filter(platform__name=value)
+ @staticmethod
+ def filter_is_gateway(queryset, name, value):
+ queryset = queryset.gateways(value)
+ return queryset
+
@staticmethod
def filter_domain(queryset, name, value):
if is_uuid(value):
@@ -298,6 +307,7 @@ class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
def check_permissions(self, request):
action_perm_require = {
"refresh": "assets.refresh_assethardwareinfo",
+ "test": "assets.test_assetconnectivity",
}
_action = request.data.get("action")
perm_required = action_perm_require.get(_action)
diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py
index bce8505cf..ca6dc882f 100644
--- a/apps/assets/api/platform.py
+++ b/apps/assets/api/platform.py
@@ -1,4 +1,5 @@
from django.db.models import Count
+from django_filters import rest_framework as filters
from rest_framework import generics
from rest_framework import serializers
from rest_framework.decorators import action
@@ -14,6 +15,14 @@ from common.serializers import GroupedChoiceSerializer
__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi', 'PlatformProtocolViewSet']
+class PlatformFilter(filters.FilterSet):
+ name__startswith = filters.CharFilter(field_name='name', lookup_expr='istartswith')
+
+ class Meta:
+ model = Platform
+ fields = ['name', 'category', 'type']
+
+
class AssetPlatformViewSet(JMSModelViewSet):
queryset = Platform.objects.all()
serializer_classes = {
@@ -21,7 +30,7 @@ class AssetPlatformViewSet(JMSModelViewSet):
'list': PlatformListSerializer,
'categories': GroupedChoiceSerializer,
}
- filterset_fields = ['name', 'category', 'type']
+ filterset_class = PlatformFilter
search_fields = ['name']
ordering = ['-internal', 'name']
rbac_perms = {
diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py
index c9de04baf..da3e2353f 100644
--- a/apps/assets/automations/base/manager.py
+++ b/apps/assets/automations/base/manager.py
@@ -170,6 +170,7 @@ class BasePlaybookManager:
result = self.write_cert_to_file(
os.path.join(cert_dir, f), specific.get(f)
)
+ os.chmod(result, 0o600)
host['jms_asset']['secret_info'][f] = result
return host
diff --git a/apps/assets/automations/gather_facts/database/mysql/main.yml b/apps/assets/automations/gather_facts/database/mysql/main.yml
index 348a2150d..ac8c27ac2 100644
--- a/apps/assets/automations/gather_facts/database/mysql/main.yml
+++ b/apps/assets/automations/gather_facts/database/mysql/main.yml
@@ -3,6 +3,9 @@
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -12,9 +15,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
register: db_info
diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml
index c35d2ab7b..e730f8f85 100644
--- a/apps/assets/automations/gather_facts/database/postgresql/main.yml
+++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml
@@ -2,6 +2,10 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
+ check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Get info
@@ -11,6 +15,10 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: db_info
- name: Define info by set_fact
diff --git a/apps/assets/automations/ping/custom/rdp/main.yml b/apps/assets/automations/ping/custom/rdp/main.yml
index c0808e976..bcf07c15c 100644
--- a/apps/assets/automations/ping/custom/rdp/main.yml
+++ b/apps/assets/automations/ping/custom/rdp/main.yml
@@ -13,4 +13,3 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
- login_private_key_path: "{{ jms_account.private_key_path }}"
diff --git a/apps/assets/automations/ping/database/mysql/main.yml b/apps/assets/automations/ping/database/mysql/main.yml
index f99333bdb..8326402cd 100644
--- a/apps/assets/automations/ping/database/mysql/main.yml
+++ b/apps/assets/automations/ping/database/mysql/main.yml
@@ -3,6 +3,9 @@
vars:
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test MySQL connection
@@ -12,7 +15,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
- ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
- client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
- client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
filter: version
diff --git a/apps/assets/automations/ping/database/postgresql/main.yml b/apps/assets/automations/ping/database/postgresql/main.yml
index bf50d7a2b..3edc7daec 100644
--- a/apps/assets/automations/ping/database/postgresql/main.yml
+++ b/apps/assets/automations/ping/database/postgresql/main.yml
@@ -2,6 +2,10 @@
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
+ check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
+ ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
+ ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
+ ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
tasks:
- name: Test PostgreSQL connection
@@ -11,5 +15,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_db: "{{ jms_asset.spec_info.db_name }}"
+ ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
+ ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
+ ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
+ ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
register: result
failed_when: not result.is_available
diff --git a/apps/assets/const/__init__.py b/apps/assets/const/__init__.py
index abf7e71b1..415179539 100644
--- a/apps/assets/const/__init__.py
+++ b/apps/assets/const/__init__.py
@@ -1,6 +1,7 @@
from .automation import *
from .base import *
from .category import *
+from .database import *
from .host import *
from .platform import *
from .protocol import *
diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py
index 9457cc427..8acbdbb43 100644
--- a/apps/assets/const/database.py
+++ b/apps/assets/const/database.py
@@ -1,3 +1,5 @@
+from django.db.models import TextChoices
+
from .base import BaseType
@@ -120,3 +122,10 @@ class DatabaseTypes(BaseType):
cls.MYSQL, cls.MARIADB, cls.POSTGRESQL,
cls.MONGODB, cls.REDIS,
]
+
+
+class PostgresqlSSLMode(TextChoices):
+ PREFER = 'prefer', 'Prefer'
+ REQUIRE = 'require', 'Require'
+ VERIFY_CA = 'verify-ca', 'Verify CA'
+ VERIFY_FULL = 'verify-full', 'Verify Full'
diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py
index 07a60c3de..dde7ccabe 100644
--- a/apps/assets/const/protocol.py
+++ b/apps/assets/const/protocol.py
@@ -45,6 +45,12 @@ class Protocol(ChoicesMixin, models.TextChoices):
'default': False,
'label': _('Old SSH version'),
'help_text': _('Old SSH version like openssh 5.x or 6.x')
+ },
+ 'nc': {
+ 'type': 'bool',
+ 'default': False,
+ 'label': 'Netcat (nc)',
+ 'help_text': _('Netcat help text')
}
}
},
diff --git a/apps/assets/migrations/0006_database_pg_ssl_mode.py b/apps/assets/migrations/0006_database_pg_ssl_mode.py
new file mode 100644
index 000000000..6382f2ea7
--- /dev/null
+++ b/apps/assets/migrations/0006_database_pg_ssl_mode.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.1.13 on 2024-09-13 08:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('assets', '0005_myasset'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='database',
+ name='pg_ssl_mode',
+ field=models.CharField(choices=[
+ ('prefer', 'Prefer'),
+ ('require', 'Require'),
+ ('verify-ca', 'Verify CA'),
+ ('verify-full', 'Verify Full')
+ ], default='prefer',
+ max_length=16, verbose_name='Postgresql SSL mode'),
+ ),
+ ]
diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py
index 6f7df504d..833d8f1be 100644
--- a/apps/assets/models/asset/common.py
+++ b/apps/assets/models/asset/common.py
@@ -38,6 +38,13 @@ class AssetQuerySet(models.QuerySet):
def valid(self):
return self.active()
+ def gateways(self, is_gateway=1):
+ kwargs = {'platform__name__startswith': 'Gateway'}
+ if is_gateway:
+ return self.filter(**kwargs)
+ else:
+ return self.exclude(**kwargs)
+
def has_protocol(self, name):
return self.filter(protocols__contains=name)
@@ -158,10 +165,16 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
name = models.CharField(max_length=128, verbose_name=_('Name'))
address = models.CharField(max_length=767, verbose_name=_('Address'), db_index=True)
- platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
- domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
- verbose_name=_("Zone"), on_delete=models.SET_NULL)
- nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
+ platform = models.ForeignKey(
+ Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets'
+ )
+ domain = models.ForeignKey(
+ "assets.Domain", null=True, blank=True, related_name='assets',
+ verbose_name=_("Zone"), on_delete=models.SET_NULL
+ )
+ nodes = models.ManyToManyField(
+ 'assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")
+ )
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
gathered_info = models.JSONField(verbose_name=_('Gathered info'), default=dict, blank=True) # 资产的一些信息,如 硬件信息
custom_info = models.JSONField(verbose_name=_('Custom info'), default=dict)
diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py
index 12da55b30..b62a78f04 100644
--- a/apps/assets/models/asset/database.py
+++ b/apps/assets/models/asset/database.py
@@ -1,6 +1,7 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
+from assets.const import PostgresqlSSLMode
from common.db.fields import EncryptTextField
from .common import Asset
@@ -12,6 +13,10 @@ class Database(Asset):
client_cert = EncryptTextField(verbose_name=_("Client cert"), blank=True)
client_key = EncryptTextField(verbose_name=_("Client key"), blank=True)
allow_invalid_cert = models.BooleanField(default=False, verbose_name=_('Allow invalid cert'))
+ pg_ssl_mode = models.CharField(
+ max_length=16, choices=PostgresqlSSLMode.choices,
+ default=PostgresqlSSLMode.PREFER, verbose_name=_('Postgresql SSL mode')
+ )
def __str__(self):
return '{}({}://{}/{})'.format(self.name, self.type, self.address, self.db_name)
diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py
index 0c75e22b7..4a9311aed 100644
--- a/apps/assets/models/domain.py
+++ b/apps/assets/models/domain.py
@@ -31,7 +31,7 @@ class Domain(LabeledMixin, JMSOrgBaseModel):
@lazyproperty
def assets_amount(self):
- return self.assets.exclude(platform__name='Gateway').count()
+ return self.assets.gateways(0).count()
def random_gateway(self):
gateways = [gw for gw in self.active_gateways if gw.is_connective]
diff --git a/apps/assets/models/gateway.py b/apps/assets/models/gateway.py
index 36b2a5a72..7e9f10da7 100644
--- a/apps/assets/models/gateway.py
+++ b/apps/assets/models/gateway.py
@@ -16,7 +16,7 @@ __all__ = ['Gateway']
class GatewayManager(OrgManager):
def get_queryset(self):
queryset = super().get_queryset()
- queryset = queryset.filter(platform__name=GATEWAY_NAME)
+ queryset = queryset.filter(platform__name__startswith=GATEWAY_NAME)
return queryset
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):
@@ -33,10 +33,6 @@ class Gateway(Host):
proxy = True
verbose_name = _("Gateway")
- def save(self, *args, **kwargs):
- self.platform = self.default_platform()
- return super().save(*args, **kwargs)
-
@classmethod
def default_platform(cls):
return Platform.objects.get(name=GATEWAY_NAME, internal=True)
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index 9a6f21a11..5f1cee9ab 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -31,6 +31,12 @@ __all__ = [
class AssetProtocolsSerializer(serializers.ModelSerializer):
port = serializers.IntegerField(required=False, allow_null=True, max_value=65535, min_value=0)
+ def get_render_help_text(self):
+ if self.parent and self.parent.many:
+ return _('Protocols, format is ["protocol/port"]')
+ else:
+ return _('Protocol, format is name/port')
+
def to_file_representation(self, data):
return '{name}/{port}'.format(**data)
@@ -97,6 +103,9 @@ class AssetAccountSerializer(AccountSerializer):
attrs = super().validate(attrs)
return self.set_secret(attrs)
+ def get_render_help_text(self):
+ return _('Accounts, format [{"name": "x", "username": "x", "secret": "x", "secret_type": "password"}]')
+
class Meta(AccountSerializer.Meta):
fields = [
f for f in AccountSerializer.Meta.fields
@@ -121,19 +130,30 @@ class AccountSecretSerializer(SecretReadableMixin, CommonModelSerializer):
}
+class NodeDisplaySerializer(serializers.ListField):
+ def get_render_help_text(self):
+ return _('Node path, format ["/org_name/node_name"], if node not exist, will create it')
+
+ def to_internal_value(self, data):
+ return data
+
+ def to_representation(self, data):
+ return data
+
+
class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, WritableNestedModelSerializer):
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=())
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Accounts'))
- nodes_display = serializers.ListField(read_only=False, required=False, label=_("Node path"))
+ nodes_display = NodeDisplaySerializer(read_only=False, required=False, label=_("Node path"))
_accounts = None
class Meta:
model = Asset
- fields_mini = ['id', 'name', 'address']
- fields_small = fields_mini + ['is_active', 'comment']
fields_fk = ['domain', 'platform']
+ fields_mini = ['id', 'name', 'address'] + fields_fk
+ fields_small = fields_mini + ['is_active', 'comment']
fields_m2m = [
'nodes', 'labels', 'protocols',
'nodes_display', 'accounts',
diff --git a/apps/assets/serializers/asset/custom.py b/apps/assets/serializers/asset/custom.py
index 151597b44..457c425d6 100644
--- a/apps/assets/serializers/asset/custom.py
+++ b/apps/assets/serializers/asset/custom.py
@@ -16,6 +16,7 @@ class CustomSerializer(AssetSerializer):
class Meta(AssetSerializer.Meta):
model = Custom
fields = AssetSerializer.Meta.fields + ['custom_info']
+ fields_unimport_template = ['custom_info']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py
index d9a4bf1b0..84703b74f 100644
--- a/apps/assets/serializers/asset/database.py
+++ b/apps/assets/serializers/asset/database.py
@@ -16,9 +16,14 @@ class DatabaseSerializer(AssetSerializer):
model = Database
extra_fields = [
'db_name', 'use_ssl', 'ca_cert', 'client_cert',
- 'client_key', 'allow_invalid_cert'
+ 'client_key', 'allow_invalid_cert', 'pg_ssl_mode'
]
fields = AssetSerializer.Meta.fields + extra_fields
+ extra_kwargs = {
+ 'ca_cert': {'help_text': _('CA cert help text')},
+ 'pg_ssl_mode': {'help_text': _('Postgresql ssl model help text')},
+ }
+ extra_kwargs.update(AssetSerializer.Meta.extra_kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py
index bba7e7310..e538a3d0c 100644
--- a/apps/assets/serializers/domain.py
+++ b/apps/assets/serializers/domain.py
@@ -68,7 +68,7 @@ class DomainListSerializer(DomainSerializer):
@classmethod
def setup_eager_loading(cls, queryset):
queryset = queryset.annotate(
- assets_amount=Count('assets', filter=~Q(assets__platform__name='Gateway'), distinct=True),
+ assets_amount=Count('assets', filter=~Q(assets__platform__name__startswith='Gateway'), distinct=True),
)
return queryset
diff --git a/apps/assets/serializers/gateway.py b/apps/assets/serializers/gateway.py
index e596ef5a3..e435f0af7 100644
--- a/apps/assets/serializers/gateway.py
+++ b/apps/assets/serializers/gateway.py
@@ -14,6 +14,11 @@ class GatewaySerializer(HostSerializer):
class Meta(HostSerializer.Meta):
model = Gateway
+ def validate_platform(self, p):
+ if not p.name.startswith('Gateway'):
+ raise serializers.ValidationError(_('The platform must start with Gateway'))
+ return p
+
def validate_name(self, value):
queryset = Asset.objects.filter(name=value)
if self.instance:
diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py
index 2eb5a44b2..38c8bd134 100644
--- a/apps/assets/serializers/platform.py
+++ b/apps/assets/serializers/platform.py
@@ -147,6 +147,10 @@ class PlatformProtocolSerializer(serializers.ModelSerializer):
name, port = data.split('/')
return {'name': name, 'port': port}
+ @staticmethod
+ def get_render_help_text():
+ return _('Protocols, format is ["protocol/port"]')
+
class PlatformCustomField(serializers.Serializer):
TYPE_CHOICES = [(t, t) for t, c in type_field_map.items()]
diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py
index 20426451c..6fa4e2df2 100644
--- a/apps/assets/tasks/automation.py
+++ b/apps/assets/tasks/automation.py
@@ -21,8 +21,10 @@ def task_activity_callback(self, pid, trigger, tp, *args, **kwargs):
@shared_task(
- queue='ansible', verbose_name=_('Asset execute automation'),
- activity_callback=task_activity_callback
+ queue='ansible',
+ verbose_name=_('Asset execute automation'),
+ activity_callback=task_activity_callback,
+ description=_("Unused")
)
def execute_asset_automation_task(pid, trigger, tp):
model = AutomationTypes.get_type_model(tp)
diff --git a/apps/assets/tasks/gather_facts.py b/apps/assets/tasks/gather_facts.py
index b541bde69..60e7c9396 100644
--- a/apps/assets/tasks/gather_facts.py
+++ b/apps/assets/tasks/gather_facts.py
@@ -18,8 +18,13 @@ __all__ = [
@shared_task(
- queue="ansible", verbose_name=_('Gather assets facts'),
- activity_callback=lambda self, asset_ids, org_id, *args, **kwargs: (asset_ids, org_id)
+ queue="ansible",
+ verbose_name=_('Gather assets facts'),
+ activity_callback=lambda self, asset_ids, org_id, *args, **kwargs: (asset_ids, org_id),
+ description=_(
+ """When clicking 'Refresh hardware info' in 'Console - Asset Details - Basic' this task
+ will be executed"""
+ )
)
def gather_assets_facts_task(asset_ids, org_id, task_name=None):
from assets.models import GatherFactsAutomation
diff --git a/apps/assets/tasks/nodes_amount.py b/apps/assets/tasks/nodes_amount.py
index 58344aa11..db435bd34 100644
--- a/apps/assets/tasks/nodes_amount.py
+++ b/apps/assets/tasks/nodes_amount.py
@@ -1,19 +1,25 @@
from celery import shared_task
from django.utils.translation import gettext_lazy as _
-from orgs.models import Organization
-from orgs.utils import tmp_to_org
-from ops.celery.decorator import register_as_period_task
from assets.utils import check_node_assets_amount
-
-from common.utils.lock import AcquireFailed
-from common.utils import get_logger
from common.const.crontab import CRONTAB_AT_AM_TWO
+from common.utils import get_logger
+from common.utils.lock import AcquireFailed
+from ops.celery.decorator import register_as_period_task
+from orgs.models import Organization
+from orgs.utils import tmp_to_org
logger = get_logger(__file__)
-@shared_task(verbose_name=_('Check the amount of assets under the node'))
+@shared_task(
+ verbose_name=_('Check the amount of assets under the node'),
+ description=_(
+ """Manually verifying asset quantities updates the asset count for nodes under the
+ current organization. This task will be called in the following two cases: when updating
+ nodes and when the number of nodes exceeds 100"""
+ )
+)
def check_node_assets_amount_task(org_id=None):
if org_id is None:
orgs = Organization.objects.all()
@@ -30,7 +36,13 @@ def check_node_assets_amount_task(org_id=None):
logger.error(error)
-@shared_task(verbose_name=_('Periodic check the amount of assets under the node'))
+@shared_task(
+ verbose_name=_('Periodic check the amount of assets under the node'),
+ description=_(
+ """Schedule the check_node_assets_amount_task to periodically update the asset count of
+ all nodes under all organizations"""
+ )
+)
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
def check_node_assets_amount_period_task():
check_node_assets_amount_task()
diff --git a/apps/assets/tasks/ping.py b/apps/assets/tasks/ping.py
index 3882fe0b3..3d6d29bd9 100644
--- a/apps/assets/tasks/ping.py
+++ b/apps/assets/tasks/ping.py
@@ -17,8 +17,12 @@ __all__ = [
@shared_task(
- verbose_name=_('Test assets connectivity'), queue='ansible',
- activity_callback=lambda self, asset_ids, org_id, *args, **kwargs: (asset_ids, org_id)
+ verbose_name=_('Test assets connectivity'),
+ queue='ansible',
+ activity_callback=lambda self, asset_ids, org_id, *args, **kwargs: (asset_ids, org_id),
+ description=_(
+ "When clicking 'Test Asset Connectivity' in 'Asset Details - Basic Settings' this task will be executed"
+ )
)
def test_assets_connectivity_task(asset_ids, org_id, task_name=None):
from assets.models import PingAutomation
diff --git a/apps/assets/tasks/ping_gateway.py b/apps/assets/tasks/ping_gateway.py
index 05ab7fb0c..4bdfc2cc7 100644
--- a/apps/assets/tasks/ping_gateway.py
+++ b/apps/assets/tasks/ping_gateway.py
@@ -16,8 +16,12 @@ __all__ = [
@shared_task(
- verbose_name=_('Test gateways connectivity'), queue='ansible',
- activity_callback=lambda self, asset_ids, org_id, *args, **kwargs: (asset_ids, org_id)
+ verbose_name=_('Test gateways connectivity'),
+ queue='ansible',
+ activity_callback=lambda self, asset_ids, org_id, *args, **kwargs: (asset_ids, org_id),
+ description=_(
+ "When clicking 'Test Connection' in 'Domain Details - Gateway' this task will be executed"
+ )
)
def test_gateways_connectivity_task(asset_ids, org_id, local_port, task_name=None):
from assets.models import PingAutomation
@@ -33,4 +37,5 @@ def test_gateways_connectivity_task(asset_ids, org_id, local_port, task_name=Non
def test_gateways_connectivity_manual(gateway_ids, local_port):
task_name = gettext_noop("Test gateways connectivity")
gateway_ids = [str(i) for i in gateway_ids]
- return test_gateways_connectivity_task.delay(gateway_ids, str(current_org.id), local_port, task_name)
+ return test_gateways_connectivity_task.delay(gateway_ids, str(current_org.id), local_port,
+ task_name)
diff --git a/apps/assets/utils/__init__.py b/apps/assets/utils/__init__.py
index 9f588b6d2..97431995d 100644
--- a/apps/assets/utils/__init__.py
+++ b/apps/assets/utils/__init__.py
@@ -1,2 +1 @@
-from .k8s import *
from .node import *
diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py
deleted file mode 100644
index 8e1069566..000000000
--- a/apps/assets/utils/k8s.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# -*- coding: utf-8 -*-
-from urllib.parse import urlencode, urlparse
-
-from kubernetes import client
-from kubernetes.client import api_client
-from kubernetes.client.api import core_v1_api
-from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
-
-from common.utils import get_logger
-from ..const import CloudTypes, Category
-
-logger = get_logger(__file__)
-
-
-class KubernetesClient:
- def __init__(self, asset, token):
- self.url = asset.address
- self.token = token or ''
- self.server = self.get_gateway_server(asset)
-
- @property
- def api(self):
- configuration = client.Configuration()
- scheme = urlparse(self.url).scheme
- if not self.server:
- host = self.url
- else:
- host = f'{scheme}://127.0.0.1:{self.server.local_bind_port}'
- configuration.host = host
- configuration.verify_ssl = False
- configuration.api_key = {"authorization": "Bearer " + self.token}
- c = api_client.ApiClient(configuration=configuration)
- api = core_v1_api.CoreV1Api(c)
- return api
-
- def get_namespaces(self):
- namespaces = []
- resp = self.api.list_namespace()
- for ns in resp.items:
- namespaces.append(ns.metadata.name)
- return namespaces
-
- def get_pods(self, namespace):
- pods = []
- resp = self.api.list_namespaced_pod(namespace)
- for pd in resp.items:
- pods.append(pd.metadata.name)
- return pods
-
- def get_containers(self, namespace, pod_name):
- containers = []
- resp = self.api.read_namespaced_pod(pod_name, namespace)
- for container in resp.spec.containers:
- containers.append(container.name)
- return containers
-
- @staticmethod
- def get_gateway_server(asset):
- gateway = None
- if not asset.is_gateway and asset.domain:
- gateway = asset.domain.select_gateway()
-
- if not gateway:
- return
-
- remote_bind_address = (
- urlparse(asset.address).hostname,
- urlparse(asset.address).port or 443
- )
- server = SSHTunnelForwarder(
- (gateway.address, gateway.port),
- ssh_username=gateway.username,
- ssh_password=gateway.password,
- ssh_pkey=gateway.private_key_path,
- remote_bind_address=remote_bind_address
- )
- try:
- server.start()
- except BaseSSHTunnelForwarderError:
- err_msg = 'Gateway is not active: %s' % asset.get('name', '')
- print('\033[31m %s \033[0m\n' % err_msg)
- return server
-
- def run(self, tp, *args):
- func_name = f'get_{tp}s'
- data = []
- if hasattr(self, func_name):
- try:
- data = getattr(self, func_name)(*args)
- except Exception as e:
- logger.error(f'K8S tree get {tp} error: {e}')
-
- if self.server:
- self.server.stop()
- return data
-
-
-class KubernetesTree:
- def __init__(self, asset, secret):
- self.asset = asset
- self.secret = secret
-
- def as_asset_tree_node(self):
- i = str(self.asset.id)
- name = str(self.asset)
- node = self.create_tree_node(
- i, i, name, 'asset', icon='k8s', is_open=True,
- )
- return node
-
- def as_namespace_node(self, name, tp):
- i = urlencode({'namespace': name})
- pid = str(self.asset.id)
- node = self.create_tree_node(i, pid, name, tp, icon='cloud')
- return node
-
- def as_pod_tree_node(self, namespace, name, tp):
- pid = urlencode({'namespace': namespace})
- i = urlencode({'namespace': namespace, 'pod': name})
- node = self.create_tree_node(i, pid, name, tp, icon='cloud')
- return node
-
- def as_container_tree_node(self, namespace, pod, name, tp):
- pid = urlencode({'namespace': namespace, 'pod': pod})
- i = urlencode({'namespace': namespace, 'pod': pod, 'container': name})
- node = self.create_tree_node(
- i, pid, name, tp, icon='cloud', is_container=True
- )
- return node
-
- @staticmethod
- def create_tree_node(id_, pid, name, identity, icon='', is_container=False, is_open=False):
- node = {
- 'id': id_,
- 'name': name,
- 'title': name,
- 'pId': pid,
- 'isParent': not is_container,
- 'open': is_open,
- 'iconSkin': icon,
- 'meta': {
- 'type': 'k8s',
- 'data': {
- 'category': Category.CLOUD,
- 'type': CloudTypes.K8S,
- 'identity': identity
- }
- }
- }
- return node
-
- def async_tree_node(self, namespace, pod):
- tree = []
- k8s_client = KubernetesClient(self.asset, self.secret)
- if pod:
- tp = 'container'
- containers = k8s_client.run(
- tp, namespace, pod
- )
- for container in containers:
- container_node = self.as_container_tree_node(
- namespace, pod, container, tp
- )
- tree.append(container_node)
- elif namespace:
- tp = 'pod'
- pods = k8s_client.run(tp, namespace)
- for pod in pods:
- pod_node = self.as_pod_tree_node(namespace, pod, tp)
- tree.append(pod_node)
- else:
- tp = 'namespace'
- namespaces = k8s_client.run(tp)
- for namespace in namespaces:
- namespace_node = self.as_namespace_node(namespace, tp)
- tree.append(namespace_node)
- return tree
diff --git a/apps/audits/tasks.py b/apps/audits/tasks.py
index f59d515ee..013141f82 100644
--- a/apps/audits/tasks.py
+++ b/apps/audits/tasks.py
@@ -128,7 +128,15 @@ def clean_expired_session_period():
logger.info("Clean session replay done")
-@shared_task(verbose_name=_('Clean audits session task log'))
+@shared_task(
+ verbose_name=_('Clean audits session task log'),
+ description=_(
+ """Since the system generates login logs, operation logs, file upload logs, activity
+ logs, Celery execution logs, session recordings, command records, and password change
+ logs, it will perform cleanup of records that exceed the time limit according to the
+ 'Tasks - Regular clean-up' in the system settings at 2 a.m daily"""
+ )
+)
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
def clean_audits_log_period():
print("Start clean audit session task log")
@@ -142,7 +150,13 @@ def clean_audits_log_period():
clean_password_change_log_period()
-@shared_task(verbose_name=_('Upload FTP file to external storage'))
+@shared_task(
+ verbose_name=_('Upload FTP file to external storage'),
+ description=_(
+ """If SERVER_REPLAY_STORAGE is configured, files uploaded through file management will be
+ synchronized to external storage"""
+ )
+)
def upload_ftp_file_to_external_storage(ftp_log_id, file_name):
logger.info(f'Start upload FTP file record to external storage: {ftp_log_id} - {file_name}')
ftp_log = FTPLog.objects.filter(id=ftp_log_id).first()
diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py
index 616052af2..26ae2bb31 100644
--- a/apps/authentication/backends/ldap.py
+++ b/apps/authentication/backends/ldap.py
@@ -1,6 +1,6 @@
# coding:utf-8
#
-
+import abc
import ldap
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
@@ -15,13 +15,16 @@ from .base import JMSBaseAuthBackend
logger = _LDAPConfig.get_logger()
-class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
- """
- Override this class to override _LDAPUser to LDAPUser
- """
- @staticmethod
- def is_enabled():
- return settings.AUTH_LDAP
+class LDAPBaseBackend(LDAPBackend):
+
+ @abc.abstractmethod
+ def is_enabled(self):
+ raise NotImplementedError('is_enabled')
+
+ @property
+ @abc.abstractmethod
+ def is_user_login_only_in_users(self):
+ raise NotImplementedError('is_authenticated')
def get_or_build_user(self, username, ldap_user):
"""
@@ -56,38 +59,6 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
return user, built
- def pre_check(self, username, password):
- if not settings.AUTH_LDAP:
- error = 'Not enabled auth ldap'
- return False, error
- if not username:
- error = 'Username is None'
- return False, error
- if not password:
- error = 'Password is None'
- return False, error
- if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS:
- user_model = self.get_user_model()
- exist = user_model.objects.filter(username=username).exists()
- if not exist:
- error = 'user ({}) is not in the user list'.format(username)
- return False, error
- return True, ''
-
- def authenticate(self, request=None, username=None, password=None, **kwargs):
- logger.info('Authentication LDAP backend')
- if username is None or password is None:
- logger.info('No username or password')
- return None
- match, msg = self.pre_check(username, password)
- if not match:
- logger.info('Authenticate failed: {}'.format(msg))
- return None
- ldap_user = LDAPUser(self, username=username.strip(), request=request)
- user = self.authenticate_ldap_user(ldap_user, password)
- logger.info('Authenticate user: {}'.format(user))
- return user if self.user_can_authenticate(user) else None
-
def get_user(self, user_id):
user = None
try:
@@ -111,6 +82,67 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
user = ldap_user.populate_user()
return user
+ def authenticate(self, request=None, username=None, password=None, **kwargs):
+ logger.info('Authentication LDAP backend')
+ if username is None or password is None:
+ logger.info('No username or password')
+ return None
+ match, msg = self.pre_check(username, password)
+ if not match:
+ logger.info('Authenticate failed: {}'.format(msg))
+ return None
+ ldap_user = LDAPUser(self, username=username.strip(), request=request)
+ user = self.authenticate_ldap_user(ldap_user, password)
+ logger.info('Authenticate user: {}'.format(user))
+ return user if self.user_can_authenticate(user) else None
+
+ def pre_check(self, username, password):
+ if not self.is_enabled():
+ error = 'Not enabled auth ldap'
+ return False, error
+ if not username:
+ error = 'Username is None'
+ return False, error
+ if not password:
+ error = 'Password is None'
+ return False, error
+ if self.is_user_login_only_in_users:
+ user_model = self.get_user_model()
+ exist = user_model.objects.filter(username=username).exists()
+ if not exist:
+ error = 'user ({}) is not in the user list'.format(username)
+ return False, error
+ return True, ''
+
+
+class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBaseBackend):
+ """
+ Override this class to override _LDAPUser to LDAPUser
+ """
+
+ @staticmethod
+ def is_enabled():
+ return settings.AUTH_LDAP
+
+ @property
+ def is_user_login_only_in_users(self):
+ return settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
+
+
+class LDAPHAAuthorizationBackend(JMSBaseAuthBackend, LDAPBaseBackend):
+ """
+ Override this class to override _LDAPUser to LDAPUser
+ """
+ settings_prefix = "AUTH_LDAP_HA_"
+
+ @staticmethod
+ def is_enabled():
+ return settings.AUTH_LDAP_HA
+
+ @property
+ def is_user_login_only_in_users(self):
+ return settings.AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS
+
class LDAPUser(_LDAPUser):
@@ -126,13 +158,18 @@ class LDAPUser(_LDAPUser):
configuration in the settings.py file
is configured with a `lambda` problem value
"""
-
+ if isinstance(self.backend, LDAPAuthorizationBackend):
+ search_filter = settings.AUTH_LDAP_SEARCH_FILTER
+ search_ou = settings.AUTH_LDAP_SEARCH_OU
+ else:
+ search_filter = settings.AUTH_LDAP_HA_SEARCH_FILTER
+ search_ou = settings.AUTH_LDAP_HA_SEARCH_OU
user_search_union = [
LDAPSearch(
USER_SEARCH, ldap.SCOPE_SUBTREE,
- settings.AUTH_LDAP_SEARCH_FILTER
+ search_filter
)
- for USER_SEARCH in str(settings.AUTH_LDAP_SEARCH_OU).split("|")
+ for USER_SEARCH in str(search_ou).split("|")
]
search = LDAPSearchUnion(*user_search_union)
@@ -169,7 +206,8 @@ class LDAPUser(_LDAPUser):
else:
value = is_true(value)
except LookupError:
- logger.warning("{} does not have a value for the attribute {}".format(self.dn, attr))
+ logger.warning(
+ "{} does not have a value for the attribute {}".format(self.dn, attr))
else:
if not hasattr(self._user, field):
continue
diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py
index f29bf95e5..7586eb479 100644
--- a/apps/authentication/backends/oidc/backends.py
+++ b/apps/authentication/backends/oidc/backends.py
@@ -8,27 +8,26 @@
"""
import base64
-import requests
-from rest_framework.exceptions import ParseError
+import requests
+from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import SuspiciousOperation
from django.db import transaction
from django.urls import reverse
-from django.conf import settings
+from rest_framework.exceptions import ParseError
-from common.utils import get_logger
+from authentication.signals import user_auth_success, user_auth_failed
from authentication.utils import build_absolute_uri_for_oidc
+from common.utils import get_logger
from users.utils import construct_user_email
-
-from ..base import JMSBaseAuthBackend
-from .utils import validate_and_return_id_token
from .decorator import ssl_verification
from .signals import (
openid_create_or_update_user
)
-from authentication.signals import user_auth_success, user_auth_failed
+from .utils import validate_and_return_id_token
+from ..base import JMSBaseAuthBackend
logger = get_logger(__file__)
@@ -55,16 +54,17 @@ class UserMixin:
logger.debug(log_prompt.format(user_attrs))
username = user_attrs.get('username')
- name = user_attrs.get('name')
+ groups = user_attrs.pop('groups', None)
user, created = get_user_model().objects.get_or_create(
username=username, defaults=user_attrs
)
+ user_attrs['groups'] = groups
logger.debug(log_prompt.format("user: {}|created: {}".format(user, created)))
logger.debug(log_prompt.format("Send signal => openid create or update user"))
openid_create_or_update_user.send(
- sender=self.__class__, request=request, user=user, created=created,
- name=name, username=username, email=email
+ sender=self.__class__, request=request, user=user,
+ created=created, attrs=user_attrs,
)
return user, created
@@ -269,7 +269,8 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
# Calls the token endpoint.
logger.debug(log_prompt.format('Call the token endpoint'))
- token_response = requests.post(settings.AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT, data=token_payload, timeout=request_timeout)
+ token_response = requests.post(settings.AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT, data=token_payload,
+ timeout=request_timeout)
try:
token_response.raise_for_status()
token_response_data = token_response.json()
diff --git a/apps/authentication/backends/oidc/views.py b/apps/authentication/backends/oidc/views.py
index 56c1e4fb0..6d3df51a4 100644
--- a/apps/authentication/backends/oidc/views.py
+++ b/apps/authentication/backends/oidc/views.py
@@ -17,13 +17,16 @@ import time
from django.conf import settings
from django.contrib import auth
from django.core.exceptions import SuspiciousOperation
+from django.db import IntegrityError
from django.http import HttpResponseRedirect, QueryDict
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.http import urlencode
from django.views.generic import View
+from django.utils.translation import gettext_lazy as _
from authentication.utils import build_absolute_uri_for_oidc
+from authentication.views.mixins import FlashMessageMixin
from common.utils import safe_next_url
from .utils import get_logger
@@ -113,7 +116,7 @@ class OIDCAuthRequestView(View):
return HttpResponseRedirect(redirect_url)
-class OIDCAuthCallbackView(View):
+class OIDCAuthCallbackView(View, FlashMessageMixin):
""" Allows to complete the authentication process.
This view acts as the main endpoint to complete the authentication process involving the OIDC
@@ -165,7 +168,13 @@ class OIDCAuthCallbackView(View):
next_url = request.session.get('oidc_auth_next_url', None)
code_verifier = request.session.get('oidc_auth_code_verifier', None)
logger.debug(log_prompt.format('Process authenticate'))
- user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier)
+ try:
+ user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier)
+ except IntegrityError:
+ title = _("OpenID Error")
+ msg = _('Please check if a user with the same username or email already exists')
+ response = self.get_failed_response('/', title, msg)
+ return response
if user:
logger.debug(log_prompt.format('Login: {}'.format(user)))
auth.login(self.request, user)
diff --git a/apps/authentication/backends/saml2/backends.py b/apps/authentication/backends/saml2/backends.py
index 570557c1d..ac2aa7bb7 100644
--- a/apps/authentication/backends/saml2/backends.py
+++ b/apps/authentication/backends/saml2/backends.py
@@ -27,9 +27,13 @@ class SAML2Backend(JMSModelBackend):
log_prompt = "Get or Create user [SAML2Backend]: {}"
logger.debug(log_prompt.format('start'))
+ groups = saml_user_data.pop('groups', None)
+
user, created = get_user_model().objects.get_or_create(
username=saml_user_data['username'], defaults=saml_user_data
)
+
+ saml_user_data['groups'] = groups
logger.debug(log_prompt.format("user: {}|created: {}".format(user, created)))
logger.debug(log_prompt.format("Send signal => saml2 create or update user"))
diff --git a/apps/authentication/backends/saml2/views.py b/apps/authentication/backends/saml2/views.py
index 5a866cc47..abc79cf68 100644
--- a/apps/authentication/backends/saml2/views.py
+++ b/apps/authentication/backends/saml2/views.py
@@ -3,8 +3,10 @@ from urllib import parse
from django.conf import settings
from django.contrib import auth
+from django.db import IntegrityError
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseServerError
from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from onelogin.saml2.auth import OneLogin_Saml2_Auth
@@ -14,6 +16,7 @@ from onelogin.saml2.idp_metadata_parser import (
dict_deep_merge
)
+from authentication.views.mixins import FlashMessageMixin
from common.utils import get_logger
from .settings import JmsSaml2Settings
@@ -87,6 +90,7 @@ class PrepareRequestMixin:
('name', 'name', False),
('phone', 'phone', False),
('comment', 'comment', False),
+ ('groups', 'groups', False),
)
attr_list = []
for name, friend_name, is_required in need_attrs:
@@ -185,7 +189,7 @@ class PrepareRequestMixin:
user_attrs = {}
attr_mapping = settings.SAML2_RENAME_ATTRIBUTES
attrs = saml_instance.get_attributes()
- valid_attrs = ['username', 'name', 'email', 'comment', 'phone']
+ valid_attrs = ['username', 'name', 'email', 'comment', 'phone', 'groups']
for attr, value in attrs.items():
attr = attr.rsplit('/', 1)[-1]
@@ -242,7 +246,7 @@ class Saml2EndSessionView(View, PrepareRequestMixin):
return HttpResponseRedirect(logout_url)
-class Saml2AuthCallbackView(View, PrepareRequestMixin):
+class Saml2AuthCallbackView(View, PrepareRequestMixin, FlashMessageMixin):
def post(self, request):
log_prompt = "Process SAML2 POST requests: {}"
@@ -271,7 +275,13 @@ class Saml2AuthCallbackView(View, PrepareRequestMixin):
logger.debug(log_prompt.format('Process authenticate'))
saml_user_data = self.get_attributes(saml_instance)
- user = auth.authenticate(request=request, saml_user_data=saml_user_data)
+ try:
+ user = auth.authenticate(request=request, saml_user_data=saml_user_data)
+ except IntegrityError:
+ title = _("SAML2 Error")
+ msg = _('Please check if a user with the same username or email already exists')
+ response = self.get_failed_response('/', title, msg)
+ return response
if user and user.is_valid:
logger.debug(log_prompt.format('Login: {}'.format(user)))
auth.login(self.request, user)
diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py
index 99526e156..f1bbadcac 100644
--- a/apps/authentication/mixins.py
+++ b/apps/authentication/mixins.py
@@ -301,6 +301,7 @@ class MFAMixin:
class AuthPostCheckMixin:
+
@classmethod
def generate_reset_password_url_with_flash_msg(cls, user, message):
reset_passwd_url = reverse('authentication:reset-password')
@@ -319,20 +320,26 @@ class AuthPostCheckMixin:
@classmethod
def _check_passwd_is_too_simple(cls, user: User, password):
- if password == 'admin' or password == 'ChangeMe':
+ if not user.is_auth_backend_model():
+ return
+ if user.check_passwd_too_simple(password):
message = _('Your password is too simple, please change it for security')
url = cls.generate_reset_password_url_with_flash_msg(user, message=message)
raise errors.PasswordTooSimple(url)
@classmethod
def _check_passwd_need_update(cls, user: User):
- if user.need_update_password:
+ if not user.is_auth_backend_model():
+ return
+ if user.check_need_update_password():
message = _('You should to change your password before login')
url = cls.generate_reset_password_url_with_flash_msg(user, message)
raise errors.PasswordNeedUpdate(url)
@classmethod
def _check_password_require_reset_or_not(cls, user: User):
+ if not user.is_auth_backend_model():
+ return
if user.password_has_expired:
message = _('Your password has expired, please reset before logging in')
url = cls.generate_reset_password_url_with_flash_msg(user, message)
diff --git a/apps/authentication/tasks.py b/apps/authentication/tasks.py
index f731d6670..acd940978 100644
--- a/apps/authentication/tasks.py
+++ b/apps/authentication/tasks.py
@@ -2,13 +2,19 @@
#
from celery import shared_task
-from ops.celery.decorator import register_as_period_task
from django.contrib.sessions.models import Session
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
+from ops.celery.decorator import register_as_period_task
+
-@shared_task(verbose_name=_('Clean expired session'))
+@shared_task(
+ verbose_name=_('Clean expired session'),
+ description=_(
+ "Since user logins create sessions, the system will clean up expired sessions every 24 hours"
+ )
+)
@register_as_period_task(interval=3600 * 24)
def clean_django_sessions():
Session.objects.filter(expire_date__lt=timezone.now()).delete()
diff --git a/apps/common/api/action.py b/apps/common/api/action.py
index fb780705a..627a1468a 100644
--- a/apps/common/api/action.py
+++ b/apps/common/api/action.py
@@ -38,9 +38,15 @@ class SuggestionMixin:
class RenderToJsonMixin:
@action(methods=[POST, PUT], detail=False, url_path='render-to-json')
def render_to_json(self, request: Request, *args, **kwargs):
+ rows = request.data
+ if rows and isinstance(rows[0], dict):
+ first = list(rows[0].values())[0]
+ if first.startswith('#Help'):
+ rows.pop(0)
+
data = {
'title': (),
- 'data': request.data,
+ 'data': rows,
}
jms_context = getattr(request, 'jms_context', {})
diff --git a/apps/common/apps.py b/apps/common/apps.py
index 5fb85992a..18ed8a9a9 100644
--- a/apps/common/apps.py
+++ b/apps/common/apps.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+import os
import sys
from django.apps import AppConfig
@@ -12,8 +13,11 @@ class CommonConfig(AppConfig):
from . import signal_handlers # noqa
from . import tasks # noqa
from .signals import django_ready
+
excludes = ['migrate', 'compilemessages', 'makemigrations']
for i in excludes:
if i in sys.argv:
return
- django_ready.send(CommonConfig)
+
+ if not os.environ.get('DJANGO_DEBUG_SHELL'):
+ django_ready.send(CommonConfig)
diff --git a/apps/common/const/crontab.py b/apps/common/const/crontab.py
index e4de195eb..401f54eaa 100644
--- a/apps/common/const/crontab.py
+++ b/apps/common/const/crontab.py
@@ -1,5 +1,6 @@
CRONTAB_AT_AM_TWO = '0 2 * * *'
+CRONTAB_AT_AM_THREE = '0 3 * * *'
CRONTAB_AT_AM_TEN = '0 10 * * *'
CRONTAB_AT_PM_TWO = '0 14 * * *'
diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py
index 33b344457..07126b541 100644
--- a/apps/common/drf/parsers/base.py
+++ b/apps/common/drf/parsers/base.py
@@ -108,7 +108,7 @@ class BaseFileParser(BaseParser):
if not matched:
return v
obj_name, obj_id = matched.groups()
- if len(obj_id) < 36:
+ if obj_id.isdigit():
obj_id = int(obj_id)
return {'pk': obj_id, 'name': obj_name}
@@ -119,8 +119,6 @@ class BaseFileParser(BaseParser):
value = field.to_file_internal_value(value)
elif isinstance(field, serializers.BooleanField):
value = value.lower() in ['true', '1', 'yes']
- elif isinstance(field, serializers.ChoiceField):
- value = value
elif isinstance(field, ObjectRelatedField):
if field.many:
value = [self.id_name_to_obj(v) for v in value]
@@ -164,6 +162,15 @@ class BaseFileParser(BaseParser):
data.append(row_data)
return data
+ @staticmethod
+ def pop_help_text_if_need(rows):
+ rows = list(rows)
+ if not rows:
+ return rows
+ if rows[0][0].startswith('#Help'):
+ rows.pop(0)
+ return rows
+
def parse(self, stream, media_type=None, parser_context=None):
assert parser_context is not None, '`parser_context` should not be `None`'
@@ -192,6 +199,7 @@ class BaseFileParser(BaseParser):
request.jms_context = {}
request.jms_context['column_title_field_pairs'] = column_title_field_pairs
+ rows = self.pop_help_text_if_need(rows)
data = self.generate_data(field_names, rows)
return data
except Exception as e:
diff --git a/apps/common/drf/renders/base.py b/apps/common/drf/renders/base.py
index a6eae282e..db081c3d3 100644
--- a/apps/common/drf/renders/base.py
+++ b/apps/common/drf/renders/base.py
@@ -5,12 +5,13 @@ from datetime import datetime
import pyzipper
from django.conf import settings
+from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import encoders, json
-from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
+from common.serializers import fields as common_fields
from common.utils import get_logger
logger = get_logger(__file__)
@@ -38,24 +39,27 @@ class BaseFileRenderer(BaseRenderer):
filename_prefix = serializer.Meta.model.__name__.lower()
else:
filename_prefix = 'download'
- now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
- filename = "{}_{}.{}".format(filename_prefix, now, self.format)
+ suffix = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
+ if self.template == 'import':
+ suffix = 'template'
+ filename = "{}_{}.{}".format(filename_prefix, suffix, self.format)
disposition = 'attachment; filename="{}"'.format(filename)
response['Content-Disposition'] = disposition
def get_rendered_fields(self):
fields = self.serializer.fields
+ meta = getattr(self.serializer, 'Meta', None)
if self.template == 'import':
fields = [v for k, v in fields.items() if not v.read_only and k != "org_id" and k != 'id']
+ fields_unimport = getattr(meta, 'fields_unimport_template', [])
+ fields = [v for v in fields if v.field_name not in fields_unimport]
elif self.template == 'update':
fields = [v for k, v in fields.items() if not v.read_only and k != "org_id"]
else:
fields = [v for k, v in fields.items() if not v.write_only and k != "org_id"]
- meta = getattr(self.serializer, 'Meta', None)
- if meta:
- fields_unexport = getattr(meta, 'fields_unexport', [])
- fields = [v for v in fields if v.field_name not in fields_unexport]
+ fields_unexport = getattr(meta, 'fields_unexport', [])
+ fields = [v for v in fields if v.field_name not in fields_unexport]
return fields
@staticmethod
@@ -105,10 +109,10 @@ class BaseFileRenderer(BaseRenderer):
value = field.to_file_representation(value)
elif isinstance(value, bool):
value = 'Yes' if value else 'No'
- elif isinstance(field, LabeledChoiceField):
+ elif isinstance(field, common_fields.LabeledChoiceField):
value = value or {}
value = '{}({})'.format(value.get('label'), value.get('value'))
- elif isinstance(field, ObjectRelatedField):
+ elif isinstance(field, common_fields.ObjectRelatedField):
if field.many:
value = [self.to_id_name(v) for v in value]
else:
@@ -126,6 +130,53 @@ class BaseFileRenderer(BaseRenderer):
value = json.dumps(value, cls=encoders.JSONEncoder, ensure_ascii=False)
return str(value)
+ def get_field_help_text(self, field):
+ text = ''
+ if hasattr(field, 'get_render_help_text'):
+ text = field.get_render_help_text()
+ elif isinstance(field, serializers.BooleanField):
+ text = _('Yes/No')
+ elif isinstance(field, serializers.CharField):
+ if field.max_length:
+ text = _('Text, max length {}').format(field.max_length)
+ else:
+ text = _("Long text, no length limit")
+ elif isinstance(field, serializers.IntegerField):
+ text = _('Number, min {} max {}').format(field.min_value, field.max_value)
+ text = text.replace('min None', '').replace('max None', '')
+ elif isinstance(field, serializers.DateTimeField):
+ text = _('Datetime format {}').format(timezone.now().strftime(settings.REST_FRAMEWORK['DATETIME_FORMAT']))
+ elif isinstance(field, serializers.IPAddressField):
+ text = _('IP')
+ elif isinstance(field, serializers.ChoiceField):
+ choices = [str(v) for v in field.choices.keys()]
+ if isinstance(field, common_fields.LabeledChoiceField):
+ text = _("Choices, format name(value), name is optional for human read,"
+ " value is requisite, options {}").format(','.join(choices))
+ else:
+ text = _("Choices, options {}").format(",".join(choices))
+ elif isinstance(field, common_fields.PhoneField):
+ text = _("Phone number, format +8612345678901")
+ elif isinstance(field, common_fields.LabeledChoiceField):
+ text = _('Label, format ["key:value"]')
+ elif isinstance(field, common_fields.ObjectRelatedField):
+ text = _("Object, format name(id), name is optional for human read, id is requisite")
+ elif isinstance(field, serializers.PrimaryKeyRelatedField):
+ text = _('Object, format id')
+ elif isinstance(field, serializers.ManyRelatedField):
+ child_relation_class_name = field.child_relation.__class__.__name__
+ if child_relation_class_name == "ObjectRelatedField":
+ text = _('Objects, format ["name(id)", ...], name is optional for human read, id is requisite')
+ elif child_relation_class_name == "LabelRelatedField":
+ text = _('Labels, format ["key:value", ...], if label not exists, will create it')
+ else:
+ text = _('Objects, format ["id", ...]')
+ elif isinstance(field, serializers.ListSerializer):
+ child = field.child
+ if hasattr(child, 'get_render_help_text'):
+ text = child.get_render_help_text()
+ return text
+
def generate_rows(self, data, render_fields):
for item in data:
row = []
@@ -135,6 +186,17 @@ class BaseFileRenderer(BaseRenderer):
row.append(value)
yield row
+ def write_help_text_if_need(self):
+ if self.template == 'export':
+ return
+ fields = self.get_rendered_fields()
+ row = []
+ for f in fields:
+ text = self.get_field_help_text(f)
+ row.append(text)
+ row[0] = '#Help ' + str(row[0])
+ self.write_row(row)
+
@abc.abstractmethod
def initial_writer(self):
raise NotImplementedError
@@ -184,6 +246,7 @@ class BaseFileRenderer(BaseRenderer):
rows = self.generate_rows(data, rendered_fields)
self.initial_writer()
self.write_column_titles(column_titles)
+ self.write_help_text_if_need()
self.write_rows(rows)
self.after_render()
value = self.get_rendered_value()
diff --git a/apps/common/drf/renders/csv.py b/apps/common/drf/renders/csv.py
index a8729294b..8c676b60f 100644
--- a/apps/common/drf/renders/csv.py
+++ b/apps/common/drf/renders/csv.py
@@ -2,17 +2,17 @@
#
import codecs
+
import unicodecsv
from six import BytesIO
from .base import BaseFileRenderer
from ..const import CSV_FILE_ESCAPE_CHARS
-class CSVFileRenderer(BaseFileRenderer):
+class CSVFileRenderer(BaseFileRenderer):
media_type = 'text/csv'
format = 'csv'
-
writer = None
buffer = None
diff --git a/apps/common/plugins/es.py b/apps/common/plugins/es.py
index c4b2d0921..df53d3f46 100644
--- a/apps/common/plugins/es.py
+++ b/apps/common/plugins/es.py
@@ -409,8 +409,13 @@ class QuerySet(DJQuerySet):
if not filter_calls:
return {}
names, multi_args, multi_kwargs = zip(*filter_calls)
+ args = {
+ key: value
+ for arg in multi_args if arg
+ for key, value in arg[0].children
+ }
kwargs = reduce(lambda x, y: {**x, **y}, multi_kwargs, {})
-
+ kwargs.update(args)
striped_kwargs = {}
for k, v in kwargs.items():
k = k.replace('__exact', '')
diff --git a/apps/common/storage/base.py b/apps/common/storage/base.py
index cb1d1b5b7..99ddc51ba 100644
--- a/apps/common/storage/base.py
+++ b/apps/common/storage/base.py
@@ -1,15 +1,32 @@
import os
import jms_storage
-
from django.conf import settings
+from django.core.files.storage import default_storage
-from terminal.models import default_storage, ReplayStorage
from common.utils import get_logger, make_dirs
+from terminal.models import ReplayStorage
logger = get_logger(__name__)
+def get_multi_object_storage():
+ replay_storages = ReplayStorage.objects.all()
+ configs = {}
+ for storage in replay_storages:
+ if storage.type_sftp:
+ continue
+ if storage.type_null_or_server:
+ continue
+ configs[storage.name] = storage.config
+ if settings.SERVER_REPLAY_STORAGE:
+ configs['SERVER_REPLAY_STORAGE'] = settings.SERVER_REPLAY_STORAGE
+ if not configs:
+ return None
+ storage = jms_storage.get_multi_object_storage(configs)
+ return storage
+
+
class BaseStorageHandler(object):
NAME = ''
@@ -24,20 +41,10 @@ class BaseStorageHandler(object):
raise NotImplementedError
def download(self):
- replay_storages = ReplayStorage.objects.all()
- configs = {}
- for storage in replay_storages:
- if storage.type_sftp:
- continue
- if storage.type_null_or_server:
- continue
- configs[storage.name] = storage.config
- if settings.SERVER_REPLAY_STORAGE:
- configs['SERVER_REPLAY_STORAGE'] = settings.SERVER_REPLAY_STORAGE
- if not configs:
+ storage = get_multi_object_storage()
+ if not storage:
msg = f"Not found {self.NAME} file, and not remote storage set"
return None, msg
- storage = jms_storage.get_multi_object_storage(configs)
remote_path, local_path = self.get_file_path(storage=storage)
if not remote_path:
diff --git a/apps/common/storage/replay.py b/apps/common/storage/replay.py
index 63b58c6cd..de1a56e82 100644
--- a/apps/common/storage/replay.py
+++ b/apps/common/storage/replay.py
@@ -1,7 +1,15 @@
+import json
+import os
+import tarfile
from itertools import chain
-from terminal.models import default_storage
-from .base import BaseStorageHandler
+from django.core.files.storage import default_storage
+
+from common.utils import make_dirs, get_logger
+from terminal.models import Session
+from .base import BaseStorageHandler, get_multi_object_storage
+
+logger = get_logger(__name__)
class ReplayStorageHandler(BaseStorageHandler):
@@ -29,3 +37,74 @@ class ReplayStorageHandler(BaseStorageHandler):
url = default_storage.url(_local_path)
return _local_path, url
return None, f'{self.NAME} not found.'
+
+
+class SessionPartReplayStorageHandler(object):
+ Name = 'SessionPartReplayStorageHandler'
+
+ def __init__(self, obj: Session):
+ self.obj = obj
+
+ def find_local_part_file_path(self, part_filename):
+ local_path = self.obj.get_replay_part_file_local_storage_path(part_filename)
+ if default_storage.exists(local_path):
+ url = default_storage.url(local_path)
+ return local_path, url
+ return None, '{} not found.'.format(part_filename)
+
+ def download_part_file(self, part_filename):
+ storage = get_multi_object_storage()
+ if not storage:
+ msg = "Not found {} file, and not remote storage set".format(part_filename)
+ return None, msg
+ local_path = self.obj.get_replay_part_file_local_storage_path(part_filename)
+ remote_path = self.obj.get_replay_part_file_relative_path(part_filename)
+
+ # 保存到storage的路径
+ target_path = os.path.join(default_storage.base_location, local_path)
+
+ target_dir = os.path.dirname(target_path)
+ if not os.path.isdir(target_dir):
+ make_dirs(target_dir, exist_ok=True)
+
+ ok, err = storage.download(remote_path, target_path)
+ if not ok:
+ msg = 'Failed download {} file: {}'.format(part_filename, err)
+ logger.error(msg)
+ return None, msg
+ url = default_storage.url(local_path)
+ return local_path, url
+
+ def get_part_file_path_url(self, part_filename):
+ local_path, url = self.find_local_part_file_path(part_filename)
+ if local_path is None:
+ local_path, url = self.download_part_file(part_filename)
+ return local_path, url
+
+ def prepare_offline_tar_file(self):
+ replay_meta_filename = '{}.replay.json'.format(self.obj.id)
+ meta_local_path, url_or_error = self.get_part_file_path_url(replay_meta_filename)
+ if not meta_local_path:
+ raise FileNotFoundError(f'{replay_meta_filename} not found: {url_or_error}')
+ meta_local_abs_path = os.path.join(default_storage.base_location, meta_local_path)
+ with open(meta_local_abs_path, 'r') as f:
+ meta_data = json.load(f)
+ if not meta_data:
+ raise FileNotFoundError(f'{replay_meta_filename} is empty')
+ part_filenames = [part_file.get('name') for part_file in meta_data.get('files', [])]
+ for part_filename in part_filenames:
+ if not part_filename:
+ continue
+ local_path, url_or_error = self.get_part_file_path_url(part_filename)
+ if not local_path:
+ raise FileNotFoundError(f'{part_filename} not found: {url_or_error}')
+ dir_path = os.path.dirname(meta_local_abs_path)
+ offline_filename = '{}.tar'.format(self.obj.id)
+ offline_filename_abs_path = os.path.join(dir_path, offline_filename)
+ if not os.path.exists(offline_filename_abs_path):
+ with tarfile.open(offline_filename_abs_path, 'w') as f:
+ f.add(str(meta_local_abs_path), arcname=replay_meta_filename)
+ for part_filename in part_filenames:
+ local_abs_path = os.path.join(dir_path, part_filename)
+ f.add(local_abs_path, arcname=part_filename)
+ return open(offline_filename_abs_path, 'rb')
diff --git a/apps/common/tasks.py b/apps/common/tasks.py
index 5bffe07f4..06f15a4c3 100644
--- a/apps/common/tasks.py
+++ b/apps/common/tasks.py
@@ -1,10 +1,10 @@
import os
+import jms_storage
from celery import shared_task
from django.conf import settings
from django.core.mail import send_mail, EmailMultiAlternatives, get_connection
from django.utils.translation import gettext_lazy as _
-import jms_storage
from .utils import get_logger
@@ -28,7 +28,13 @@ def task_activity_callback(self, subject, message, recipient_list, *args, **kwar
return resource_ids,
-@shared_task(verbose_name=_("Send email"), activity_callback=task_activity_callback)
+@shared_task(
+ verbose_name=_("Send email"),
+ activity_callback=task_activity_callback,
+ description=_(
+ "This task will be executed when sending email notifications"
+ )
+)
def send_mail_async(*args, **kwargs):
""" Using celery to send email async
@@ -55,7 +61,14 @@ def send_mail_async(*args, **kwargs):
logger.error("Sending mail error: {}".format(e))
-@shared_task(verbose_name=_("Send email attachment"), activity_callback=task_activity_callback)
+@shared_task(
+ verbose_name=_("Send email attachment"),
+ activity_callback=task_activity_callback,
+ description=_(
+ """When an account password is changed or an account backup generates attachments,
+ this task needs to be executed for sending emails and handling attachments"""
+ )
+)
def send_mail_attachment_async(subject, message, recipient_list, attachment_list=None):
if attachment_list is None:
attachment_list = []
@@ -77,7 +90,12 @@ def send_mail_attachment_async(subject, message, recipient_list, attachment_list
logger.error("Sending mail attachment error: {}".format(e))
-@shared_task(verbose_name=_('Upload session replay to external storage'))
+@shared_task(
+ verbose_name=_('Upload account backup to external storage'),
+ description=_(
+ "When performing an account backup, this task needs to be executed to external storage (SFTP)"
+ )
+)
def upload_backup_to_obj_storage(recipient, upload_file):
logger.info(f'Start upload file : {upload_file}')
remote_path = os.path.join('account_backup', os.path.basename(upload_file))
diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py
index 680e7cc1b..cb4794b66 100644
--- a/apps/common/utils/common.py
+++ b/apps/common/utils/common.py
@@ -13,6 +13,7 @@ from collections import OrderedDict
from functools import wraps
from itertools import chain
+import html2text
import psutil
from django.conf import settings
from django.templatetags.static import static
@@ -157,7 +158,7 @@ def is_uuid(seq):
def get_request_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
if x_forwarded_for and x_forwarded_for[0]:
- login_ip = x_forwarded_for[0]
+ login_ip = x_forwarded_for[0].split(":")[0]
return login_ip
login_ip = request.META.get('REMOTE_ADDR', '')
@@ -292,7 +293,7 @@ def get_docker_mem_usage_if_limit():
inactive_file = int(inactive_file)
return ((usage_in_bytes - inactive_file) / limit_in_bytes) * 100
- except Exception as e:
+ except Exception:
return None
@@ -421,3 +422,14 @@ def distinct(seq, key=None):
def is_macos():
return platform.system() == 'Darwin'
+
+
+def convert_html_to_markdown(html_str):
+ h = html2text.HTML2Text()
+ h.body_width = 0
+ h.ignore_links = False
+
+ markdown = h.handle(html_str)
+ markdown = markdown.replace('\n\n', '\n')
+ markdown = markdown.replace('\n ', '\n')
+ return markdown
diff --git a/apps/common/utils/http.py b/apps/common/utils/http.py
index baf741407..30baa8d01 100644
--- a/apps/common/utils/http.py
+++ b/apps/common/utils/http.py
@@ -39,10 +39,6 @@ def iso8601_to_unixtime(time_string):
return to_unixtime(time_string, _ISO8601_FORMAT)
-def get_remote_addr(request):
- return request.META.get("HTTP_X_FORWARDED_HOST") or request.META.get("REMOTE_ADDR")
-
-
def is_true(value):
return value in BooleanField.TRUE_VALUES
diff --git a/apps/common/utils/verify_code.py b/apps/common/utils/verify_code.py
index dea844768..a5f6c668c 100644
--- a/apps/common/utils/verify_code.py
+++ b/apps/common/utils/verify_code.py
@@ -13,7 +13,13 @@ from common.utils.random import random_string
logger = get_logger(__file__)
-@shared_task(verbose_name=_('Send SMS code'))
+@shared_task(
+ verbose_name=_('Send SMS code'),
+ description=_(
+ """When resetting a password, forgetting a password, or verifying MFA, this task needs to
+ be executed to send SMS messages"""
+ )
+)
def send_sms_async(target, code):
SMS().send_verify_code(target, code)
diff --git a/apps/i18n/chen/en.json b/apps/i18n/chen/en.json
index dbb7953f0..ba0347f7c 100644
--- a/apps/i18n/chen/en.json
+++ b/apps/i18n/chen/en.json
@@ -67,5 +67,8 @@
"Version": "Version",
"ViewData": "View data",
"WaitCommandReviewMessage": "The review request has been initiated, please wait for the review results",
- "initializingDatasourceFailedMessage": "Connection failed, please check if the database connection configuration is correct"
+ "initializingDatasourceFailedMessage": "Connection failed, please check if the database connection configuration is correct",
+ "Warning": "Warning",
+ "ExecutionCanceled": "Execution Canceled",
+ "CommandWarningDialogMessage": "The command you executed is risky and an alert notification will be sent to the administrator. Do you want to continue?"
}
\ No newline at end of file
diff --git a/apps/i18n/chen/ja.json b/apps/i18n/chen/ja.json
index 981db677e..b04aca68a 100644
--- a/apps/i18n/chen/ja.json
+++ b/apps/i18n/chen/ja.json
@@ -9,6 +9,7 @@
"CommandReviewMessage": "入力されたコマンドはレビュー後に実行されます。レビューリクエストを送信しますか?",
"CommandReviewRejectBy": "コマンドレビューが%sに拒否されました",
"CommandReviewTimeoutError": "コマンドレビューがタイムアウトしました",
+ "CommandWarningDialogMessage": "あなたが実行したコマンドにはリスクがあり、警告通知が管理者に送信されます。続行しますか?",
"Confirm": "確認",
"ConnectError": "接続に失敗しました",
"ConnectSuccess": "接続に成功しました",
@@ -22,6 +23,7 @@
"ErrorMessage": "エラーメッセージ",
"ExecuteError": "実行に失敗しました",
"ExecuteSuccess": "実行に成功しました",
+ "ExecutionCanceled": "実行がキャンセルされました",
"ExportALL": "すべてのデータをエクスポート",
"ExportAll": "すべてエクスポート",
"ExportCurrent": "現在のページをエクスポート",
@@ -67,5 +69,6 @@
"Version": "バージョン",
"ViewData": "データを見る",
"WaitCommandReviewMessage": "レビューリクエストが送信されました。レビュー結果をお待ちください",
+ "Warning": "警告",
"initializingDatasourceFailedMessage": "接続に失敗しました。データベース接続設定が正しいか確認してください"
}
\ No newline at end of file
diff --git a/apps/i18n/chen/zh.json b/apps/i18n/chen/zh.json
index 523475168..62ae1aaa6 100644
--- a/apps/i18n/chen/zh.json
+++ b/apps/i18n/chen/zh.json
@@ -67,5 +67,8 @@
"Version": "版本",
"ViewData": "查看数据",
"WaitCommandReviewMessage": "复核请求已发起, 请等待复核结果",
- "initializingDatasourceFailedMessage": "连接失败,请检查数据库连接配置是否正确"
+ "initializingDatasourceFailedMessage": "连接失败,请检查数据库连接配置是否正确",
+ "Warning": "警告",
+ "ExecutionCanceled": "执行已取消",
+ "CommandWarningDialogMessage": "您执行的命令存在风险,告警通知将发送给管理员。是否继续?"
}
\ No newline at end of file
diff --git a/apps/i18n/chen/zh_hant.json b/apps/i18n/chen/zh_hant.json
index 83eed337d..253fb449b 100644
--- a/apps/i18n/chen/zh_hant.json
+++ b/apps/i18n/chen/zh_hant.json
@@ -9,6 +9,7 @@
"CommandReviewMessage": "您輸入的命令需要覆核後才可以執行,是否發起覆核請求?",
"CommandReviewRejectBy": "命令覆核被 %s 拒絕",
"CommandReviewTimeoutError": "命令覆核超時",
+ "CommandWarningDialogMessage": "您進行的動作存在風險,警告通知將會寄給管理員。你確定要繼續嗎?",
"Confirm": "確認",
"ConnectError": "連接失敗",
"ConnectSuccess": "連接成功",
@@ -22,6 +23,7 @@
"ErrorMessage": "錯誤消息",
"ExecuteError": "執行失敗",
"ExecuteSuccess": "執行成功",
+ "ExecutionCanceled": "動作已取消",
"ExportALL": "匯出所有資料",
"ExportAll": "匯出全部",
"ExportCurrent": "匯出當前頁面",
@@ -67,5 +69,6 @@
"Version": "版本",
"ViewData": "查看資料",
"WaitCommandReviewMessage": "覆核請求已發起,請等待覆核結果",
+ "Warning": "警告。",
"initializingDatasourceFailedMessage": "連接失敗,請檢查資料庫連接配置是否正確"
}
\ No newline at end of file
diff --git a/apps/i18n/core/en/LC_MESSAGES/django.po b/apps/i18n/core/en/LC_MESSAGES/django.po
index 5006b894a..1c9fab98d 100644
--- a/apps/i18n/core/en/LC_MESSAGES/django.po
+++ b/apps/i18n/core/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-12 18:34+0800\n"
+"POT-Creation-Date: 2024-09-19 16:31+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -109,11 +109,11 @@ msgstr ""
msgid "Plan execution end"
msgstr ""
-#: accounts/automations/change_secret/manager.py:100
+#: accounts/automations/change_secret/manager.py:97
msgid "No pending accounts found"
msgstr ""
-#: accounts/automations/change_secret/manager.py:227
+#: accounts/automations/change_secret/manager.py:225
#, python-format
msgid "Success: %s, Failed: %s, Total: %s"
msgstr ""
@@ -124,10 +124,11 @@ msgstr ""
#: authentication/confirm/password.py:24 authentication/confirm/password.py:26
#: authentication/forms.py:28
#: authentication/templates/authentication/login.html:362
-#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:51
-#: settings/serializers/msg.py:37 settings/serializers/terminal.py:28
-#: terminal/serializers/storage.py:123 terminal/serializers/storage.py:142
-#: users/forms/profile.py:21 users/serializers/user.py:144
+#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap_ha.py:34 settings/serializers/msg.py:37
+#: settings/serializers/terminal.py:28 terminal/serializers/storage.py:123
+#: terminal/serializers/storage.py:142 users/forms/profile.py:21
+#: users/serializers/user.py:144
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -145,7 +146,7 @@ msgid "Access key"
msgstr ""
#: accounts/const/account.py:9 authentication/backends/passkey/models.py:16
-#: authentication/models/sso_token.py:14 settings/serializers/feature.py:52
+#: authentication/models/sso_token.py:14 settings/serializers/feature.py:55
msgid "Token"
msgstr ""
@@ -208,8 +209,8 @@ msgstr ""
msgid "Verify account"
msgstr ""
-#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:24
-#: accounts/tasks/remove_account.py:33
+#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:25
+#: accounts/tasks/remove_account.py:38
msgid "Remove account"
msgstr ""
@@ -311,11 +312,11 @@ msgid "Pending"
msgstr ""
#: accounts/const/vault.py:8 assets/const/category.py:12
-#: assets/models/asset/database.py:9 assets/models/asset/database.py:24
+#: assets/models/asset/database.py:10 assets/models/asset/database.py:29
msgid "Database"
msgstr ""
-#: accounts/const/vault.py:9 settings/serializers/feature.py:43
+#: accounts/const/vault.py:9 settings/serializers/feature.py:46
msgid "HCP Vault"
msgstr ""
@@ -340,14 +341,14 @@ msgstr ""
#: accounts/models/account.py:49
#: accounts/models/automations/gather_account.py:16
#: accounts/serializers/account/account.py:226
-#: accounts/serializers/account/account.py:271
+#: accounts/serializers/account/account.py:272
#: accounts/serializers/account/gathered_account.py:10
#: accounts/serializers/automations/change_secret.py:111
#: accounts/serializers/automations/change_secret.py:143
#: accounts/templates/accounts/asset_account_change_info.html:7
#: accounts/templates/accounts/change_secret_failed_info.html:11
-#: acls/serializers/base.py:123 assets/models/asset/common.py:95
-#: assets/models/asset/common.py:349 assets/models/cmd_filter.py:36
+#: acls/serializers/base.py:123 assets/models/asset/common.py:102
+#: assets/models/asset/common.py:362 assets/models/cmd_filter.py:36
#: audits/models.py:58 authentication/models/connection_token.py:36
#: perms/models/asset_permission.py:69 terminal/backends/command/models.py:17
#: terminal/models/session/session.py:32 terminal/notifications.py:155
@@ -360,13 +361,13 @@ msgstr ""
#: accounts/models/account.py:53 accounts/models/template.py:16
#: accounts/serializers/account/account.py:233
-#: accounts/serializers/account/account.py:281
-#: accounts/serializers/account/template.py:27
+#: accounts/serializers/account/account.py:282
+#: accounts/serializers/account/template.py:37
#: authentication/serializers/connect_token_secret.py:50
msgid "Su from"
msgstr "Switch from"
-#: accounts/models/account.py:55 assets/const/protocol.py:189
+#: accounts/models/account.py:55 assets/const/protocol.py:195
#: settings/serializers/auth/cas.py:25 terminal/models/applet/applet.py:36
#: terminal/models/virtualapp/virtualapp.py:21
msgid "Version"
@@ -387,7 +388,7 @@ msgstr ""
#: accounts/templates/accounts/change_secret_failed_info.html:12
#: acls/serializers/base.py:124
#: acls/templates/acls/asset_login_reminder.html:10
-#: assets/serializers/gateway.py:28 audits/models.py:59
+#: assets/serializers/gateway.py:33 audits/models.py:59
#: authentication/api/connection_token.py:411 ops/models/base.py:18
#: perms/models/asset_permission.py:75 settings/serializers/msg.py:33
#: terminal/backends/command/models.py:18 terminal/models/session/session.py:34
@@ -459,9 +460,9 @@ msgstr ""
#: accounts/models/automations/backup_account.py:120
#: assets/models/automations/base.py:115 audits/models.py:65
-#: ops/models/base.py:55 ops/models/celery.py:88 ops/models/job.py:242
+#: ops/models/base.py:55 ops/models/celery.py:89 ops/models/job.py:242
#: ops/templates/ops/celery_task_log.html:101
-#: perms/models/asset_permission.py:78
+#: perms/models/asset_permission.py:78 settings/serializers/feature.py:25
#: settings/templates/ldap/_msg_import_ldap_user.html:5
#: terminal/models/applet/host.py:141 terminal/models/session/session.py:45
#: tickets/models/ticket/apply_application.py:30
@@ -471,7 +472,7 @@ msgstr ""
#: accounts/models/automations/backup_account.py:123
#: authentication/templates/authentication/_msg_oauth_bind.html:11
-#: notifications/notifications.py:194
+#: notifications/notifications.py:199
#: settings/templates/ldap/_msg_import_ldap_user.html:3
msgid "Time"
msgstr ""
@@ -485,7 +486,7 @@ msgstr ""
#: accounts/serializers/automations/base.py:56
#: assets/models/automations/base.py:122
#: assets/serializers/automations/base.py:40 xpack/plugins/cloud/models.py:240
-#: xpack/plugins/cloud/serializers/task.py:237
+#: xpack/plugins/cloud/serializers/task.py:243
msgid "Trigger mode"
msgstr ""
@@ -550,7 +551,8 @@ msgstr ""
#: accounts/models/automations/gather_account.py:58
#: accounts/serializers/account/backup.py:40
#: accounts/serializers/automations/change_secret.py:58
-#: settings/serializers/auth/ldap.py:99 settings/serializers/msg.py:45
+#: settings/serializers/auth/ldap.py:100
+#: settings/serializers/auth/ldap_ha.py:82 settings/serializers/msg.py:45
msgid "Recipient"
msgstr "Recipients"
@@ -572,7 +574,7 @@ msgstr ""
#: accounts/models/automations/change_secret.py:42
#: assets/models/automations/base.py:116 ops/models/base.py:56
-#: ops/models/celery.py:89 ops/models/job.py:243
+#: ops/models/celery.py:90 ops/models/job.py:243
#: terminal/models/applet/host.py:142
msgid "Date finished"
msgstr ""
@@ -584,7 +586,7 @@ msgstr ""
#: terminal/models/applet/applet.py:331 terminal/models/applet/host.py:140
#: terminal/models/component/status.py:30
#: terminal/models/virtualapp/virtualapp.py:99
-#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:136
+#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:148
#: terminal/serializers/virtualapp.py:35 tickets/models/ticket/general.py:284
#: tickets/serializers/super_ticket.py:13
#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:225
@@ -592,8 +594,8 @@ msgstr ""
msgid "Status"
msgstr ""
-#: accounts/models/automations/change_secret.py:47
-#: accounts/serializers/account/account.py:273
+#: accounts/models/automations/change_secret.py:46
+#: accounts/serializers/account/account.py:274
#: accounts/templates/accounts/change_secret_failed_info.html:13
#: assets/const/automation.py:8
#: authentication/templates/authentication/passkey.html:173
@@ -603,7 +605,7 @@ msgstr ""
msgid "Error"
msgstr ""
-#: accounts/models/automations/change_secret.py:51
+#: accounts/models/automations/change_secret.py:50
msgid "Change secret record"
msgstr ""
@@ -634,7 +636,7 @@ msgid "Address login"
msgstr ""
#: accounts/models/automations/gather_account.py:44
-#: accounts/tasks/gather_accounts.py:29
+#: accounts/tasks/gather_accounts.py:30
msgid "Gather asset accounts"
msgstr ""
@@ -655,7 +657,7 @@ msgstr ""
#: audits/models.py:92 audits/serializers.py:84
#: authentication/serializers/connect_token_secret.py:119
#: authentication/templates/authentication/_access_key_modal.html:34
-#: perms/serializers/permission.py:41 perms/serializers/permission.py:63
+#: perms/serializers/permission.py:52 perms/serializers/permission.py:74
#: tickets/serializers/ticket/ticket.py:21
msgid "Action"
msgstr ""
@@ -669,7 +671,7 @@ msgid "Verify asset account"
msgstr ""
#: accounts/models/base.py:37 accounts/models/base.py:67
-#: accounts/serializers/account/account.py:463
+#: accounts/serializers/account/account.py:464
#: accounts/serializers/account/base.py:17
#: accounts/serializers/automations/change_secret.py:47
#: authentication/serializers/connect_token_secret.py:42
@@ -691,28 +693,28 @@ msgstr ""
msgid "Secret strategy"
msgstr ""
-#: accounts/models/base.py:44 accounts/serializers/account/template.py:24
+#: accounts/models/base.py:44 accounts/serializers/account/template.py:34
#: accounts/serializers/automations/change_secret.py:46
msgid "Password rules"
msgstr ""
#: accounts/models/base.py:64 accounts/serializers/account/virtual.py:20
#: acls/models/base.py:35 acls/models/base.py:96 acls/models/command_acl.py:21
-#: acls/serializers/base.py:35 assets/models/asset/common.py:93
-#: assets/models/asset/common.py:159 assets/models/cmd_filter.py:21
+#: acls/serializers/base.py:35 assets/models/asset/common.py:100
+#: assets/models/asset/common.py:166 assets/models/cmd_filter.py:21
#: assets/models/domain.py:19 assets/models/label.py:18
#: assets/models/platform.py:15 assets/models/platform.py:94
-#: assets/serializers/asset/common.py:149 assets/serializers/platform.py:153
-#: assets/serializers/platform.py:282
+#: assets/serializers/asset/common.py:169 assets/serializers/platform.py:157
+#: assets/serializers/platform.py:277
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:113
#: authentication/serializers/connect_token_secret.py:169 labels/models.py:11
-#: ops/mixin.py:21 ops/models/adhoc.py:20 ops/models/celery.py:15
-#: ops/models/celery.py:80 ops/models/job.py:142 ops/models/playbook.py:28
+#: ops/mixin.py:28 ops/models/adhoc.py:19 ops/models/celery.py:15
+#: ops/models/celery.py:81 ops/models/job.py:142 ops/models/playbook.py:30
#: ops/serializers/job.py:18 orgs/models.py:82
#: perms/models/asset_permission.py:61 rbac/models/role.py:29
-#: rbac/serializers/role.py:28 settings/models.py:34 settings/models.py:183
+#: rbac/serializers/role.py:28 settings/models.py:35 settings/models.py:184
#: settings/serializers/msg.py:89 settings/serializers/terminal.py:9
#: terminal/models/applet/applet.py:34 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:109
@@ -824,7 +826,6 @@ msgid ""
msgstr ""
#: accounts/notifications.py:83
-#: accounts/templates/accounts/asset_account_change_info.html:3
msgid "Gather account change information"
msgstr ""
@@ -844,11 +845,16 @@ msgstr ""
msgid "Exist policy"
msgstr ""
+#: accounts/serializers/account/account.py:181
+#: accounts/serializers/account/account.py:340
+msgid "Account already exists"
+msgstr ""
+
#: accounts/serializers/account/account.py:206 assets/models/label.py:21
-#: assets/models/platform.py:95 assets/serializers/asset/common.py:125
-#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:177
-#: assets/serializers/platform.py:283 perms/serializers/user_permission.py:26
-#: settings/models.py:36 tickets/models/ticket/apply_application.py:13
+#: assets/models/platform.py:95 assets/serializers/asset/common.py:145
+#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:172
+#: assets/serializers/platform.py:278 perms/serializers/user_permission.py:26
+#: settings/models.py:37 tickets/models/ticket/apply_application.py:13
#: users/models/preference.py:12
msgid "Category"
msgstr ""
@@ -857,15 +863,15 @@ msgstr ""
#: accounts/serializers/automations/base.py:55 acls/models/command_acl.py:24
#: acls/serializers/command_acl.py:19 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:96
-#: assets/serializers/asset/common.py:126 assets/serializers/platform.py:155
-#: assets/serializers/platform.py:176 audits/serializers.py:53
+#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:159
+#: assets/serializers/platform.py:171 audits/serializers.py:53
#: audits/serializers.py:170
#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:150
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
#: terminal/models/component/storage.py:58
-#: terminal/models/component/storage.py:154 terminal/serializers/applet.py:29
-#: terminal/serializers/session.py:23 terminal/serializers/storage.py:278
-#: terminal/serializers/storage.py:291 tickets/models/comment.py:26
+#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:29
+#: terminal/serializers/session.py:23 terminal/serializers/storage.py:281
+#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
@@ -876,62 +882,58 @@ msgstr ""
msgid "Asset not found"
msgstr ""
-#: accounts/serializers/account/account.py:262
+#: accounts/serializers/account/account.py:263
msgid "Has secret"
msgstr ""
-#: accounts/serializers/account/account.py:272 ops/models/celery.py:83
+#: accounts/serializers/account/account.py:273 ops/models/celery.py:84
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:49
#: tickets/models/ticket/general.py:280 tickets/serializers/super_ticket.py:14
msgid "State"
msgstr ""
-#: accounts/serializers/account/account.py:274
+#: accounts/serializers/account/account.py:275
msgid "Changed"
msgstr ""
-#: accounts/serializers/account/account.py:284
+#: accounts/serializers/account/account.py:285
#: accounts/serializers/automations/base.py:22 acls/models/base.py:97
#: acls/templates/acls/asset_login_reminder.html:9
#: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:34
-#: assets/serializers/platform.py:185 assets/serializers/platform.py:217
+#: assets/serializers/platform.py:180 assets/serializers/platform.py:212
#: authentication/api/connection_token.py:410 ops/models/base.py:17
#: ops/models/job.py:152 ops/serializers/job.py:19
-#: perms/serializers/permission.py:35
+#: perms/serializers/permission.py:46
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
#: xpack/plugins/cloud/manager.py:83
msgid "Assets"
msgstr ""
-#: accounts/serializers/account/account.py:339
-msgid "Account already exists"
-msgstr ""
-
-#: accounts/serializers/account/account.py:389
+#: accounts/serializers/account/account.py:390
#, python-format
msgid "Asset does not support this secret type: %s"
msgstr ""
-#: accounts/serializers/account/account.py:421
+#: accounts/serializers/account/account.py:422
msgid "Account has exist"
msgstr ""
-#: accounts/serializers/account/account.py:458
+#: accounts/serializers/account/account.py:459
#: accounts/serializers/account/base.py:93
-#: accounts/serializers/account/template.py:72
-#: assets/serializers/asset/common.py:387
+#: accounts/serializers/account/template.py:83
+#: assets/serializers/asset/common.py:407
msgid "Spec info"
msgstr ""
-#: accounts/serializers/account/account.py:464
+#: accounts/serializers/account/account.py:465
#: authentication/serializers/connect_token_secret.py:159
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
msgstr ""
-#: accounts/serializers/account/account.py:474 acls/serializers/base.py:116
+#: accounts/serializers/account/account.py:475 acls/serializers/base.py:116
#: acls/templates/acls/asset_login_reminder.html:8
#: acls/templates/acls/user_login_reminder.html:8
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:54
@@ -940,7 +942,7 @@ msgstr ""
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:63
-#: rbac/builtin.py:124 rbac/models/rolebinding.py:49
+#: rbac/builtin.py:125 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:34
#: terminal/notifications.py:156 terminal/notifications.py:205
@@ -953,7 +955,7 @@ msgstr ""
msgid "User"
msgstr ""
-#: accounts/serializers/account/account.py:475
+#: accounts/serializers/account/account.py:476
#: authentication/templates/authentication/_access_key_modal.html:33
#: terminal/notifications.py:158 terminal/notifications.py:207
msgid "Date"
@@ -1013,24 +1015,38 @@ msgstr ""
msgid "Exclude symbol"
msgstr ""
-#: accounts/serializers/account/template.py:39
+#: accounts/serializers/account/template.py:24
+msgid ""
+"length is the length of the password, and the range is 8 to 30.\n"
+"lowercase indicates whether the password contains lowercase letters, \n"
+"uppercase indicates whether it contains uppercase letters,\n"
+"digit indicates whether it contains numbers, and symbol indicates whether it "
+"contains special symbols.\n"
+"exclude_symbols is used to exclude specific symbols. You can fill in the "
+"symbol characters to be excluded (up to 16). \n"
+"If you do not need to exclude symbols, you can leave it blank.\n"
+"default: {\"length\": 16, \"lowercase\": true, \"uppercase\": true, "
+"\"digit\": true, \"symbol\": true, \"exclude_symbols\": \"\"}"
+msgstr ""
+
+#: accounts/serializers/account/template.py:49
msgid "Secret generation strategy for account creation"
msgstr ""
-#: accounts/serializers/account/template.py:40
+#: accounts/serializers/account/template.py:50
msgid "Whether to automatically push the account to the asset"
msgstr ""
-#: accounts/serializers/account/template.py:43
+#: accounts/serializers/account/template.py:53
msgid ""
"Associated platform, you can configure push parameters. If not associated, "
"default parameters will be used"
msgstr ""
#: accounts/serializers/account/virtual.py:19 assets/models/cmd_filter.py:40
-#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:26
-#: ops/models/job.py:158 ops/models/playbook.py:31 rbac/models/role.py:37
-#: settings/models.py:39 terminal/models/applet/applet.py:46
+#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:25
+#: ops/models/job.py:158 ops/models/playbook.py:33 rbac/models/role.py:37
+#: settings/models.py:40 terminal/models/applet/applet.py:46
#: terminal/models/applet/applet.py:332 terminal/models/applet/host.py:143
#: terminal/models/component/endpoint.py:25
#: terminal/models/component/endpoint.py:119
@@ -1049,8 +1065,8 @@ msgid ""
msgstr ""
#: accounts/serializers/automations/base.py:23
-#: assets/models/asset/common.py:164 assets/serializers/asset/common.py:152
-#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:36
+#: assets/models/asset/common.py:176 assets/serializers/asset/common.py:172
+#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:47
msgid "Nodes"
msgstr ""
@@ -1112,31 +1128,101 @@ msgstr ""
msgid "Delete account: %s"
msgstr ""
-#: accounts/tasks/automation.py:25
+#: accounts/tasks/automation.py:32
msgid "Account execute automation"
msgstr ""
-#: accounts/tasks/automation.py:51 accounts/tasks/automation.py:56
+#: accounts/tasks/automation.py:35
+msgid ""
+"Unified execution entry for account automation tasks: when the system "
+"performs tasks \n"
+" such as account push, password change, account verification, account "
+"collection, \n"
+" and gateway account verification, all tasks are executed through "
+"this unified entry"
+msgstr ""
+
+#: accounts/tasks/automation.py:64 accounts/tasks/automation.py:72
msgid "Execute automation record"
msgstr ""
-#: accounts/tasks/backup_account.py:25
+#: accounts/tasks/automation.py:67
+msgid "When manually executing password change records, this task is used"
+msgstr ""
+
+#: accounts/tasks/automation.py:96
+msgid "Clean change secret and push record period"
+msgstr ""
+
+#: accounts/tasks/automation.py:98
+msgid ""
+"The system will periodically clean up unnecessary password change and push "
+"records, \n"
+" including their associated change tasks, execution logs, assets, and "
+"accounts. When any \n"
+" of these associated items are deleted, the corresponding password "
+"change and push records \n"
+" become invalid. Therefore, to maintain a clean and efficient "
+"database, the system will \n"
+" clean up expired records at 2 a.m daily, based on the interval "
+"specified by \n"
+" PERM_EXPIRED_CHECK_PERIODIC in the config.txt configuration file. "
+"This periodic cleanup \n"
+" mechanism helps free up storage space and enhances the security and "
+"overall performance \n"
+" of data management"
+msgstr ""
+
+#: accounts/tasks/backup_account.py:26
msgid "Execute account backup plan"
msgstr ""
-#: accounts/tasks/gather_accounts.py:34
+#: accounts/tasks/backup_account.py:29
+msgid "When performing scheduled or manual account backups, this task is used"
+msgstr ""
+
+#: accounts/tasks/gather_accounts.py:32 assets/tasks/automation.py:27
+#: orgs/tasks.py:11 terminal/tasks.py:33
+msgid "Unused"
+msgstr ""
+
+#: accounts/tasks/gather_accounts.py:36
msgid "Gather assets accounts"
msgstr ""
-#: accounts/tasks/push_account.py:15 accounts/tasks/push_account.py:23
+#: accounts/tasks/push_account.py:16 accounts/tasks/push_account.py:27
msgid "Push accounts to assets"
msgstr ""
-#: accounts/tasks/remove_account.py:44
+#: accounts/tasks/push_account.py:19
+msgid ""
+"When creating or modifying an account requires account push, this task is "
+"executed"
+msgstr ""
+
+#: accounts/tasks/remove_account.py:28
+msgid ""
+"When clicking \"Sync deletion\" in 'Console - Gather Account - Gathered "
+"accounts' this \n"
+" task will be executed"
+msgstr ""
+
+#: accounts/tasks/remove_account.py:50
msgid "Clean historical accounts"
msgstr ""
-#: accounts/tasks/remove_account.py:76
+#: accounts/tasks/remove_account.py:52
+msgid ""
+"Each time an asset account is updated, a historical account is generated, so "
+"it is \n"
+" necessary to clean up the asset account history. The system will "
+"clean up excess account \n"
+" records at 2 a.m. daily based on the configuration in the \"System "
+"settings - Features - \n"
+" Account storage - Record limit"
+msgstr ""
+
+#: accounts/tasks/remove_account.py:89
msgid "Remove historical accounts that are out of range."
msgstr ""
@@ -1144,23 +1230,42 @@ msgstr ""
msgid "Template sync info to related accounts"
msgstr ""
-#: accounts/tasks/vault.py:31
+#: accounts/tasks/template.py:14
+msgid ""
+"When clicking 'Sync new secret to accounts' in 'Console - Account - "
+"Templates - \n"
+" Accounts' this task will be executed"
+msgstr ""
+
+#: accounts/tasks/vault.py:32
msgid "Sync secret to vault"
msgstr ""
+#: accounts/tasks/vault.py:34
+msgid ""
+"When clicking 'Sync' in 'System Settings - Features - Account Storage' this "
+"task will be executed"
+msgstr ""
+
#: accounts/tasks/verify_account.py:49
msgid "Verify asset account availability"
msgstr ""
-#: accounts/tasks/verify_account.py:55
+#: accounts/tasks/verify_account.py:52
+msgid ""
+"When clicking 'Test' in 'Console - Asset details - Accounts' this task will "
+"be executed"
+msgstr ""
+
+#: accounts/tasks/verify_account.py:58
msgid "Verify accounts connectivity"
msgstr ""
-#: accounts/templates/accounts/asset_account_change_info.html:8
+#: accounts/templates/accounts/asset_account_change_info.html:10
msgid "Added account"
msgstr ""
-#: accounts/templates/accounts/asset_account_change_info.html:9
+#: accounts/templates/accounts/asset_account_change_info.html:13
msgid "Deleted account"
msgstr ""
@@ -1218,6 +1323,10 @@ msgstr ""
msgid "Notify"
msgstr ""
+#: acls/const.py:11
+msgid "Notify and warn"
+msgstr ""
+
#: acls/models/base.py:37 assets/models/cmd_filter.py:76
#: terminal/models/component/endpoint.py:112 xpack/plugins/cloud/models.py:314
msgid "Priority"
@@ -1233,7 +1342,7 @@ msgstr ""
msgid "Reviewers"
msgstr ""
-#: acls/models/base.py:43 assets/models/asset/common.py:165
+#: acls/models/base.py:43 assets/models/asset/common.py:178
#: authentication/models/access_key.py:25
#: authentication/models/connection_token.py:53
#: authentication/models/ssh_key.py:13
@@ -1245,15 +1354,15 @@ msgstr ""
msgid "Active"
msgstr "Active"
-#: acls/models/base.py:81 perms/serializers/permission.py:31
+#: acls/models/base.py:81 perms/serializers/permission.py:42
#: tickets/models/flow.py:23 users/models/preference.py:16
#: users/serializers/group.py:21 users/serializers/user.py:424
msgid "Users"
msgstr ""
#: acls/models/base.py:98 assets/models/automations/base.py:17
-#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:128
-#: assets/serializers/asset/common.py:386 perms/serializers/permission.py:44
+#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:148
+#: assets/serializers/asset/common.py:406 perms/serializers/permission.py:55
#: perms/serializers/user_permission.py:75 rbac/tree.py:35
msgid "Accounts"
msgstr ""
@@ -1273,7 +1382,7 @@ msgid "Regex"
msgstr ""
#: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79
-#: settings/models.py:184 settings/serializers/feature.py:19
+#: settings/models.py:185 settings/serializers/feature.py:20
#: settings/serializers/msg.py:78 xpack/plugins/license/models.py:30
msgid "Content"
msgstr ""
@@ -1385,7 +1494,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
-#: xpack/plugins/cloud/models.py:390
+#: common/drf/renders/base.py:150 xpack/plugins/cloud/models.py:390
msgid "IP"
msgstr ""
@@ -1445,11 +1554,11 @@ msgstr ""
msgid "User agent"
msgstr ""
-#: assets/api/asset/asset.py:181
+#: assets/api/asset/asset.py:190
msgid "Cannot create asset directly, you should create a host or other"
msgstr ""
-#: assets/api/asset/asset.py:185
+#: assets/api/asset/asset.py:194
msgid "The number of assets exceeds the limit of 5000"
msgstr ""
@@ -1477,32 +1586,32 @@ msgstr ""
msgid "App Assets"
msgstr "Assets"
-#: assets/automations/base/manager.py:187
+#: assets/automations/base/manager.py:188
msgid "{} disabled"
msgstr ""
-#: assets/automations/base/manager.py:250
+#: assets/automations/base/manager.py:251
msgid " - Platform {} ansible disabled"
msgstr ""
-#: assets/automations/base/manager.py:323
+#: assets/automations/base/manager.py:324
msgid ">>> Task preparation phase"
msgstr ""
-#: assets/automations/base/manager.py:326
+#: assets/automations/base/manager.py:327
#, python-brace-format
msgid ">>> Executing tasks in batches, total {runner_count}"
msgstr ""
-#: assets/automations/base/manager.py:328
+#: assets/automations/base/manager.py:329
msgid ">>> Start executing tasks"
msgstr ""
-#: assets/automations/base/manager.py:330
+#: assets/automations/base/manager.py:331
msgid ">>> No tasks need to be executed"
msgstr ""
-#: assets/automations/base/manager.py:335
+#: assets/automations/base/manager.py:336
#, python-brace-format
msgid ">>> Begin executing batch {index} of tasks"
msgstr ""
@@ -1554,7 +1663,7 @@ msgid "Gather facts"
msgstr ""
#: assets/const/base.py:32 audits/const.py:58
-#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:31
+#: terminal/serializers/applet_host.py:32 users/models/user/_auth.py:32
msgid "Disabled"
msgstr ""
@@ -1564,14 +1673,14 @@ msgstr ""
msgid "Basic"
msgstr ""
-#: assets/const/base.py:34 assets/const/protocol.py:292
+#: assets/const/base.py:34 assets/const/protocol.py:298
#: assets/models/asset/web.py:13
msgid "Script"
msgstr ""
#: assets/const/category.py:10 assets/models/asset/host.py:8
#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:76
-#: settings/serializers/feature.py:49 settings/serializers/msg.py:30
+#: settings/serializers/feature.py:52 settings/serializers/msg.py:30
#: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17
#: xpack/plugins/cloud/manager.py:83
#: xpack/plugins/cloud/serializers/account_attrs.py:72
@@ -1642,11 +1751,19 @@ msgstr ""
msgid "Old SSH version like openssh 5.x or 6.x"
msgstr ""
-#: assets/const/protocol.py:58
+#: assets/const/protocol.py:53
+msgid "Netcat help text"
+msgstr ""
+"Use netcat (nc) as a proxy tool to forward connections from the proxy server "
+"to the target host. This is useful in environments that do not support the "
+"SSH native proxy option (-W), or when more flexibility and timeout control "
+"are needed."
+
+#: assets/const/protocol.py:64
msgid "SFTP root"
msgstr ""
-#: assets/const/protocol.py:60
+#: assets/const/protocol.py:66
#, python-brace-format
msgid ""
"SFTP root directory, Support variable:
- ${ACCOUNT} The connected "
@@ -1654,24 +1771,24 @@ msgid ""
"
- ${USER} The username of the user"
msgstr ""
-#: assets/const/protocol.py:75
+#: assets/const/protocol.py:81
msgid "Console"
msgstr ""
-#: assets/const/protocol.py:76
+#: assets/const/protocol.py:82
msgid "Connect to console session"
msgstr ""
-#: assets/const/protocol.py:80
+#: assets/const/protocol.py:86
msgid "Any"
msgstr ""
-#: assets/const/protocol.py:82 rbac/tree.py:62
+#: assets/const/protocol.py:88 rbac/tree.py:62
#: settings/serializers/security.py:232
msgid "Security"
msgstr ""
-#: assets/const/protocol.py:83
+#: assets/const/protocol.py:89
msgid ""
"Security layer to use for the connection:
Any
Automatically select the "
"security mode based on the security protocols supported by both the client "
@@ -1682,100 +1799,100 @@ msgid ""
"and password to be given in advance"
msgstr ""
-#: assets/const/protocol.py:100
+#: assets/const/protocol.py:106
msgid "AD domain"
msgstr ""
-#: assets/const/protocol.py:115
+#: assets/const/protocol.py:121
msgid "Username prompt"
msgstr ""
-#: assets/const/protocol.py:116
+#: assets/const/protocol.py:122
msgid "We will send username when we see this prompt"
msgstr ""
-#: assets/const/protocol.py:121
+#: assets/const/protocol.py:127
msgid "Password prompt"
msgstr ""
-#: assets/const/protocol.py:122
+#: assets/const/protocol.py:128
msgid "We will send password when we see this prompt"
msgstr ""
-#: assets/const/protocol.py:127
+#: assets/const/protocol.py:133
msgid "Success prompt"
msgstr ""
-#: assets/const/protocol.py:128
+#: assets/const/protocol.py:134
msgid "We will consider login success when we see this prompt"
msgstr ""
-#: assets/const/protocol.py:139 assets/models/asset/database.py:10
+#: assets/const/protocol.py:145 assets/models/asset/database.py:11
#: settings/serializers/msg.py:49
msgid "Use SSL"
msgstr ""
-#: assets/const/protocol.py:174
+#: assets/const/protocol.py:180
msgid "SYSDBA"
msgstr ""
-#: assets/const/protocol.py:175
+#: assets/const/protocol.py:181
msgid "Connect as SYSDBA"
msgstr ""
-#: assets/const/protocol.py:190
+#: assets/const/protocol.py:196
msgid ""
"SQL Server version, Different versions have different connection drivers"
msgstr ""
-#: assets/const/protocol.py:220
+#: assets/const/protocol.py:226
msgid "Auth source"
msgstr ""
-#: assets/const/protocol.py:221
+#: assets/const/protocol.py:227
msgid "The database to authenticate against"
msgstr ""
-#: assets/const/protocol.py:226 authentication/models/connection_token.py:43
+#: assets/const/protocol.py:232 authentication/models/connection_token.py:43
msgid "Connect options"
msgstr ""
-#: assets/const/protocol.py:227
+#: assets/const/protocol.py:233
msgid "The connection specific options eg. retryWrites=false&retryReads=false"
msgstr ""
-#: assets/const/protocol.py:239
+#: assets/const/protocol.py:245
msgid "Auth username"
msgstr ""
-#: assets/const/protocol.py:262
+#: assets/const/protocol.py:268
msgid "Safe mode"
msgstr ""
-#: assets/const/protocol.py:264
+#: assets/const/protocol.py:270
msgid ""
"When safe mode is enabled, some operations will be disabled, such as: New "
"tab, right click, visit other website, etc."
msgstr ""
-#: assets/const/protocol.py:269 assets/models/asset/web.py:9
+#: assets/const/protocol.py:275 assets/models/asset/web.py:9
#: assets/serializers/asset/info/spec.py:16
msgid "Autofill"
msgstr ""
-#: assets/const/protocol.py:277 assets/models/asset/web.py:10
+#: assets/const/protocol.py:283 assets/models/asset/web.py:10
msgid "Username selector"
msgstr ""
-#: assets/const/protocol.py:282 assets/models/asset/web.py:11
+#: assets/const/protocol.py:288 assets/models/asset/web.py:11
msgid "Password selector"
msgstr ""
-#: assets/const/protocol.py:287 assets/models/asset/web.py:12
+#: assets/const/protocol.py:293 assets/models/asset/web.py:12
msgid "Submit selector"
msgstr ""
-#: assets/const/protocol.py:310
+#: assets/const/protocol.py:316
msgid "API mode"
msgstr ""
@@ -1795,51 +1912,51 @@ msgstr ""
msgid "Cloud"
msgstr ""
-#: assets/models/asset/common.py:94 assets/models/platform.py:16
+#: assets/models/asset/common.py:101 assets/models/platform.py:16
#: settings/serializers/auth/radius.py:18 settings/serializers/auth/sms.py:77
#: settings/serializers/msg.py:31 terminal/serializers/storage.py:133
#: xpack/plugins/cloud/serializers/account_attrs.py:73
msgid "Port"
msgstr ""
-#: assets/models/asset/common.py:160 assets/serializers/asset/common.py:150
+#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:170
#: settings/serializers/terminal.py:10
msgid "Address"
msgstr ""
-#: assets/models/asset/common.py:161 assets/models/platform.py:138
+#: assets/models/asset/common.py:169 assets/models/platform.py:149
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:118
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:385
msgid "Platform"
msgstr ""
-#: assets/models/asset/common.py:163 assets/models/domain.py:22
+#: assets/models/asset/common.py:173 assets/models/domain.py:22
msgid "Zone"
msgstr ""
-#: assets/models/asset/common.py:166 assets/serializers/asset/common.py:388
+#: assets/models/asset/common.py:179 assets/serializers/asset/common.py:408
#: assets/serializers/asset/host.py:11
msgid "Gathered info"
msgstr ""
-#: assets/models/asset/common.py:167 assets/serializers/asset/custom.py:14
+#: assets/models/asset/common.py:180 assets/serializers/asset/custom.py:14
msgid "Custom info"
msgstr ""
-#: assets/models/asset/common.py:352
+#: assets/models/asset/common.py:365
msgid "Can refresh asset hardware info"
msgstr ""
-#: assets/models/asset/common.py:353
+#: assets/models/asset/common.py:366
msgid "Can test asset connectivity"
msgstr ""
-#: assets/models/asset/common.py:354
+#: assets/models/asset/common.py:367
msgid "Can match asset"
msgstr ""
-#: assets/models/asset/common.py:355
+#: assets/models/asset/common.py:368
msgid "Can change asset nodes"
msgstr ""
@@ -1847,23 +1964,27 @@ msgstr ""
msgid "Custom asset"
msgstr ""
-#: assets/models/asset/database.py:11
+#: assets/models/asset/database.py:12
msgid "CA cert"
msgstr ""
-#: assets/models/asset/database.py:12
+#: assets/models/asset/database.py:13
msgid "Client cert"
msgstr ""
-#: assets/models/asset/database.py:13
+#: assets/models/asset/database.py:14
msgid "Client key"
msgstr ""
-#: assets/models/asset/database.py:14
+#: assets/models/asset/database.py:15
msgid "Allow invalid cert"
msgstr ""
-#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:89
+#: assets/models/asset/database.py:18
+msgid "Postgresql SSL mode"
+msgstr ""
+
+#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:92
msgid "Proxy"
msgstr ""
@@ -1965,14 +2086,14 @@ msgstr ""
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:29
#: authentication/serializers/connect_token_secret.py:125
-#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:35
+#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:36
#: users/models/preference.py:13
msgid "Value"
msgstr ""
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
-#: assets/serializers/platform.py:154
+#: assets/serializers/platform.py:158
#: authentication/serializers/connect_token_secret.py:124
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90
@@ -2023,7 +2144,7 @@ msgstr ""
msgid "Required"
msgstr ""
-#: assets/models/platform.py:19 assets/serializers/platform.py:156
+#: assets/models/platform.py:19 assets/serializers/platform.py:160
#: terminal/models/component/storage.py:28
#: xpack/plugins/cloud/providers/nutanix.py:30
msgid "Default"
@@ -2040,8 +2161,8 @@ msgid "Setting"
msgstr ""
#: assets/models/platform.py:38 audits/const.py:59
-#: authentication/backends/passkey/models.py:11 settings/models.py:38
-#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:32
+#: authentication/backends/passkey/models.py:11 settings/models.py:39
+#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:33
msgid "Enabled"
msgstr ""
@@ -2131,23 +2252,23 @@ msgstr ""
msgid "Internal"
msgstr "Builtin"
-#: assets/models/platform.py:102 assets/serializers/platform.py:175
+#: assets/models/platform.py:102 assets/serializers/platform.py:170
msgid "Charset"
msgstr ""
-#: assets/models/platform.py:104 assets/serializers/platform.py:213
+#: assets/models/platform.py:104 assets/serializers/platform.py:208
msgid "Gateway enabled"
msgstr ""
-#: assets/models/platform.py:106 assets/serializers/platform.py:206
+#: assets/models/platform.py:106 assets/serializers/platform.py:201
msgid "Su enabled"
msgstr "Switch account enabled"
-#: assets/models/platform.py:107 assets/serializers/platform.py:181
+#: assets/models/platform.py:107 assets/serializers/platform.py:176
msgid "Su method"
msgstr "Switch account method"
-#: assets/models/platform.py:108 assets/serializers/platform.py:184
+#: assets/models/platform.py:108 assets/serializers/platform.py:179
msgid "Custom fields"
msgstr ""
@@ -2162,38 +2283,58 @@ msgid ""
"type"
msgstr ""
-#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:178
+#: assets/serializers/asset/common.py:36 assets/serializers/platform.py:152
+msgid "Protocols, format is [\"protocol/port\"]"
+msgstr ""
+
+#: assets/serializers/asset/common.py:38
+msgid "Protocol, format is name/port"
+msgstr ""
+
+#: assets/serializers/asset/common.py:107
+msgid ""
+"Accounts, format [{\"name\": \"x\", \"username\": \"x\", \"secret\": \"x\", "
+"\"secret_type\": \"password\"}]"
+msgstr ""
+
+#: assets/serializers/asset/common.py:135
+msgid ""
+"Node path, format [\"/org_name/node_name\"], if node not exist, will create "
+"it"
+msgstr ""
+
+#: assets/serializers/asset/common.py:147 assets/serializers/platform.py:173
#: authentication/serializers/connect_token_secret.py:30
#: authentication/serializers/connect_token_secret.py:75
-#: perms/models/asset_permission.py:76 perms/serializers/permission.py:45
+#: perms/models/asset_permission.py:76 perms/serializers/permission.py:56
#: perms/serializers/user_permission.py:74 xpack/plugins/cloud/models.py:388
#: xpack/plugins/cloud/serializers/task.py:35
msgid "Protocols"
msgstr ""
-#: assets/serializers/asset/common.py:129
-#: assets/serializers/asset/common.py:151
+#: assets/serializers/asset/common.py:149
+#: assets/serializers/asset/common.py:171
msgid "Node path"
msgstr ""
-#: assets/serializers/asset/common.py:148
-#: assets/serializers/asset/common.py:389
+#: assets/serializers/asset/common.py:168
+#: assets/serializers/asset/common.py:409
msgid "Auto info"
msgstr ""
-#: assets/serializers/asset/common.py:245
+#: assets/serializers/asset/common.py:265
msgid "Platform not exist"
msgstr ""
-#: assets/serializers/asset/common.py:281
+#: assets/serializers/asset/common.py:301
msgid "port out of range (0-65535)"
msgstr ""
-#: assets/serializers/asset/common.py:288
+#: assets/serializers/asset/common.py:308
msgid "Protocol is required: {}"
msgstr ""
-#: assets/serializers/asset/common.py:316
+#: assets/serializers/asset/common.py:336
msgid "Invalid data"
msgstr ""
@@ -2201,6 +2342,25 @@ msgstr ""
msgid "Default database"
msgstr ""
+#: assets/serializers/asset/database.py:23
+msgid "CA cert help text"
+msgstr ""
+"The Common Name (CN) field has been deprecated. Please use the Subject "
+"Alternative Name (SAN) field to verify the domain name according to RFC 5280 "
+"for better security."
+
+#: assets/serializers/asset/database.py:24
+msgid "Postgresql ssl model help text"
+msgstr ""
+"Prefer: I don't care about encryption, but I wish to pay the overhead of "
+"encryption if the server supports it.Require: I want my data to be "
+"encrypted, and I accept the overhead. I trust that the network will make "
+"sure I always connect to the server I want.Verify CA: I want my data "
+"encrypted, and I accept the overhead. I want to be sure that I connect to a "
+"server that I trust.Verify Full: I want my data encrypted, and I accept the "
+"overhead. I want to be sure that I connect to a server I trust, and that "
+"it's the one I specify."
+
#: assets/serializers/asset/gpt.py:20
msgid ""
"If the server cannot directly connect to the API address, you need set up an "
@@ -2279,12 +2439,16 @@ msgid ""
"the zone, the connection is routed through the gateway."
msgstr ""
-#: assets/serializers/domain.py:24 assets/serializers/platform.py:186
-#: orgs/serializers.py:13 perms/serializers/permission.py:39
+#: assets/serializers/domain.py:24 assets/serializers/platform.py:181
+#: orgs/serializers.py:13 perms/serializers/permission.py:50
msgid "Assets amount"
msgstr ""
-#: assets/serializers/gateway.py:23 common/validators.py:34
+#: assets/serializers/gateway.py:19
+msgid "The platform must start with Gateway"
+msgstr ""
+
+#: assets/serializers/gateway.py:28 common/validators.py:34
msgid "This field must be unique."
msgstr ""
@@ -2360,42 +2524,42 @@ msgstr ""
msgid "This protocol is public, asset will show this protocol to user"
msgstr ""
-#: assets/serializers/platform.py:157
+#: assets/serializers/platform.py:161
msgid "Help text"
msgstr ""
-#: assets/serializers/platform.py:158
+#: assets/serializers/platform.py:162
msgid "Choices"
msgstr ""
-#: assets/serializers/platform.py:179
+#: assets/serializers/platform.py:174
msgid "Automation"
msgstr ""
-#: assets/serializers/platform.py:208
+#: assets/serializers/platform.py:203
msgid ""
"Login with account when accessing assets, then automatically switch to "
"another, similar to logging in with a regular account and then switching to "
"root"
msgstr ""
-#: assets/serializers/platform.py:214
+#: assets/serializers/platform.py:209
msgid "Assets can be connected using a zone gateway"
msgstr ""
-#: assets/serializers/platform.py:216
+#: assets/serializers/platform.py:211
msgid "Default Domain"
msgstr ""
-#: assets/serializers/platform.py:238
+#: assets/serializers/platform.py:233
msgid "type is required"
msgstr ""
-#: assets/serializers/platform.py:253
+#: assets/serializers/platform.py:248
msgid "Protocols is required"
msgstr ""
-#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:35
+#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:39
msgid "Test assets connectivity "
msgstr ""
@@ -2403,19 +2567,26 @@ msgstr ""
msgid "Gather asset hardware info"
msgstr ""
-#: assets/tasks/automation.py:24
+#: assets/tasks/automation.py:25
msgid "Asset execute automation"
msgstr ""
-#: assets/tasks/gather_facts.py:21 assets/tasks/gather_facts.py:27
+#: assets/tasks/gather_facts.py:22 assets/tasks/gather_facts.py:32
msgid "Gather assets facts"
msgstr ""
-#: assets/tasks/gather_facts.py:39
+#: assets/tasks/gather_facts.py:25
+msgid ""
+"When clicking 'Refresh hardware info' in 'Console - Asset Details - Basic' "
+"this task \n"
+" will be executed"
+msgstr ""
+
+#: assets/tasks/gather_facts.py:44
msgid "Update assets hardware info: "
msgstr ""
-#: assets/tasks/gather_facts.py:47
+#: assets/tasks/gather_facts.py:52
msgid "Update node asset hardware information: "
msgstr ""
@@ -2423,28 +2594,56 @@ msgstr ""
msgid "Check the amount of assets under the node"
msgstr ""
-#: assets/tasks/nodes_amount.py:28
+#: assets/tasks/nodes_amount.py:18
+msgid ""
+"Manually verifying asset quantities updates the asset count for nodes under "
+"the \n"
+" current organization. This task will be called in the following two "
+"cases: when updating \n"
+" nodes and when the number of nodes exceeds 100"
+msgstr ""
+
+#: assets/tasks/nodes_amount.py:34
msgid ""
"The task of self-checking is already running and cannot be started repeatedly"
msgstr ""
-#: assets/tasks/nodes_amount.py:33
+#: assets/tasks/nodes_amount.py:40
msgid "Periodic check the amount of assets under the node"
msgstr ""
-#: assets/tasks/ping.py:20 assets/tasks/ping.py:26
+#: assets/tasks/nodes_amount.py:42
+msgid ""
+"Schedule the check_node_assets_amount_task to periodically update the asset "
+"count of \n"
+" all nodes under all organizations"
+msgstr ""
+
+#: assets/tasks/ping.py:20 assets/tasks/ping.py:30
msgid "Test assets connectivity"
msgstr ""
-#: assets/tasks/ping.py:42
+#: assets/tasks/ping.py:24
+msgid ""
+"When clicking 'Test Asset Connectivity' in 'Asset Details - Basic Settings' "
+"this task will be executed"
+msgstr ""
+
+#: assets/tasks/ping.py:46
msgid "Test if the assets under the node are connectable "
msgstr ""
-#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:25
-#: assets/tasks/ping_gateway.py:34
+#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:29
+#: assets/tasks/ping_gateway.py:38
msgid "Test gateways connectivity"
msgstr ""
+#: assets/tasks/ping_gateway.py:23
+msgid ""
+"When clicking 'Test Connection' in 'Domain Details - Gateway' this task will "
+"be executed"
+msgstr ""
+
#: assets/tasks/utils.py:16
msgid "Asset has been disabled, skipped: {}"
msgstr ""
@@ -2501,7 +2700,7 @@ msgstr ""
#: audits/const.py:18 audits/const.py:28
#: ops/templates/ops/celery_task_log.html:86
-#: terminal/api/session/session.py:149
+#: terminal/api/session/session.py:153
msgid "Download"
msgstr ""
@@ -2509,7 +2708,7 @@ msgstr ""
msgid "Rename dir"
msgstr ""
-#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:274
+#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:281
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:84
@@ -2551,16 +2750,16 @@ msgstr ""
msgid "Close"
msgstr ""
-#: audits/const.py:41 ops/models/celery.py:84
+#: audits/const.py:41 ops/models/celery.py:85
#: terminal/models/session/sharing.py:128 tickets/const.py:25
#: xpack/plugins/cloud/const.py:67
msgid "Finished"
msgstr ""
#: audits/const.py:46 settings/serializers/terminal.py:6
-#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:175
+#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:174
#: terminal/models/virtualapp/provider.py:14 terminal/serializers/session.py:55
-#: terminal/serializers/session.py:69
+#: terminal/serializers/session.py:79
msgid "Terminal"
msgstr ""
@@ -2712,9 +2911,9 @@ msgstr ""
msgid "Offline user session"
msgstr ""
-#: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16
-#: ops/models/base.py:53 ops/models/celery.py:86 ops/models/job.py:151
-#: ops/models/job.py:240 ops/models/playbook.py:30
+#: audits/serializers.py:33 ops/models/adhoc.py:24 ops/models/base.py:16
+#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:151
+#: ops/models/job.py:240 ops/models/playbook.py:32
#: terminal/models/session/sharing.py:25
msgid "Creator"
msgstr ""
@@ -2769,7 +2968,7 @@ msgstr ""
#: audits/signal_handlers/login_log.py:37 authentication/notifications.py:73
#: authentication/views/login.py:78 notifications/backends/__init__.py:11
#: settings/serializers/auth/wecom.py:11 settings/serializers/auth/wecom.py:16
-#: users/models/user/__init__.py:122 users/models/user/_source.py:18
+#: users/models/user/__init__.py:122 users/models/user/_source.py:19
msgid "WeCom"
msgstr ""
@@ -2777,21 +2976,21 @@ msgstr ""
#: authentication/views/login.py:90 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:12
#: settings/serializers/auth/feishu.py:14 users/models/user/__init__.py:128
-#: users/models/user/_source.py:20
+#: users/models/user/_source.py:21
msgid "FeiShu"
msgstr ""
#: audits/signal_handlers/login_log.py:40 authentication/views/login.py:102
#: authentication/views/slack.py:79 notifications/backends/__init__.py:16
#: settings/serializers/auth/slack.py:11 settings/serializers/auth/slack.py:13
-#: users/models/user/__init__.py:134 users/models/user/_source.py:22
+#: users/models/user/__init__.py:134 users/models/user/_source.py:23
msgid "Slack"
msgstr ""
#: audits/signal_handlers/login_log.py:41 authentication/views/dingtalk.py:151
#: authentication/views/login.py:84 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:11 users/models/user/__init__.py:125
-#: users/models/user/_source.py:19
+#: users/models/user/_source.py:20
msgid "DingTalk"
msgstr ""
@@ -2806,14 +3005,32 @@ msgstr ""
msgid "Passkey"
msgstr ""
-#: audits/tasks.py:131
+#: audits/tasks.py:132
msgid "Clean audits session task log"
msgstr ""
-#: audits/tasks.py:145
+#: audits/tasks.py:134
+msgid ""
+"Since the system generates login logs, operation logs, file upload logs, "
+"activity \n"
+" logs, Celery execution logs, session recordings, command records, "
+"and password change \n"
+" logs, it will perform cleanup of records that exceed the time limit "
+"according to the \n"
+" 'Tasks - Regular clean-up' in the system settings at 2 a.m daily"
+msgstr ""
+
+#: audits/tasks.py:154
msgid "Upload FTP file to external storage"
msgstr ""
+#: audits/tasks.py:156
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured, files uploaded through file "
+"management will be \n"
+" synchronized to external storage"
+msgstr ""
+
#: authentication/api/access_key.py:39
msgid "Access keys can be created at most 10"
msgstr ""
@@ -2855,7 +3072,7 @@ msgstr ""
msgid "Current user not support mfa type: {}"
msgstr ""
-#: authentication/api/password.py:33 terminal/api/session/session.py:322
+#: authentication/api/password.py:33 terminal/api/session/session.py:334
#: users/views/profile/reset.py:63
msgid "User does not exist: {}"
msgstr ""
@@ -2905,6 +3122,14 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr ""
+#: authentication/backends/oidc/views.py:174
+msgid "OpenID Error"
+msgstr ""
+
+#: authentication/backends/oidc/views.py:175
+msgid "Please check if a user with the same username or email already exists"
+msgstr ""
+
#: authentication/backends/passkey/api.py:37
msgid "Only register passkey for local user"
msgstr ""
@@ -3050,7 +3275,7 @@ msgstr ""
msgid "Please enter SMS code"
msgstr ""
-#: authentication/errors/failed.py:164 users/exceptions.py:15
+#: authentication/errors/failed.py:164 users/exceptions.py:14
msgid "Phone not set"
msgstr ""
@@ -3093,15 +3318,15 @@ msgstr ""
msgid "Please wait for %s seconds before retry"
msgstr ""
-#: authentication/errors/redirect.py:85 authentication/mixins.py:323
+#: authentication/errors/redirect.py:85 authentication/mixins.py:326
msgid "Your password is too simple, please change it for security"
msgstr ""
-#: authentication/errors/redirect.py:93 authentication/mixins.py:330
+#: authentication/errors/redirect.py:93 authentication/mixins.py:335
msgid "You should to change your password before login"
msgstr ""
-#: authentication/errors/redirect.py:101 authentication/mixins.py:337
+#: authentication/errors/redirect.py:101 authentication/mixins.py:344
msgid "Your password has expired, please reset before logging in"
msgstr ""
@@ -3199,7 +3424,7 @@ msgstr ""
msgid "Clear phone number to disable"
msgstr ""
-#: authentication/middleware.py:94 settings/utils/ldap.py:679
+#: authentication/middleware.py:94 settings/utils/ldap.py:691
msgid "Authentication failed (before login check failed): {}"
msgstr ""
@@ -3217,7 +3442,7 @@ msgstr ""
msgid "The MFA type ({}) is not enabled"
msgstr ""
-#: authentication/mixins.py:313
+#: authentication/mixins.py:314
msgid "Please change your password"
msgstr ""
@@ -3395,7 +3620,7 @@ msgid "Actions"
msgstr ""
#: authentication/serializers/connection_token.py:42
-#: perms/serializers/permission.py:43 perms/serializers/permission.py:64
+#: perms/serializers/permission.py:54 perms/serializers/permission.py:75
#: users/serializers/user.py:127 users/serializers/user.py:273
msgid "Is expired"
msgstr "Expired"
@@ -3410,7 +3635,25 @@ msgstr "Organization"
msgid "The {} cannot be empty"
msgstr ""
-#: authentication/serializers/ssh_key.py:43 users/forms/profile.py:161
+#: authentication/serializers/ssh_key.py:17
+msgid "Automatically Generate Key Pair"
+msgstr ""
+
+#: authentication/serializers/ssh_key.py:19
+msgid "Import Existing Key Pair"
+msgstr ""
+
+#: authentication/serializers/ssh_key.py:31
+msgid "Create Type"
+msgstr ""
+
+#: authentication/serializers/ssh_key.py:33
+msgid ""
+"Please download the private key after creation. Each private key can only be "
+"downloaded once"
+msgstr ""
+
+#: authentication/serializers/ssh_key.py:57 users/forms/profile.py:161
#: users/serializers/profile.py:133 users/serializers/profile.py:160
msgid "Not a valid ssh public key"
msgstr ""
@@ -3419,16 +3662,22 @@ msgstr ""
msgid "Access IP"
msgstr ""
-#: authentication/serializers/token.py:92 perms/serializers/permission.py:42
-#: perms/serializers/permission.py:65 users/serializers/user.py:128
+#: authentication/serializers/token.py:92 perms/serializers/permission.py:53
+#: perms/serializers/permission.py:76 users/serializers/user.py:128
#: users/serializers/user.py:270
msgid "Is valid"
msgstr "Is Valid"
-#: authentication/tasks.py:11
+#: authentication/tasks.py:13
msgid "Clean expired session"
msgstr ""
+#: authentication/tasks.py:15
+msgid ""
+"Since user logins create sessions, the system will clean up expired sessions "
+"every 24 hours"
+msgstr ""
+
#: authentication/templates/authentication/_access_key_modal.html:6
msgid "API key list"
msgstr ""
@@ -3488,7 +3737,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:3
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_reset_password_code.html:9
-#: jumpserver/conf.py:502
+#: jumpserver/conf.py:522
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:32
#: users/templates/users/_msg_account_expire_reminder.html:4
@@ -3647,22 +3896,22 @@ msgstr ""
msgid "LAN"
msgstr ""
-#: authentication/views/base.py:74
+#: authentication/views/base.py:71
#: perms/templates/perms/_msg_permed_items_expire.html:21
msgid "If you have any question, please contact the administrator"
msgstr ""
-#: authentication/views/base.py:147
+#: authentication/views/base.py:141
#, python-format
msgid "%s query user failed"
msgstr ""
-#: authentication/views/base.py:155
+#: authentication/views/base.py:149
#, python-format
msgid "The %s is already bound to another user"
msgstr ""
-#: authentication/views/base.py:161
+#: authentication/views/base.py:155
#, python-format
msgid "Binding %s successfully"
msgstr ""
@@ -3799,7 +4048,7 @@ msgstr ""
msgid "Please login with a password and then bind the WeCom"
msgstr ""
-#: common/api/action.py:51
+#: common/api/action.py:57
msgid "Request file format may be wrong"
msgstr ""
@@ -3827,7 +4076,7 @@ msgstr ""
msgid "Canceled"
msgstr ""
-#: common/const/common.py:5 xpack/plugins/cloud/manager.py:412
+#: common/const/common.py:5 xpack/plugins/cloud/manager.py:411
#, python-format
msgid "%(name)s was created successfully"
msgstr ""
@@ -3928,7 +4177,7 @@ msgstr "Organization ID"
msgid "The file content overflowed (The maximum length `{}` bytes)"
msgstr ""
-#: common/drf/parsers/base.py:199
+#: common/drf/parsers/base.py:207
msgid "Parse file error: {}"
msgstr ""
@@ -3936,7 +4185,69 @@ msgstr ""
msgid "Invalid excel file"
msgstr ""
-#: common/drf/renders/base.py:208
+#: common/drf/renders/base.py:138
+msgid "Yes/No"
+msgstr ""
+
+#: common/drf/renders/base.py:141
+msgid "Text, max length {}"
+msgstr ""
+
+#: common/drf/renders/base.py:143
+msgid "Long text, no length limit"
+msgstr ""
+
+#: common/drf/renders/base.py:145
+msgid "Number, min {} max {}"
+msgstr ""
+
+#: common/drf/renders/base.py:148
+msgid "Datetime format {}"
+msgstr ""
+
+#: common/drf/renders/base.py:154
+msgid ""
+"Choices, format name(value), name is optional for human read, value is "
+"requisite, options {}"
+msgstr ""
+
+#: common/drf/renders/base.py:157
+msgid "Choices, options {}"
+msgstr ""
+
+#: common/drf/renders/base.py:159
+msgid "Phone number, format +8612345678901"
+msgstr ""
+
+#: common/drf/renders/base.py:161
+msgid "Label, format [\"key:value\"]"
+msgstr ""
+
+#: common/drf/renders/base.py:163
+msgid ""
+"Object, format name(id), name is optional for human read, id is requisite"
+msgstr ""
+
+#: common/drf/renders/base.py:165
+msgid "Object, format id"
+msgstr ""
+
+#: common/drf/renders/base.py:169
+msgid ""
+"Objects, format [\"name(id)\", ...], name is optional for human read, id is "
+"requisite"
+msgstr ""
+
+#: common/drf/renders/base.py:171
+msgid ""
+"Labels, format [\"key:value\", ...], if label not exists, will create it"
+msgstr ""
+
+#: common/drf/renders/base.py:173
+msgid "Objects, format [\"id\", ...]"
+msgstr ""
+
+#: common/drf/renders/base.py:271
msgid ""
"{} - The encryption password has not been set - please go to personal "
"information -> file encryption password to set the encryption password"
@@ -3983,7 +4294,7 @@ msgstr ""
msgid ""
"Connection failed: Self-signed certificate used. Please check server "
"certificate configuration"
-msgstr "連接失敗:使用了自簽名證書,請檢查伺服器證書配置"
+msgstr ""
#: common/sdk/im/exceptions.py:23
msgid "Network error, please contact system administrator"
@@ -4093,16 +4404,34 @@ msgstr ""
msgid "Tags"
msgstr ""
-#: common/tasks.py:31
+#: common/tasks.py:32
msgid "Send email"
msgstr ""
-#: common/tasks.py:58
+#: common/tasks.py:35
+msgid "This task will be executed when sending email notifications"
+msgstr ""
+
+#: common/tasks.py:65
msgid "Send email attachment"
msgstr ""
-#: common/tasks.py:80 terminal/tasks.py:58
-msgid "Upload session replay to external storage"
+#: common/tasks.py:68
+msgid ""
+"When an account password is changed or an account backup generates "
+"attachments, \n"
+" this task needs to be executed for sending emails and handling "
+"attachments"
+msgstr ""
+
+#: common/tasks.py:94
+msgid "Upload account backup to external storage"
+msgstr ""
+
+#: common/tasks.py:96
+msgid ""
+"When performing an account backup, this task needs to be executed to "
+"external storage (SFTP)"
msgstr ""
#: common/utils/ip/geoip/utils.py:26
@@ -4118,10 +4447,17 @@ msgstr ""
msgid "Hello %s"
msgstr ""
-#: common/utils/verify_code.py:16
+#: common/utils/verify_code.py:17
msgid "Send SMS code"
msgstr ""
+#: common/utils/verify_code.py:19
+msgid ""
+"When resetting a password, forgetting a password, or verifying MFA, this "
+"task needs to \n"
+" be executed to send SMS messages"
+msgstr ""
+
#: common/validators.py:16
msgid "Special char not allowed"
msgstr ""
@@ -4134,16 +4470,16 @@ msgstr ""
msgid "The mobile phone number format is incorrect"
msgstr ""
-#: jumpserver/conf.py:496
+#: jumpserver/conf.py:516
#, python-brace-format
msgid "The verification code is: {code}"
msgstr ""
-#: jumpserver/conf.py:501
+#: jumpserver/conf.py:521
msgid "Create account successfully"
msgstr ""
-#: jumpserver/conf.py:503
+#: jumpserver/conf.py:523
msgid "Your account has been created successfully"
msgstr ""
@@ -4233,15 +4569,22 @@ msgstr ""
msgid "Publish the station message"
msgstr ""
-#: ops/ansible/inventory.py:106 ops/models/job.py:65
+#: notifications/notifications.py:48
+msgid ""
+"This task needs to be executed for sending internal messages for system "
+"alerts, \n"
+" work orders, and other notifications"
+msgstr ""
+
+#: ops/ansible/inventory.py:116 ops/models/job.py:65
msgid "No account available"
msgstr ""
-#: ops/ansible/inventory.py:285
+#: ops/ansible/inventory.py:296
msgid "Ansible disabled"
msgstr ""
-#: ops/ansible/inventory.py:301
+#: ops/ansible/inventory.py:312
msgid "Skip hosts below:"
msgstr ""
@@ -4289,31 +4632,31 @@ msgid ""
"The task is being created and cannot be interrupted. Please try again later."
msgstr ""
-#: ops/api/playbook.py:39
+#: ops/api/playbook.py:50
msgid "Currently playbook is being used in a job"
msgstr ""
-#: ops/api/playbook.py:97
+#: ops/api/playbook.py:113
msgid "Unsupported file content"
msgstr ""
-#: ops/api/playbook.py:99 ops/api/playbook.py:145 ops/api/playbook.py:193
+#: ops/api/playbook.py:115 ops/api/playbook.py:161 ops/api/playbook.py:209
msgid "Invalid file path"
msgstr ""
-#: ops/api/playbook.py:171
+#: ops/api/playbook.py:187
msgid "This file can not be rename"
msgstr ""
-#: ops/api/playbook.py:190
+#: ops/api/playbook.py:206
msgid "File already exists"
msgstr ""
-#: ops/api/playbook.py:208
+#: ops/api/playbook.py:224
msgid "File key is required"
msgstr ""
-#: ops/api/playbook.py:211
+#: ops/api/playbook.py:227
msgid "This file can not be delete"
msgstr ""
@@ -4353,11 +4696,11 @@ msgstr ""
msgid "VCS"
msgstr ""
-#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:120
+#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:123
msgid "Adhoc"
msgstr ""
-#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:88
+#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:91
msgid "Playbook"
msgstr ""
@@ -4421,53 +4764,69 @@ msgstr ""
msgid "Command execution disabled"
msgstr ""
+#: ops/const.py:86
+msgctxt "scope"
+msgid "Public"
+msgstr ""
+
+#: ops/const.py:87
+msgid "Private"
+msgstr ""
+
#: ops/exception.py:6
msgid "no valid program entry found."
msgstr ""
-#: ops/mixin.py:23 ops/mixin.py:102 settings/serializers/auth/ldap.py:72
+#: ops/mixin.py:30 ops/mixin.py:110 settings/serializers/auth/ldap.py:73
+#: settings/serializers/auth/ldap_ha.py:55
msgid "Periodic run"
msgstr "Periodic"
-#: ops/mixin.py:25 ops/mixin.py:88 ops/mixin.py:108
-#: settings/serializers/auth/ldap.py:79
+#: ops/mixin.py:32 ops/mixin.py:96 ops/mixin.py:116
+#: settings/serializers/auth/ldap.py:80 settings/serializers/auth/ldap_ha.py:62
msgid "Interval"
msgstr ""
-#: ops/mixin.py:28 ops/mixin.py:86 ops/mixin.py:105
-#: settings/serializers/auth/ldap.py:76
+#: ops/mixin.py:35 ops/mixin.py:94 ops/mixin.py:113
+#: settings/serializers/auth/ldap.py:77 settings/serializers/auth/ldap_ha.py:59
msgid "Crontab"
msgstr ""
-#: ops/mixin.py:110
+#: ops/mixin.py:118
msgid "Run period"
msgstr "Period"
-#: ops/mixin.py:119
+#: ops/mixin.py:127
msgid "* Please enter a valid crontab expression"
msgstr ""
-#: ops/mixin.py:126
+#: ops/mixin.py:134
msgid "Range {} to {}"
msgstr ""
-#: ops/mixin.py:137
+#: ops/mixin.py:145
msgid "Require interval or crontab setting"
msgstr ""
-#: ops/models/adhoc.py:21
+#: ops/models/adhoc.py:20
msgid "Pattern"
msgstr ""
-#: ops/models/adhoc.py:23 ops/models/job.py:146
+#: ops/models/adhoc.py:22 ops/models/job.py:146
msgid "Module"
msgstr ""
-#: ops/models/adhoc.py:24 ops/models/celery.py:81 ops/models/job.py:144
+#: ops/models/adhoc.py:23 ops/models/celery.py:82 ops/models/job.py:144
#: terminal/models/component/task.py:14
msgid "Args"
msgstr ""
+#: ops/models/adhoc.py:26 ops/models/playbook.py:36 ops/serializers/mixin.py:10
+#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
+#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
+msgid "Scope"
+msgstr ""
+
#: ops/models/base.py:19
msgid "Account policy"
msgstr ""
@@ -4494,23 +4853,23 @@ msgstr ""
msgid "Date last publish"
msgstr ""
-#: ops/models/celery.py:70
+#: ops/models/celery.py:71
msgid "Celery Task"
msgstr ""
-#: ops/models/celery.py:73
+#: ops/models/celery.py:74
msgid "Can view task monitor"
msgstr ""
-#: ops/models/celery.py:82 terminal/models/component/task.py:15
+#: ops/models/celery.py:83 terminal/models/component/task.py:15
msgid "Kwargs"
msgstr ""
-#: ops/models/celery.py:87
+#: ops/models/celery.py:88
msgid "Date published"
msgstr ""
-#: ops/models/celery.py:112
+#: ops/models/celery.py:113
msgid "Celery Task Execution"
msgstr ""
@@ -4555,11 +4914,11 @@ msgstr ""
msgid "Job Execution"
msgstr ""
-#: ops/models/playbook.py:33
+#: ops/models/playbook.py:35
msgid "CreateMethod"
msgstr ""
-#: ops/models/playbook.py:34
+#: ops/models/playbook.py:37
msgid "VCS URL"
msgstr ""
@@ -4623,34 +4982,87 @@ msgstr ""
msgid "You do not have permission for the current job."
msgstr ""
-#: ops/tasks.py:38
+#: ops/tasks.py:51
msgid "Run ansible task"
msgstr ""
-#: ops/tasks.py:72
+#: ops/tasks.py:54
+msgid ""
+"Execute scheduled adhoc and playbooks, periodically invoking the task for "
+"execution"
+msgstr ""
+
+#: ops/tasks.py:82
msgid "Run ansible task execution"
msgstr ""
-#: ops/tasks.py:94
+#: ops/tasks.py:85
+msgid "Execute the task when manually adhoc or playbooks"
+msgstr ""
+
+#: ops/tasks.py:99
msgid "Clear celery periodic tasks"
msgstr ""
-#: ops/tasks.py:115
+#: ops/tasks.py:101
+msgid "At system startup, clean up celery tasks that no longer exist"
+msgstr ""
+
+#: ops/tasks.py:125
msgid "Create or update periodic tasks"
msgstr ""
-#: ops/tasks.py:123
+#: ops/tasks.py:127
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, tasks will be "
+"registered or the parameters \n"
+" of scheduled tasks will be updated"
+msgstr ""
+
+#: ops/tasks.py:140
msgid "Periodic check service performance"
msgstr ""
-#: ops/tasks.py:129
+#: ops/tasks.py:142
+msgid ""
+"Check every hour whether each component is offline and whether the CPU, "
+"memory, \n"
+" and disk usage exceed the thresholds, and send an alert message to "
+"the administrator"
+msgstr ""
+
+#: ops/tasks.py:152
msgid "Clean up unexpected jobs"
msgstr ""
-#: ops/tasks.py:136
+#: ops/tasks.py:154
+msgid ""
+"Due to exceptions caused by executing adhoc and playbooks in the Job "
+"Center, \n"
+" which result in the task status not being updated, the system will "
+"clean up abnormal jobs \n"
+" that have not been completed for more than 3 hours every hour and "
+"mark these tasks as \n"
+" failed"
+msgstr ""
+
+#: ops/tasks.py:167
msgid "Clean job_execution db record"
msgstr ""
+#: ops/tasks.py:169
+msgid ""
+"Due to the execution of adhoc and playbooks in the Job Center, execution "
+"records will \n"
+" be generated. The system will clean up records that exceed the "
+"retention period every day \n"
+" at 2 a.m., based on the configuration of 'System Settings - Tasks - "
+"Regular clean-up - \n"
+" Job execution retention days'"
+msgstr ""
+
#: ops/templates/ops/celery_task_log.html:4
msgid "Task log"
msgstr ""
@@ -4691,17 +5103,17 @@ msgstr ""
msgid "Name of the job"
msgstr ""
-#: orgs/api.py:61
+#: orgs/api.py:60
msgid "The current organization ({}) cannot be deleted"
msgstr ""
-#: orgs/api.py:66
+#: orgs/api.py:65
msgid ""
"LDAP synchronization is set to the current organization. Please switch to "
"another organization before deleting"
msgstr ""
-#: orgs/api.py:76
+#: orgs/api.py:75
msgid "The organization have resource ({}) cannot be deleted"
msgstr ""
@@ -4715,7 +5127,7 @@ msgstr "請選擇一個組織後再保存"
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91
#: rbac/const.py:7 rbac/models/rolebinding.py:56
-#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:52
+#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:53
#: terminal/templates/terminal/_msg_command_warning.html:21
#: terminal/templates/terminal/_msg_session_sharing.html:14
#: tickets/models/ticket/general.py:303 tickets/serializers/ticket/ticket.py:60
@@ -4734,7 +5146,7 @@ msgstr ""
msgid "SYSTEM"
msgstr ""
-#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:185
+#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:186
#: terminal/models/applet/applet.py:42
msgid "Builtin"
msgstr "Builtin"
@@ -4751,7 +5163,7 @@ msgstr ""
msgid "Can not delete virtual org"
msgstr ""
-#: orgs/serializers.py:10 perms/serializers/permission.py:37
+#: orgs/serializers.py:10 perms/serializers/permission.py:48
#: rbac/serializers/role.py:27 users/serializers/group.py:54
msgid "Users amount"
msgstr ""
@@ -4760,7 +5172,7 @@ msgstr ""
msgid "User groups amount"
msgstr ""
-#: orgs/serializers.py:14 perms/serializers/permission.py:40
+#: orgs/serializers.py:14 perms/serializers/permission.py:51
msgid "Nodes amount"
msgstr ""
@@ -4776,7 +5188,7 @@ msgstr ""
msgid "Asset permissions amount"
msgstr ""
-#: orgs/tasks.py:9
+#: orgs/tasks.py:10
msgid "Refresh organization cache"
msgstr ""
@@ -4857,7 +5269,7 @@ msgid "today"
msgstr ""
#: perms/notifications.py:12 perms/notifications.py:44
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "day"
msgstr ""
@@ -4877,22 +5289,55 @@ msgstr ""
msgid "asset permissions of organization {}"
msgstr ""
-#: perms/serializers/permission.py:33 users/serializers/user.py:257
-msgid "Groups"
+#: perms/serializers/permission.py:32
+msgid ""
+"Accounts, format [\"@virtual\", \"root\", \"%template_id\"], virtual "
+"choices: @ALL, @SPEC, @USER, @ANON, @INPUT"
msgstr ""
#: perms/serializers/permission.py:38
+msgid "Protocols, format [\"ssh\", \"rdp\", \"vnc\"] or [\"all\"]"
+msgstr ""
+
+#: perms/serializers/permission.py:44 users/serializers/user.py:257
+msgid "Groups"
+msgstr ""
+
+#: perms/serializers/permission.py:49
msgid "Groups amount"
msgstr ""
-#: perms/tasks.py:27
+#: perms/tasks.py:28
msgid "Check asset permission expired"
msgstr ""
-#: perms/tasks.py:40
+#: perms/tasks.py:30
+msgid ""
+"The cache of organizational collections, which have completed user "
+"authorization tree \n"
+" construction, will expire. Therefore, expired collections need to be "
+"cleared from the \n"
+" cache, and this task will be executed periodically based on the time "
+"interval specified \n"
+" by PERM_EXPIRED_CHECK_PERIODIC in the system configuration file "
+"config.txt"
+msgstr ""
+
+#: perms/tasks.py:49
msgid "Send asset permission expired notification"
msgstr ""
+#: perms/tasks.py:51
+msgid ""
+"Check every day at 10 a.m. and send a notification message to users "
+"associated with \n"
+" assets whose authorization is about to expire, as well as to the "
+"organization's \n"
+" administrators, 3 days in advance, to remind them that the asset "
+"authorization will \n"
+" expire in a few days"
+msgstr ""
+
#: perms/templates/perms/_msg_item_permissions_expire.html:7
#: perms/templates/perms/_msg_permed_items_expire.html:7
#, python-format
@@ -4922,27 +5367,27 @@ msgstr ""
msgid "App RBAC"
msgstr "RBAC"
-#: rbac/builtin.py:115
+#: rbac/builtin.py:116
msgid "SystemAdmin"
msgstr "System Admin"
-#: rbac/builtin.py:118
+#: rbac/builtin.py:119
msgid "SystemAuditor"
msgstr "System Auditor"
-#: rbac/builtin.py:121
+#: rbac/builtin.py:122
msgid "SystemComponent"
msgstr "System Component"
-#: rbac/builtin.py:127
+#: rbac/builtin.py:128
msgid "OrgAdmin"
msgstr "Organizational Admin"
-#: rbac/builtin.py:130
+#: rbac/builtin.py:131
msgid "OrgAuditor"
msgstr "Organizational Auditor"
-#: rbac/builtin.py:133
+#: rbac/builtin.py:134
msgid "OrgUser"
msgstr "Organizational user"
@@ -4982,11 +5427,6 @@ msgstr ""
msgid "Permissions"
msgstr ""
-#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
-#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
-msgid "Scope"
-msgstr ""
-
#: rbac/models/role.py:46 rbac/models/rolebinding.py:52
#: users/models/user/__init__.py:66
msgid "Role"
@@ -5046,7 +5486,7 @@ msgstr "Workbench"
msgid "Audit view"
msgstr "Audits"
-#: rbac/tree.py:27 settings/models.py:161
+#: rbac/tree.py:27 settings/models.py:162
msgid "System setting"
msgstr ""
@@ -5074,7 +5514,7 @@ msgstr ""
msgid "App ops"
msgstr "Ops"
-#: rbac/tree.py:57 settings/serializers/feature.py:117
+#: rbac/tree.py:57 settings/serializers/feature.py:120
msgid "Feature"
msgstr ""
@@ -5109,8 +5549,8 @@ msgstr "Organizations"
msgid "Ticket comment"
msgstr ""
-#: rbac/tree.py:159 settings/serializers/feature.py:98
-#: settings/serializers/feature.py:100 tickets/models/ticket/general.py:308
+#: rbac/tree.py:159 settings/serializers/feature.py:101
+#: settings/serializers/feature.py:103 tickets/models/ticket/general.py:308
msgid "Ticket"
msgstr ""
@@ -5140,7 +5580,7 @@ msgstr ""
msgid "Test smtp setting"
msgstr ""
-#: settings/api/ldap.py:89
+#: settings/api/ldap.py:92
msgid ""
"Users are not synchronized, please click the user synchronization button"
msgstr ""
@@ -5157,75 +5597,75 @@ msgstr ""
msgid "App Settings"
msgstr "Settings"
-#: settings/models.py:37 users/models/preference.py:14
+#: settings/models.py:38 users/models/preference.py:14
msgid "Encrypted"
msgstr ""
-#: settings/models.py:163
+#: settings/models.py:164
msgid "Can change email setting"
msgstr ""
-#: settings/models.py:164
+#: settings/models.py:165
msgid "Can change auth setting"
msgstr ""
-#: settings/models.py:165
+#: settings/models.py:166
msgid "Can change auth ops"
msgstr ""
-#: settings/models.py:166
+#: settings/models.py:167
msgid "Can change auth ticket"
msgstr ""
-#: settings/models.py:167
+#: settings/models.py:168
msgid "Can change virtual app setting"
msgstr ""
-#: settings/models.py:168
+#: settings/models.py:169
msgid "Can change auth announcement"
msgstr ""
-#: settings/models.py:169
+#: settings/models.py:170
msgid "Can change vault setting"
msgstr ""
-#: settings/models.py:170
+#: settings/models.py:171
msgid "Can change chat ai setting"
msgstr ""
-#: settings/models.py:171
+#: settings/models.py:172
msgid "Can change system msg sub setting"
msgstr ""
-#: settings/models.py:172
+#: settings/models.py:173
msgid "Can change sms setting"
msgstr ""
-#: settings/models.py:173
+#: settings/models.py:174
msgid "Can change security setting"
msgstr ""
-#: settings/models.py:174
+#: settings/models.py:175
msgid "Can change clean setting"
msgstr ""
-#: settings/models.py:175
+#: settings/models.py:176
msgid "Can change interface setting"
msgstr ""
-#: settings/models.py:176
+#: settings/models.py:177
msgid "Can change license setting"
msgstr ""
-#: settings/models.py:177
+#: settings/models.py:178
msgid "Can change terminal setting"
msgstr ""
-#: settings/models.py:178
+#: settings/models.py:179
msgid "Can change other setting"
msgstr ""
-#: settings/models.py:188
+#: settings/models.py:189
msgid "Chat prompt"
msgstr ""
@@ -5238,58 +5678,62 @@ msgid "LDAP Auth"
msgstr ""
#: settings/serializers/auth/base.py:14
-msgid "CAS Auth"
+msgid "LDAP Auth HA"
msgstr ""
#: settings/serializers/auth/base.py:15
-msgid "OPENID Auth"
+msgid "CAS Auth"
msgstr ""
#: settings/serializers/auth/base.py:16
-msgid "SAML2 Auth"
+msgid "OPENID Auth"
msgstr ""
#: settings/serializers/auth/base.py:17
-msgid "OAuth2 Auth"
+msgid "SAML2 Auth"
msgstr ""
#: settings/serializers/auth/base.py:18
-msgid "RADIUS Auth"
+msgid "OAuth2 Auth"
msgstr ""
#: settings/serializers/auth/base.py:19
-msgid "DingTalk Auth"
+msgid "RADIUS Auth"
msgstr ""
#: settings/serializers/auth/base.py:20
-msgid "FeiShu Auth"
+msgid "DingTalk Auth"
msgstr ""
#: settings/serializers/auth/base.py:21
-msgid "Lark Auth"
+msgid "FeiShu Auth"
msgstr ""
#: settings/serializers/auth/base.py:22
-msgid "Slack Auth"
+msgid "Lark Auth"
msgstr ""
#: settings/serializers/auth/base.py:23
-msgid "WeCom Auth"
+msgid "Slack Auth"
msgstr ""
#: settings/serializers/auth/base.py:24
-msgid "SSO Auth"
+msgid "WeCom Auth"
msgstr ""
#: settings/serializers/auth/base.py:25
+msgid "SSO Auth"
+msgstr ""
+
+#: settings/serializers/auth/base.py:26
msgid "Passkey Auth"
msgstr ""
-#: settings/serializers/auth/base.py:27
+#: settings/serializers/auth/base.py:28
msgid "Email suffix"
msgstr ""
-#: settings/serializers/auth/base.py:29
+#: settings/serializers/auth/base.py:30
msgid ""
"After third-party user authentication is successful, if the third-party "
"authentication service platform does not return the user's email "
@@ -5297,26 +5741,26 @@ msgid ""
"suffix"
msgstr ""
-#: settings/serializers/auth/base.py:36
+#: settings/serializers/auth/base.py:37
msgid "Forgot Password URL"
msgstr ""
-#: settings/serializers/auth/base.py:37
+#: settings/serializers/auth/base.py:38
msgid "The URL for Forgotten Password on the user login page"
msgstr ""
-#: settings/serializers/auth/base.py:40
+#: settings/serializers/auth/base.py:41
msgid "Login redirection"
msgstr ""
-#: settings/serializers/auth/base.py:42
+#: settings/serializers/auth/base.py:43
msgid ""
"Should an flash page be displayed before the user is redirected to third-"
"party authentication when the administrator enables third-party redirect "
"authentication"
msgstr ""
-#: settings/serializers/auth/base.py:54
+#: settings/serializers/auth/base.py:55
msgid ""
"When you create a user, you associate the user to the organization of your "
"choice. Users always belong to the Default organization."
@@ -5326,8 +5770,8 @@ msgstr ""
msgid "CAS"
msgstr ""
-#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:43
-#: settings/serializers/auth/oidc.py:61
+#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap_ha.py:26 settings/serializers/auth/oidc.py:61
msgid "Server"
msgstr ""
@@ -5354,9 +5798,10 @@ msgstr ""
#: settings/serializers/auth/cas.py:34 settings/serializers/auth/dingtalk.py:18
#: settings/serializers/auth/feishu.py:18 settings/serializers/auth/lark.py:17
-#: settings/serializers/auth/ldap.py:65 settings/serializers/auth/oauth2.py:60
-#: settings/serializers/auth/oidc.py:39 settings/serializers/auth/saml2.py:35
-#: settings/serializers/auth/slack.py:18 settings/serializers/auth/wecom.py:18
+#: settings/serializers/auth/ldap.py:66 settings/serializers/auth/ldap_ha.py:48
+#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:39
+#: settings/serializers/auth/saml2.py:35 settings/serializers/auth/slack.py:18
+#: settings/serializers/auth/wecom.py:18
msgid "User attribute"
msgstr ""
@@ -5392,7 +5837,7 @@ msgid ""
"name and the `value` is the FeiShu service user attribute name"
msgstr ""
-#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:21
+#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:22
msgid "Lark"
msgstr ""
@@ -5402,71 +5847,88 @@ msgid ""
"name and the `value` is the Lark service user attribute name"
msgstr ""
-#: settings/serializers/auth/ldap.py:40 settings/serializers/auth/ldap.py:102
+#: settings/serializers/auth/ldap.py:41 settings/serializers/auth/ldap.py:103
msgid "LDAP"
msgstr ""
-#: settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap.py:45
msgid "LDAP server URI"
msgstr ""
-#: settings/serializers/auth/ldap.py:47
+#: settings/serializers/auth/ldap.py:48 settings/serializers/auth/ldap_ha.py:30
msgid "Bind DN"
msgstr ""
-#: settings/serializers/auth/ldap.py:48
+#: settings/serializers/auth/ldap.py:49 settings/serializers/auth/ldap_ha.py:31
msgid "Binding Distinguished Name"
msgstr ""
-#: settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap.py:53 settings/serializers/auth/ldap_ha.py:35
msgid "Binding password"
msgstr ""
-#: settings/serializers/auth/ldap.py:55
+#: settings/serializers/auth/ldap.py:56 settings/serializers/auth/ldap_ha.py:38
msgid "Search OU"
msgstr ""
-#: settings/serializers/auth/ldap.py:57
+#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/ldap_ha.py:40
msgid ""
"User Search Base, if there are multiple OUs, you can separate them with the "
"`|` symbol"
msgstr ""
-#: settings/serializers/auth/ldap.py:61
+#: settings/serializers/auth/ldap.py:62 settings/serializers/auth/ldap_ha.py:44
msgid "Search filter"
msgstr ""
-#: settings/serializers/auth/ldap.py:62
+#: settings/serializers/auth/ldap.py:63 settings/serializers/auth/ldap_ha.py:45
#, python-format
msgid "Selection could include (cn|uid|sAMAccountName=%(user)s)"
msgstr ""
-#: settings/serializers/auth/ldap.py:67
+#: settings/serializers/auth/ldap.py:68 settings/serializers/auth/ldap_ha.py:50
msgid ""
"User attribute mapping, where the `key` is the JumpServer user attribute "
"name and the `value` is the LDAP service user attribute name"
msgstr ""
-#: settings/serializers/auth/ldap.py:83
+#: settings/serializers/auth/ldap.py:84 settings/serializers/auth/ldap_ha.py:66
msgid "Connect timeout (s)"
msgstr ""
-#: settings/serializers/auth/ldap.py:88
+#: settings/serializers/auth/ldap.py:89 settings/serializers/auth/ldap_ha.py:71
msgid "User DN cache timeout (s)"
msgstr ""
-#: settings/serializers/auth/ldap.py:90
+#: settings/serializers/auth/ldap.py:91
msgid ""
"Caching the User DN obtained during user login authentication can "
-"effectivelyimprove the speed of user authentication., 0 means no cache
If "
-"the user OU structure has been adjusted, click Submit to clear the user DN "
-"cache"
+"effectively improve the speed of user authentication., 0 means no "
+"cache
If the user OU structure has been adjusted, click Submit to clear "
+"the user DN cache"
msgstr ""
-#: settings/serializers/auth/ldap.py:96
+#: settings/serializers/auth/ldap.py:97 settings/serializers/auth/ldap_ha.py:79
msgid "Search paged size (piece)"
msgstr ""
+#: settings/serializers/auth/ldap_ha.py:23
+#: settings/serializers/auth/ldap_ha.py:85
+msgid "LDAP HA"
+msgstr ""
+
+#: settings/serializers/auth/ldap_ha.py:27
+msgid "LDAP HA server URI"
+msgstr ""
+
+#: settings/serializers/auth/ldap_ha.py:73
+msgid ""
+"Caching the User DN obtained during user login authentication can "
+"effectivelyimprove the speed of user authentication., 0 means no cache
If "
+"the user OU structure has been adjusted, click Submit to clear the user DN "
+"cache"
+msgstr ""
+
#: settings/serializers/auth/oauth2.py:19
#: settings/serializers/auth/oauth2.py:22
msgid "OAuth2"
@@ -5887,32 +6349,42 @@ msgid ""
"database, OSS will not be affected."
msgstr ""
-#: settings/serializers/feature.py:18 settings/serializers/msg.py:68
+#: settings/serializers/cleaning.py:53
+msgid "Change secret and push record retention days (day)"
+msgstr ""
+
+#: settings/serializers/feature.py:19 settings/serializers/msg.py:68
msgid "Subject"
msgstr ""
-#: settings/serializers/feature.py:22
+#: settings/serializers/feature.py:23
msgid "More Link"
msgstr ""
-#: settings/serializers/feature.py:36 settings/serializers/feature.py:38
-#: settings/serializers/feature.py:39
+#: settings/serializers/feature.py:26
+#: settings/templates/ldap/_msg_import_ldap_user.html:6
+#: terminal/models/session/session.py:46
+msgid "Date end"
+msgstr ""
+
+#: settings/serializers/feature.py:39 settings/serializers/feature.py:41
+#: settings/serializers/feature.py:42
msgid "Announcement"
msgstr ""
-#: settings/serializers/feature.py:46
+#: settings/serializers/feature.py:49
msgid "Vault"
msgstr ""
-#: settings/serializers/feature.py:55
+#: settings/serializers/feature.py:58
msgid "Mount Point"
msgstr ""
-#: settings/serializers/feature.py:61
+#: settings/serializers/feature.py:64
msgid "Record limit"
msgstr ""
-#: settings/serializers/feature.py:63
+#: settings/serializers/feature.py:66
msgid ""
"If the specific value is less than 999 (default), the system will "
"automatically perform a task every night: check and delete historical "
@@ -5920,74 +6392,74 @@ msgid ""
"exceeds 999 (default), no historical account deletion will be performed"
msgstr ""
-#: settings/serializers/feature.py:73 settings/serializers/feature.py:79
+#: settings/serializers/feature.py:76 settings/serializers/feature.py:82
msgid "Chat AI"
msgstr ""
-#: settings/serializers/feature.py:82
+#: settings/serializers/feature.py:85
msgid "GPT Base URL"
msgstr ""
-#: settings/serializers/feature.py:83
+#: settings/serializers/feature.py:86
msgid "The base URL of the GPT service. For example: https://api.openai.com/v1"
msgstr ""
-#: settings/serializers/feature.py:86 templates/_header_bar.html:96
+#: settings/serializers/feature.py:89 templates/_header_bar.html:96
msgid "API Key"
msgstr ""
-#: settings/serializers/feature.py:90
+#: settings/serializers/feature.py:93
msgid ""
"The proxy server address of the GPT service. For example: http://ip:port"
msgstr ""
-#: settings/serializers/feature.py:93
+#: settings/serializers/feature.py:96
msgid "GPT Model"
msgstr ""
-#: settings/serializers/feature.py:102
+#: settings/serializers/feature.py:105
msgid "Approval without login"
msgstr ""
-#: settings/serializers/feature.py:103
+#: settings/serializers/feature.py:106
msgid "Allow direct approval ticket without login"
msgstr ""
-#: settings/serializers/feature.py:107
+#: settings/serializers/feature.py:110
msgid "Period"
msgstr ""
-#: settings/serializers/feature.py:108
+#: settings/serializers/feature.py:111
msgid ""
"The default authorization time period when applying for assets via a ticket"
msgstr ""
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "hour"
msgstr ""
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "Unit"
msgstr ""
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "The unit of period"
msgstr ""
-#: settings/serializers/feature.py:121
+#: settings/serializers/feature.py:124
msgid ""
"Allow users to execute batch commands in the Workbench - Job Center - Adhoc"
msgstr ""
-#: settings/serializers/feature.py:125
+#: settings/serializers/feature.py:128
msgid "Command blacklist"
msgstr ""
-#: settings/serializers/feature.py:126
+#: settings/serializers/feature.py:129
msgid "Command blacklist in Adhoc"
msgstr ""
-#: settings/serializers/feature.py:131
+#: settings/serializers/feature.py:134
#: terminal/models/virtualapp/provider.py:17
#: terminal/models/virtualapp/virtualapp.py:36
#: terminal/models/virtualapp/virtualapp.py:97
@@ -5995,11 +6467,11 @@ msgstr ""
msgid "Virtual app"
msgstr ""
-#: settings/serializers/feature.py:134
+#: settings/serializers/feature.py:137
msgid "Virtual App"
msgstr ""
-#: settings/serializers/feature.py:136
+#: settings/serializers/feature.py:139
msgid ""
"Virtual applications, you can use the Linux operating system as an "
"application server in remote applications."
@@ -6419,21 +6891,44 @@ msgid ""
"in the workbench"
msgstr ""
-#: settings/tasks/ldap.py:28
+#: settings/tasks/ldap.py:73
msgid "Periodic import ldap user"
msgstr ""
-#: settings/tasks/ldap.py:66
+#: settings/tasks/ldap.py:75 settings/tasks/ldap.py:85
+msgid ""
+"When LDAP auto-sync is configured, this task will be invoked to synchronize "
+"users"
+msgstr ""
+
+#: settings/tasks/ldap.py:83
+msgid "Periodic import ldap ha user"
+msgstr ""
+
+#: settings/tasks/ldap.py:117
msgid "Registration periodic import ldap user task"
msgstr ""
-#: settings/templates/ldap/_msg_import_ldap_user.html:2
-msgid "Sync task finish"
+#: settings/tasks/ldap.py:119
+msgid ""
+"When LDAP auto-sync parameters change, such as Crontab parameters, the LDAP "
+"sync task \n"
+" will be re-registered or updated, and this task will be invoked"
msgstr ""
-#: settings/templates/ldap/_msg_import_ldap_user.html:6
-#: terminal/models/session/session.py:46
-msgid "Date end"
+#: settings/tasks/ldap.py:133
+msgid "Registration periodic import ldap ha user task"
+msgstr ""
+
+#: settings/tasks/ldap.py:135
+msgid ""
+"When LDAP HA auto-sync parameters change, such as Crontab parameters, the "
+"LDAP HA sync task \n"
+" will be re-registered or updated, and this task will be invoked"
+msgstr ""
+
+#: settings/templates/ldap/_msg_import_ldap_user.html:2
+msgid "Sync task finish"
msgstr ""
#: settings/templates/ldap/_msg_import_ldap_user.html:9
@@ -6448,109 +6943,109 @@ msgstr ""
msgid "No user synchronization required"
msgstr ""
-#: settings/utils/ldap.py:494
+#: settings/utils/ldap.py:509
msgid "ldap:// or ldaps:// protocol is used."
msgstr ""
-#: settings/utils/ldap.py:505
+#: settings/utils/ldap.py:520
msgid "Host or port is disconnected: {}"
msgstr ""
-#: settings/utils/ldap.py:507
+#: settings/utils/ldap.py:522
msgid "The port is not the port of the LDAP service: {}"
msgstr ""
-#: settings/utils/ldap.py:509
+#: settings/utils/ldap.py:524
msgid "Please add certificate: {}"
msgstr ""
-#: settings/utils/ldap.py:513 settings/utils/ldap.py:540
-#: settings/utils/ldap.py:570 settings/utils/ldap.py:598
+#: settings/utils/ldap.py:528 settings/utils/ldap.py:555
+#: settings/utils/ldap.py:585 settings/utils/ldap.py:613
msgid "Unknown error: {}"
msgstr ""
-#: settings/utils/ldap.py:527
+#: settings/utils/ldap.py:542
msgid "Bind DN or Password incorrect"
msgstr ""
-#: settings/utils/ldap.py:534
+#: settings/utils/ldap.py:549
msgid "Please enter Bind DN: {}"
msgstr ""
-#: settings/utils/ldap.py:536
+#: settings/utils/ldap.py:551
msgid "Please enter Password: {}"
msgstr ""
-#: settings/utils/ldap.py:538
+#: settings/utils/ldap.py:553
msgid "Please enter correct Bind DN and Password: {}"
msgstr ""
-#: settings/utils/ldap.py:556
+#: settings/utils/ldap.py:571
msgid "Invalid User OU or User search filter: {}"
msgstr ""
-#: settings/utils/ldap.py:587
+#: settings/utils/ldap.py:602
msgid "LDAP User attr map not include: {}"
msgstr ""
-#: settings/utils/ldap.py:594
+#: settings/utils/ldap.py:609
msgid "LDAP User attr map is not dict"
msgstr ""
-#: settings/utils/ldap.py:613
+#: settings/utils/ldap.py:628
msgid "LDAP authentication is not enabled"
msgstr ""
-#: settings/utils/ldap.py:631
+#: settings/utils/ldap.py:646
msgid "Error (Invalid LDAP server): {}"
msgstr ""
-#: settings/utils/ldap.py:633
+#: settings/utils/ldap.py:648
msgid "Error (Invalid Bind DN): {}"
msgstr ""
-#: settings/utils/ldap.py:635
+#: settings/utils/ldap.py:650
msgid "Error (Invalid LDAP User attr map): {}"
msgstr ""
-#: settings/utils/ldap.py:637
+#: settings/utils/ldap.py:652
msgid "Error (Invalid User OU or User search filter): {}"
msgstr ""
-#: settings/utils/ldap.py:639
+#: settings/utils/ldap.py:654
msgid "Error (Not enabled LDAP authentication): {}"
msgstr ""
-#: settings/utils/ldap.py:641
+#: settings/utils/ldap.py:656
msgid "Error (Unknown): {}"
msgstr ""
-#: settings/utils/ldap.py:644
+#: settings/utils/ldap.py:659
msgid "Succeed: Match {} users"
msgstr ""
-#: settings/utils/ldap.py:677
+#: settings/utils/ldap.py:689
msgid "Authentication failed (configuration incorrect): {}"
msgstr ""
-#: settings/utils/ldap.py:681
+#: settings/utils/ldap.py:693
msgid "Authentication failed (username or password incorrect): {}"
msgstr ""
-#: settings/utils/ldap.py:683
+#: settings/utils/ldap.py:695
msgid "Authentication failed (Unknown): {}"
msgstr ""
-#: settings/utils/ldap.py:686
+#: settings/utils/ldap.py:698
msgid "Authentication success: {}"
msgstr ""
-#: settings/ws.py:195
+#: settings/ws.py:199
msgid "No LDAP user was found"
msgstr ""
-#: settings/ws.py:201
-msgid "Imported total: {} new: {}, failed: {} Organization: {}"
+#: settings/ws.py:205
+msgid "Total {}, success {}, failure {}"
msgstr ""
#: templates/_csv_import_export.html:8
@@ -6749,27 +7244,27 @@ msgstr ""
msgid "Deleting the default storage is not allowed"
msgstr ""
-#: terminal/api/component/storage.py:34
-msgid "Cannot delete storage that is being used"
+#: terminal/api/component/storage.py:36
+msgid "Cannot delete storage that is being used: {}"
msgstr ""
-#: terminal/api/component/storage.py:75 terminal/api/component/storage.py:76
+#: terminal/api/component/storage.py:77 terminal/api/component/storage.py:78
msgid "Command storages"
msgstr ""
-#: terminal/api/component/storage.py:82
+#: terminal/api/component/storage.py:84
msgid "Invalid"
msgstr ""
-#: terminal/api/component/storage.py:130 terminal/tasks.py:149
+#: terminal/api/component/storage.py:132 terminal/tasks.py:208
msgid "Test failure: {}"
msgstr ""
-#: terminal/api/component/storage.py:133
+#: terminal/api/component/storage.py:135
msgid "Test successful"
msgstr ""
-#: terminal/api/component/storage.py:135
+#: terminal/api/component/storage.py:137
msgid "Test failure: Please check configuration"
msgstr ""
@@ -6782,15 +7277,15 @@ msgstr ""
msgid "User %s %s session %s replay"
msgstr ""
-#: terminal/api/session/session.py:314
+#: terminal/api/session/session.py:326
msgid "Session does not exist: {}"
msgstr ""
-#: terminal/api/session/session.py:317
+#: terminal/api/session/session.py:329
msgid "Session is finished or the protocol not supported"
msgstr ""
-#: terminal/api/session/session.py:330
+#: terminal/api/session/session.py:342
msgid "User does not have permission"
msgstr ""
@@ -6954,7 +7449,7 @@ msgstr ""
msgid "Can concurrent"
msgstr ""
-#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:167
+#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:179
#: terminal/serializers/storage.py:193
msgid "Hosts"
msgstr ""
@@ -6985,7 +7480,7 @@ msgstr ""
msgid "Applet Publication"
msgstr ""
-#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:69
+#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:81
msgid "Deploy options"
msgstr ""
@@ -7097,12 +7592,12 @@ msgstr ""
msgid "Boot Time"
msgstr ""
-#: terminal/models/component/storage.py:146
+#: terminal/models/component/storage.py:144
#: terminal/models/component/terminal.py:91
msgid "Command storage"
msgstr ""
-#: terminal/models/component/storage.py:214
+#: terminal/models/component/storage.py:212
#: terminal/models/component/terminal.py:92
msgid "Replay storage"
msgstr ""
@@ -7119,7 +7614,7 @@ msgstr ""
msgid "Application User"
msgstr ""
-#: terminal/models/component/terminal.py:177
+#: terminal/models/component/terminal.py:176
msgid "Can view terminal config"
msgstr ""
@@ -7151,7 +7646,7 @@ msgstr ""
msgid "Replay"
msgstr ""
-#: terminal/models/session/session.py:48 terminal/serializers/session.py:68
+#: terminal/models/session/session.py:48 terminal/serializers/session.py:78
msgid "Command amount"
msgstr ""
@@ -7159,23 +7654,23 @@ msgstr ""
msgid "Error reason"
msgstr ""
-#: terminal/models/session/session.py:290
+#: terminal/models/session/session.py:308
msgid "Session record"
msgstr ""
-#: terminal/models/session/session.py:292
+#: terminal/models/session/session.py:310
msgid "Can monitor session"
msgstr ""
-#: terminal/models/session/session.py:293
+#: terminal/models/session/session.py:311
msgid "Can share session"
msgstr ""
-#: terminal/models/session/session.py:294
+#: terminal/models/session/session.py:312
msgid "Can terminate session"
msgstr ""
-#: terminal/models/session/session.py:295
+#: terminal/models/session/session.py:313
msgid "Can validate session action perm"
msgstr ""
@@ -7275,8 +7770,8 @@ msgstr ""
msgid "Command and replay storage"
msgstr "Storage"
-#: terminal/notifications.py:240 terminal/tasks.py:153
-#: xpack/plugins/cloud/api.py:154
+#: terminal/notifications.py:240 terminal/tasks.py:212
+#: xpack/plugins/cloud/api.py:160
#: xpack/plugins/cloud/serializers/account.py:121
#: xpack/plugins/cloud/serializers/account.py:123
msgid "Test failure: Account invalid"
@@ -7292,11 +7787,11 @@ msgid "Icon"
msgstr ""
#: terminal/serializers/applet_host.py:24
-msgid "Per Session"
+msgid "Per Device (Device number limit)"
msgstr ""
#: terminal/serializers/applet_host.py:25
-msgid "Per Device"
+msgid "Per User (User number limit)"
msgstr ""
#: terminal/serializers/applet_host.py:37
@@ -7321,48 +7816,61 @@ msgstr ""
msgid "Ignore Certificate Verification"
msgstr ""
-#: terminal/serializers/applet_host.py:47
+#: terminal/serializers/applet_host.py:48
msgid "Existing RDS license"
msgstr ""
-#: terminal/serializers/applet_host.py:48
+#: terminal/serializers/applet_host.py:50
+msgid ""
+"If not exist, the RDS will be in trial mode, and the trial period is 120 "
+"days. Detail"
+msgstr ""
+
+#: terminal/serializers/applet_host.py:55
msgid "RDS License Server"
msgstr ""
-#: terminal/serializers/applet_host.py:49
+#: terminal/serializers/applet_host.py:57
msgid "RDS Licensing Mode"
msgstr ""
-#: terminal/serializers/applet_host.py:51
+#: terminal/serializers/applet_host.py:60
msgid "RDS Single Session Per User"
msgstr ""
-#: terminal/serializers/applet_host.py:53
+#: terminal/serializers/applet_host.py:61
+msgid ""
+"Tips: A RDS user can have only one session at a time. If set, when next "
+"login connected, previous session will be disconnected."
+msgstr ""
+
+#: terminal/serializers/applet_host.py:65
msgid "RDS Max Disconnection Time (ms)"
msgstr ""
-#: terminal/serializers/applet_host.py:55
+#: terminal/serializers/applet_host.py:67
msgid ""
"Tips: Set the maximum duration for keeping a disconnected session active on "
"the server (log off the session after 60000 milliseconds)."
msgstr ""
-#: terminal/serializers/applet_host.py:60
+#: terminal/serializers/applet_host.py:72
msgid "RDS Remote App Logoff Time Limit (ms)"
msgstr ""
-#: terminal/serializers/applet_host.py:62
+#: terminal/serializers/applet_host.py:74
msgid ""
"Tips: Set the logoff time for RemoteApp sessions after closing all RemoteApp "
"programs (0 milliseconds, log off the session immediately)."
msgstr ""
-#: terminal/serializers/applet_host.py:71 terminal/serializers/terminal.py:47
+#: terminal/serializers/applet_host.py:83 terminal/serializers/terminal.py:47
#: terminal/serializers/virtualapp_provider.py:13
msgid "Load status"
msgstr ""
-#: terminal/serializers/applet_host.py:85
+#: terminal/serializers/applet_host.py:97
msgid ""
"These accounts are used to connect to the published application, the account "
"is now divided into two types, one is dedicated to each account, each user "
@@ -7371,26 +7879,26 @@ msgid ""
"be used to connect"
msgstr ""
-#: terminal/serializers/applet_host.py:92
+#: terminal/serializers/applet_host.py:104
msgid "The number of public accounts created automatically"
msgstr ""
-#: terminal/serializers/applet_host.py:95
+#: terminal/serializers/applet_host.py:107
msgid ""
"Connect to the host using the same account first. For security reasons, "
"please set the configuration item CACHE_LOGIN_PASSWORD_ENABLED=true and "
"restart the service to enable it."
msgstr ""
-#: terminal/serializers/applet_host.py:137
+#: terminal/serializers/applet_host.py:149
msgid "Install applets"
msgstr ""
-#: terminal/serializers/applet_host.py:167
+#: terminal/serializers/applet_host.py:179
msgid "Host ID"
msgstr ""
-#: terminal/serializers/applet_host.py:168
+#: terminal/serializers/applet_host.py:180
msgid "Applet ID"
msgstr ""
@@ -7566,11 +8074,11 @@ msgstr ""
msgid "Store locally"
msgstr ""
-#: terminal/serializers/storage.py:257
+#: terminal/serializers/storage.py:258
msgid "Do not save"
msgstr ""
-#: terminal/serializers/storage.py:270
+#: terminal/serializers/storage.py:273
msgid ""
"set as the default storage, will make new Component use the current storage "
"by default, without affecting existing Component"
@@ -7709,52 +8217,112 @@ msgstr ""
msgid "storage is null"
msgstr ""
-#: terminal/tasks.py:31
+#: terminal/tasks.py:32
msgid "Periodic delete terminal status"
msgstr ""
-#: terminal/tasks.py:39
+#: terminal/tasks.py:43
msgid "Clean orphan session"
msgstr ""
-#: terminal/tasks.py:87
+#: terminal/tasks.py:45
+msgid ""
+"Check every 10 minutes for asset connection sessions that have been inactive "
+"for 3 \n"
+" minutes and mark these sessions as completed"
+msgstr ""
+
+#: terminal/tasks.py:68
+msgid "Upload session replay to external storage"
+msgstr ""
+
+#: terminal/tasks.py:70 terminal/tasks.py:104
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured in the config.txt, session commands "
+"and \n"
+" recordings will be uploaded to external storage"
+msgstr ""
+
+#: terminal/tasks.py:102
+msgid "Upload session replay part file to external storage"
+msgstr ""
+
+#: terminal/tasks.py:123
msgid "Run applet host deployment"
msgstr ""
-#: terminal/tasks.py:97
+#: terminal/tasks.py:126
+msgid ""
+"When deploying from the remote application publisher details page, and the "
+"'Deploy' \n"
+" button is clicked, this task will be executed"
+msgstr ""
+
+#: terminal/tasks.py:137
msgid "Install applet"
msgstr ""
-#: terminal/tasks.py:108
+#: terminal/tasks.py:140
+msgid ""
+"When the 'Deploy' button is clicked in the 'Remote Application' section of "
+"the remote \n"
+" application publisher details page, this task will be executed"
+msgstr ""
+
+#: terminal/tasks.py:152
msgid "Uninstall applet"
msgstr ""
-#: terminal/tasks.py:119
+#: terminal/tasks.py:155
+msgid ""
+"When the 'Uninstall' button is clicked in the 'Remote Application' section "
+"of the \n"
+" remote application publisher details page, this task will be executed"
+msgstr ""
+
+#: terminal/tasks.py:167
msgid "Generate applet host accounts"
msgstr ""
-#: terminal/tasks.py:131
+#: terminal/tasks.py:170
+msgid ""
+"When a remote publishing server is created and an account needs to be "
+"created \n"
+" automatically, this task will be executed"
+msgstr ""
+
+#: terminal/tasks.py:184
msgid "Check command replay storage connectivity"
msgstr ""
+#: terminal/tasks.py:186
+msgid ""
+"Check every day at midnight whether the external storage for commands and "
+"recordings \n"
+" is accessible. If it is not accessible, send a notification to the "
+"recipients specified \n"
+" in 'System Settings - Notifications - Subscription - Storage - "
+"Connectivity'"
+msgstr ""
+
#: terminal/templates/terminal/_msg_command_alert.html:10
msgid "view"
msgstr ""
-#: terminal/utils/db_port_mapper.py:85
+#: terminal/utils/db_port_mapper.py:88
msgid ""
"No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the "
"administrator to open more ports."
msgstr ""
-#: terminal/utils/db_port_mapper.py:113
+#: terminal/utils/db_port_mapper.py:116
msgid ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
msgstr ""
-#: terminal/utils/db_port_mapper.py:115
+#: terminal/utils/db_port_mapper.py:118
msgid "All available port count: {}, Already use port count: {}"
msgstr ""
@@ -7816,19 +8384,19 @@ msgid ""
"processor: {} ticket ID: {}"
msgstr ""
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Change field"
msgstr ""
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Before change"
msgstr ""
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "After change"
msgstr ""
-#: tickets/handlers/base.py:97
+#: tickets/handlers/base.py:96
msgid "{} {} the ticket"
msgstr ""
@@ -8066,11 +8634,15 @@ msgstr ""
msgid "This user is not authorized to approve this ticket"
msgstr ""
-#: users/api/user.py:155
+#: users/api/user.py:63
+msgid "Cannot delete the admin user. Please disable it instead."
+msgstr ""
+
+#: users/api/user.py:161
msgid "Can not invite self"
msgstr ""
-#: users/api/user.py:208
+#: users/api/user.py:214
msgid "Could not reset self otp, use profile reset instead"
msgstr ""
@@ -8142,14 +8714,18 @@ msgstr ""
msgid "Suffix"
msgstr ""
-#: users/exceptions.py:10
+#: users/exceptions.py:9
msgid "MFA not enabled"
msgstr ""
-#: users/exceptions.py:20
+#: users/exceptions.py:19
msgid "Unable to delete all users"
msgstr ""
+#: users/exceptions.py:24
+msgid "Create failed. The number of SSH keys has reached the limit"
+msgstr ""
+
#: users/forms/profile.py:48
msgid ""
"When enabled, you will enter the MFA binding process the next time you log "
@@ -8269,7 +8845,7 @@ msgstr ""
msgid "User password history"
msgstr ""
-#: users/models/user/_auth.py:33
+#: users/models/user/_auth.py:34
msgid "Force enabled"
msgstr ""
@@ -8512,38 +9088,86 @@ msgstr ""
msgid "name not unique"
msgstr ""
-#: users/signal_handlers.py:39
+#: users/signal_handlers.py:41
msgid ""
"The administrator has enabled \"Only allow existing users to log in\", \n"
" and the current user is not in the user list. Please contact the "
"administrator."
msgstr ""
-#: users/signal_handlers.py:183
+#: users/signal_handlers.py:196
msgid "Clean up expired user sessions"
msgstr ""
-#: users/tasks.py:25
+#: users/signal_handlers.py:198
+msgid ""
+"After logging in via the web, a user session record is created. At 2 a.m. "
+"every day, \n"
+" the system cleans up inactive user devices"
+msgstr ""
+
+#: users/tasks.py:26
msgid "Check password expired"
msgstr ""
-#: users/tasks.py:39
+#: users/tasks.py:28
+msgid ""
+"Check every day at 10 AM whether the passwords of users in the system are "
+"expired, \n"
+" and send a notification 5 days in advance"
+msgstr ""
+
+#: users/tasks.py:46
msgid "Periodic check password expired"
msgstr ""
-#: users/tasks.py:53
+#: users/tasks.py:48
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if passwords have expired"
+msgstr ""
+
+#: users/tasks.py:67
msgid "Check user expired"
msgstr ""
-#: users/tasks.py:70
+#: users/tasks.py:69
+msgid ""
+"Check every day at 2 p.m whether the users in the system are expired, and "
+"send a \n"
+" notification 5 days in advance"
+msgstr ""
+
+#: users/tasks.py:90
msgid "Periodic check user expired"
msgstr ""
-#: users/tasks.py:84
+#: users/tasks.py:92
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if users have expired"
+msgstr ""
+
+#: users/tasks.py:111
msgid "Check unused users"
msgstr ""
-#: users/tasks.py:123
+#: users/tasks.py:113
+msgid ""
+"At 2 p.m. every day, according to the configuration in \"System Settings - "
+"Security - \n"
+" Auth security - Auto disable threshold\" users who have not logged "
+"in or whose API keys \n"
+" have not been used for a long time will be disabled"
+msgstr ""
+
+#: users/tasks.py:157
msgid "The user has not logged in recently and has been disabled."
msgstr ""
@@ -8793,15 +9417,15 @@ msgid ""
"strategy will skipped."
msgstr ""
-#: xpack/plugins/cloud/api.py:66
+#: xpack/plugins/cloud/api.py:72
msgid "Test connection successful"
msgstr ""
-#: xpack/plugins/cloud/api.py:68
+#: xpack/plugins/cloud/api.py:74
msgid "Test connection failed: {}"
msgstr ""
-#: xpack/plugins/cloud/api.py:165
+#: xpack/plugins/cloud/api.py:171
msgid "User {} deleted the current resource and released the assets"
msgstr ""
@@ -8988,49 +9612,49 @@ msgstr ""
msgid "Failed to synchronize the instance \"%s\""
msgstr ""
-#: xpack/plugins/cloud/manager.py:337
+#: xpack/plugins/cloud/manager.py:336
#, python-format
msgid ""
"The updated platform of asset \"%s\" is inconsistent with the original "
"platform type. Skip platform and protocol updates"
msgstr ""
-#: xpack/plugins/cloud/manager.py:393
+#: xpack/plugins/cloud/manager.py:392
#, python-format
msgid "The asset \"%s\" already exists"
msgstr ""
-#: xpack/plugins/cloud/manager.py:395
+#: xpack/plugins/cloud/manager.py:394
#, python-format
msgid "Update asset \"%s\""
msgstr ""
-#: xpack/plugins/cloud/manager.py:398
+#: xpack/plugins/cloud/manager.py:397
#, python-format
msgid "Asset \"%s\" has been updated"
msgstr ""
-#: xpack/plugins/cloud/manager.py:408
+#: xpack/plugins/cloud/manager.py:407
#, python-format
msgid "Prepare to create asset \"%s\""
msgstr ""
-#: xpack/plugins/cloud/manager.py:429
+#: xpack/plugins/cloud/manager.py:428
#, python-format
msgid "Set nodes \"%s\""
msgstr ""
-#: xpack/plugins/cloud/manager.py:455
+#: xpack/plugins/cloud/manager.py:454
#, python-format
msgid "Set accounts \"%s\""
msgstr ""
-#: xpack/plugins/cloud/manager.py:471
+#: xpack/plugins/cloud/manager.py:470
#, python-format
msgid "Set protocols \"%s\""
msgstr ""
-#: xpack/plugins/cloud/manager.py:485 xpack/plugins/cloud/tasks.py:28
+#: xpack/plugins/cloud/manager.py:484 xpack/plugins/cloud/tasks.py:31
msgid "Run sync instance task"
msgstr ""
@@ -9503,10 +10127,29 @@ msgstr ""
msgid "Instance count"
msgstr ""
-#: xpack/plugins/cloud/tasks.py:42
+#: xpack/plugins/cloud/tasks.py:33
+msgid ""
+"\n"
+" Execute this task when manually or scheduled cloud synchronization "
+"tasks are performed\n"
+" "
+msgstr ""
+
+#: xpack/plugins/cloud/tasks.py:52
msgid "Period clean sync instance task execution"
msgstr ""
+#: xpack/plugins/cloud/tasks.py:54
+msgid ""
+"\n"
+" Every day, according to the configuration in \"System Settings - "
+"Tasks - Regular \n"
+" clean-up - Cloud sync task history retention days\" the system will "
+"clean up the execution \n"
+" records generated by cloud synchronization\n"
+" "
+msgstr ""
+
#: xpack/plugins/interface/api.py:52
msgid "Restore default successfully."
msgstr ""
@@ -9576,3 +10219,14 @@ msgstr ""
#: xpack/plugins/license/models.py:86
msgid "Ultimate edition"
msgstr ""
+
+#~ msgid "Clean change secret and push record period description"
+#~ msgstr ""
+#~ "The system will periodically clean up unnecessary change secret records "
+#~ "and push records, including those associated with change tasks, execution "
+#~ "records, assets, and accounts. When any of these associated items are "
+#~ "deleted, the corresponding change secret and push records become invalid. "
+#~ "Therefore, to maintain a tidy and efficient database, the system "
+#~ "automatically cleans up these invalid records every 180 days by default. "
+#~ "This regular cleanup process helps free up storage space and improves the "
+#~ "security and overall performance of data management."
diff --git a/apps/i18n/core/ja/LC_MESSAGES/django.po b/apps/i18n/core/ja/LC_MESSAGES/django.po
index 85b96c1b0..fa9a02acf 100644
--- a/apps/i18n/core/ja/LC_MESSAGES/django.po
+++ b/apps/i18n/core/ja/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-15 14:04+0800\n"
+"POT-Creation-Date: 2024-09-19 16:31+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -109,11 +109,11 @@ msgstr "アカウントのバックアップ計画を実行中です"
msgid "Plan execution end"
msgstr "計画実行終了"
-#: accounts/automations/change_secret/manager.py:100
+#: accounts/automations/change_secret/manager.py:97
msgid "No pending accounts found"
msgstr "保留中のアカウントが見つかりません"
-#: accounts/automations/change_secret/manager.py:227
+#: accounts/automations/change_secret/manager.py:225
#, python-format
msgid "Success: %s, Failed: %s, Total: %s"
msgstr "成功: %s、失敗: %s、合計: %s"
@@ -124,10 +124,11 @@ msgstr "成功: %s、失敗: %s、合計: %s"
#: authentication/confirm/password.py:24 authentication/confirm/password.py:26
#: authentication/forms.py:28
#: authentication/templates/authentication/login.html:362
-#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:51
-#: settings/serializers/msg.py:37 settings/serializers/terminal.py:28
-#: terminal/serializers/storage.py:123 terminal/serializers/storage.py:142
-#: users/forms/profile.py:21 users/serializers/user.py:144
+#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap_ha.py:34 settings/serializers/msg.py:37
+#: settings/serializers/terminal.py:28 terminal/serializers/storage.py:123
+#: terminal/serializers/storage.py:142 users/forms/profile.py:21
+#: users/serializers/user.py:144
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -145,7 +146,7 @@ msgid "Access key"
msgstr "アクセスキー"
#: accounts/const/account.py:9 authentication/backends/passkey/models.py:16
-#: authentication/models/sso_token.py:14 settings/serializers/feature.py:52
+#: authentication/models/sso_token.py:14 settings/serializers/feature.py:55
msgid "Token"
msgstr "トークン"
@@ -208,8 +209,8 @@ msgstr "パスワードを変更する"
msgid "Verify account"
msgstr "アカウントを確認"
-#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:24
-#: accounts/tasks/remove_account.py:33
+#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:25
+#: accounts/tasks/remove_account.py:38
msgid "Remove account"
msgstr "アカウントの削除"
@@ -311,11 +312,11 @@ msgid "Pending"
msgstr "未定"
#: accounts/const/vault.py:8 assets/const/category.py:12
-#: assets/models/asset/database.py:9 assets/models/asset/database.py:24
+#: assets/models/asset/database.py:10 assets/models/asset/database.py:29
msgid "Database"
msgstr "データベース"
-#: accounts/const/vault.py:9 settings/serializers/feature.py:43
+#: accounts/const/vault.py:9 settings/serializers/feature.py:46
msgid "HCP Vault"
msgstr "HashiCorp Vault"
@@ -340,14 +341,14 @@ msgstr "ユーザー %s がパスワードを閲覧/導き出しました"
#: accounts/models/account.py:49
#: accounts/models/automations/gather_account.py:16
#: accounts/serializers/account/account.py:226
-#: accounts/serializers/account/account.py:271
+#: accounts/serializers/account/account.py:272
#: accounts/serializers/account/gathered_account.py:10
#: accounts/serializers/automations/change_secret.py:111
#: accounts/serializers/automations/change_secret.py:143
#: accounts/templates/accounts/asset_account_change_info.html:7
#: accounts/templates/accounts/change_secret_failed_info.html:11
-#: acls/serializers/base.py:123 assets/models/asset/common.py:95
-#: assets/models/asset/common.py:349 assets/models/cmd_filter.py:36
+#: acls/serializers/base.py:123 assets/models/asset/common.py:102
+#: assets/models/asset/common.py:362 assets/models/cmd_filter.py:36
#: audits/models.py:58 authentication/models/connection_token.py:36
#: perms/models/asset_permission.py:69 terminal/backends/command/models.py:17
#: terminal/models/session/session.py:32 terminal/notifications.py:155
@@ -360,13 +361,13 @@ msgstr "資産"
#: accounts/models/account.py:53 accounts/models/template.py:16
#: accounts/serializers/account/account.py:233
-#: accounts/serializers/account/account.py:281
-#: accounts/serializers/account/template.py:27
+#: accounts/serializers/account/account.py:282
+#: accounts/serializers/account/template.py:37
#: authentication/serializers/connect_token_secret.py:50
msgid "Su from"
msgstr "から切り替え"
-#: accounts/models/account.py:55 assets/const/protocol.py:189
+#: accounts/models/account.py:55 assets/const/protocol.py:195
#: settings/serializers/auth/cas.py:25 terminal/models/applet/applet.py:36
#: terminal/models/virtualapp/virtualapp.py:21
msgid "Version"
@@ -387,7 +388,7 @@ msgstr "ソース ID"
#: accounts/templates/accounts/change_secret_failed_info.html:12
#: acls/serializers/base.py:124
#: acls/templates/acls/asset_login_reminder.html:10
-#: assets/serializers/gateway.py:28 audits/models.py:59
+#: assets/serializers/gateway.py:33 audits/models.py:59
#: authentication/api/connection_token.py:411 ops/models/base.py:18
#: perms/models/asset_permission.py:75 settings/serializers/msg.py:33
#: terminal/backends/command/models.py:18 terminal/models/session/session.py:34
@@ -459,9 +460,9 @@ msgstr "アカウントバックアップ計画"
#: accounts/models/automations/backup_account.py:120
#: assets/models/automations/base.py:115 audits/models.py:65
-#: ops/models/base.py:55 ops/models/celery.py:88 ops/models/job.py:242
+#: ops/models/base.py:55 ops/models/celery.py:89 ops/models/job.py:242
#: ops/templates/ops/celery_task_log.html:101
-#: perms/models/asset_permission.py:78
+#: perms/models/asset_permission.py:78 settings/serializers/feature.py:25
#: settings/templates/ldap/_msg_import_ldap_user.html:5
#: terminal/models/applet/host.py:141 terminal/models/session/session.py:45
#: tickets/models/ticket/apply_application.py:30
@@ -471,7 +472,7 @@ msgstr "開始日"
#: accounts/models/automations/backup_account.py:123
#: authentication/templates/authentication/_msg_oauth_bind.html:11
-#: notifications/notifications.py:194
+#: notifications/notifications.py:199
#: settings/templates/ldap/_msg_import_ldap_user.html:3
msgid "Time"
msgstr "時間"
@@ -550,7 +551,8 @@ msgstr "SSHキープッシュ方式"
#: accounts/models/automations/gather_account.py:58
#: accounts/serializers/account/backup.py:40
#: accounts/serializers/automations/change_secret.py:58
-#: settings/serializers/auth/ldap.py:99 settings/serializers/msg.py:45
+#: settings/serializers/auth/ldap.py:100
+#: settings/serializers/auth/ldap_ha.py:82 settings/serializers/msg.py:45
msgid "Recipient"
msgstr "受信者"
@@ -572,7 +574,7 @@ msgstr "開始日"
#: accounts/models/automations/change_secret.py:42
#: assets/models/automations/base.py:116 ops/models/base.py:56
-#: ops/models/celery.py:89 ops/models/job.py:243
+#: ops/models/celery.py:90 ops/models/job.py:243
#: terminal/models/applet/host.py:142
msgid "Date finished"
msgstr "終了日"
@@ -584,7 +586,7 @@ msgstr "終了日"
#: terminal/models/applet/applet.py:331 terminal/models/applet/host.py:140
#: terminal/models/component/status.py:30
#: terminal/models/virtualapp/virtualapp.py:99
-#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:136
+#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:148
#: terminal/serializers/virtualapp.py:35 tickets/models/ticket/general.py:284
#: tickets/serializers/super_ticket.py:13
#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:225
@@ -592,8 +594,8 @@ msgstr "終了日"
msgid "Status"
msgstr "ステータス"
-#: accounts/models/automations/change_secret.py:47
-#: accounts/serializers/account/account.py:273
+#: accounts/models/automations/change_secret.py:46
+#: accounts/serializers/account/account.py:274
#: accounts/templates/accounts/change_secret_failed_info.html:13
#: assets/const/automation.py:8
#: authentication/templates/authentication/passkey.html:173
@@ -603,7 +605,7 @@ msgstr "ステータス"
msgid "Error"
msgstr "間違い"
-#: accounts/models/automations/change_secret.py:51
+#: accounts/models/automations/change_secret.py:50
msgid "Change secret record"
msgstr "パスワード レコードの変更"
@@ -634,7 +636,7 @@ msgid "Address login"
msgstr "最終ログインアドレス"
#: accounts/models/automations/gather_account.py:44
-#: accounts/tasks/gather_accounts.py:29
+#: accounts/tasks/gather_accounts.py:30
msgid "Gather asset accounts"
msgstr "アカウントのコレクション"
@@ -655,7 +657,7 @@ msgstr "トリガー方式"
#: audits/models.py:92 audits/serializers.py:84
#: authentication/serializers/connect_token_secret.py:119
#: authentication/templates/authentication/_access_key_modal.html:34
-#: perms/serializers/permission.py:41 perms/serializers/permission.py:63
+#: perms/serializers/permission.py:52 perms/serializers/permission.py:74
#: tickets/serializers/ticket/ticket.py:21
msgid "Action"
msgstr "アクション"
@@ -669,7 +671,7 @@ msgid "Verify asset account"
msgstr "アカウントの確認"
#: accounts/models/base.py:37 accounts/models/base.py:67
-#: accounts/serializers/account/account.py:463
+#: accounts/serializers/account/account.py:464
#: accounts/serializers/account/base.py:17
#: accounts/serializers/automations/change_secret.py:47
#: authentication/serializers/connect_token_secret.py:42
@@ -691,28 +693,28 @@ msgstr "ひみつ"
msgid "Secret strategy"
msgstr "鍵ポリシー"
-#: accounts/models/base.py:44 accounts/serializers/account/template.py:24
+#: accounts/models/base.py:44 accounts/serializers/account/template.py:34
#: accounts/serializers/automations/change_secret.py:46
msgid "Password rules"
msgstr "パスワードルール"
#: accounts/models/base.py:64 accounts/serializers/account/virtual.py:20
#: acls/models/base.py:35 acls/models/base.py:96 acls/models/command_acl.py:21
-#: acls/serializers/base.py:35 assets/models/asset/common.py:93
-#: assets/models/asset/common.py:159 assets/models/cmd_filter.py:21
+#: acls/serializers/base.py:35 assets/models/asset/common.py:100
+#: assets/models/asset/common.py:166 assets/models/cmd_filter.py:21
#: assets/models/domain.py:19 assets/models/label.py:18
#: assets/models/platform.py:15 assets/models/platform.py:94
-#: assets/serializers/asset/common.py:149 assets/serializers/platform.py:153
-#: assets/serializers/platform.py:273
+#: assets/serializers/asset/common.py:169 assets/serializers/platform.py:157
+#: assets/serializers/platform.py:277
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:113
#: authentication/serializers/connect_token_secret.py:169 labels/models.py:11
-#: ops/mixin.py:21 ops/models/adhoc.py:20 ops/models/celery.py:15
-#: ops/models/celery.py:80 ops/models/job.py:142 ops/models/playbook.py:28
+#: ops/mixin.py:28 ops/models/adhoc.py:19 ops/models/celery.py:15
+#: ops/models/celery.py:81 ops/models/job.py:142 ops/models/playbook.py:30
#: ops/serializers/job.py:18 orgs/models.py:82
#: perms/models/asset_permission.py:61 rbac/models/role.py:29
-#: rbac/serializers/role.py:28 settings/models.py:34 settings/models.py:183
+#: rbac/serializers/role.py:28 settings/models.py:35 settings/models.py:184
#: settings/serializers/msg.py:89 settings/serializers/terminal.py:9
#: terminal/models/applet/applet.py:34 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:109
@@ -834,7 +836,6 @@ msgstr ""
"情報にアクセスしてください-> 環境設定で暗号化パスワードを設定する"
#: accounts/notifications.py:83
-#: accounts/templates/accounts/asset_account_change_info.html:3
msgid "Gather account change information"
msgstr "アカウント変更情報"
@@ -854,11 +855,16 @@ msgstr "パラメータ"
msgid "Exist policy"
msgstr "アカウントの存在ポリシー"
+#: accounts/serializers/account/account.py:181
+#: accounts/serializers/account/account.py:340
+msgid "Account already exists"
+msgstr "アカウントはすでに存在しています"
+
#: accounts/serializers/account/account.py:206 assets/models/label.py:21
-#: assets/models/platform.py:95 assets/serializers/asset/common.py:125
-#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:168
-#: assets/serializers/platform.py:274 perms/serializers/user_permission.py:26
-#: settings/models.py:36 tickets/models/ticket/apply_application.py:13
+#: assets/models/platform.py:95 assets/serializers/asset/common.py:145
+#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:172
+#: assets/serializers/platform.py:278 perms/serializers/user_permission.py:26
+#: settings/models.py:37 tickets/models/ticket/apply_application.py:13
#: users/models/preference.py:12
msgid "Category"
msgstr "カテゴリ"
@@ -867,13 +873,13 @@ msgstr "カテゴリ"
#: accounts/serializers/automations/base.py:55 acls/models/command_acl.py:24
#: acls/serializers/command_acl.py:19 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:96
-#: assets/serializers/asset/common.py:126 assets/serializers/platform.py:155
-#: assets/serializers/platform.py:167 audits/serializers.py:53
+#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:159
+#: assets/serializers/platform.py:171 audits/serializers.py:53
#: audits/serializers.py:170
#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:150
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
#: terminal/models/component/storage.py:58
-#: terminal/models/component/storage.py:154 terminal/serializers/applet.py:29
+#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:29
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
@@ -886,62 +892,58 @@ msgstr "タイプ"
msgid "Asset not found"
msgstr "資産が存在しません"
-#: accounts/serializers/account/account.py:262
+#: accounts/serializers/account/account.py:263
msgid "Has secret"
msgstr "エスクローされたパスワード"
-#: accounts/serializers/account/account.py:272 ops/models/celery.py:83
+#: accounts/serializers/account/account.py:273 ops/models/celery.py:84
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:49
#: tickets/models/ticket/general.py:280 tickets/serializers/super_ticket.py:14
msgid "State"
msgstr "状態"
-#: accounts/serializers/account/account.py:274
+#: accounts/serializers/account/account.py:275
msgid "Changed"
msgstr "編集済み"
-#: accounts/serializers/account/account.py:284
+#: accounts/serializers/account/account.py:285
#: accounts/serializers/automations/base.py:22 acls/models/base.py:97
#: acls/templates/acls/asset_login_reminder.html:9
#: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:34
-#: assets/serializers/platform.py:176 assets/serializers/platform.py:208
+#: assets/serializers/platform.py:180 assets/serializers/platform.py:212
#: authentication/api/connection_token.py:410 ops/models/base.py:17
#: ops/models/job.py:152 ops/serializers/job.py:19
-#: perms/serializers/permission.py:35
+#: perms/serializers/permission.py:46
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
#: xpack/plugins/cloud/manager.py:83
msgid "Assets"
msgstr "資産"
-#: accounts/serializers/account/account.py:339
-msgid "Account already exists"
-msgstr "アカウントはすでに存在しています"
-
-#: accounts/serializers/account/account.py:389
+#: accounts/serializers/account/account.py:390
#, python-format
msgid "Asset does not support this secret type: %s"
msgstr "アセットはアカウント タイプをサポートしていません: %s"
-#: accounts/serializers/account/account.py:421
+#: accounts/serializers/account/account.py:422
msgid "Account has exist"
msgstr "アカウントはすでに存在しています"
-#: accounts/serializers/account/account.py:458
+#: accounts/serializers/account/account.py:459
#: accounts/serializers/account/base.py:93
-#: accounts/serializers/account/template.py:72
-#: assets/serializers/asset/common.py:387
+#: accounts/serializers/account/template.py:83
+#: assets/serializers/asset/common.py:407
msgid "Spec info"
msgstr "特別情報"
-#: accounts/serializers/account/account.py:464
+#: accounts/serializers/account/account.py:465
#: authentication/serializers/connect_token_secret.py:159
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
msgstr "ID"
-#: accounts/serializers/account/account.py:474 acls/serializers/base.py:116
+#: accounts/serializers/account/account.py:475 acls/serializers/base.py:116
#: acls/templates/acls/asset_login_reminder.html:8
#: acls/templates/acls/user_login_reminder.html:8
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:54
@@ -950,7 +952,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:63
-#: rbac/builtin.py:124 rbac/models/rolebinding.py:49
+#: rbac/builtin.py:125 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:34
#: terminal/notifications.py:156 terminal/notifications.py:205
@@ -963,7 +965,7 @@ msgstr "ID"
msgid "User"
msgstr "ユーザー"
-#: accounts/serializers/account/account.py:475
+#: accounts/serializers/account/account.py:476
#: authentication/templates/authentication/_access_key_modal.html:33
#: terminal/notifications.py:158 terminal/notifications.py:207
msgid "Date"
@@ -1025,24 +1027,43 @@ msgstr "特殊記号"
msgid "Exclude symbol"
msgstr "除外文字"
-#: accounts/serializers/account/template.py:39
+#: accounts/serializers/account/template.py:24
+msgid ""
+"length is the length of the password, and the range is 8 to 30.\n"
+"lowercase indicates whether the password contains lowercase letters, \n"
+"uppercase indicates whether it contains uppercase letters,\n"
+"digit indicates whether it contains numbers, and symbol indicates whether it "
+"contains special symbols.\n"
+"exclude_symbols is used to exclude specific symbols. You can fill in the "
+"symbol characters to be excluded (up to 16). \n"
+"If you do not need to exclude symbols, you can leave it blank.\n"
+"default: {\"length\": 16, \"lowercase\": true, \"uppercase\": true, "
+"\"digit\": true, \"symbol\": true, \"exclude_symbols\": \"\"}"
+msgstr ""
+"length はパスワードの長さで、範囲は 8 ~ 30 です。"
+"小文字はパスワードに小文字が含まれるかどうかを示し、大文字はパスワードに大文字が含まれるかどうかを示します。"
+"digit は数字が含まれているかどうかを示し、symbol は特殊記号が含まれているかどうかを示します。"
+"exclude_symbols は、特定のシンボルを除外するために使用します (最大 16 文字)。シンボルを除外する必要がない場合は、空白のままにすることができます。"
+"デフォルト: {\"長さ\": 16、\"小文字\": true、\"大文字\": true、\"数字\": true、\"シンボル\": true、\"exclude_symbols\": \"\"}"
+
+#: accounts/serializers/account/template.py:49
msgid "Secret generation strategy for account creation"
msgstr "账号创建时,密文生成策略"
-#: accounts/serializers/account/template.py:40
+#: accounts/serializers/account/template.py:50
msgid "Whether to automatically push the account to the asset"
msgstr "是否自动推送账号到资产"
-#: accounts/serializers/account/template.py:43
+#: accounts/serializers/account/template.py:53
msgid ""
"Associated platform, you can configure push parameters. If not associated, "
"default parameters will be used"
msgstr "关联平台,可以配置推送参数,如果不关联,则使用默认参数"
#: accounts/serializers/account/virtual.py:19 assets/models/cmd_filter.py:40
-#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:26
-#: ops/models/job.py:158 ops/models/playbook.py:31 rbac/models/role.py:37
-#: settings/models.py:39 terminal/models/applet/applet.py:46
+#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:25
+#: ops/models/job.py:158 ops/models/playbook.py:33 rbac/models/role.py:37
+#: settings/models.py:40 terminal/models/applet/applet.py:46
#: terminal/models/applet/applet.py:332 terminal/models/applet/host.py:143
#: terminal/models/component/endpoint.py:25
#: terminal/models/component/endpoint.py:119
@@ -1065,8 +1086,8 @@ msgstr ""
"ください。 "
#: accounts/serializers/automations/base.py:23
-#: assets/models/asset/common.py:164 assets/serializers/asset/common.py:152
-#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:36
+#: assets/models/asset/common.py:176 assets/serializers/asset/common.py:172
+#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:47
msgid "Nodes"
msgstr "ノード"
@@ -1127,31 +1148,122 @@ msgstr "アカウントを追加: %s"
msgid "Delete account: %s"
msgstr "アカウントを削除: %s"
-#: accounts/tasks/automation.py:25
+#: accounts/tasks/automation.py:32
msgid "Account execute automation"
msgstr "アカウント実行の自動化"
-#: accounts/tasks/automation.py:51 accounts/tasks/automation.py:56
+#: accounts/tasks/automation.py:35
+msgid ""
+"Unified execution entry for account automation tasks: when the system "
+"performs tasks \n"
+" such as account push, password change, account verification, account "
+"collection, \n"
+" and gateway account verification, all tasks are executed through "
+"this unified entry"
+msgstr ""
+"アカウント自動化タスクの一元的な実行入口で、システムがアカウントのプッシュ、"
+"パスワードの変更、アカウントの確認、アカウントの収集、ゲートウェイアカウント"
+"のバリデーションタスクを実行する際、統一して現行のタスクを実行します"
+
+#: accounts/tasks/automation.py:64 accounts/tasks/automation.py:72
msgid "Execute automation record"
msgstr "自動化レコードを実行する"
-#: accounts/tasks/backup_account.py:25
+#: accounts/tasks/automation.py:67
+msgid "When manually executing password change records, this task is used"
+msgstr "パスワード変更記録を手動で実行する際は、このタスクを通じて実行します"
+
+#: accounts/tasks/automation.py:96
+msgid "Clean change secret and push record period"
+msgstr "パスワード変更記録とプッシュ記録を定期的にクリアする"
+
+#: accounts/tasks/automation.py:98
+msgid ""
+"The system will periodically clean up unnecessary password change and push "
+"records, \n"
+" including their associated change tasks, execution logs, assets, and "
+"accounts. When any \n"
+" of these associated items are deleted, the corresponding password "
+"change and push records \n"
+" become invalid. Therefore, to maintain a clean and efficient "
+"database, the system will \n"
+" clean up expired records at 2 a.m daily, based on the interval "
+"specified by \n"
+" PERM_EXPIRED_CHECK_PERIODIC in the config.txt configuration file. "
+"This periodic cleanup \n"
+" mechanism helps free up storage space and enhances the security and "
+"overall performance \n"
+" of data management"
+msgstr ""
+"システムは定期的に不要なパスワード変更記録とプッシュ記録をクリーンアップしま"
+"す。これには、関連するパスワード変更タスク、実行記録、資産、アカウントが含ま"
+"れます。これらの関連項目のいずれかが削除されると、対応するパスワード変更記録"
+"とプッシュ記録は無効となります。したがって、データベースの整理と高速運用のた"
+"めに、システム設定ファイルの config.txt の PERM_EXPIRED_CHECK_PERIODIC の時間"
+"間隔に従って毎日午前2時に時間を超えた記録をクリーニングします。この定期的なク"
+"リーニングメカニズムは、ストレージスペースの解放とデータ管理のセキュリティと"
+"パフォーマンスの向上の両方に役立ちます"
+
+#: accounts/tasks/backup_account.py:26
msgid "Execute account backup plan"
msgstr "アカウントのバックアップ計画を実施する"
-#: accounts/tasks/gather_accounts.py:34
+#: accounts/tasks/backup_account.py:29
+msgid "When performing scheduled or manual account backups, this task is used"
+msgstr ""
+"定時または手動でアカウントバックアップを実行する際は、このタスクを通じて実行"
+"します"
+
+#: accounts/tasks/gather_accounts.py:32 assets/tasks/automation.py:27
+#: orgs/tasks.py:11 terminal/tasks.py:33
+msgid "Unused"
+msgstr "未使用"
+
+#: accounts/tasks/gather_accounts.py:36
msgid "Gather assets accounts"
msgstr "資産の口座番号を収集する"
-#: accounts/tasks/push_account.py:15 accounts/tasks/push_account.py:23
+#: accounts/tasks/push_account.py:16 accounts/tasks/push_account.py:27
msgid "Push accounts to assets"
msgstr "アカウントをアセットにプッシュ:"
-#: accounts/tasks/remove_account.py:44
+#: accounts/tasks/push_account.py:19
+msgid ""
+"When creating or modifying an account requires account push, this task is "
+"executed"
+msgstr ""
+"アカウントの作成、アカウントの変更を行う際、アカウントプッシュが必要な場合は"
+"このタスクを実行します"
+
+#: accounts/tasks/remove_account.py:28
+msgid ""
+"When clicking \"Sync deletion\" in 'Console - Gather Account - Gathered "
+"accounts' this \n"
+" task will be executed"
+msgstr ""
+"コントロールパネル-オートメーション-アカウント収集-収集したアカウント-同期削"
+"除をクリックすると、このタスクが実行されます"
+
+#: accounts/tasks/remove_account.py:50
msgid "Clean historical accounts"
msgstr "過去のアカウントをクリアする"
-#: accounts/tasks/remove_account.py:76
+#: accounts/tasks/remove_account.py:52
+msgid ""
+"Each time an asset account is updated, a historical account is generated, so "
+"it is \n"
+" necessary to clean up the asset account history. The system will "
+"clean up excess account \n"
+" records at 2 a.m. daily based on the configuration in the \"System "
+"settings - Features - \n"
+" Account storage - Record limit"
+msgstr ""
+"資産アカウントを更新するたびに、歴史的なアカウントが生成されるため、資産アカ"
+"ウントの履歴をクリーニングする必要があります。システムは、アカウントストレー"
+"ジ-レコード制限の設定に基づき、毎日午前2時に超過した数量のアカウントレコード"
+"をクリーニングします"
+
+#: accounts/tasks/remove_account.py:89
msgid "Remove historical accounts that are out of range."
msgstr "範囲外の履歴アカウントを削除する"
@@ -1159,23 +1271,48 @@ msgstr "範囲外の履歴アカウントを削除する"
msgid "Template sync info to related accounts"
msgstr "関連するアカウントへの情報の同期"
-#: accounts/tasks/vault.py:31
+#: accounts/tasks/template.py:14
+msgid ""
+"When clicking 'Sync new secret to accounts' in 'Console - Account - "
+"Templates - \n"
+" Accounts' this task will be executed"
+msgstr ""
+"コントロールパネル-アカウントテンプレート-アカウント-同期アカウント情報更新を"
+"クリックして同期すると、このタスクが実行されます"
+
+#: accounts/tasks/vault.py:32
msgid "Sync secret to vault"
msgstr "秘密をVaultに同期する"
+#: accounts/tasks/vault.py:34
+msgid ""
+"When clicking 'Sync' in 'System Settings - Features - Account Storage' this "
+"task will be executed"
+msgstr ""
+"システム設定-機能設定-アカウントストレージをクリックして同期すると、このタス"
+"クが実行されます"
+
#: accounts/tasks/verify_account.py:49
msgid "Verify asset account availability"
msgstr "アセット アカウントの可用性を確認する"
-#: accounts/tasks/verify_account.py:55
+#: accounts/tasks/verify_account.py:52
+msgid ""
+"When clicking 'Test' in 'Console - Asset details - Accounts' this task will "
+"be executed"
+msgstr ""
+"コントロールパネル-資産詳細-アカウントをクリックしてテストを実行すると、この"
+"タスクが実行されます"
+
+#: accounts/tasks/verify_account.py:58
msgid "Verify accounts connectivity"
msgstr "アカウント接続のテスト"
-#: accounts/templates/accounts/asset_account_change_info.html:8
+#: accounts/templates/accounts/asset_account_change_info.html:10
msgid "Added account"
msgstr "新規アカウント"
-#: accounts/templates/accounts/asset_account_change_info.html:9
+#: accounts/templates/accounts/asset_account_change_info.html:13
msgid "Deleted account"
msgstr "アカウントの削除"
@@ -1236,6 +1373,10 @@ msgstr "警告"
msgid "Notify"
msgstr "通知する"
+#: acls/const.py:11
+msgid "Notify and warn"
+msgstr "プロンプトと警告"
+
#: acls/models/base.py:37 assets/models/cmd_filter.py:76
#: terminal/models/component/endpoint.py:112 xpack/plugins/cloud/models.py:314
msgid "Priority"
@@ -1251,7 +1392,7 @@ msgstr "1-100、低い値は最初に一致します"
msgid "Reviewers"
msgstr "レビュー担当者"
-#: acls/models/base.py:43 assets/models/asset/common.py:165
+#: acls/models/base.py:43 assets/models/asset/common.py:178
#: authentication/models/access_key.py:25
#: authentication/models/connection_token.py:53
#: authentication/models/ssh_key.py:13
@@ -1263,15 +1404,15 @@ msgstr "レビュー担当者"
msgid "Active"
msgstr "アクティブ"
-#: acls/models/base.py:81 perms/serializers/permission.py:31
+#: acls/models/base.py:81 perms/serializers/permission.py:42
#: tickets/models/flow.py:23 users/models/preference.py:16
#: users/serializers/group.py:21 users/serializers/user.py:424
msgid "Users"
msgstr "ユーザー"
#: acls/models/base.py:98 assets/models/automations/base.py:17
-#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:128
-#: assets/serializers/asset/common.py:386 perms/serializers/permission.py:44
+#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:148
+#: assets/serializers/asset/common.py:406 perms/serializers/permission.py:55
#: perms/serializers/user_permission.py:75 rbac/tree.py:35
msgid "Accounts"
msgstr "アカウント"
@@ -1291,7 +1432,7 @@ msgid "Regex"
msgstr "正規情報"
#: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79
-#: settings/models.py:184 settings/serializers/feature.py:19
+#: settings/models.py:185 settings/serializers/feature.py:20
#: settings/serializers/msg.py:78 xpack/plugins/license/models.py:30
msgid "Content"
msgstr "コンテンツ"
@@ -1408,7 +1549,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
-#: xpack/plugins/cloud/models.py:390
+#: common/drf/renders/base.py:150 xpack/plugins/cloud/models.py:390
msgid "IP"
msgstr "IP"
@@ -1471,13 +1612,13 @@ msgstr "ログイン都市"
msgid "User agent"
msgstr "ユーザーエージェント"
-#: assets/api/asset/asset.py:181
+#: assets/api/asset/asset.py:190
msgid "Cannot create asset directly, you should create a host or other"
msgstr ""
"資産を直接作成することはできません。ホストまたはその他を作成する必要がありま"
"す"
-#: assets/api/asset/asset.py:185
+#: assets/api/asset/asset.py:194
msgid "The number of assets exceeds the limit of 5000"
msgstr "資産の数が5000の制限を超えています"
@@ -1505,32 +1646,32 @@ msgstr "同じレベルのノード名を同じにすることはできません
msgid "App Assets"
msgstr "アプリ資産"
-#: assets/automations/base/manager.py:187
+#: assets/automations/base/manager.py:188
msgid "{} disabled"
msgstr "{} 無効"
-#: assets/automations/base/manager.py:250
+#: assets/automations/base/manager.py:251
msgid " - Platform {} ansible disabled"
msgstr " - プラットフォーム {} ansible 無効"
-#: assets/automations/base/manager.py:323
+#: assets/automations/base/manager.py:324
msgid ">>> Task preparation phase"
msgstr "タスク準備段階"
-#: assets/automations/base/manager.py:326
+#: assets/automations/base/manager.py:327
#, python-brace-format
msgid ">>> Executing tasks in batches, total {runner_count}"
msgstr ">>> バッチでタスクを実行、合計 {runner_count}"
-#: assets/automations/base/manager.py:328
+#: assets/automations/base/manager.py:329
msgid ">>> Start executing tasks"
msgstr ">>> タスクの実行を開始"
-#: assets/automations/base/manager.py:330
+#: assets/automations/base/manager.py:331
msgid ">>> No tasks need to be executed"
msgstr ">>> 実行する必要があるタスクはありません"
-#: assets/automations/base/manager.py:335
+#: assets/automations/base/manager.py:336
#, python-brace-format
msgid ">>> Begin executing batch {index} of tasks"
msgstr ">>> 第 {index} バッチのタスクの実行を開始"
@@ -1567,7 +1708,7 @@ msgstr "不明"
#: assets/const/automation.py:7
msgid "OK"
-msgstr ""
+msgstr "成功"
#: assets/const/automation.py:12
msgid "Ping"
@@ -1592,14 +1733,14 @@ msgstr "無効"
msgid "Basic"
msgstr "基本"
-#: assets/const/base.py:34 assets/const/protocol.py:292
+#: assets/const/base.py:34 assets/const/protocol.py:298
#: assets/models/asset/web.py:13
msgid "Script"
msgstr "脚本"
#: assets/const/category.py:10 assets/models/asset/host.py:8
#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:76
-#: settings/serializers/feature.py:49 settings/serializers/msg.py:30
+#: settings/serializers/feature.py:52 settings/serializers/msg.py:30
#: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17
#: xpack/plugins/cloud/manager.py:83
#: xpack/plugins/cloud/serializers/account_attrs.py:72
@@ -1670,11 +1811,18 @@ msgstr "古いSSHバージョン"
msgid "Old SSH version like openssh 5.x or 6.x"
msgstr "openssh 5.x または 6.x などの古い SSH バージョン"
-#: assets/const/protocol.py:58
+#: assets/const/protocol.py:53
+msgid "Netcat help text"
+msgstr ""
+"netcat (nc) をプロキシ ツールとして使用し、プロキシ サーバーからターゲット ホ"
+"ストに接続を転送します。 SSH ネイティブ エージェント オプション (-W) がサポー"
+"トされていない環境、またはより柔軟なタイムアウト制御が必要な環境に最適です。"
+
+#: assets/const/protocol.py:64
msgid "SFTP root"
msgstr "SFTPルート"
-#: assets/const/protocol.py:60
+#: assets/const/protocol.py:66
#, python-brace-format
msgid ""
"SFTP root directory, Support variable:
- ${ACCOUNT} The connected "
@@ -1685,24 +1833,24 @@ msgstr ""
"ユーザー名
-${HOME}接続されたアカウントのホームディレクトリ
-${USER}"
"ユーザーのユーザー名"
-#: assets/const/protocol.py:75
+#: assets/const/protocol.py:81
msgid "Console"
msgstr "Console"
-#: assets/const/protocol.py:76
+#: assets/const/protocol.py:82
msgid "Connect to console session"
msgstr "コンソールセッションに接続"
-#: assets/const/protocol.py:80
+#: assets/const/protocol.py:86
msgid "Any"
msgstr "任意"
-#: assets/const/protocol.py:82 rbac/tree.py:62
+#: assets/const/protocol.py:88 rbac/tree.py:62
#: settings/serializers/security.py:232
msgid "Security"
msgstr "セキュリティ"
-#: assets/const/protocol.py:83
+#: assets/const/protocol.py:89
msgid ""
"Security layer to use for the connection:
Any
Automatically select the "
"security mode based on the security protocols supported by both the client "
@@ -1719,77 +1867,77 @@ msgstr ""
"たRDP認証と暗号化
NLA
このモードはTLS暗号化を使用し、事前にユーザー名と"
"パスワードを提供する必要があります
"
-#: assets/const/protocol.py:100
+#: assets/const/protocol.py:106
msgid "AD domain"
msgstr "AD ドメイン"
-#: assets/const/protocol.py:115
+#: assets/const/protocol.py:121
msgid "Username prompt"
msgstr "ユーザー名プロンプト"
-#: assets/const/protocol.py:116
+#: assets/const/protocol.py:122
msgid "We will send username when we see this prompt"
msgstr "このプロンプトが表示されたらユーザー名を送信します"
-#: assets/const/protocol.py:121
+#: assets/const/protocol.py:127
msgid "Password prompt"
msgstr "パスワードプロンプト"
-#: assets/const/protocol.py:122
+#: assets/const/protocol.py:128
msgid "We will send password when we see this prompt"
msgstr "このプロンプトが表示されたらパスワードを送信します"
-#: assets/const/protocol.py:127
+#: assets/const/protocol.py:133
msgid "Success prompt"
msgstr "成功プロンプト"
-#: assets/const/protocol.py:128
+#: assets/const/protocol.py:134
msgid "We will consider login success when we see this prompt"
msgstr "このプロンプトが表示されたらログイン成功とみなします"
-#: assets/const/protocol.py:139 assets/models/asset/database.py:10
+#: assets/const/protocol.py:145 assets/models/asset/database.py:11
#: settings/serializers/msg.py:49
msgid "Use SSL"
msgstr "SSLの使用"
-#: assets/const/protocol.py:174
+#: assets/const/protocol.py:180
msgid "SYSDBA"
msgstr "SYSDBA"
-#: assets/const/protocol.py:175
+#: assets/const/protocol.py:181
msgid "Connect as SYSDBA"
msgstr "SYSDBA として接続"
-#: assets/const/protocol.py:190
+#: assets/const/protocol.py:196
msgid ""
"SQL Server version, Different versions have different connection drivers"
msgstr "SQL Server のバージョン。バージョンによって接続ドライバが異なります"
-#: assets/const/protocol.py:220
+#: assets/const/protocol.py:226
msgid "Auth source"
msgstr "認証データベース"
-#: assets/const/protocol.py:221
+#: assets/const/protocol.py:227
msgid "The database to authenticate against"
msgstr "認証するデータベース"
-#: assets/const/protocol.py:226 authentication/models/connection_token.py:43
+#: assets/const/protocol.py:232 authentication/models/connection_token.py:43
msgid "Connect options"
msgstr "接続アイテム"
-#: assets/const/protocol.py:227
+#: assets/const/protocol.py:233
msgid "The connection specific options eg. retryWrites=false&retryReads=false"
msgstr "接続固有のオプション (例: retryWrites=false&retryReads=false)"
-#: assets/const/protocol.py:239
+#: assets/const/protocol.py:245
msgid "Auth username"
msgstr "ユーザー名で認証する"
-#: assets/const/protocol.py:262
+#: assets/const/protocol.py:268
msgid "Safe mode"
msgstr "安全モード"
-#: assets/const/protocol.py:264
+#: assets/const/protocol.py:270
msgid ""
"When safe mode is enabled, some operations will be disabled, such as: New "
"tab, right click, visit other website, etc."
@@ -1797,24 +1945,24 @@ msgstr ""
"安全モードが有効になっている場合、新しいタブ、右クリック、他のウェブサイトへ"
"のアクセスなど、一部の操作が無効になります"
-#: assets/const/protocol.py:269 assets/models/asset/web.py:9
+#: assets/const/protocol.py:275 assets/models/asset/web.py:9
#: assets/serializers/asset/info/spec.py:16
msgid "Autofill"
msgstr "自動充填"
-#: assets/const/protocol.py:277 assets/models/asset/web.py:10
+#: assets/const/protocol.py:283 assets/models/asset/web.py:10
msgid "Username selector"
msgstr "ユーザー名ピッカー"
-#: assets/const/protocol.py:282 assets/models/asset/web.py:11
+#: assets/const/protocol.py:288 assets/models/asset/web.py:11
msgid "Password selector"
msgstr "パスワードセレクター"
-#: assets/const/protocol.py:287 assets/models/asset/web.py:12
+#: assets/const/protocol.py:293 assets/models/asset/web.py:12
msgid "Submit selector"
msgstr "ボタンセレクターを確認する"
-#: assets/const/protocol.py:310
+#: assets/const/protocol.py:316
msgid "API mode"
msgstr "APIモード"
@@ -1834,51 +1982,51 @@ msgstr "この機能は一時的にサポートされていません"
msgid "Cloud"
msgstr "クラウド サービス"
-#: assets/models/asset/common.py:94 assets/models/platform.py:16
+#: assets/models/asset/common.py:101 assets/models/platform.py:16
#: settings/serializers/auth/radius.py:18 settings/serializers/auth/sms.py:77
#: settings/serializers/msg.py:31 terminal/serializers/storage.py:133
#: xpack/plugins/cloud/serializers/account_attrs.py:73
msgid "Port"
msgstr "ポート"
-#: assets/models/asset/common.py:160 assets/serializers/asset/common.py:150
+#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:170
#: settings/serializers/terminal.py:10
msgid "Address"
msgstr "アドレス"
-#: assets/models/asset/common.py:161 assets/models/platform.py:149
+#: assets/models/asset/common.py:169 assets/models/platform.py:149
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:118
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:385
msgid "Platform"
msgstr "プラットフォーム"
-#: assets/models/asset/common.py:163 assets/models/domain.py:22
+#: assets/models/asset/common.py:173 assets/models/domain.py:22
msgid "Zone"
msgstr "ゾーン"
-#: assets/models/asset/common.py:166 assets/serializers/asset/common.py:388
+#: assets/models/asset/common.py:179 assets/serializers/asset/common.py:408
#: assets/serializers/asset/host.py:11
msgid "Gathered info"
msgstr "資産ハードウェア情報の収集"
-#: assets/models/asset/common.py:167 assets/serializers/asset/custom.py:14
+#: assets/models/asset/common.py:180 assets/serializers/asset/custom.py:14
msgid "Custom info"
msgstr "カスタム属性"
-#: assets/models/asset/common.py:352
+#: assets/models/asset/common.py:365
msgid "Can refresh asset hardware info"
msgstr "資産ハードウェア情報を更新できます"
-#: assets/models/asset/common.py:353
+#: assets/models/asset/common.py:366
msgid "Can test asset connectivity"
msgstr "資産接続をテストできます"
-#: assets/models/asset/common.py:354
+#: assets/models/asset/common.py:367
msgid "Can match asset"
msgstr "アセットを一致させることができます"
-#: assets/models/asset/common.py:355
+#: assets/models/asset/common.py:368
msgid "Can change asset nodes"
msgstr "資産ノードを変更できます"
@@ -1886,23 +2034,27 @@ msgstr "資産ノードを変更できます"
msgid "Custom asset"
msgstr "カスタム アセット"
-#: assets/models/asset/database.py:11
+#: assets/models/asset/database.py:12
msgid "CA cert"
msgstr "CA 証明書"
-#: assets/models/asset/database.py:12
+#: assets/models/asset/database.py:13
msgid "Client cert"
msgstr "クライアント証明書"
-#: assets/models/asset/database.py:13
+#: assets/models/asset/database.py:14
msgid "Client key"
msgstr "クライアントキー"
-#: assets/models/asset/database.py:14
+#: assets/models/asset/database.py:15
msgid "Allow invalid cert"
msgstr "証明書チェックを無視"
-#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:89
+#: assets/models/asset/database.py:18
+msgid "Postgresql SSL mode"
+msgstr "PostgreSQL SSL モード"
+
+#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:92
msgid "Proxy"
msgstr "プロキシー"
@@ -2004,14 +2156,14 @@ msgstr "システム"
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:29
#: authentication/serializers/connect_token_secret.py:125
-#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:35
+#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:36
#: users/models/preference.py:13
msgid "Value"
msgstr "値"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
-#: assets/serializers/platform.py:154
+#: assets/serializers/platform.py:158
#: authentication/serializers/connect_token_secret.py:124
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90
@@ -2062,7 +2214,7 @@ msgstr "主要"
msgid "Required"
msgstr "必要"
-#: assets/models/platform.py:19 assets/serializers/platform.py:156
+#: assets/models/platform.py:19 assets/serializers/platform.py:160
#: terminal/models/component/storage.py:28
#: xpack/plugins/cloud/providers/nutanix.py:30
msgid "Default"
@@ -2079,7 +2231,7 @@ msgid "Setting"
msgstr "設定"
#: assets/models/platform.py:38 audits/const.py:59
-#: authentication/backends/passkey/models.py:11 settings/models.py:38
+#: authentication/backends/passkey/models.py:11 settings/models.py:39
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:33
msgid "Enabled"
msgstr "有効化"
@@ -2170,23 +2322,23 @@ msgstr "メタ"
msgid "Internal"
msgstr "ビルトイン"
-#: assets/models/platform.py:102 assets/serializers/platform.py:166
+#: assets/models/platform.py:102 assets/serializers/platform.py:170
msgid "Charset"
msgstr "シャーセット"
-#: assets/models/platform.py:104 assets/serializers/platform.py:204
+#: assets/models/platform.py:104 assets/serializers/platform.py:208
msgid "Gateway enabled"
msgstr "ゲートウェイが有効になりました"
-#: assets/models/platform.py:106 assets/serializers/platform.py:197
+#: assets/models/platform.py:106 assets/serializers/platform.py:201
msgid "Su enabled"
msgstr "アカウントの切り替えを有効にする"
-#: assets/models/platform.py:107 assets/serializers/platform.py:172
+#: assets/models/platform.py:107 assets/serializers/platform.py:176
msgid "Su method"
msgstr "アカウントの切り替え方法"
-#: assets/models/platform.py:108 assets/serializers/platform.py:175
+#: assets/models/platform.py:108 assets/serializers/platform.py:179
msgid "Custom fields"
msgstr "カスタムフィールド"
@@ -2203,38 +2355,62 @@ msgstr ""
"プラットフォームタイプがスキップされた資産に合致しない、資産内の一括更新プ"
"ラットフォーム"
-#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:169
+#: assets/serializers/asset/common.py:36 assets/serializers/platform.py:152
+msgid "Protocols, format is [\"protocol/port\"]"
+msgstr "契約書、形式は[\"契約書/ポート\"]"
+
+#: assets/serializers/asset/common.py:38
+msgid "Protocol, format is name/port"
+msgstr "契約書、形式は 名前/ポート"
+
+#: assets/serializers/asset/common.py:107
+msgid ""
+"Accounts, format [{\"name\": \"x\", \"username\": \"x\", \"secret\": \"x\", "
+"\"secret_type\": \"password\"}]"
+msgstr ""
+"アカウント、形式は [{\"name\": \"x\", \"username\": \"x\", \"secret\": "
+"\"x\", \"secret_type\": \"パスワード\"}]"
+
+#: assets/serializers/asset/common.py:135
+msgid ""
+"Node path, format [\"/org_name/node_name\"], if node not exist, will create "
+"it"
+msgstr ""
+"ノードパス、形式は [\"/組織/ノード名\"]、もしノードが存在しない場合、それを作"
+"成します"
+
+#: assets/serializers/asset/common.py:147 assets/serializers/platform.py:173
#: authentication/serializers/connect_token_secret.py:30
#: authentication/serializers/connect_token_secret.py:75
-#: perms/models/asset_permission.py:76 perms/serializers/permission.py:45
+#: perms/models/asset_permission.py:76 perms/serializers/permission.py:56
#: perms/serializers/user_permission.py:74 xpack/plugins/cloud/models.py:388
#: xpack/plugins/cloud/serializers/task.py:35
msgid "Protocols"
msgstr "プロトコル"
-#: assets/serializers/asset/common.py:129
-#: assets/serializers/asset/common.py:151
+#: assets/serializers/asset/common.py:149
+#: assets/serializers/asset/common.py:171
msgid "Node path"
msgstr "ノードパスです"
-#: assets/serializers/asset/common.py:148
-#: assets/serializers/asset/common.py:389
+#: assets/serializers/asset/common.py:168
+#: assets/serializers/asset/common.py:409
msgid "Auto info"
msgstr "自動情報"
-#: assets/serializers/asset/common.py:245
+#: assets/serializers/asset/common.py:265
msgid "Platform not exist"
msgstr "プラットフォームが存在しません"
-#: assets/serializers/asset/common.py:281
+#: assets/serializers/asset/common.py:301
msgid "port out of range (0-65535)"
msgstr "ポート番号が範囲外です (0-65535)"
-#: assets/serializers/asset/common.py:288
+#: assets/serializers/asset/common.py:308
msgid "Protocol is required: {}"
msgstr "プロトコルが必要です: {}"
-#: assets/serializers/asset/common.py:316
+#: assets/serializers/asset/common.py:336
msgid "Invalid data"
msgstr "無効なデータ"
@@ -2242,6 +2418,25 @@ msgstr "無効なデータ"
msgid "Default database"
msgstr "デフォルト・データベース"
+#: assets/serializers/asset/database.py:23
+msgid "CA cert help text"
+msgstr ""
+" Common Name (CN) フィールドは廃止されました。RFC 5280に基づき、Subject "
+"Alternative Name (SAN) フィールドを使用してドメイン名を確認し、セキュリティを"
+"強化してください"
+
+#: assets/serializers/asset/database.py:24
+msgid "Postgresql ssl model help text"
+msgstr ""
+"Prefer:私は暗号化に関心はありませんが、サーバーが暗号化をサポートしているな"
+"ら、私は暗号化のコストを支払うことを喜んでいます。Require:私のデータを暗号化"
+"してほしい、そのコストを受け入れます。私はネットワークが私が接続したいサー"
+"バーに常に接続できるように保証してくれると信じています。Verify CA:私はデータ"
+"が暗号化され、コストを受け入れます。私が信頼するサーバーに接続されていること"
+"を確認したい。Verify Full:私はデータが暗号化され、コストを受け入れます。私が"
+"信頼するサーバーに接続されていること、そしてそれが私が指定したサーバーである"
+"ことを確認したい"
+
#: assets/serializers/asset/gpt.py:20
msgid ""
"If the server cannot directly connect to the API address, you need set up an "
@@ -2324,12 +2519,16 @@ msgstr ""
"ゲートウェイはドメインのネットワーク代理であり、ドメイン内のリソースに接続す"
"る際には、接続はゲートウェイを通してルーティングされます。"
-#: assets/serializers/domain.py:24 assets/serializers/platform.py:177
-#: orgs/serializers.py:13 perms/serializers/permission.py:39
+#: assets/serializers/domain.py:24 assets/serializers/platform.py:181
+#: orgs/serializers.py:13 perms/serializers/permission.py:50
msgid "Assets amount"
msgstr "資産数量"
-#: assets/serializers/gateway.py:23 common/validators.py:34
+#: assets/serializers/gateway.py:19
+msgid "The platform must start with Gateway"
+msgstr ""
+
+#: assets/serializers/gateway.py:28 common/validators.py:34
msgid "This field must be unique."
msgstr "このフィールドは一意である必要があります。"
@@ -2410,19 +2609,19 @@ msgid "This protocol is public, asset will show this protocol to user"
msgstr ""
"このプロトコルは公開されており、資産はこのプロトコルをユーザーに表示します"
-#: assets/serializers/platform.py:157
+#: assets/serializers/platform.py:161
msgid "Help text"
msgstr "ヘルプ"
-#: assets/serializers/platform.py:158
+#: assets/serializers/platform.py:162
msgid "Choices"
msgstr "せんたく"
-#: assets/serializers/platform.py:170
+#: assets/serializers/platform.py:174
msgid "Automation"
msgstr "オートメーション"
-#: assets/serializers/platform.py:199
+#: assets/serializers/platform.py:203
msgid ""
"Login with account when accessing assets, then automatically switch to "
"another, similar to logging in with a regular account and then switching to "
@@ -2432,23 +2631,23 @@ msgstr ""
"切り替えます。これは、通常のアカウントでログインした後に root に切り替えるの"
"と似ています"
-#: assets/serializers/platform.py:205
+#: assets/serializers/platform.py:209
msgid "Assets can be connected using a zone gateway"
msgstr "資産はゾーンゲートウェイを使用して接続できます"
-#: assets/serializers/platform.py:207
+#: assets/serializers/platform.py:211
msgid "Default Domain"
msgstr "デフォルト ドメイン"
-#: assets/serializers/platform.py:229
+#: assets/serializers/platform.py:233
msgid "type is required"
msgstr "タイプ このフィールドは必須です."
-#: assets/serializers/platform.py:244
+#: assets/serializers/platform.py:248
msgid "Protocols is required"
msgstr "同意が必要です"
-#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:35
+#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:39
msgid "Test assets connectivity "
msgstr "アセット接続のテスト。"
@@ -2456,19 +2655,28 @@ msgstr "アセット接続のテスト。"
msgid "Gather asset hardware info"
msgstr "資産ハードウェア情報の収集"
-#: assets/tasks/automation.py:24
+#: assets/tasks/automation.py:25
msgid "Asset execute automation"
msgstr "アセット実行の自動化"
-#: assets/tasks/gather_facts.py:21 assets/tasks/gather_facts.py:27
+#: assets/tasks/gather_facts.py:22 assets/tasks/gather_facts.py:32
msgid "Gather assets facts"
msgstr "資産情報の収集"
-#: assets/tasks/gather_facts.py:39
+#: assets/tasks/gather_facts.py:25
+msgid ""
+"When clicking 'Refresh hardware info' in 'Console - Asset Details - Basic' "
+"this task \n"
+" will be executed"
+msgstr ""
+"コントロールパネル資産詳細-基本設定をクリックしてハードウェア情報を更新する"
+"と、このタスクが実行されます"
+
+#: assets/tasks/gather_facts.py:44
msgid "Update assets hardware info: "
msgstr "資産のハードウェア情報を更新する:"
-#: assets/tasks/gather_facts.py:47
+#: assets/tasks/gather_facts.py:52
msgid "Update node asset hardware information: "
msgstr "ノード資産のハードウェア情報を更新します。"
@@ -2476,30 +2684,66 @@ msgstr "ノード資産のハードウェア情報を更新します。"
msgid "Check the amount of assets under the node"
msgstr "ノード下のアセット数を確認する"
-#: assets/tasks/nodes_amount.py:28
+#: assets/tasks/nodes_amount.py:18
+msgid ""
+"Manually verifying asset quantities updates the asset count for nodes under "
+"the \n"
+" current organization. This task will be called in the following two "
+"cases: when updating \n"
+" nodes and when the number of nodes exceeds 100"
+msgstr ""
+"手動で資産数を校正して現在の組織のノード資産数を更新する;ノードを更新する、"
+"ノード数が100を超えると、このタスクが呼び出されます"
+
+#: assets/tasks/nodes_amount.py:34
msgid ""
"The task of self-checking is already running and cannot be started repeatedly"
msgstr ""
"セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ"
"ん"
-#: assets/tasks/nodes_amount.py:33
+#: assets/tasks/nodes_amount.py:40
msgid "Periodic check the amount of assets under the node"
msgstr "ノードの下にあるアセットの数を定期的に確認する"
-#: assets/tasks/ping.py:20 assets/tasks/ping.py:26
+#: assets/tasks/nodes_amount.py:42
+msgid ""
+"Schedule the check_node_assets_amount_task to periodically update the asset "
+"count of \n"
+" all nodes under all organizations"
+msgstr ""
+"check_node_assets_amount_taskタスクを定期的に呼び出し、すべての組織のすべての"
+"ノードの資産数を更新します"
+
+#: assets/tasks/ping.py:20 assets/tasks/ping.py:30
msgid "Test assets connectivity"
msgstr "アセット接続のテスト。"
-#: assets/tasks/ping.py:42
+#: assets/tasks/ping.py:24
+msgid ""
+"When clicking 'Test Asset Connectivity' in 'Asset Details - Basic Settings' "
+"this task will be executed"
+msgstr ""
+"資産詳細-基本設定をクリックして資産の接続性をテストすると、このタスクが実行さ"
+"れます"
+
+#: assets/tasks/ping.py:46
msgid "Test if the assets under the node are connectable "
msgstr "ノード配下のアセットが接続できるかテストする"
-#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:25
-#: assets/tasks/ping_gateway.py:34
+#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:29
+#: assets/tasks/ping_gateway.py:38
msgid "Test gateways connectivity"
msgstr "ゲートウェイ接続のテスト。"
+#: assets/tasks/ping_gateway.py:23
+msgid ""
+"When clicking 'Test Connection' in 'Domain Details - Gateway' this task will "
+"be executed"
+msgstr ""
+"ネットワーク詳細-ゲートウェイ-接続テストを実行する際に、このタスクを実行しま"
+"す"
+
#: assets/tasks/utils.py:16
msgid "Asset has been disabled, skipped: {}"
msgstr "資産が無効化されました。スキップ: {}"
@@ -2556,7 +2800,7 @@ msgstr "Symlink"
#: audits/const.py:18 audits/const.py:28
#: ops/templates/ops/celery_task_log.html:86
-#: terminal/api/session/session.py:149
+#: terminal/api/session/session.py:153
msgid "Download"
msgstr "ダウンロード"
@@ -2564,7 +2808,7 @@ msgstr "ダウンロード"
msgid "Rename dir"
msgstr "マップディレクトリ"
-#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:274
+#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:281
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:84
@@ -2606,16 +2850,16 @@ msgstr "承認"
msgid "Close"
msgstr "閉じる"
-#: audits/const.py:41 ops/models/celery.py:84
+#: audits/const.py:41 ops/models/celery.py:85
#: terminal/models/session/sharing.py:128 tickets/const.py:25
#: xpack/plugins/cloud/const.py:67
msgid "Finished"
msgstr "終了"
#: audits/const.py:46 settings/serializers/terminal.py:6
-#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:175
+#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:174
#: terminal/models/virtualapp/provider.py:14 terminal/serializers/session.py:55
-#: terminal/serializers/session.py:69
+#: terminal/serializers/session.py:79
msgid "Terminal"
msgstr "ターミナル"
@@ -2767,9 +3011,9 @@ msgstr "ユーザーセッション"
msgid "Offline user session"
msgstr "オフラインユーザセッション"
-#: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16
-#: ops/models/base.py:53 ops/models/celery.py:86 ops/models/job.py:151
-#: ops/models/job.py:240 ops/models/playbook.py:30
+#: audits/serializers.py:33 ops/models/adhoc.py:24 ops/models/base.py:16
+#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:151
+#: ops/models/job.py:240 ops/models/playbook.py:32
#: terminal/models/session/sharing.py:25
msgid "Creator"
msgstr "作成者"
@@ -2824,7 +3068,7 @@ msgstr "認証トークン"
#: audits/signal_handlers/login_log.py:37 authentication/notifications.py:73
#: authentication/views/login.py:78 notifications/backends/__init__.py:11
#: settings/serializers/auth/wecom.py:11 settings/serializers/auth/wecom.py:16
-#: users/models/user/__init__.py:122 users/models/user/_source.py:18
+#: users/models/user/__init__.py:122 users/models/user/_source.py:19
msgid "WeCom"
msgstr "企業微信"
@@ -2832,21 +3076,21 @@ msgstr "企業微信"
#: authentication/views/login.py:90 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:12
#: settings/serializers/auth/feishu.py:14 users/models/user/__init__.py:128
-#: users/models/user/_source.py:20
+#: users/models/user/_source.py:21
msgid "FeiShu"
msgstr "本を飛ばす"
#: audits/signal_handlers/login_log.py:40 authentication/views/login.py:102
#: authentication/views/slack.py:79 notifications/backends/__init__.py:16
#: settings/serializers/auth/slack.py:11 settings/serializers/auth/slack.py:13
-#: users/models/user/__init__.py:134 users/models/user/_source.py:22
+#: users/models/user/__init__.py:134 users/models/user/_source.py:23
msgid "Slack"
msgstr "Slack"
#: audits/signal_handlers/login_log.py:41 authentication/views/dingtalk.py:151
#: authentication/views/login.py:84 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:11 users/models/user/__init__.py:125
-#: users/models/user/_source.py:19
+#: users/models/user/_source.py:20
msgid "DingTalk"
msgstr "DingTalk"
@@ -2861,14 +3105,38 @@ msgstr "仮パスワード"
msgid "Passkey"
msgstr "Passkey"
-#: audits/tasks.py:131
+#: audits/tasks.py:132
msgid "Clean audits session task log"
msgstr "資産監査セッションタスクログのクリーンアップ"
-#: audits/tasks.py:145
+#: audits/tasks.py:134
+msgid ""
+"Since the system generates login logs, operation logs, file upload logs, "
+"activity \n"
+" logs, Celery execution logs, session recordings, command records, "
+"and password change \n"
+" logs, it will perform cleanup of records that exceed the time limit "
+"according to the \n"
+" 'Tasks - Regular clean-up' in the system settings at 2 a.m daily"
+msgstr ""
+"システムはログインログ、操作ログ、ファイルアップロードログ、アクティビティロ"
+"グ、セルリー実行ログ、セッション録画、コマンド記録、パスワード変更ログを生成"
+"します。システムは、システム設定-タスクリスト-定期クリーニング設定に基づき、"
+"毎日午前2時に時間を超えたものをクリーニングします"
+
+#: audits/tasks.py:154
msgid "Upload FTP file to external storage"
msgstr "外部ストレージへのFTPファイルのアップロード"
+#: audits/tasks.py:156
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured, files uploaded through file "
+"management will be \n"
+" synchronized to external storage"
+msgstr ""
+"SERVER_REPLAY_STORAGEが設定されている場合は、ファイルマネージャーでアップロー"
+"ドしたファイルを外部ストレージに同期します"
+
#: authentication/api/access_key.py:39
msgid "Access keys can be created at most 10"
msgstr "最大10個のアクセスキーを作成できます"
@@ -2912,7 +3180,7 @@ msgstr "ACL アクションはレビューです"
msgid "Current user not support mfa type: {}"
msgstr "現在のユーザーはmfaタイプをサポートしていません: {}"
-#: authentication/api/password.py:33 terminal/api/session/session.py:322
+#: authentication/api/password.py:33 terminal/api/session/session.py:334
#: users/views/profile/reset.py:63
msgid "User does not exist: {}"
msgstr "ユーザーが存在しない: {}"
@@ -2965,6 +3233,16 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr "無効なトークンまたはキャッシュの更新。"
+#: authentication/backends/oidc/views.py:174
+msgid "OpenID Error"
+msgstr "OpenID エラー"
+
+#: authentication/backends/oidc/views.py:175
+msgid "Please check if a user with the same username or email already exists"
+msgstr ""
+"同じユーザー名またはメールアドレスのユーザーが既に存在するかどうかを確認して"
+"ください"
+
#: authentication/backends/passkey/api.py:37
msgid "Only register passkey for local user"
msgstr "ローカル・ユーザーのみの鍵の登録"
@@ -3162,15 +3440,15 @@ msgstr "パスワードが無効です"
msgid "Please wait for %s seconds before retry"
msgstr "%s 秒後に再試行してください"
-#: authentication/errors/redirect.py:85 authentication/mixins.py:323
+#: authentication/errors/redirect.py:85 authentication/mixins.py:326
msgid "Your password is too simple, please change it for security"
msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください"
-#: authentication/errors/redirect.py:93 authentication/mixins.py:330
+#: authentication/errors/redirect.py:93 authentication/mixins.py:335
msgid "You should to change your password before login"
msgstr "ログインする前にパスワードを変更する必要があります"
-#: authentication/errors/redirect.py:101 authentication/mixins.py:337
+#: authentication/errors/redirect.py:101 authentication/mixins.py:344
msgid "Your password has expired, please reset before logging in"
msgstr ""
"パスワードの有効期限が切れました。ログインする前にリセットしてください。"
@@ -3270,7 +3548,7 @@ msgstr "電話番号を設定して有効にする"
msgid "Clear phone number to disable"
msgstr "無効にする電話番号をクリアする"
-#: authentication/middleware.py:94 settings/utils/ldap.py:679
+#: authentication/middleware.py:94 settings/utils/ldap.py:691
msgid "Authentication failed (before login check failed): {}"
msgstr "認証に失敗しました (ログインチェックが失敗する前): {}"
@@ -3290,7 +3568,7 @@ msgstr ""
msgid "The MFA type ({}) is not enabled"
msgstr "MFAタイプ ({}) が有効になっていない"
-#: authentication/mixins.py:313
+#: authentication/mixins.py:314
msgid "Please change your password"
msgstr "パスワードを変更してください"
@@ -3468,7 +3746,7 @@ msgid "Actions"
msgstr "アクション"
#: authentication/serializers/connection_token.py:42
-#: perms/serializers/permission.py:43 perms/serializers/permission.py:64
+#: perms/serializers/permission.py:54 perms/serializers/permission.py:75
#: users/serializers/user.py:127 users/serializers/user.py:273
msgid "Is expired"
msgstr "期限切れです"
@@ -3512,16 +3790,24 @@ msgstr "有効なssh公開鍵ではありません"
msgid "Access IP"
msgstr "Access IP"
-#: authentication/serializers/token.py:92 perms/serializers/permission.py:42
-#: perms/serializers/permission.py:65 users/serializers/user.py:128
+#: authentication/serializers/token.py:92 perms/serializers/permission.py:53
+#: perms/serializers/permission.py:76 users/serializers/user.py:128
#: users/serializers/user.py:270
msgid "Is valid"
msgstr "有効です"
-#: authentication/tasks.py:11
+#: authentication/tasks.py:13
msgid "Clean expired session"
msgstr "期限切れのセッションをクリアする"
+#: authentication/tasks.py:15
+msgid ""
+"Since user logins create sessions, the system will clean up expired sessions "
+"every 24 hours"
+msgstr ""
+"ユーザーがシステムにログインするとセッションが生成されます。システムは24時間"
+"ごとに期限切れのセッションをクリーニングします"
+
#: authentication/templates/authentication/_access_key_modal.html:6
msgid "API key list"
msgstr "APIキーリスト"
@@ -3581,7 +3867,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_oauth_bind.html:3
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_reset_password_code.html:9
-#: jumpserver/conf.py:502
+#: jumpserver/conf.py:522
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:32
#: users/templates/users/_msg_account_expire_reminder.html:4
@@ -3908,7 +4194,7 @@ msgstr "企業の微信からユーザーを取得できませんでした"
msgid "Please login with a password and then bind the WeCom"
msgstr "パスワードでログインしてからWeComをバインドしてください"
-#: common/api/action.py:51
+#: common/api/action.py:57
msgid "Request file format may be wrong"
msgstr "リクエストファイルの形式が間違っている可能性があります"
@@ -3936,7 +4222,7 @@ msgstr "ランニング"
msgid "Canceled"
msgstr "キャンセル"
-#: common/const/common.py:5 xpack/plugins/cloud/manager.py:412
+#: common/const/common.py:5 xpack/plugins/cloud/manager.py:411
#, python-format
msgid "%(name)s was created successfully"
msgstr "%(name)s が正常に作成されました"
@@ -4040,7 +4326,7 @@ msgstr "組織 ID"
msgid "The file content overflowed (The maximum length `{}` bytes)"
msgstr "ファイルの内容がオーバーフローしました (最大長 '{}' バイト)"
-#: common/drf/parsers/base.py:199
+#: common/drf/parsers/base.py:207
msgid "Parse file error: {}"
msgstr "解析ファイルエラー: {}"
@@ -4048,7 +4334,76 @@ msgstr "解析ファイルエラー: {}"
msgid "Invalid excel file"
msgstr "無効 excel 書類"
-#: common/drf/renders/base.py:208
+#: common/drf/renders/base.py:138
+msgid "Yes/No"
+msgstr ""
+
+#: common/drf/renders/base.py:141
+msgid "Text, max length {}"
+msgstr "テキスト、最大長 {}"
+
+#: common/drf/renders/base.py:143
+msgid "Long text, no length limit"
+msgstr "長文テキスト、長さ制限なし"
+
+#: common/drf/renders/base.py:145
+msgid "Number, min {} max {}"
+msgstr "数字、最小 {} 最大 {}"
+
+#: common/drf/renders/base.py:148
+msgid "Datetime format {}"
+msgstr "日付時刻形式 {}"
+
+#: common/drf/renders/base.py:154
+msgid ""
+"Choices, format name(value), name is optional for human read, value is "
+"requisite, options {}"
+msgstr ""
+"選択、形式: 名前(値)、名前はオプショナルで、読みやすいように、値は必須です。"
+"選択肢は {}"
+
+#: common/drf/renders/base.py:157
+msgid "Choices, options {}"
+msgstr "オプション、可能なオプションは {}"
+
+#: common/drf/renders/base.py:159
+msgid "Phone number, format +8612345678901"
+msgstr "電話番号、形式 +8612345678901"
+
+#: common/drf/renders/base.py:161
+msgid "Label, format [\"key:value\"]"
+msgstr "タグ、形式: [\"キー:値\"]"
+
+#: common/drf/renders/base.py:163
+msgid ""
+"Object, format name(id), name is optional for human read, id is requisite"
+msgstr ""
+"関連項目、形式: 名前(id)、名前はオプショナルで、読みやすいように、idは必須で"
+"す"
+
+#: common/drf/renders/base.py:165
+msgid "Object, format id"
+msgstr "関連項目、形式は id"
+
+#: common/drf/renders/base.py:169
+msgid ""
+"Objects, format [\"name(id)\", ...], name is optional for human read, id is "
+"requisite"
+msgstr ""
+"多関連項目、形式: [\"名前(id)\", ...]、名前はオプショナルで、読みやすいよう"
+"に、idは必須です"
+
+#: common/drf/renders/base.py:171
+msgid ""
+"Labels, format [\"key:value\", ...], if label not exists, will create it"
+msgstr ""
+"タグ、形式: [\"キー:値\", ...]、もしタグが存在しない場合、それを作成します"
+
+#: common/drf/renders/base.py:173
+msgid "Objects, format [\"id\", ...]"
+msgstr "多関連項目、形式は [\"id\", ...]"
+
+#: common/drf/renders/base.py:271
msgid ""
"{} - The encryption password has not been set - please go to personal "
"information -> file encryption password to set the encryption password"
@@ -4209,17 +4564,40 @@ msgstr "無効なオプション: {}"
msgid "Tags"
msgstr "ラベル"
-#: common/tasks.py:31
+#: common/tasks.py:32
msgid "Send email"
msgstr "メールを送る"
-#: common/tasks.py:58
+#: common/tasks.py:35
+msgid "This task will be executed when sending email notifications"
+msgstr "メールメッセージを送信するときは、このタスクを実行します"
+
+#: common/tasks.py:65
msgid "Send email attachment"
msgstr "メールの添付ファイルを送信"
-#: common/tasks.py:80 terminal/tasks.py:58
-msgid "Upload session replay to external storage"
-msgstr "セッションの記録を外部ストレージにアップロードする"
+#: common/tasks.py:68
+msgid ""
+"When an account password is changed or an account backup generates "
+"attachments, \n"
+" this task needs to be executed for sending emails and handling "
+"attachments"
+msgstr ""
+"アカウントのパスワードを変更したり、アカウントのバックアップが添付ファイルを"
+"生成したりすると、メールと添付ファイルを送信するためのタスクを実行する必要が"
+"あります"
+
+#: common/tasks.py:94
+msgid "Upload account backup to external storage"
+msgstr " セッション映像を外部ストレージにアップロードする"
+
+#: common/tasks.py:96
+msgid ""
+"When performing an account backup, this task needs to be executed to "
+"external storage (SFTP)"
+msgstr ""
+"アカウントのバックアップを実行するときに外部ストレージ(sftp)にアクセスする"
+"ため、このタスクを実行します"
#: common/utils/ip/geoip/utils.py:26
msgid "Invalid ip"
@@ -4234,10 +4612,19 @@ msgstr "無効なアドレス。"
msgid "Hello %s"
msgstr "こんにちは %s"
-#: common/utils/verify_code.py:16
+#: common/utils/verify_code.py:17
msgid "Send SMS code"
msgstr "SMS 認証コードを送信する"
+#: common/utils/verify_code.py:19
+msgid ""
+"When resetting a password, forgetting a password, or verifying MFA, this "
+"task needs to \n"
+" be executed to send SMS messages"
+msgstr ""
+"パスワードをリセットするか、パスワードを忘れるか、mfaを検証するときにSMSを送"
+"信する必要がある場合、このタスクを実行します"
+
#: common/validators.py:16
msgid "Special char not allowed"
msgstr "特別なcharは許可されていません"
@@ -4250,16 +4637,16 @@ msgstr "特殊文字を含むべきではない"
msgid "The mobile phone number format is incorrect"
msgstr "携帯電話番号の形式が正しくありません"
-#: jumpserver/conf.py:496
+#: jumpserver/conf.py:516
#, python-brace-format
msgid "The verification code is: {code}"
msgstr "認証コードは: {code}"
-#: jumpserver/conf.py:501
+#: jumpserver/conf.py:521
msgid "Create account successfully"
msgstr "アカウントを正常に作成"
-#: jumpserver/conf.py:503
+#: jumpserver/conf.py:523
msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました"
@@ -4359,15 +4746,22 @@ msgstr "システムメッセージ"
msgid "Publish the station message"
msgstr "投稿サイトニュース"
-#: ops/ansible/inventory.py:106 ops/models/job.py:65
+#: notifications/notifications.py:48
+msgid ""
+"This task needs to be executed for sending internal messages for system "
+"alerts, \n"
+" work orders, and other notifications"
+msgstr "システムの警告やチケットなどを送信するためには、このタスクを実行します"
+
+#: ops/ansible/inventory.py:116 ops/models/job.py:65
msgid "No account available"
msgstr "利用可能なアカウントがありません"
-#: ops/ansible/inventory.py:285
+#: ops/ansible/inventory.py:296
msgid "Ansible disabled"
msgstr "Ansible 無効"
-#: ops/ansible/inventory.py:301
+#: ops/ansible/inventory.py:312
msgid "Skip hosts below:"
msgstr "次のホストをスキップします: "
@@ -4420,31 +4814,31 @@ msgid ""
"The task is being created and cannot be interrupted. Please try again later."
msgstr "タスクを作成中で、中断できません。後でもう一度お試しください。"
-#: ops/api/playbook.py:39
+#: ops/api/playbook.py:50
msgid "Currently playbook is being used in a job"
msgstr "現在プレイブックは1つのジョブで使用されています"
-#: ops/api/playbook.py:97
+#: ops/api/playbook.py:113
msgid "Unsupported file content"
msgstr "サポートされていないファイルの内容"
-#: ops/api/playbook.py:99 ops/api/playbook.py:145 ops/api/playbook.py:193
+#: ops/api/playbook.py:115 ops/api/playbook.py:161 ops/api/playbook.py:209
msgid "Invalid file path"
msgstr "無効なファイルパス"
-#: ops/api/playbook.py:171
+#: ops/api/playbook.py:187
msgid "This file can not be rename"
msgstr "ファイル名を変更することはできません"
-#: ops/api/playbook.py:190
+#: ops/api/playbook.py:206
msgid "File already exists"
msgstr "ファイルは既に存在します。"
-#: ops/api/playbook.py:208
+#: ops/api/playbook.py:224
msgid "File key is required"
msgstr "ファイルキーこのフィールドは必須です"
-#: ops/api/playbook.py:211
+#: ops/api/playbook.py:227
msgid "This file can not be delete"
msgstr "このファイルを削除できません"
@@ -4484,11 +4878,11 @@ msgstr "空欄"
msgid "VCS"
msgstr "VCS"
-#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:120
+#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:123
msgid "Adhoc"
msgstr "コマンド"
-#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:88
+#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:91
msgid "Playbook"
msgstr "Playbook"
@@ -4552,53 +4946,69 @@ msgstr "タイムアウト"
msgid "Command execution disabled"
msgstr "コマンド実行が無効"
+#: ops/const.py:86
+msgctxt "scope"
+msgid "Public"
+msgstr "公有"
+
+#: ops/const.py:87
+msgid "Private"
+msgstr "私有"
+
#: ops/exception.py:6
msgid "no valid program entry found."
msgstr "利用可能なプログラムポータルがありません"
-#: ops/mixin.py:23 ops/mixin.py:102 settings/serializers/auth/ldap.py:72
+#: ops/mixin.py:30 ops/mixin.py:110 settings/serializers/auth/ldap.py:73
+#: settings/serializers/auth/ldap_ha.py:55
msgid "Periodic run"
msgstr "定期的なパフォーマンス"
-#: ops/mixin.py:25 ops/mixin.py:88 ops/mixin.py:108
-#: settings/serializers/auth/ldap.py:79
+#: ops/mixin.py:32 ops/mixin.py:96 ops/mixin.py:116
+#: settings/serializers/auth/ldap.py:80 settings/serializers/auth/ldap_ha.py:62
msgid "Interval"
msgstr "間隔"
-#: ops/mixin.py:28 ops/mixin.py:86 ops/mixin.py:105
-#: settings/serializers/auth/ldap.py:76
+#: ops/mixin.py:35 ops/mixin.py:94 ops/mixin.py:113
+#: settings/serializers/auth/ldap.py:77 settings/serializers/auth/ldap_ha.py:59
msgid "Crontab"
msgstr "含む"
-#: ops/mixin.py:110
+#: ops/mixin.py:118
msgid "Run period"
msgstr "ユーザーの実行"
-#: ops/mixin.py:119
+#: ops/mixin.py:127
msgid "* Please enter a valid crontab expression"
msgstr "* 有効なcrontab式を入力してください"
-#: ops/mixin.py:126
+#: ops/mixin.py:134
msgid "Range {} to {}"
msgstr "{} から {} までの範囲"
-#: ops/mixin.py:137
+#: ops/mixin.py:145
msgid "Require interval or crontab setting"
msgstr "定期的または定期的に設定を行う必要があります"
-#: ops/models/adhoc.py:21
+#: ops/models/adhoc.py:20
msgid "Pattern"
msgstr "パターン"
-#: ops/models/adhoc.py:23 ops/models/job.py:146
+#: ops/models/adhoc.py:22 ops/models/job.py:146
msgid "Module"
msgstr "モジュール"
-#: ops/models/adhoc.py:24 ops/models/celery.py:81 ops/models/job.py:144
+#: ops/models/adhoc.py:23 ops/models/celery.py:82 ops/models/job.py:144
#: terminal/models/component/task.py:14
msgid "Args"
msgstr "アルグ"
+#: ops/models/adhoc.py:26 ops/models/playbook.py:36 ops/serializers/mixin.py:10
+#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
+#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
+msgid "Scope"
+msgstr "スコープ"
+
#: ops/models/base.py:19
msgid "Account policy"
msgstr "アカウント ポリシー"
@@ -4625,23 +5035,23 @@ msgstr "Summary"
msgid "Date last publish"
msgstr "発売日"
-#: ops/models/celery.py:70
+#: ops/models/celery.py:71
msgid "Celery Task"
msgstr "Celery タスク#タスク#"
-#: ops/models/celery.py:73
+#: ops/models/celery.py:74
msgid "Can view task monitor"
msgstr "タスクモニターを表示できます"
-#: ops/models/celery.py:82 terminal/models/component/task.py:15
+#: ops/models/celery.py:83 terminal/models/component/task.py:15
msgid "Kwargs"
msgstr "クワーグ"
-#: ops/models/celery.py:87
+#: ops/models/celery.py:88
msgid "Date published"
msgstr "発売日"
-#: ops/models/celery.py:112
+#: ops/models/celery.py:113
msgid "Celery Task Execution"
msgstr "Celery タスク実行"
@@ -4686,11 +5096,11 @@ msgstr "Material を選択してオプションを設定します。"
msgid "Job Execution"
msgstr "ジョブ実行"
-#: ops/models/playbook.py:33
+#: ops/models/playbook.py:35
msgid "CreateMethod"
msgstr "创建方式"
-#: ops/models/playbook.py:34
+#: ops/models/playbook.py:37
msgid "VCS URL"
msgstr "VCS URL"
@@ -4754,34 +5164,103 @@ msgstr "タスク ID"
msgid "You do not have permission for the current job."
msgstr "あなたは現在のジョブの権限を持っていません。"
-#: ops/tasks.py:38
+#: ops/tasks.py:51
msgid "Run ansible task"
msgstr "Ansible タスクを実行する"
-#: ops/tasks.py:72
+#: ops/tasks.py:54
+msgid ""
+"Execute scheduled adhoc and playbooks, periodically invoking the task for "
+"execution"
+msgstr ""
+"タイムスケジュールのショートカットコマンドやplaybookを実行するときは、このタ"
+"スクを呼び出します"
+
+#: ops/tasks.py:82
msgid "Run ansible task execution"
msgstr "Ansible タスクの実行を開始する"
-#: ops/tasks.py:94
+#: ops/tasks.py:85
+msgid "Execute the task when manually adhoc or playbooks"
+msgstr ""
+"手動でショートカットコマンドやplaybookを実行するときは、このタスクを実行しま"
+"す"
+
+#: ops/tasks.py:99
msgid "Clear celery periodic tasks"
msgstr "タスクログを定期的にクリアする"
-#: ops/tasks.py:115
+#: ops/tasks.py:101
+msgid "At system startup, clean up celery tasks that no longer exist"
+msgstr "システム起動時、既に存在しないceleryのタスクをクリーニングします"
+
+#: ops/tasks.py:125
msgid "Create or update periodic tasks"
msgstr "定期的なタスクの作成または更新"
-#: ops/tasks.py:123
+#: ops/tasks.py:127
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, tasks will be "
+"registered or the parameters \n"
+" of scheduled tasks will be updated"
+msgstr ""
+"バージョンがアップグレードされると、新しいタスクが追加され、タスクの名前や実"
+"行時間が変更される可能性があるため、システムが起動すると、タスクを登録した"
+"り、タスクのパラメータを更新したりします"
+
+#: ops/tasks.py:140
msgid "Periodic check service performance"
msgstr "サービスのパフォーマンスを定期的に確認する"
-#: ops/tasks.py:129
+#: ops/tasks.py:142
+msgid ""
+"Check every hour whether each component is offline and whether the CPU, "
+"memory, \n"
+" and disk usage exceed the thresholds, and send an alert message to "
+"the administrator"
+msgstr ""
+"毎時、各コンポーネントがオフラインになっていないか、CPU、メモリ、ディスク使用"
+"率が閾値を超えていないかをチェックし、管理者にメッセージで警告を送ります"
+
+#: ops/tasks.py:152
msgid "Clean up unexpected jobs"
msgstr "例外ジョブのクリーンアップ"
-#: ops/tasks.py:136
+#: ops/tasks.py:154
+msgid ""
+"Due to exceptions caused by executing adhoc and playbooks in the Job "
+"Center, \n"
+" which result in the task status not being updated, the system will "
+"clean up abnormal jobs \n"
+" that have not been completed for more than 3 hours every hour and "
+"mark these tasks as \n"
+" failed"
+msgstr ""
+"ショートカットコマンドやplaybookを実行するジョブセンターでは異常が発生し、タ"
+"スクの状態が更新されないことがあります。そのため、システムは毎時間、3時間以上"
+"終了していない異常なジョブをクリーニングし、タスクを失敗とマークします"
+
+#: ops/tasks.py:167
msgid "Clean job_execution db record"
msgstr "ジョブセンター実行履歴のクリーンアップ"
+#: ops/tasks.py:169
+msgid ""
+"Due to the execution of adhoc and playbooks in the Job Center, execution "
+"records will \n"
+" be generated. The system will clean up records that exceed the "
+"retention period every day \n"
+" at 2 a.m., based on the configuration of 'System Settings - Tasks - "
+"Regular clean-up - \n"
+" Job execution retention days'"
+msgstr ""
+"ショートカットコマンドやplaybookを実行するジョブセンターでは、実行レコードが"
+"生成されます。システムは、システム設定-タスクリスト-定期的なクリーニング-ジョ"
+"ブセンター実行履歴の設定に基づき、毎日午前2時に保存期間を超過したレコードをク"
+"リーニングします。"
+
#: ops/templates/ops/celery_task_log.html:4
msgid "Task log"
msgstr "タスクログ"
@@ -4822,18 +5301,18 @@ msgstr "ジョブのID"
msgid "Name of the job"
msgstr "ジョブの名前"
-#: orgs/api.py:61
+#: orgs/api.py:60
msgid "The current organization ({}) cannot be deleted"
msgstr "現在の組織 ({}) は削除できません"
-#: orgs/api.py:66
+#: orgs/api.py:65
msgid ""
"LDAP synchronization is set to the current organization. Please switch to "
"another organization before deleting"
msgstr ""
"LDAP 同期は現在の組織に設定されます。削除する前に別の組織に切り替えてください"
-#: orgs/api.py:76
+#: orgs/api.py:75
msgid "The organization have resource ({}) cannot be deleted"
msgstr "組織のリソース ({}) は削除できません"
@@ -4847,7 +5326,7 @@ msgstr "組織を選択してから保存してください"
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91
#: rbac/const.py:7 rbac/models/rolebinding.py:56
-#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:52
+#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:53
#: terminal/templates/terminal/_msg_command_warning.html:21
#: terminal/templates/terminal/_msg_session_sharing.html:14
#: tickets/models/ticket/general.py:303 tickets/serializers/ticket/ticket.py:60
@@ -4866,7 +5345,7 @@ msgstr "デフォルト組織"
msgid "SYSTEM"
msgstr "システム組織"
-#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:185
+#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:186
#: terminal/models/applet/applet.py:42
msgid "Builtin"
msgstr "ビルトイン"
@@ -4883,7 +5362,7 @@ msgstr "参加しているすべての組織を表示できます"
msgid "Can not delete virtual org"
msgstr "仮想組織を削除できませんでした"
-#: orgs/serializers.py:10 perms/serializers/permission.py:37
+#: orgs/serializers.py:10 perms/serializers/permission.py:48
#: rbac/serializers/role.py:27 users/serializers/group.py:54
msgid "Users amount"
msgstr "ユーザー数"
@@ -4892,7 +5371,7 @@ msgstr "ユーザー数"
msgid "User groups amount"
msgstr "ユーザーグループの数"
-#: orgs/serializers.py:14 perms/serializers/permission.py:40
+#: orgs/serializers.py:14 perms/serializers/permission.py:51
msgid "Nodes amount"
msgstr "ノード数"
@@ -4908,7 +5387,7 @@ msgstr "アカウントを集める"
msgid "Asset permissions amount"
msgstr "資産権限"
-#: orgs/tasks.py:9
+#: orgs/tasks.py:10
msgid "Refresh organization cache"
msgstr "組織キャッシュを更新する"
@@ -4989,7 +5468,7 @@ msgid "today"
msgstr "今日"
#: perms/notifications.py:12 perms/notifications.py:44
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "day"
msgstr "日"
@@ -5009,22 +5488,63 @@ msgstr "資産権限の有効期限が近づいています"
msgid "asset permissions of organization {}"
msgstr "組織 {} の資産権限"
-#: perms/serializers/permission.py:33 users/serializers/user.py:257
+#: perms/serializers/permission.py:32
+msgid ""
+"Accounts, format [\"@virtual\", \"root\", \"%template_id\"], virtual "
+"choices: @ALL, @SPEC, @USER, @ANON, @INPUT"
+msgstr ""
+"アカウント、形式 [\"@バーチャルアカウント\", \"root\", \"%テンプレートid\"], "
+"バーチャルオプション: @ALL, @SPEC, @USER, @ANON, @INPUT"
+
+#: perms/serializers/permission.py:38
+msgid "Protocols, format [\"ssh\", \"rdp\", \"vnc\"] or [\"all\"]"
+msgstr "プロトコル、形式は [\"ssh\", \"rdp\", \"vnc\"] または [\"all\"]"
+
+#: perms/serializers/permission.py:44 users/serializers/user.py:257
msgid "Groups"
msgstr "ユーザーグループ"
-#: perms/serializers/permission.py:38
+#: perms/serializers/permission.py:49
msgid "Groups amount"
msgstr "ユーザーグループの数"
-#: perms/tasks.py:27
+#: perms/tasks.py:28
msgid "Check asset permission expired"
msgstr "アセット認証ルールの有効期限が切れていることを確認する"
-#: perms/tasks.py:40
+#: perms/tasks.py:30
+msgid ""
+"The cache of organizational collections, which have completed user "
+"authorization tree \n"
+" construction, will expire. Therefore, expired collections need to be "
+"cleared from the \n"
+" cache, and this task will be executed periodically based on the time "
+"interval specified \n"
+" by PERM_EXPIRED_CHECK_PERIODIC in the system configuration file "
+"config.txt"
+msgstr ""
+"利用者権限ツリーの組織集合キャッシュは期限切れになるため、期限切れの集合を"
+"キャッシュからクリアする必要があります。このActionは、システム設定ファイル"
+"config.txt中のPERM_EXPIRED_CHECK_PERIODICの時間間隔に基づいて定期的に実行され"
+"ます"
+
+#: perms/tasks.py:49
msgid "Send asset permission expired notification"
msgstr "アセット許可の有効期限通知を送信する"
+#: perms/tasks.py:51
+msgid ""
+"Check every day at 10 a.m. and send a notification message to users "
+"associated with \n"
+" assets whose authorization is about to expire, as well as to the "
+"organization's \n"
+" administrators, 3 days in advance, to remind them that the asset "
+"authorization will \n"
+" expire in a few days"
+msgstr ""
+"毎日午前10時にチェックを行い、資産の承認が近く期限切れになる利用者及びその組"
+"織の管理者に、資産が何日で期限切れになるかを3日前に通知を送ります"
+
#: perms/templates/perms/_msg_item_permissions_expire.html:7
#: perms/templates/perms/_msg_permed_items_expire.html:7
#, python-format
@@ -5057,27 +5577,27 @@ msgstr "{} 少なくとも1つのシステムロール"
msgid "App RBAC"
msgstr "RBAC"
-#: rbac/builtin.py:115
+#: rbac/builtin.py:116
msgid "SystemAdmin"
msgstr "システム管理者"
-#: rbac/builtin.py:118
+#: rbac/builtin.py:119
msgid "SystemAuditor"
msgstr "システム監査人"
-#: rbac/builtin.py:121
+#: rbac/builtin.py:122
msgid "SystemComponent"
msgstr "システムコンポーネント"
-#: rbac/builtin.py:127
+#: rbac/builtin.py:128
msgid "OrgAdmin"
msgstr "組織管理者"
-#: rbac/builtin.py:130
+#: rbac/builtin.py:131
msgid "OrgAuditor"
msgstr "監査員を組織する"
-#: rbac/builtin.py:133
+#: rbac/builtin.py:134
msgid "OrgUser"
msgstr "組織ユーザー"
@@ -5117,11 +5637,6 @@ msgstr "コンテンツタイプ"
msgid "Permissions"
msgstr "権限"
-#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
-#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
-msgid "Scope"
-msgstr "スコープ"
-
#: rbac/models/role.py:46 rbac/models/rolebinding.py:52
#: users/models/user/__init__.py:66
msgid "Role"
@@ -5182,7 +5697,7 @@ msgstr "ワークスペースビュー"
msgid "Audit view"
msgstr "監査ビュー"
-#: rbac/tree.py:27 settings/models.py:161
+#: rbac/tree.py:27 settings/models.py:162
msgid "System setting"
msgstr "システム設定"
@@ -5210,7 +5725,7 @@ msgstr "アカウントの秘密の変更"
msgid "App ops"
msgstr "アプリ操作"
-#: rbac/tree.py:57 settings/serializers/feature.py:117
+#: rbac/tree.py:57 settings/serializers/feature.py:120
msgid "Feature"
msgstr "機能"
@@ -5245,8 +5760,8 @@ msgstr "アプリ組織"
msgid "Ticket comment"
msgstr "チケットコメント"
-#: rbac/tree.py:159 settings/serializers/feature.py:98
-#: settings/serializers/feature.py:100 tickets/models/ticket/general.py:308
+#: rbac/tree.py:159 settings/serializers/feature.py:101
+#: settings/serializers/feature.py:103 tickets/models/ticket/general.py:308
msgid "Ticket"
msgstr "チケット"
@@ -5276,7 +5791,7 @@ msgstr "{}に送信されたテストメールを確認してください"
msgid "Test smtp setting"
msgstr "SMTP設定のテスト"
-#: settings/api/ldap.py:89
+#: settings/api/ldap.py:92
msgid ""
"Users are not synchronized, please click the user synchronization button"
msgstr ""
@@ -5295,75 +5810,75 @@ msgstr "携帯番号をテストこのフィールドは必須です"
msgid "App Settings"
msgstr "設定"
-#: settings/models.py:37 users/models/preference.py:14
+#: settings/models.py:38 users/models/preference.py:14
msgid "Encrypted"
msgstr "暗号化された"
-#: settings/models.py:163
+#: settings/models.py:164
msgid "Can change email setting"
msgstr "メール設定を変更できます"
-#: settings/models.py:164
+#: settings/models.py:165
msgid "Can change auth setting"
msgstr "資格認定の設定"
-#: settings/models.py:165
+#: settings/models.py:166
msgid "Can change auth ops"
msgstr "タスクセンターの設定"
-#: settings/models.py:166
+#: settings/models.py:167
msgid "Can change auth ticket"
msgstr "製造オーダ設定"
-#: settings/models.py:167
+#: settings/models.py:168
msgid "Can change virtual app setting"
msgstr "仮想アプリケーション設定を変更できます"
-#: settings/models.py:168
+#: settings/models.py:169
msgid "Can change auth announcement"
msgstr "公告の設定"
-#: settings/models.py:169
+#: settings/models.py:170
msgid "Can change vault setting"
msgstr "金庫の設定を変えることができます"
-#: settings/models.py:170
+#: settings/models.py:171
msgid "Can change chat ai setting"
msgstr "チャットAI設定を変更できます"
-#: settings/models.py:171
+#: settings/models.py:172
msgid "Can change system msg sub setting"
msgstr "システムmsgサブ设定を変更できます"
-#: settings/models.py:172
+#: settings/models.py:173
msgid "Can change sms setting"
msgstr "Smsの設定を変えることができます"
-#: settings/models.py:173
+#: settings/models.py:174
msgid "Can change security setting"
msgstr "セキュリティ設定を変更できます"
-#: settings/models.py:174
+#: settings/models.py:175
msgid "Can change clean setting"
msgstr "きれいな設定を変えることができます"
-#: settings/models.py:175
+#: settings/models.py:176
msgid "Can change interface setting"
msgstr "インターフェイスの設定を変えることができます"
-#: settings/models.py:176
+#: settings/models.py:177
msgid "Can change license setting"
msgstr "ライセンス設定を変更できます"
-#: settings/models.py:177
+#: settings/models.py:178
msgid "Can change terminal setting"
msgstr "ターミナルの設定を変えることができます"
-#: settings/models.py:178
+#: settings/models.py:179
msgid "Can change other setting"
msgstr "他の設定を変えることができます"
-#: settings/models.py:188
+#: settings/models.py:189
msgid "Chat prompt"
msgstr "チャットのヒント"
@@ -5376,58 +5891,62 @@ msgid "LDAP Auth"
msgstr "LDAP 認証"
#: settings/serializers/auth/base.py:14
+msgid "LDAP Auth HA"
+msgstr "LDAP HA 認証"
+
+#: settings/serializers/auth/base.py:15
msgid "CAS Auth"
msgstr "CAS 認証"
-#: settings/serializers/auth/base.py:15
+#: settings/serializers/auth/base.py:16
msgid "OPENID Auth"
msgstr "OPENID 認証"
-#: settings/serializers/auth/base.py:16
+#: settings/serializers/auth/base.py:17
msgid "SAML2 Auth"
msgstr "SAML2 認証"
-#: settings/serializers/auth/base.py:17
+#: settings/serializers/auth/base.py:18
msgid "OAuth2 Auth"
msgstr "OAuth2 認証"
-#: settings/serializers/auth/base.py:18
+#: settings/serializers/auth/base.py:19
msgid "RADIUS Auth"
msgstr "RADIUS 認証"
-#: settings/serializers/auth/base.py:19
+#: settings/serializers/auth/base.py:20
msgid "DingTalk Auth"
msgstr "くぎ 認証"
-#: settings/serializers/auth/base.py:20
+#: settings/serializers/auth/base.py:21
msgid "FeiShu Auth"
msgstr "飛本 認証"
-#: settings/serializers/auth/base.py:21
+#: settings/serializers/auth/base.py:22
msgid "Lark Auth"
msgstr "Lark 認証"
-#: settings/serializers/auth/base.py:22
+#: settings/serializers/auth/base.py:23
msgid "Slack Auth"
msgstr "Slack 認証"
-#: settings/serializers/auth/base.py:23
+#: settings/serializers/auth/base.py:24
msgid "WeCom Auth"
msgstr "企業微信 認証"
-#: settings/serializers/auth/base.py:24
+#: settings/serializers/auth/base.py:25
msgid "SSO Auth"
msgstr "SSO Token 認証"
-#: settings/serializers/auth/base.py:25
+#: settings/serializers/auth/base.py:26
msgid "Passkey Auth"
msgstr "Passkey 認証"
-#: settings/serializers/auth/base.py:27
+#: settings/serializers/auth/base.py:28
msgid "Email suffix"
msgstr "メールのサフィックス"
-#: settings/serializers/auth/base.py:29
+#: settings/serializers/auth/base.py:30
msgid ""
"After third-party user authentication is successful, if the third-party "
"authentication service platform does not return the user's email "
@@ -5438,19 +5957,19 @@ msgstr ""
"ザーのメール情報を返さなかった場合、システムは自動的にこのメールのサフィック"
"スでユーザーを作成します"
-#: settings/serializers/auth/base.py:36
+#: settings/serializers/auth/base.py:37
msgid "Forgot Password URL"
msgstr "パスワードを忘れた場合のリンク"
-#: settings/serializers/auth/base.py:37
+#: settings/serializers/auth/base.py:38
msgid "The URL for Forgotten Password on the user login page"
msgstr "ユーザーログイン画面のパスワードを忘れた URL"
-#: settings/serializers/auth/base.py:40
+#: settings/serializers/auth/base.py:41
msgid "Login redirection"
msgstr "ログインリダイレクトの有効化msg"
-#: settings/serializers/auth/base.py:42
+#: settings/serializers/auth/base.py:43
msgid ""
"Should an flash page be displayed before the user is redirected to third-"
"party authentication when the administrator enables third-party redirect "
@@ -5459,7 +5978,7 @@ msgstr ""
"管理者が第三者へのリダイレクトの認証を有効にした場合、ユーザーが第三者の認証"
"にリダイレクトされる前に Flash ページを表示するかどうか"
-#: settings/serializers/auth/base.py:54
+#: settings/serializers/auth/base.py:55
msgid ""
"When you create a user, you associate the user to the organization of your "
"choice. Users always belong to the Default organization."
@@ -5471,8 +5990,8 @@ msgstr ""
msgid "CAS"
msgstr "CAS"
-#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:43
-#: settings/serializers/auth/oidc.py:61
+#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap_ha.py:26 settings/serializers/auth/oidc.py:61
msgid "Server"
msgstr "LDAPサーバー"
@@ -5499,9 +6018,10 @@ msgstr "属性マップの有効化"
#: settings/serializers/auth/cas.py:34 settings/serializers/auth/dingtalk.py:18
#: settings/serializers/auth/feishu.py:18 settings/serializers/auth/lark.py:17
-#: settings/serializers/auth/ldap.py:65 settings/serializers/auth/oauth2.py:60
-#: settings/serializers/auth/oidc.py:39 settings/serializers/auth/saml2.py:35
-#: settings/serializers/auth/slack.py:18 settings/serializers/auth/wecom.py:18
+#: settings/serializers/auth/ldap.py:66 settings/serializers/auth/ldap_ha.py:48
+#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:39
+#: settings/serializers/auth/saml2.py:35 settings/serializers/auth/slack.py:18
+#: settings/serializers/auth/wecom.py:18
msgid "User attribute"
msgstr "マッピングのプロパティ"
@@ -5545,7 +6065,7 @@ msgstr ""
"ユーザー属性のマッピング、ここで `key` は JumpServer のユーザー属性名で、"
"`value` は フェイシュ サービスのユーザー属性名です"
-#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:21
+#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:22
msgid "Lark"
msgstr ""
@@ -5557,47 +6077,47 @@ msgstr ""
"ユーザー属性のマッピング、ここで `key` は JumpServer のユーザー属性名で、"
"`value` は Lark サービスのユーザー属性名です"
-#: settings/serializers/auth/ldap.py:40 settings/serializers/auth/ldap.py:102
+#: settings/serializers/auth/ldap.py:41 settings/serializers/auth/ldap.py:103
msgid "LDAP"
msgstr "LDAP"
-#: settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap.py:45
msgid "LDAP server URI"
msgstr "FIDOサーバーID"
-#: settings/serializers/auth/ldap.py:47
+#: settings/serializers/auth/ldap.py:48 settings/serializers/auth/ldap_ha.py:30
msgid "Bind DN"
msgstr "DN のバインド"
-#: settings/serializers/auth/ldap.py:48
+#: settings/serializers/auth/ldap.py:49 settings/serializers/auth/ldap_ha.py:31
msgid "Binding Distinguished Name"
msgstr "バインドディレクトリ管理者"
-#: settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap.py:53 settings/serializers/auth/ldap_ha.py:35
msgid "Binding password"
msgstr "古いパスワード"
-#: settings/serializers/auth/ldap.py:55
+#: settings/serializers/auth/ldap.py:56 settings/serializers/auth/ldap_ha.py:38
msgid "Search OU"
msgstr "システムアーキテクチャ"
-#: settings/serializers/auth/ldap.py:57
+#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/ldap_ha.py:40
msgid ""
"User Search Base, if there are multiple OUs, you can separate them with the "
"`|` symbol"
msgstr ""
"ユーザー検索ライブラリ、複数のOUがある場合は`|`の記号で分けることができます"
-#: settings/serializers/auth/ldap.py:61
+#: settings/serializers/auth/ldap.py:62 settings/serializers/auth/ldap_ha.py:44
msgid "Search filter"
msgstr "ユーザー検索フィルター"
-#: settings/serializers/auth/ldap.py:62
+#: settings/serializers/auth/ldap.py:63 settings/serializers/auth/ldap_ha.py:45
#, python-format
msgid "Selection could include (cn|uid|sAMAccountName=%(user)s)"
msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)"
-#: settings/serializers/auth/ldap.py:67
+#: settings/serializers/auth/ldap.py:68 settings/serializers/auth/ldap_ha.py:50
msgid ""
"User attribute mapping, where the `key` is the JumpServer user attribute "
"name and the `value` is the LDAP service user attribute name"
@@ -5605,29 +6125,49 @@ msgstr ""
"ユーザー属性のマッピング、ここで `key` は JumpServer のユーザー属性名で、"
"`value` は LDAP サービスのユーザー属性名です"
-#: settings/serializers/auth/ldap.py:83
+#: settings/serializers/auth/ldap.py:84 settings/serializers/auth/ldap_ha.py:66
msgid "Connect timeout (s)"
msgstr "接続タイムアウト (秒)"
-#: settings/serializers/auth/ldap.py:88
+#: settings/serializers/auth/ldap.py:89 settings/serializers/auth/ldap_ha.py:71
msgid "User DN cache timeout (s)"
msgstr "User DN キャッシュの有効期限 (秒)"
-#: settings/serializers/auth/ldap.py:90
+#: settings/serializers/auth/ldap.py:91
msgid ""
"Caching the User DN obtained during user login authentication can "
-"effectivelyimprove the speed of user authentication., 0 means no cache
If "
-"the user OU structure has been adjusted, click Submit to clear the user DN "
-"cache"
+"effectively improve the speed of user authentication., 0 means no "
+"cache
If the user OU structure has been adjusted, click Submit to clear "
+"the user DN cache"
msgstr ""
-"ユーザーログイン認証時に取得したユーザー DN をキャッシュすることで、ユーザー"
-"認証の速度を効果的に向上させることができます
ユーザー OU 構造が調整された"
-"場合、送信をクリックしてユーザー DN キャッシュをクリアします"
+"ユーザーがログイン認証時にクエリした User DN をキャッシュすると、ユーザー認証"
+"の速度を効果的に改善できます。
ユーザーの OU 構造が調整された場合は、提出"
+"をクリックしてユーザーの DN キャッシュをクリアできます。"
-#: settings/serializers/auth/ldap.py:96
+#: settings/serializers/auth/ldap.py:97 settings/serializers/auth/ldap_ha.py:79
msgid "Search paged size (piece)"
msgstr "ページサイズを検索 (じょう)"
+#: settings/serializers/auth/ldap_ha.py:23
+#: settings/serializers/auth/ldap_ha.py:85
+msgid "LDAP HA"
+msgstr "LDAP 認証"
+
+#: settings/serializers/auth/ldap_ha.py:27
+msgid "LDAP HA server URI"
+msgstr "LDAP HA サービスドメイン名"
+
+#: settings/serializers/auth/ldap_ha.py:73
+msgid ""
+"Caching the User DN obtained during user login authentication can "
+"effectivelyimprove the speed of user authentication., 0 means no cache
If "
+"the user OU structure has been adjusted, click Submit to clear the user DN "
+"cache"
+msgstr ""
+"ユーザーがログイン認証時にクエリされた User DN をキャッシュすることで、ユー"
+"ザー認証の速度を効果的に向上させることができます
ユーザーの OU 構造が調整"
+"された場合は、送信をクリックして User DN のキャッシュをクリアできます"
+
#: settings/serializers/auth/oauth2.py:19
#: settings/serializers/auth/oauth2.py:22
msgid "OAuth2"
@@ -6076,32 +6616,42 @@ msgstr ""
"この期間を超えるセッション、録音、およびコマンド レコードは削除されます (デー"
"タベースのバックアップに影響し、OSS などには影響しません)"
-#: settings/serializers/feature.py:18 settings/serializers/msg.py:68
+#: settings/serializers/cleaning.py:53
+msgid "Change secret and push record retention days (day)"
+msgstr "パスワード変更プッシュ記録を保持する日数 (日)"
+
+#: settings/serializers/feature.py:19 settings/serializers/msg.py:68
msgid "Subject"
msgstr "件名"
-#: settings/serializers/feature.py:22
+#: settings/serializers/feature.py:23
msgid "More Link"
msgstr "もっとURL"
-#: settings/serializers/feature.py:36 settings/serializers/feature.py:38
-#: settings/serializers/feature.py:39
+#: settings/serializers/feature.py:26
+#: settings/templates/ldap/_msg_import_ldap_user.html:6
+#: terminal/models/session/session.py:46
+msgid "Date end"
+msgstr "終了日"
+
+#: settings/serializers/feature.py:39 settings/serializers/feature.py:41
+#: settings/serializers/feature.py:42
msgid "Announcement"
msgstr "発表"
-#: settings/serializers/feature.py:46
+#: settings/serializers/feature.py:49
msgid "Vault"
msgstr "有効化 Vault"
-#: settings/serializers/feature.py:55
+#: settings/serializers/feature.py:58
msgid "Mount Point"
msgstr "マウントポイント"
-#: settings/serializers/feature.py:61
+#: settings/serializers/feature.py:64
msgid "Record limit"
msgstr "記録制限"
-#: settings/serializers/feature.py:63
+#: settings/serializers/feature.py:66
msgid ""
"If the specific value is less than 999 (default), the system will "
"automatically perform a task every night: check and delete historical "
@@ -6112,76 +6662,76 @@ msgstr ""
"所定の数を超える履歴アカウントを確認して削除します。 値が 999 以上の場合、履"
"歴アカウントの削除は実行されません。"
-#: settings/serializers/feature.py:73 settings/serializers/feature.py:79
+#: settings/serializers/feature.py:76 settings/serializers/feature.py:82
msgid "Chat AI"
msgstr "チャットAI"
-#: settings/serializers/feature.py:82
+#: settings/serializers/feature.py:85
msgid "GPT Base URL"
msgstr "GPTアドレス"
-#: settings/serializers/feature.py:83
+#: settings/serializers/feature.py:86
msgid "The base URL of the GPT service. For example: https://api.openai.com/v1"
msgstr "GPTサービスの基本のURL。例えば:https://api.openai.com/v1"
-#: settings/serializers/feature.py:86 templates/_header_bar.html:96
+#: settings/serializers/feature.py:89 templates/_header_bar.html:96
msgid "API Key"
msgstr "API Key"
-#: settings/serializers/feature.py:90
+#: settings/serializers/feature.py:93
msgid ""
"The proxy server address of the GPT service. For example: http://ip:port"
msgstr "GPTサービスのプロキシサーバーのアドレス。例えば:http://ip:port"
-#: settings/serializers/feature.py:93
+#: settings/serializers/feature.py:96
msgid "GPT Model"
msgstr "GPTモデル"
-#: settings/serializers/feature.py:102
+#: settings/serializers/feature.py:105
msgid "Approval without login"
msgstr "ログイン承認なし"
-#: settings/serializers/feature.py:103
+#: settings/serializers/feature.py:106
msgid "Allow direct approval ticket without login"
msgstr "ログインせずに直接承認チケットを許可します"
-#: settings/serializers/feature.py:107
+#: settings/serializers/feature.py:110
msgid "Period"
msgstr "期間"
-#: settings/serializers/feature.py:108
+#: settings/serializers/feature.py:111
msgid ""
"The default authorization time period when applying for assets via a ticket"
msgstr "ワークオーダーの資産申請に対するデフォルトの承認時間帯"
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "hour"
msgstr "時"
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "Unit"
msgstr "単位"
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "The unit of period"
msgstr "ユーザーの実行"
-#: settings/serializers/feature.py:121
+#: settings/serializers/feature.py:124
msgid ""
"Allow users to execute batch commands in the Workbench - Job Center - Adhoc"
msgstr ""
"ユーザーがワークベンチ - ジョブセンター - Adhocでバッチコマンドを実行すること"
"を許可します"
-#: settings/serializers/feature.py:125
+#: settings/serializers/feature.py:128
msgid "Command blacklist"
msgstr "コマンドフィルタリング"
-#: settings/serializers/feature.py:126
+#: settings/serializers/feature.py:129
msgid "Command blacklist in Adhoc"
msgstr "コマンドフィルタリング"
-#: settings/serializers/feature.py:131
+#: settings/serializers/feature.py:134
#: terminal/models/virtualapp/provider.py:17
#: terminal/models/virtualapp/virtualapp.py:36
#: terminal/models/virtualapp/virtualapp.py:97
@@ -6189,11 +6739,11 @@ msgstr "コマンドフィルタリング"
msgid "Virtual app"
msgstr "仮想アプリケーション"
-#: settings/serializers/feature.py:134
+#: settings/serializers/feature.py:137
msgid "Virtual App"
msgstr "仮想アプリケーション"
-#: settings/serializers/feature.py:136
+#: settings/serializers/feature.py:139
msgid ""
"Virtual applications, you can use the Linux operating system as an "
"application server in remote applications."
@@ -6674,23 +7224,53 @@ msgstr ""
"* RBAC権限を持つユーザは、ワークベンチのすべてのツールを使用できるようにしま"
"す"
-#: settings/tasks/ldap.py:28
+#: settings/tasks/ldap.py:73
msgid "Periodic import ldap user"
msgstr "LDAP ユーザーを定期的にインポートする"
-#: settings/tasks/ldap.py:66
+#: settings/tasks/ldap.py:75 settings/tasks/ldap.py:85
+msgid ""
+"When LDAP auto-sync is configured, this task will be invoked to synchronize "
+"users"
+msgstr ""
+"LDAPの自動同期が設定されている場合、このActionを呼び出して利用者の同期を行い"
+"ます"
+
+#: settings/tasks/ldap.py:83
+msgid "Periodic import ldap ha user"
+msgstr "LDAP HA ユーザーの定期インポート"
+
+#: settings/tasks/ldap.py:117
msgid "Registration periodic import ldap user task"
msgstr "登録サイクルLDAPユーザータスクのインポート"
+#: settings/tasks/ldap.py:119
+msgid ""
+"When LDAP auto-sync parameters change, such as Crontab parameters, the LDAP "
+"sync task \n"
+" will be re-registered or updated, and this task will be invoked"
+msgstr ""
+"LDAPの自動同期パラメーターが変更された場合、たとえばCrontabパラメーターが変更"
+"され、ldap同期Actionの再登録または更新が必要になった場合、そのActionはこの"
+"Actionを呼び出します"
+
+#: settings/tasks/ldap.py:133
+msgid "Registration periodic import ldap ha user task"
+msgstr "LDAP HA ユーザーの定期インポートタスクの登録"
+
+#: settings/tasks/ldap.py:135
+msgid ""
+"When LDAP HA auto-sync parameters change, such as Crontab parameters, the "
+"LDAP HA sync task \n"
+" will be re-registered or updated, and this task will be invoked"
+msgstr ""
+"LDAP HA自動同期パラメーターが変更された場合、Crontabパラメーターなど、ldap ha"
+"同期Actionを再登録または更新する際にはこのActionを呼び出します"
+
#: settings/templates/ldap/_msg_import_ldap_user.html:2
msgid "Sync task finish"
msgstr "同期タスクが完了しました"
-#: settings/templates/ldap/_msg_import_ldap_user.html:6
-#: terminal/models/session/session.py:46
-msgid "Date end"
-msgstr "終了日"
-
#: settings/templates/ldap/_msg_import_ldap_user.html:9
msgid "Synced Organization"
msgstr "組織が同期されました"
@@ -6703,108 +7283,108 @@ msgstr "同期されたユーザー"
msgid "No user synchronization required"
msgstr "ユーザーの同期は必要ありません"
-#: settings/utils/ldap.py:494
+#: settings/utils/ldap.py:509
msgid "ldap:// or ldaps:// protocol is used."
msgstr "ldap:// または ldaps:// プロトコルが使用されます。"
-#: settings/utils/ldap.py:505
+#: settings/utils/ldap.py:520
msgid "Host or port is disconnected: {}"
msgstr "ホストまたはポートが切断されました: {}"
-#: settings/utils/ldap.py:507
+#: settings/utils/ldap.py:522
msgid "The port is not the port of the LDAP service: {}"
msgstr "ポートはLDAPサービスのポートではありません: {}"
-#: settings/utils/ldap.py:509
+#: settings/utils/ldap.py:524
msgid "Please add certificate: {}"
msgstr "証明書を追加してください: {}"
-#: settings/utils/ldap.py:513 settings/utils/ldap.py:540
-#: settings/utils/ldap.py:570 settings/utils/ldap.py:598
+#: settings/utils/ldap.py:528 settings/utils/ldap.py:555
+#: settings/utils/ldap.py:585 settings/utils/ldap.py:613
msgid "Unknown error: {}"
msgstr "不明なエラー: {}"
-#: settings/utils/ldap.py:527
+#: settings/utils/ldap.py:542
msgid "Bind DN or Password incorrect"
msgstr "DNまたはパスワードのバインドが正しくありません"
-#: settings/utils/ldap.py:534
+#: settings/utils/ldap.py:549
msgid "Please enter Bind DN: {}"
msgstr "バインドDN: {} を入力してください"
-#: settings/utils/ldap.py:536
+#: settings/utils/ldap.py:551
msgid "Please enter Password: {}"
msgstr "パスワードを入力してください: {}"
-#: settings/utils/ldap.py:538
+#: settings/utils/ldap.py:553
msgid "Please enter correct Bind DN and Password: {}"
msgstr "正しいバインドDNとパスワードを入力してください: {}"
-#: settings/utils/ldap.py:556
+#: settings/utils/ldap.py:571
msgid "Invalid User OU or User search filter: {}"
msgstr "無効なユーザー OU またはユーザー検索フィルター: {}"
-#: settings/utils/ldap.py:587
+#: settings/utils/ldap.py:602
msgid "LDAP User attr map not include: {}"
msgstr "LDAP ユーザーattrマップは含まれません: {}"
-#: settings/utils/ldap.py:594
+#: settings/utils/ldap.py:609
msgid "LDAP User attr map is not dict"
msgstr "LDAPユーザーattrマップはdictではありません"
-#: settings/utils/ldap.py:613
+#: settings/utils/ldap.py:628
msgid "LDAP authentication is not enabled"
msgstr "LDAP 認証が有効になっていない"
-#: settings/utils/ldap.py:631
+#: settings/utils/ldap.py:646
msgid "Error (Invalid LDAP server): {}"
msgstr "エラー (LDAPサーバーが無効): {}"
-#: settings/utils/ldap.py:633
+#: settings/utils/ldap.py:648
msgid "Error (Invalid Bind DN): {}"
msgstr "エラー (DNのバインドが無効): {}"
-#: settings/utils/ldap.py:635
+#: settings/utils/ldap.py:650
msgid "Error (Invalid LDAP User attr map): {}"
msgstr "エラー (LDAPユーザーattrマップが無効): {}"
-#: settings/utils/ldap.py:637
+#: settings/utils/ldap.py:652
msgid "Error (Invalid User OU or User search filter): {}"
msgstr "エラー (ユーザーOUまたはユーザー検索フィルターが無効): {}"
-#: settings/utils/ldap.py:639
+#: settings/utils/ldap.py:654
msgid "Error (Not enabled LDAP authentication): {}"
msgstr "エラー (LDAP認証が有効化されていません): {}"
-#: settings/utils/ldap.py:641
+#: settings/utils/ldap.py:656
msgid "Error (Unknown): {}"
msgstr "エラー (不明): {}"
-#: settings/utils/ldap.py:644
+#: settings/utils/ldap.py:659
msgid "Succeed: Match {} users"
msgstr "成功: {} 人のユーザーに一致"
-#: settings/utils/ldap.py:677
+#: settings/utils/ldap.py:689
msgid "Authentication failed (configuration incorrect): {}"
msgstr "認証に失敗しました (設定が正しくありません): {}"
-#: settings/utils/ldap.py:681
+#: settings/utils/ldap.py:693
msgid "Authentication failed (username or password incorrect): {}"
msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}"
-#: settings/utils/ldap.py:683
+#: settings/utils/ldap.py:695
msgid "Authentication failed (Unknown): {}"
msgstr "認証に失敗しました (不明): {}"
-#: settings/utils/ldap.py:686
+#: settings/utils/ldap.py:698
msgid "Authentication success: {}"
msgstr "認証成功: {}"
-#: settings/ws.py:198
+#: settings/ws.py:199
msgid "No LDAP user was found"
msgstr "LDAPユーザーが取得されませんでした"
-#: settings/ws.py:204
+#: settings/ws.py:205
msgid "Total {}, success {}, failure {}"
msgstr "合計 {},成功 {},失敗 {}"
@@ -7034,27 +7614,27 @@ msgstr "プロトコルクエリパラメータが見つかりません"
msgid "Deleting the default storage is not allowed"
msgstr "デフォルトのストレージの削除は許可されていません"
-#: terminal/api/component/storage.py:34
-msgid "Cannot delete storage that is being used"
-msgstr "使用中のストレージを削除できません"
+#: terminal/api/component/storage.py:36
+msgid "Cannot delete storage that is being used: {}"
+msgstr "使用中のストレージは削除できません: {}"
-#: terminal/api/component/storage.py:75 terminal/api/component/storage.py:76
+#: terminal/api/component/storage.py:77 terminal/api/component/storage.py:78
msgid "Command storages"
msgstr "コマンドストア"
-#: terminal/api/component/storage.py:82
+#: terminal/api/component/storage.py:84
msgid "Invalid"
msgstr "無効"
-#: terminal/api/component/storage.py:130 terminal/tasks.py:149
+#: terminal/api/component/storage.py:132 terminal/tasks.py:208
msgid "Test failure: {}"
msgstr "テスト失敗: {}"
-#: terminal/api/component/storage.py:133
+#: terminal/api/component/storage.py:135
msgid "Test successful"
msgstr "テスト成功"
-#: terminal/api/component/storage.py:135
+#: terminal/api/component/storage.py:137
msgid "Test failure: Please check configuration"
msgstr "テストに失敗しました:構成を確認してください"
@@ -7067,15 +7647,15 @@ msgstr "オンラインセッションを持つ"
msgid "User %s %s session %s replay"
msgstr "ユーザー%s %sこのセッション %s の録画です"
-#: terminal/api/session/session.py:314
+#: terminal/api/session/session.py:326
msgid "Session does not exist: {}"
msgstr "セッションが存在しません: {}"
-#: terminal/api/session/session.py:317
+#: terminal/api/session/session.py:329
msgid "Session is finished or the protocol not supported"
msgstr "セッションが終了したか、プロトコルがサポートされていません"
-#: terminal/api/session/session.py:330
+#: terminal/api/session/session.py:342
msgid "User does not have permission"
msgstr "ユーザーに権限がありません"
@@ -7239,7 +7819,7 @@ msgstr "バージョン"
msgid "Can concurrent"
msgstr "同時実行可能"
-#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:167
+#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:179
#: terminal/serializers/storage.py:193
msgid "Hosts"
msgstr "ホスト"
@@ -7270,7 +7850,7 @@ msgstr "ホスト マシン"
msgid "Applet Publication"
msgstr "アプリケーションのリリース"
-#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:69
+#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:81
msgid "Deploy options"
msgstr "展開パラメーター"
@@ -7382,12 +7962,12 @@ msgstr "スレッド"
msgid "Boot Time"
msgstr "ブート時間"
-#: terminal/models/component/storage.py:146
+#: terminal/models/component/storage.py:144
#: terminal/models/component/terminal.py:91
msgid "Command storage"
msgstr "コマンドストレージ"
-#: terminal/models/component/storage.py:214
+#: terminal/models/component/storage.py:212
#: terminal/models/component/terminal.py:92
msgid "Replay storage"
msgstr "再生ストレージ"
@@ -7404,7 +7984,7 @@ msgstr "リモートアドレス"
msgid "Application User"
msgstr "ユーザーの適用"
-#: terminal/models/component/terminal.py:177
+#: terminal/models/component/terminal.py:176
msgid "Can view terminal config"
msgstr "ターミナル構成を表示できます"
@@ -7436,7 +8016,7 @@ msgstr "ログイン元"
msgid "Replay"
msgstr "リプレイ"
-#: terminal/models/session/session.py:48 terminal/serializers/session.py:68
+#: terminal/models/session/session.py:48 terminal/serializers/session.py:78
msgid "Command amount"
msgstr "コマンド量"
@@ -7444,23 +8024,23 @@ msgstr "コマンド量"
msgid "Error reason"
msgstr "間違った理由"
-#: terminal/models/session/session.py:290
+#: terminal/models/session/session.py:308
msgid "Session record"
msgstr "セッション記録"
-#: terminal/models/session/session.py:292
+#: terminal/models/session/session.py:310
msgid "Can monitor session"
msgstr "セッションを監視できます"
-#: terminal/models/session/session.py:293
+#: terminal/models/session/session.py:311
msgid "Can share session"
msgstr "セッションを共有できます"
-#: terminal/models/session/session.py:294
+#: terminal/models/session/session.py:312
msgid "Can terminate session"
msgstr "セッションを終了できます"
-#: terminal/models/session/session.py:295
+#: terminal/models/session/session.py:313
msgid "Can validate session action perm"
msgstr "セッションアクションのパーマを検証できます"
@@ -7560,7 +8140,7 @@ msgstr "レベル"
msgid "Command and replay storage"
msgstr "コマンド及び録画記憶"
-#: terminal/notifications.py:240 terminal/tasks.py:153
+#: terminal/notifications.py:240 terminal/tasks.py:212
#: xpack/plugins/cloud/api.py:160
#: xpack/plugins/cloud/serializers/account.py:121
#: xpack/plugins/cloud/serializers/account.py:123
@@ -7577,12 +8157,12 @@ msgid "Icon"
msgstr "アイコン"
#: terminal/serializers/applet_host.py:24
-msgid "Per Session"
-msgstr "セッションごと"
+msgid "Per Device (Device number limit)"
+msgstr ""
#: terminal/serializers/applet_host.py:25
-msgid "Per Device"
-msgstr "デバイスごと"
+msgid "Per User (User number limit)"
+msgstr ""
#: terminal/serializers/applet_host.py:37
msgid "Core API"
@@ -7611,27 +8191,40 @@ msgstr ""
msgid "Ignore Certificate Verification"
msgstr "証明書の検証を無視する"
-#: terminal/serializers/applet_host.py:47
+#: terminal/serializers/applet_host.py:48
msgid "Existing RDS license"
msgstr "既存の RDS 証明書"
-#: terminal/serializers/applet_host.py:48
+#: terminal/serializers/applet_host.py:50
+msgid ""
+"If not exist, the RDS will be in trial mode, and the trial period is 120 "
+"days. Detail"
+msgstr ""
+
+#: terminal/serializers/applet_host.py:55
msgid "RDS License Server"
msgstr "RDS ライセンス サーバー"
-#: terminal/serializers/applet_host.py:49
+#: terminal/serializers/applet_host.py:57
msgid "RDS Licensing Mode"
msgstr "RDS 認可モード"
-#: terminal/serializers/applet_host.py:51
+#: terminal/serializers/applet_host.py:60
msgid "RDS Single Session Per User"
msgstr "RDS シングル ユーザー シングル セッション"
-#: terminal/serializers/applet_host.py:53
+#: terminal/serializers/applet_host.py:61
+msgid ""
+"Tips: A RDS user can have only one session at a time. If set, when next "
+"login connected, previous session will be disconnected."
+msgstr ""
+
+#: terminal/serializers/applet_host.py:65
msgid "RDS Max Disconnection Time (ms)"
msgstr "最大切断時間(ミリ秒)"
-#: terminal/serializers/applet_host.py:55
+#: terminal/serializers/applet_host.py:67
msgid ""
"Tips: Set the maximum duration for keeping a disconnected session active on "
"the server (log off the session after 60000 milliseconds)."
@@ -7639,11 +8232,11 @@ msgstr ""
"ヒント:サーバー上で切断されたセッションがアクティブな状態で維持される最大時"
"間を設定します(60000ミリ秒後にセッションをログオフ)。"
-#: terminal/serializers/applet_host.py:60
+#: terminal/serializers/applet_host.py:72
msgid "RDS Remote App Logoff Time Limit (ms)"
msgstr "RDSリモートアプリケーションのログアウト時間制限(ミリ秒)"
-#: terminal/serializers/applet_host.py:62
+#: terminal/serializers/applet_host.py:74
msgid ""
"Tips: Set the logoff time for RemoteApp sessions after closing all RemoteApp "
"programs (0 milliseconds, log off the session immediately)."
@@ -7651,12 +8244,12 @@ msgstr ""
"ヒント:すべてのRemoteAppプログラムを閉じた後、RemoteAppセッションのログオフ"
"時間を設定します(0ミリ秒、セッションを即座にログオフ)。"
-#: terminal/serializers/applet_host.py:71 terminal/serializers/terminal.py:47
+#: terminal/serializers/applet_host.py:83 terminal/serializers/terminal.py:47
#: terminal/serializers/virtualapp_provider.py:13
msgid "Load status"
msgstr "ロードステータス"
-#: terminal/serializers/applet_host.py:85
+#: terminal/serializers/applet_host.py:97
msgid ""
"These accounts are used to connect to the published application, the account "
"is now divided into two types, one is dedicated to each account, each user "
@@ -7670,11 +8263,11 @@ msgstr ""
"開されています。アプリケーションが複数のオープンをサポートしていない場合、お"
"よび特別なものが使用されている場合、公開アカウントが使用されます。"
-#: terminal/serializers/applet_host.py:92
+#: terminal/serializers/applet_host.py:104
msgid "The number of public accounts created automatically"
msgstr "自動的に作成される公開アカウントの数"
-#: terminal/serializers/applet_host.py:95
+#: terminal/serializers/applet_host.py:107
msgid ""
"Connect to the host using the same account first. For security reasons, "
"please set the configuration item CACHE_LOGIN_PASSWORD_ENABLED=true and "
@@ -7684,15 +8277,15 @@ msgstr ""
"目 CACHE_LOGIN_PASSWORD_ENABLED=true を設定してサービスを再起動して有効にして"
"ください。"
-#: terminal/serializers/applet_host.py:137
+#: terminal/serializers/applet_host.py:149
msgid "Install applets"
msgstr "アプリをインストールする"
-#: terminal/serializers/applet_host.py:167
+#: terminal/serializers/applet_host.py:179
msgid "Host ID"
msgstr "ホスト ID"
-#: terminal/serializers/applet_host.py:168
+#: terminal/serializers/applet_host.py:180
msgid "Applet ID"
msgstr "リモートアプリケーション ID"
@@ -8023,39 +8616,112 @@ msgstr "許可が期限切れです"
msgid "storage is null"
msgstr "ストレージが空です"
-#: terminal/tasks.py:31
+#: terminal/tasks.py:32
msgid "Periodic delete terminal status"
msgstr "端末の状態を定期的にクリーンアップする"
-#: terminal/tasks.py:39
+#: terminal/tasks.py:43
msgid "Clean orphan session"
msgstr "オフライン セッションをクリアする"
-#: terminal/tasks.py:87
+#: terminal/tasks.py:45
+msgid ""
+"Check every 10 minutes for asset connection sessions that have been inactive "
+"for 3 \n"
+" minutes and mark these sessions as completed"
+msgstr ""
+"毎10分ごとに、3分間非活動状態の資産接続セッションを確認し、これらのセッション"
+"を完了とマークします"
+
+#: terminal/tasks.py:68
+msgid "Upload session replay to external storage"
+msgstr "セッションの記録を外部ストレージにアップロードする"
+
+#: terminal/tasks.py:70 terminal/tasks.py:104
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured in the config.txt, session commands "
+"and \n"
+" recordings will be uploaded to external storage"
+msgstr ""
+"SERVER_REPLAY_STORAGEが設定されている場合、ファイル管理を通じてアップロードさ"
+"れたファイルを外部ストレージに同期します"
+
+#: terminal/tasks.py:102
+msgid "Upload session replay part file to external storage"
+msgstr "セッションリプレイパートファイルを外部ストレージにアップロードする"
+
+#: terminal/tasks.py:123
msgid "Run applet host deployment"
msgstr "アプリケーション マシンの展開を実行する"
-#: terminal/tasks.py:97
+#: terminal/tasks.py:126
+msgid ""
+"When deploying from the remote application publisher details page, and the "
+"'Deploy' \n"
+" button is clicked, this task will be executed"
+msgstr "デプロイメントシステムの展開時に、このActionが実行されます"
+
+#: terminal/tasks.py:137
msgid "Install applet"
msgstr "アプリをインストールする"
-#: terminal/tasks.py:108
+#: terminal/tasks.py:140
+msgid ""
+"When the 'Deploy' button is clicked in the 'Remote Application' section of "
+"the remote \n"
+" application publisher details page, this task will be executed"
+msgstr ""
+"リモートアプリケーションの詳細-リモートアプリケーションの展開時に、このAction"
+"が実行されます"
+
+#: terminal/tasks.py:152
msgid "Uninstall applet"
msgstr "アプリをアンインストールする"
-#: terminal/tasks.py:119
+#: terminal/tasks.py:155
+msgid ""
+"When the 'Uninstall' button is clicked in the 'Remote Application' section "
+"of the \n"
+" remote application publisher details page, this task will be executed"
+msgstr ""
+"リモートアプリケーションの詳細-リモートアプリケーションのアンインストール時"
+"に、このActionが実行されます"
+
+#: terminal/tasks.py:167
msgid "Generate applet host accounts"
msgstr "リモートアプリケーション上のアカウントを収集する"
-#: terminal/tasks.py:131
+#: terminal/tasks.py:170
+msgid ""
+"When a remote publishing server is created and an account needs to be "
+"created \n"
+" automatically, this task will be executed"
+msgstr ""
+"リモートパブリッシャーを作成した後、自動でアカウントを作成する必要がある場"
+"合、このActionが実行されます"
+
+#: terminal/tasks.py:184
msgid "Check command replay storage connectivity"
msgstr "チェックコマンドと録画ストレージの接続性"
+#: terminal/tasks.py:186
+msgid ""
+"Check every day at midnight whether the external storage for commands and "
+"recordings \n"
+" is accessible. If it is not accessible, send a notification to the "
+"recipients specified \n"
+" in 'System Settings - Notifications - Subscription - Storage - "
+"Connectivity'"
+msgstr ""
+"毎日午前0時に、コマンドと映像の外部ストレージが接続可能かどうかを確認します。"
+"接続できない場合は、システム設定-通知設定-メッセージ訂閱-コマンドと映像スト"
+"レージ設定の受け取り人に送信します"
+
#: terminal/templates/terminal/_msg_command_alert.html:10
msgid "view"
msgstr "表示"
-#: terminal/utils/db_port_mapper.py:85
+#: terminal/utils/db_port_mapper.py:88
msgid ""
"No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the "
@@ -8065,7 +8731,7 @@ msgstr ""
"サービスによって開かれたポートの数を超えた可能性があります。さらにポートを開"
"くには、管理者に連絡してください。"
-#: terminal/utils/db_port_mapper.py:113
+#: terminal/utils/db_port_mapper.py:116
msgid ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
@@ -8073,7 +8739,7 @@ msgstr ""
"使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の"
"制限を確認して変更してください. "
-#: terminal/utils/db_port_mapper.py:115
+#: terminal/utils/db_port_mapper.py:118
msgid "All available port count: {}, Already use port count: {}"
msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}"
@@ -8137,19 +8803,19 @@ msgstr ""
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: "
"{}"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Change field"
msgstr "フィールドを変更"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Before change"
msgstr "変更前"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "After change"
msgstr "変更後"
-#: tickets/handlers/base.py:97
+#: tickets/handlers/base.py:96
msgid "{} {} the ticket"
msgstr "{} {} チケット"
@@ -8389,11 +9055,15 @@ msgstr "無効な承認アクション"
msgid "This user is not authorized to approve this ticket"
msgstr "このユーザーはこの作業指示を承認する権限がありません"
-#: users/api/user.py:155
+#: users/api/user.py:63
+msgid "Cannot delete the admin user. Please disable it instead."
+msgstr "管理ユーザーを削除することはできません。それを無効にしてください。"
+
+#: users/api/user.py:161
msgid "Can not invite self"
msgstr "自分自身を招待することはできません"
-#: users/api/user.py:208
+#: users/api/user.py:214
msgid "Could not reset self otp, use profile reset instead"
msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用"
@@ -8865,7 +9535,7 @@ msgstr ""
msgid "name not unique"
msgstr "名前が一意ではない"
-#: users/signal_handlers.py:39
+#: users/signal_handlers.py:41
msgid ""
"The administrator has enabled \"Only allow existing users to log in\", \n"
" and the current user is not in the user list. Please contact the "
@@ -8874,31 +9544,94 @@ msgstr ""
"管理者は「既存のユーザーのみログインを許可」をオンにしており、現在のユーザー"
"はユーザーリストにありません。管理者に連絡してください。"
-#: users/signal_handlers.py:183
+#: users/signal_handlers.py:196
msgid "Clean up expired user sessions"
msgstr "期限切れのユーザー・セッションのパージ"
-#: users/tasks.py:25
+#: users/signal_handlers.py:198
+msgid ""
+"After logging in via the web, a user session record is created. At 2 a.m. "
+"every day, \n"
+" the system cleans up inactive user devices"
+msgstr ""
+"webでログインすると、利用者のセッションのオンライン記録が生じます。毎日午前2"
+"時に、オンラインではない利用者デバイスをクリアします"
+
+#: users/tasks.py:26
msgid "Check password expired"
msgstr "パスワードの有効期限が切れていることを確認する"
-#: users/tasks.py:39
+#: users/tasks.py:28
+msgid ""
+"Check every day at 10 AM whether the passwords of users in the system are "
+"expired, \n"
+" and send a notification 5 days in advance"
+msgstr ""
+"毎日午前10時にチェックし、システム内の利用者のパスワードが期限切れになってい"
+"るかどうかを確認し、5日前に通知を送ります"
+
+#: users/tasks.py:46
msgid "Periodic check password expired"
msgstr "定期認証パスワードの有効期限"
-#: users/tasks.py:53
+#: users/tasks.py:48
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if passwords have expired"
+msgstr ""
+"バージョンが進化するにつれて、新たなActionが追加されたり、Actionの名前、実行"
+"時間が変更されたりする可能性があります。そのため、システムが起動するときに、"
+"パスワードの期限切れを確認するActionのパラメータを登録または更新します"
+
+#: users/tasks.py:67
msgid "Check user expired"
msgstr "ユーザーの有効期限が切れていることを確認する"
-#: users/tasks.py:70
+#: users/tasks.py:69
+msgid ""
+"Check every day at 2 p.m whether the users in the system are expired, and "
+"send a \n"
+" notification 5 days in advance"
+msgstr ""
+"毎日午前10時に確認し、システム内のユーザーが期限切れになっているか確認し、5日"
+"前に通知を送信します"
+
+#: users/tasks.py:90
msgid "Periodic check user expired"
msgstr "ユーザーの有効期限の定期的な検出"
-#: users/tasks.py:84
+#: users/tasks.py:92
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if users have expired"
+msgstr ""
+"バージョンのイテレーションに伴い、新たなタスクが追加されたり、タスクの名称、"
+"実行時間が変更される可能性があるため、システム起動時に、登録または更新された"
+"ユーザーが期限切れのタスクのパラメータをチェックします"
+
+#: users/tasks.py:111
msgid "Check unused users"
msgstr "未使用のユーザーのチェック"
-#: users/tasks.py:123
+#: users/tasks.py:113
+msgid ""
+"At 2 p.m. every day, according to the configuration in \"System Settings - "
+"Security - \n"
+" Auth security - Auto disable threshold\" users who have not logged "
+"in or whose API keys \n"
+" have not been used for a long time will be disabled"
+msgstr ""
+"毎日午前2時、システム設定-セキュリティ設定-非アクティブユーザー自動無効化設定"
+"に基づき、長時間ログインしないユーザーやapi_keyを使用しないユーザーを無効にし"
+"ます"
+
+#: users/tasks.py:157
msgid "The user has not logged in recently and has been disabled."
msgstr "ユーザーは最近ログインしておらず、無効になっています。"
@@ -9357,7 +10090,7 @@ msgstr "地域 \"%s\" のインスタンスを取得できませんでした、
msgid "Failed to synchronize the instance \"%s\""
msgstr "インスタンス \"%s\" の同期に失敗しました"
-#: xpack/plugins/cloud/manager.py:337
+#: xpack/plugins/cloud/manager.py:336
#, python-format
msgid ""
"The updated platform of asset \"%s\" is inconsistent with the original "
@@ -9366,42 +10099,42 @@ msgstr ""
"更新された資産 \"%s\" のプラットフォームタイプと元のタイプは一致しません。プ"
"ラットフォームとプロトコルの更新をスキップ"
-#: xpack/plugins/cloud/manager.py:393
+#: xpack/plugins/cloud/manager.py:392
#, python-format
msgid "The asset \"%s\" already exists"
msgstr "資産 \"%s\" はすでに存在します"
-#: xpack/plugins/cloud/manager.py:395
+#: xpack/plugins/cloud/manager.py:394
#, python-format
msgid "Update asset \"%s\""
msgstr "資産の更新 \"%s\""
-#: xpack/plugins/cloud/manager.py:398
+#: xpack/plugins/cloud/manager.py:397
#, python-format
msgid "Asset \"%s\" has been updated"
msgstr "資産 \"%s\" が更新されました"
-#: xpack/plugins/cloud/manager.py:408
+#: xpack/plugins/cloud/manager.py:407
#, python-format
msgid "Prepare to create asset \"%s\""
msgstr "資産 \"%s\" の作成準備"
-#: xpack/plugins/cloud/manager.py:429
+#: xpack/plugins/cloud/manager.py:428
#, python-format
msgid "Set nodes \"%s\""
msgstr "ノード \"%s\" の設定"
-#: xpack/plugins/cloud/manager.py:455
+#: xpack/plugins/cloud/manager.py:454
#, python-format
msgid "Set accounts \"%s\""
msgstr "アカウント \"%s\" の設定"
-#: xpack/plugins/cloud/manager.py:471
+#: xpack/plugins/cloud/manager.py:470
#, python-format
msgid "Set protocols \"%s\""
msgstr "プロトコル \"%s\" の設定"
-#: xpack/plugins/cloud/manager.py:485 xpack/plugins/cloud/tasks.py:30
+#: xpack/plugins/cloud/manager.py:484 xpack/plugins/cloud/tasks.py:31
msgid "Run sync instance task"
msgstr "同期インスタンス タスクを実行する"
@@ -9877,10 +10610,32 @@ msgstr "実行回数"
msgid "Instance count"
msgstr "インスタンス数"
-#: xpack/plugins/cloud/tasks.py:44
+#: xpack/plugins/cloud/tasks.py:33
+msgid ""
+"\n"
+" Execute this task when manually or scheduled cloud synchronization "
+"tasks are performed\n"
+" "
+msgstr "\n"
+"手動で、定時にクラウド同期タスクを実行する時にこのタスクを実行します"
+
+#: xpack/plugins/cloud/tasks.py:52
msgid "Period clean sync instance task execution"
msgstr "同期インスタンス タスクの実行記録を定期的にクリアする"
+#: xpack/plugins/cloud/tasks.py:54
+msgid ""
+"\n"
+" Every day, according to the configuration in \"System Settings - "
+"Tasks - Regular \n"
+" clean-up - Cloud sync task history retention days\" the system will "
+"clean up the execution \n"
+" records generated by cloud synchronization\n"
+" "
+msgstr "\n"
+"毎日、システム設定-タスクリスト-定期的なクリーニング設定-クラウド同期記録設定"
+"に基づき、クラウド同期によって生成された実行記録をクリーニングします。"
+
#: xpack/plugins/interface/api.py:52
msgid "Restore default successfully."
msgstr "デフォルトの復元に成功しました。"
diff --git a/apps/i18n/core/zh/LC_MESSAGES/django.po b/apps/i18n/core/zh/LC_MESSAGES/django.po
index bca835822..aba5997d7 100644
--- a/apps/i18n/core/zh/LC_MESSAGES/django.po
+++ b/apps/i18n/core/zh/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-15 14:04+0800\n"
+"POT-Creation-Date: 2024-09-19 17:03+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler \n"
"Language-Team: JumpServer team\n"
@@ -108,11 +108,11 @@ msgstr "账号备份计划正在执行"
msgid "Plan execution end"
msgstr "计划执行结束"
-#: accounts/automations/change_secret/manager.py:100
+#: accounts/automations/change_secret/manager.py:97
msgid "No pending accounts found"
msgstr "未找到待处理帐户"
-#: accounts/automations/change_secret/manager.py:227
+#: accounts/automations/change_secret/manager.py:225
#, python-format
msgid "Success: %s, Failed: %s, Total: %s"
msgstr "成功: %s, 失败: %s, 总数: %s"
@@ -123,10 +123,11 @@ msgstr "成功: %s, 失败: %s, 总数: %s"
#: authentication/confirm/password.py:24 authentication/confirm/password.py:26
#: authentication/forms.py:28
#: authentication/templates/authentication/login.html:362
-#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:51
-#: settings/serializers/msg.py:37 settings/serializers/terminal.py:28
-#: terminal/serializers/storage.py:123 terminal/serializers/storage.py:142
-#: users/forms/profile.py:21 users/serializers/user.py:144
+#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap_ha.py:34 settings/serializers/msg.py:37
+#: settings/serializers/terminal.py:28 terminal/serializers/storage.py:123
+#: terminal/serializers/storage.py:142 users/forms/profile.py:21
+#: users/serializers/user.py:144
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -144,7 +145,7 @@ msgid "Access key"
msgstr "Access key"
#: accounts/const/account.py:9 authentication/backends/passkey/models.py:16
-#: authentication/models/sso_token.py:14 settings/serializers/feature.py:52
+#: authentication/models/sso_token.py:14 settings/serializers/feature.py:55
msgid "Token"
msgstr "令牌"
@@ -207,8 +208,8 @@ msgstr "更改密码"
msgid "Verify account"
msgstr "验证账号"
-#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:24
-#: accounts/tasks/remove_account.py:33
+#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:25
+#: accounts/tasks/remove_account.py:38
msgid "Remove account"
msgstr "移除账号"
@@ -310,11 +311,11 @@ msgid "Pending"
msgstr "待定的"
#: accounts/const/vault.py:8 assets/const/category.py:12
-#: assets/models/asset/database.py:9 assets/models/asset/database.py:24
+#: assets/models/asset/database.py:10 assets/models/asset/database.py:29
msgid "Database"
msgstr "数据库"
-#: accounts/const/vault.py:9 settings/serializers/feature.py:43
+#: accounts/const/vault.py:9 settings/serializers/feature.py:46
msgid "HCP Vault"
msgstr "HashiCorp Vault"
@@ -339,14 +340,14 @@ msgstr "用户 %s 查看/导出 了密码"
#: accounts/models/account.py:49
#: accounts/models/automations/gather_account.py:16
#: accounts/serializers/account/account.py:226
-#: accounts/serializers/account/account.py:271
+#: accounts/serializers/account/account.py:272
#: accounts/serializers/account/gathered_account.py:10
#: accounts/serializers/automations/change_secret.py:111
#: accounts/serializers/automations/change_secret.py:143
#: accounts/templates/accounts/asset_account_change_info.html:7
#: accounts/templates/accounts/change_secret_failed_info.html:11
-#: acls/serializers/base.py:123 assets/models/asset/common.py:95
-#: assets/models/asset/common.py:349 assets/models/cmd_filter.py:36
+#: acls/serializers/base.py:123 assets/models/asset/common.py:102
+#: assets/models/asset/common.py:362 assets/models/cmd_filter.py:36
#: audits/models.py:58 authentication/models/connection_token.py:36
#: perms/models/asset_permission.py:69 terminal/backends/command/models.py:17
#: terminal/models/session/session.py:32 terminal/notifications.py:155
@@ -359,13 +360,13 @@ msgstr "资产"
#: accounts/models/account.py:53 accounts/models/template.py:16
#: accounts/serializers/account/account.py:233
-#: accounts/serializers/account/account.py:281
-#: accounts/serializers/account/template.py:27
+#: accounts/serializers/account/account.py:282
+#: accounts/serializers/account/template.py:37
#: authentication/serializers/connect_token_secret.py:50
msgid "Su from"
msgstr "切换自"
-#: accounts/models/account.py:55 assets/const/protocol.py:189
+#: accounts/models/account.py:55 assets/const/protocol.py:195
#: settings/serializers/auth/cas.py:25 terminal/models/applet/applet.py:36
#: terminal/models/virtualapp/virtualapp.py:21
msgid "Version"
@@ -386,7 +387,7 @@ msgstr "来源 ID"
#: accounts/templates/accounts/change_secret_failed_info.html:12
#: acls/serializers/base.py:124
#: acls/templates/acls/asset_login_reminder.html:10
-#: assets/serializers/gateway.py:28 audits/models.py:59
+#: assets/serializers/gateway.py:33 audits/models.py:59
#: authentication/api/connection_token.py:411 ops/models/base.py:18
#: perms/models/asset_permission.py:75 settings/serializers/msg.py:33
#: terminal/backends/command/models.py:18 terminal/models/session/session.py:34
@@ -458,9 +459,9 @@ msgstr "账号备份计划"
#: accounts/models/automations/backup_account.py:120
#: assets/models/automations/base.py:115 audits/models.py:65
-#: ops/models/base.py:55 ops/models/celery.py:88 ops/models/job.py:242
+#: ops/models/base.py:55 ops/models/celery.py:89 ops/models/job.py:242
#: ops/templates/ops/celery_task_log.html:101
-#: perms/models/asset_permission.py:78
+#: perms/models/asset_permission.py:78 settings/serializers/feature.py:25
#: settings/templates/ldap/_msg_import_ldap_user.html:5
#: terminal/models/applet/host.py:141 terminal/models/session/session.py:45
#: tickets/models/ticket/apply_application.py:30
@@ -470,7 +471,7 @@ msgstr "开始日期"
#: accounts/models/automations/backup_account.py:123
#: authentication/templates/authentication/_msg_oauth_bind.html:11
-#: notifications/notifications.py:194
+#: notifications/notifications.py:199
#: settings/templates/ldap/_msg_import_ldap_user.html:3
msgid "Time"
msgstr "时间"
@@ -549,7 +550,8 @@ msgstr "SSH 密钥推送方式"
#: accounts/models/automations/gather_account.py:58
#: accounts/serializers/account/backup.py:40
#: accounts/serializers/automations/change_secret.py:58
-#: settings/serializers/auth/ldap.py:99 settings/serializers/msg.py:45
+#: settings/serializers/auth/ldap.py:100
+#: settings/serializers/auth/ldap_ha.py:82 settings/serializers/msg.py:45
msgid "Recipient"
msgstr "收件人"
@@ -571,7 +573,7 @@ msgstr "开始日期"
#: accounts/models/automations/change_secret.py:42
#: assets/models/automations/base.py:116 ops/models/base.py:56
-#: ops/models/celery.py:89 ops/models/job.py:243
+#: ops/models/celery.py:90 ops/models/job.py:243
#: terminal/models/applet/host.py:142
msgid "Date finished"
msgstr "结束日期"
@@ -583,7 +585,7 @@ msgstr "结束日期"
#: terminal/models/applet/applet.py:331 terminal/models/applet/host.py:140
#: terminal/models/component/status.py:30
#: terminal/models/virtualapp/virtualapp.py:99
-#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:136
+#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:148
#: terminal/serializers/virtualapp.py:35 tickets/models/ticket/general.py:284
#: tickets/serializers/super_ticket.py:13
#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:225
@@ -591,8 +593,8 @@ msgstr "结束日期"
msgid "Status"
msgstr "状态"
-#: accounts/models/automations/change_secret.py:47
-#: accounts/serializers/account/account.py:273
+#: accounts/models/automations/change_secret.py:46
+#: accounts/serializers/account/account.py:274
#: accounts/templates/accounts/change_secret_failed_info.html:13
#: assets/const/automation.py:8
#: authentication/templates/authentication/passkey.html:173
@@ -602,7 +604,7 @@ msgstr "状态"
msgid "Error"
msgstr "错误"
-#: accounts/models/automations/change_secret.py:51
+#: accounts/models/automations/change_secret.py:50
msgid "Change secret record"
msgstr "改密记录"
@@ -633,7 +635,7 @@ msgid "Address login"
msgstr "最后登录地址"
#: accounts/models/automations/gather_account.py:44
-#: accounts/tasks/gather_accounts.py:29
+#: accounts/tasks/gather_accounts.py:30
msgid "Gather asset accounts"
msgstr "收集账号"
@@ -654,7 +656,7 @@ msgstr "触发方式"
#: audits/models.py:92 audits/serializers.py:84
#: authentication/serializers/connect_token_secret.py:119
#: authentication/templates/authentication/_access_key_modal.html:34
-#: perms/serializers/permission.py:41 perms/serializers/permission.py:63
+#: perms/serializers/permission.py:52 perms/serializers/permission.py:74
#: tickets/serializers/ticket/ticket.py:21
msgid "Action"
msgstr "动作"
@@ -668,7 +670,7 @@ msgid "Verify asset account"
msgstr "账号验证"
#: accounts/models/base.py:37 accounts/models/base.py:67
-#: accounts/serializers/account/account.py:463
+#: accounts/serializers/account/account.py:464
#: accounts/serializers/account/base.py:17
#: accounts/serializers/automations/change_secret.py:47
#: authentication/serializers/connect_token_secret.py:42
@@ -690,28 +692,28 @@ msgstr "密文"
msgid "Secret strategy"
msgstr "密文策略"
-#: accounts/models/base.py:44 accounts/serializers/account/template.py:24
+#: accounts/models/base.py:44 accounts/serializers/account/template.py:34
#: accounts/serializers/automations/change_secret.py:46
msgid "Password rules"
msgstr "密码规则"
#: accounts/models/base.py:64 accounts/serializers/account/virtual.py:20
#: acls/models/base.py:35 acls/models/base.py:96 acls/models/command_acl.py:21
-#: acls/serializers/base.py:35 assets/models/asset/common.py:93
-#: assets/models/asset/common.py:159 assets/models/cmd_filter.py:21
+#: acls/serializers/base.py:35 assets/models/asset/common.py:100
+#: assets/models/asset/common.py:166 assets/models/cmd_filter.py:21
#: assets/models/domain.py:19 assets/models/label.py:18
#: assets/models/platform.py:15 assets/models/platform.py:94
-#: assets/serializers/asset/common.py:149 assets/serializers/platform.py:153
-#: assets/serializers/platform.py:273
+#: assets/serializers/asset/common.py:169 assets/serializers/platform.py:157
+#: assets/serializers/platform.py:277
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:113
#: authentication/serializers/connect_token_secret.py:169 labels/models.py:11
-#: ops/mixin.py:21 ops/models/adhoc.py:20 ops/models/celery.py:15
-#: ops/models/celery.py:80 ops/models/job.py:142 ops/models/playbook.py:28
+#: ops/mixin.py:28 ops/models/adhoc.py:19 ops/models/celery.py:15
+#: ops/models/celery.py:81 ops/models/job.py:142 ops/models/playbook.py:30
#: ops/serializers/job.py:18 orgs/models.py:82
#: perms/models/asset_permission.py:61 rbac/models/role.py:29
-#: rbac/serializers/role.py:28 settings/models.py:34 settings/models.py:183
+#: rbac/serializers/role.py:28 settings/models.py:35 settings/models.py:184
#: settings/serializers/msg.py:89 settings/serializers/terminal.py:9
#: terminal/models/applet/applet.py:34 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:109
@@ -832,7 +834,6 @@ msgstr ""
"码"
#: accounts/notifications.py:83
-#: accounts/templates/accounts/asset_account_change_info.html:3
msgid "Gather account change information"
msgstr "账号变更信息"
@@ -852,11 +853,16 @@ msgstr "参数"
msgid "Exist policy"
msgstr "账号存在策略"
+#: accounts/serializers/account/account.py:181
+#: accounts/serializers/account/account.py:340
+msgid "Account already exists"
+msgstr "账号已存在"
+
#: accounts/serializers/account/account.py:206 assets/models/label.py:21
-#: assets/models/platform.py:95 assets/serializers/asset/common.py:125
-#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:168
-#: assets/serializers/platform.py:274 perms/serializers/user_permission.py:26
-#: settings/models.py:36 tickets/models/ticket/apply_application.py:13
+#: assets/models/platform.py:95 assets/serializers/asset/common.py:145
+#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:172
+#: assets/serializers/platform.py:278 perms/serializers/user_permission.py:26
+#: settings/models.py:37 tickets/models/ticket/apply_application.py:13
#: users/models/preference.py:12
msgid "Category"
msgstr "类别"
@@ -865,13 +871,13 @@ msgstr "类别"
#: accounts/serializers/automations/base.py:55 acls/models/command_acl.py:24
#: acls/serializers/command_acl.py:19 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:96
-#: assets/serializers/asset/common.py:126 assets/serializers/platform.py:155
-#: assets/serializers/platform.py:167 audits/serializers.py:53
+#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:159
+#: assets/serializers/platform.py:171 audits/serializers.py:53
#: audits/serializers.py:170
#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:150
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
#: terminal/models/component/storage.py:58
-#: terminal/models/component/storage.py:154 terminal/serializers/applet.py:29
+#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:29
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
@@ -884,62 +890,58 @@ msgstr "类型"
msgid "Asset not found"
msgstr "资产不存在"
-#: accounts/serializers/account/account.py:262
+#: accounts/serializers/account/account.py:263
msgid "Has secret"
msgstr "已托管密码"
-#: accounts/serializers/account/account.py:272 ops/models/celery.py:83
+#: accounts/serializers/account/account.py:273 ops/models/celery.py:84
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:49
#: tickets/models/ticket/general.py:280 tickets/serializers/super_ticket.py:14
msgid "State"
msgstr "状态"
-#: accounts/serializers/account/account.py:274
+#: accounts/serializers/account/account.py:275
msgid "Changed"
msgstr "已修改"
-#: accounts/serializers/account/account.py:284
+#: accounts/serializers/account/account.py:285
#: accounts/serializers/automations/base.py:22 acls/models/base.py:97
#: acls/templates/acls/asset_login_reminder.html:9
#: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:34
-#: assets/serializers/platform.py:176 assets/serializers/platform.py:208
+#: assets/serializers/platform.py:180 assets/serializers/platform.py:212
#: authentication/api/connection_token.py:410 ops/models/base.py:17
#: ops/models/job.py:152 ops/serializers/job.py:19
-#: perms/serializers/permission.py:35
+#: perms/serializers/permission.py:46
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
#: xpack/plugins/cloud/manager.py:83
msgid "Assets"
msgstr "资产"
-#: accounts/serializers/account/account.py:339
-msgid "Account already exists"
-msgstr "账号已存在"
-
-#: accounts/serializers/account/account.py:389
+#: accounts/serializers/account/account.py:390
#, python-format
msgid "Asset does not support this secret type: %s"
msgstr "资产不支持账号类型: %s"
-#: accounts/serializers/account/account.py:421
+#: accounts/serializers/account/account.py:422
msgid "Account has exist"
msgstr "账号已存在"
-#: accounts/serializers/account/account.py:458
+#: accounts/serializers/account/account.py:459
#: accounts/serializers/account/base.py:93
-#: accounts/serializers/account/template.py:72
-#: assets/serializers/asset/common.py:387
+#: accounts/serializers/account/template.py:83
+#: assets/serializers/asset/common.py:407
msgid "Spec info"
msgstr "特殊信息"
-#: accounts/serializers/account/account.py:464
+#: accounts/serializers/account/account.py:465
#: authentication/serializers/connect_token_secret.py:159
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
msgstr "ID"
-#: accounts/serializers/account/account.py:474 acls/serializers/base.py:116
+#: accounts/serializers/account/account.py:475 acls/serializers/base.py:116
#: acls/templates/acls/asset_login_reminder.html:8
#: acls/templates/acls/user_login_reminder.html:8
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:54
@@ -948,7 +950,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:63
-#: rbac/builtin.py:124 rbac/models/rolebinding.py:49
+#: rbac/builtin.py:125 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:34
#: terminal/notifications.py:156 terminal/notifications.py:205
@@ -961,7 +963,7 @@ msgstr "ID"
msgid "User"
msgstr "用户"
-#: accounts/serializers/account/account.py:475
+#: accounts/serializers/account/account.py:476
#: authentication/templates/authentication/_access_key_modal.html:33
#: terminal/notifications.py:158 terminal/notifications.py:207
msgid "Date"
@@ -1023,24 +1025,44 @@ msgstr "特殊字符"
msgid "Exclude symbol"
msgstr "排除字符"
-#: accounts/serializers/account/template.py:39
+#: accounts/serializers/account/template.py:24
+msgid ""
+"length is the length of the password, and the range is 8 to 30.\n"
+"lowercase indicates whether the password contains lowercase letters, \n"
+"uppercase indicates whether it contains uppercase letters,\n"
+"digit indicates whether it contains numbers, and symbol indicates whether it "
+"contains special symbols.\n"
+"exclude_symbols is used to exclude specific symbols. You can fill in the "
+"symbol characters to be excluded (up to 16). \n"
+"If you do not need to exclude symbols, you can leave it blank.\n"
+"default: {\"length\": 16, \"lowercase\": true, \"uppercase\": true, "
+"\"digit\": true, \"symbol\": true, \"exclude_symbols\": \"\"}"
+msgstr ""
+"length 是密码的长度,填写范围为 8 到 30。lowercase 表示密码中是否包含小写字"
+"母,uppercase 表示是否包含大写字母,digit 表示是否包含数字,symbol 表示是否包"
+"含特殊符号。exclude_symbols 用于排除特定符号,您可以填写要排除的符号字符(最"
+"多 16 个),如果无需排除符号,可以留空。默认: {\"length\": 16, "
+"\"lowercase\": true, \"uppercase\": true, \"digit\": true, \"symbol\": true, "
+"\"exclude_symbols\": \"\"}"
+
+#: accounts/serializers/account/template.py:49
msgid "Secret generation strategy for account creation"
msgstr "密码生成策略,用于账号创建时,设置密码"
-#: accounts/serializers/account/template.py:40
+#: accounts/serializers/account/template.py:50
msgid "Whether to automatically push the account to the asset"
msgstr "是否自动推送账号到资产"
-#: accounts/serializers/account/template.py:43
+#: accounts/serializers/account/template.py:53
msgid ""
"Associated platform, you can configure push parameters. If not associated, "
"default parameters will be used"
msgstr "关联平台,可配置推送参数,如果不关联,将使用默认参数"
#: accounts/serializers/account/virtual.py:19 assets/models/cmd_filter.py:40
-#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:26
-#: ops/models/job.py:158 ops/models/playbook.py:31 rbac/models/role.py:37
-#: settings/models.py:39 terminal/models/applet/applet.py:46
+#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:25
+#: ops/models/job.py:158 ops/models/playbook.py:33 rbac/models/role.py:37
+#: settings/models.py:40 terminal/models/applet/applet.py:46
#: terminal/models/applet/applet.py:332 terminal/models/applet/host.py:143
#: terminal/models/component/endpoint.py:25
#: terminal/models/component/endpoint.py:119
@@ -1062,8 +1084,8 @@ msgstr ""
"CACHE_LOGIN_PASSWORD_ENABLED=true,重启服务才能开启"
#: accounts/serializers/automations/base.py:23
-#: assets/models/asset/common.py:164 assets/serializers/asset/common.py:152
-#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:36
+#: assets/models/asset/common.py:176 assets/serializers/asset/common.py:172
+#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:47
msgid "Nodes"
msgstr "节点"
@@ -1123,31 +1145,110 @@ msgstr "添加账号: %s"
msgid "Delete account: %s"
msgstr "删除账号: %s"
-#: accounts/tasks/automation.py:25
+#: accounts/tasks/automation.py:32
msgid "Account execute automation"
msgstr "账号执行自动化"
-#: accounts/tasks/automation.py:51 accounts/tasks/automation.py:56
+#: accounts/tasks/automation.py:35
+msgid ""
+"Unified execution entry for account automation tasks: when the system "
+"performs tasks \n"
+" such as account push, password change, account verification, account "
+"collection, \n"
+" and gateway account verification, all tasks are executed through "
+"this unified entry"
+msgstr ""
+"账号自动化任务统一执行入口,当系统执行账号推送,更改密码,验证账号,收集账"
+"号,验证网关账号任务时,统一通过当前任务执行"
+
+#: accounts/tasks/automation.py:64 accounts/tasks/automation.py:72
msgid "Execute automation record"
msgstr "自动化执行记录"
-#: accounts/tasks/backup_account.py:25
+#: accounts/tasks/automation.py:67
+msgid "When manually executing password change records, this task is used"
+msgstr "当手动执行改密记录时,通过该任务执行"
+
+#: accounts/tasks/automation.py:96
+msgid "Clean change secret and push record period"
+msgstr "周期清理改密记录和推送记录"
+
+#: accounts/tasks/automation.py:98
+msgid ""
+"The system will periodically clean up unnecessary password change and push "
+"records, \n"
+" including their associated change tasks, execution logs, assets, and "
+"accounts. When any \n"
+" of these associated items are deleted, the corresponding password "
+"change and push records \n"
+" become invalid. Therefore, to maintain a clean and efficient "
+"database, the system will \n"
+" clean up expired records at 2 a.m daily, based on the interval "
+"specified by \n"
+" PERM_EXPIRED_CHECK_PERIODIC in the config.txt configuration file. "
+"This periodic cleanup \n"
+" mechanism helps free up storage space and enhances the security and "
+"overall performance \n"
+" of data management"
+msgstr ""
+"系统会定期清理不再需要的改密记录和推送记录,包括那些关联的改密任务、执行记"
+"录、资产和账号。当这些关联项中的任意一个被删除时,对应的改密和推送记录将变为"
+"无效。因此,为了保持数据库的整洁和高效运行,根据系统配置文件 config.txt 中 "
+"PERM_EXPIRED_CHECK_PERIODIC 的时间间隔对于超出时间的于每天凌晨2点进行清理。这"
+"种定期清理机制不仅有助于释放存储空间,还能提高数据管理的安全和整体性能"
+
+#: accounts/tasks/backup_account.py:26
msgid "Execute account backup plan"
msgstr "执行账号备份计划"
-#: accounts/tasks/gather_accounts.py:34
+#: accounts/tasks/backup_account.py:29
+msgid "When performing scheduled or manual account backups, this task is used"
+msgstr "定时或手动执行账号备份时,通过该任务执行"
+
+#: accounts/tasks/gather_accounts.py:32 assets/tasks/automation.py:27
+#: orgs/tasks.py:11 terminal/tasks.py:33
+msgid "Unused"
+msgstr "未使用"
+
+#: accounts/tasks/gather_accounts.py:36
msgid "Gather assets accounts"
msgstr "收集资产上的账号"
-#: accounts/tasks/push_account.py:15 accounts/tasks/push_account.py:23
+#: accounts/tasks/push_account.py:16 accounts/tasks/push_account.py:27
msgid "Push accounts to assets"
msgstr "推送账号到资产"
-#: accounts/tasks/remove_account.py:44
+#: accounts/tasks/push_account.py:19
+msgid ""
+"When creating or modifying an account requires account push, this task is "
+"executed"
+msgstr "当创建账号,修改账号时,需要账号推送时执行该任务"
+
+#: accounts/tasks/remove_account.py:28
+msgid ""
+"When clicking \"Sync deletion\" in 'Console - Gather Account - Gathered "
+"accounts' this \n"
+" task will be executed"
+msgstr "当在控制台-自动化-账号收集-收集的账号-点击同步删除会执行该任务"
+
+#: accounts/tasks/remove_account.py:50
msgid "Clean historical accounts"
msgstr "清理历史账号"
-#: accounts/tasks/remove_account.py:76
+#: accounts/tasks/remove_account.py:52
+msgid ""
+"Each time an asset account is updated, a historical account is generated, so "
+"it is \n"
+" necessary to clean up the asset account history. The system will "
+"clean up excess account \n"
+" records at 2 a.m. daily based on the configuration in the \"System "
+"settings - Features - \n"
+" Account storage - Record limit"
+msgstr ""
+"由于每次更新资产账号,都会生成历史账号,所以需要清理资产账号的历史。系统会根"
+"据账号存储-记录限制的配置,每天凌晨2点对于超出的数量的账号记录进行清理"
+
+#: accounts/tasks/remove_account.py:89
msgid "Remove historical accounts that are out of range."
msgstr "删除超出范围的历史帐户。"
@@ -1155,23 +1256,42 @@ msgstr "删除超出范围的历史帐户。"
msgid "Template sync info to related accounts"
msgstr "同步信息到关联的账号"
-#: accounts/tasks/vault.py:31
+#: accounts/tasks/template.py:14
+msgid ""
+"When clicking 'Sync new secret to accounts' in 'Console - Account - "
+"Templates - \n"
+" Accounts' this task will be executed"
+msgstr "当在控制台-账号模板-账号-同步更新账号信息点击同步时,执行该任务"
+
+#: accounts/tasks/vault.py:32
msgid "Sync secret to vault"
msgstr "同步密文到 vault"
+#: accounts/tasks/vault.py:34
+msgid ""
+"When clicking 'Sync' in 'System Settings - Features - Account Storage' this "
+"task will be executed"
+msgstr "在系统设置-功能设置-账号存储点击同步时,执行该任务"
+
#: accounts/tasks/verify_account.py:49
msgid "Verify asset account availability"
msgstr "验证资产账号可用性"
-#: accounts/tasks/verify_account.py:55
+#: accounts/tasks/verify_account.py:52
+msgid ""
+"When clicking 'Test' in 'Console - Asset details - Accounts' this task will "
+"be executed"
+msgstr "当在控制台-资产详情-账号点击测试执行该任务"
+
+#: accounts/tasks/verify_account.py:58
msgid "Verify accounts connectivity"
msgstr "测试账号可连接性"
-#: accounts/templates/accounts/asset_account_change_info.html:8
+#: accounts/templates/accounts/asset_account_change_info.html:10
msgid "Added account"
msgstr "新增账号"
-#: accounts/templates/accounts/asset_account_change_info.html:9
+#: accounts/templates/accounts/asset_account_change_info.html:13
msgid "Deleted account"
msgstr "删除账号"
@@ -1229,6 +1349,10 @@ msgstr "告警"
msgid "Notify"
msgstr "通知"
+#: acls/const.py:11
+msgid "Notify and warn"
+msgstr "提示并告警"
+
#: acls/models/base.py:37 assets/models/cmd_filter.py:76
#: terminal/models/component/endpoint.py:112 xpack/plugins/cloud/models.py:314
msgid "Priority"
@@ -1244,7 +1368,7 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)"
msgid "Reviewers"
msgstr "审批人"
-#: acls/models/base.py:43 assets/models/asset/common.py:165
+#: acls/models/base.py:43 assets/models/asset/common.py:178
#: authentication/models/access_key.py:25
#: authentication/models/connection_token.py:53
#: authentication/models/ssh_key.py:13
@@ -1256,15 +1380,15 @@ msgstr "审批人"
msgid "Active"
msgstr "激活中"
-#: acls/models/base.py:81 perms/serializers/permission.py:31
+#: acls/models/base.py:81 perms/serializers/permission.py:42
#: tickets/models/flow.py:23 users/models/preference.py:16
#: users/serializers/group.py:21 users/serializers/user.py:424
msgid "Users"
msgstr "用户"
#: acls/models/base.py:98 assets/models/automations/base.py:17
-#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:128
-#: assets/serializers/asset/common.py:386 perms/serializers/permission.py:44
+#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:148
+#: assets/serializers/asset/common.py:406 perms/serializers/permission.py:55
#: perms/serializers/user_permission.py:75 rbac/tree.py:35
msgid "Accounts"
msgstr "账号"
@@ -1284,7 +1408,7 @@ msgid "Regex"
msgstr "正则表达式"
#: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79
-#: settings/models.py:184 settings/serializers/feature.py:19
+#: settings/models.py:185 settings/serializers/feature.py:20
#: settings/serializers/msg.py:78 xpack/plugins/license/models.py:30
msgid "Content"
msgstr "内容"
@@ -1400,7 +1524,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
-#: xpack/plugins/cloud/models.py:390
+#: common/drf/renders/base.py:150 xpack/plugins/cloud/models.py:390
msgid "IP"
msgstr "IP"
@@ -1462,11 +1586,11 @@ msgstr "登录城市"
msgid "User agent"
msgstr "用户代理"
-#: assets/api/asset/asset.py:181
+#: assets/api/asset/asset.py:190
msgid "Cannot create asset directly, you should create a host or other"
msgstr "不能直接创建资产, 你应该创建主机或其他资产"
-#: assets/api/asset/asset.py:185
+#: assets/api/asset/asset.py:194
msgid "The number of assets exceeds the limit of 5000"
msgstr "资产数量超过了 5000 的限制"
@@ -1494,32 +1618,32 @@ msgstr "同级别节点名字不能重复"
msgid "App Assets"
msgstr "资产管理"
-#: assets/automations/base/manager.py:187
+#: assets/automations/base/manager.py:188
msgid "{} disabled"
msgstr "{} 已禁用"
-#: assets/automations/base/manager.py:250
+#: assets/automations/base/manager.py:251
msgid " - Platform {} ansible disabled"
msgstr " - 平台 {} Ansible 已禁用, 无法执行任务"
-#: assets/automations/base/manager.py:323
+#: assets/automations/base/manager.py:324
msgid ">>> Task preparation phase"
msgstr ">>> 任务准备阶段"
-#: assets/automations/base/manager.py:326
+#: assets/automations/base/manager.py:327
#, python-brace-format
msgid ">>> Executing tasks in batches, total {runner_count}"
msgstr ">>> 分次执行任务,总共 {runner_count}"
-#: assets/automations/base/manager.py:328
+#: assets/automations/base/manager.py:329
msgid ">>> Start executing tasks"
msgstr ">>> 开始执行任务"
-#: assets/automations/base/manager.py:330
+#: assets/automations/base/manager.py:331
msgid ">>> No tasks need to be executed"
msgstr ">>> 没有需要执行的任务"
-#: assets/automations/base/manager.py:335
+#: assets/automations/base/manager.py:336
#, python-brace-format
msgid ">>> Begin executing batch {index} of tasks"
msgstr ">>> 开始执行第 {index} 批任务"
@@ -1581,14 +1705,14 @@ msgstr "禁用"
msgid "Basic"
msgstr "基本"
-#: assets/const/base.py:34 assets/const/protocol.py:292
+#: assets/const/base.py:34 assets/const/protocol.py:298
#: assets/models/asset/web.py:13
msgid "Script"
msgstr "脚本"
#: assets/const/category.py:10 assets/models/asset/host.py:8
#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:76
-#: settings/serializers/feature.py:49 settings/serializers/msg.py:30
+#: settings/serializers/feature.py:52 settings/serializers/msg.py:30
#: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17
#: xpack/plugins/cloud/manager.py:83
#: xpack/plugins/cloud/serializers/account_attrs.py:72
@@ -1653,17 +1777,23 @@ msgstr "其它"
#: assets/const/protocol.py:46
msgid "Old SSH version"
-msgstr "Old SSH version"
+msgstr "旧 SSH 版本"
#: assets/const/protocol.py:47
msgid "Old SSH version like openssh 5.x or 6.x"
msgstr "旧的 SSH 版本,例如 openssh 5.x 或 6.x"
-#: assets/const/protocol.py:58
+#: assets/const/protocol.py:53
+msgid "Netcat help text"
+msgstr ""
+"使用 netcat (nc) 作为代理工具,将连接从代理服务器转发到目标主机。适用于不支"
+"持 SSH 原生代理选项 (-W) 的环境,或需要更多灵活性和超时控制的场景。"
+
+#: assets/const/protocol.py:64
msgid "SFTP root"
msgstr "SFTP 根路径"
-#: assets/const/protocol.py:60
+#: assets/const/protocol.py:66
#, python-brace-format
msgid ""
"SFTP root directory, Support variable:
- ${ACCOUNT} The connected "
@@ -1673,24 +1803,24 @@ msgstr ""
"SFTP根目录,支持变量:
-${ACCOUNT}已连接帐户用户名
-${HOME}连接帐户的主"
"目录
-${USER}用户的用户名"
-#: assets/const/protocol.py:75
+#: assets/const/protocol.py:81
msgid "Console"
msgstr "控制台"
-#: assets/const/protocol.py:76
+#: assets/const/protocol.py:82
msgid "Connect to console session"
msgstr "连接到控制台会话"
-#: assets/const/protocol.py:80
+#: assets/const/protocol.py:86
msgid "Any"
msgstr "任意"
-#: assets/const/protocol.py:82 rbac/tree.py:62
+#: assets/const/protocol.py:88 rbac/tree.py:62
#: settings/serializers/security.py:232
msgid "Security"
msgstr "安全"
-#: assets/const/protocol.py:83
+#: assets/const/protocol.py:89
msgid ""
"Security layer to use for the connection:
Any
Automatically select the "
"security mode based on the security protocols supported by both the client "
@@ -1705,101 +1835,101 @@ msgstr ""
"Windows 登录屏幕的情况
TLS
通过 TLS 实现的 RDP 认证和加密
NLA
该"
"模式使用 TLS 加密,并要求提前提供用户名和密码"
-#: assets/const/protocol.py:100
+#: assets/const/protocol.py:106
msgid "AD domain"
msgstr "AD 网域"
-#: assets/const/protocol.py:115
+#: assets/const/protocol.py:121
msgid "Username prompt"
msgstr "用户名提示"
-#: assets/const/protocol.py:116
+#: assets/const/protocol.py:122
msgid "We will send username when we see this prompt"
msgstr "当我们看到这个提示时,我们将发送用户名"
-#: assets/const/protocol.py:121
+#: assets/const/protocol.py:127
msgid "Password prompt"
msgstr "密码提示"
-#: assets/const/protocol.py:122
+#: assets/const/protocol.py:128
msgid "We will send password when we see this prompt"
msgstr "当我们看到这个提示时,我们将发送密码"
-#: assets/const/protocol.py:127
+#: assets/const/protocol.py:133
msgid "Success prompt"
msgstr "成功提示"
-#: assets/const/protocol.py:128
+#: assets/const/protocol.py:134
msgid "We will consider login success when we see this prompt"
msgstr "当我们看到这个提示时,我们将认为登录成功"
-#: assets/const/protocol.py:139 assets/models/asset/database.py:10
+#: assets/const/protocol.py:145 assets/models/asset/database.py:11
#: settings/serializers/msg.py:49
msgid "Use SSL"
msgstr "使用 SSL"
-#: assets/const/protocol.py:174
+#: assets/const/protocol.py:180
msgid "SYSDBA"
msgstr "SYSDBA"
-#: assets/const/protocol.py:175
+#: assets/const/protocol.py:181
msgid "Connect as SYSDBA"
msgstr "以 SYSDBA 角色连接"
-#: assets/const/protocol.py:190
+#: assets/const/protocol.py:196
msgid ""
"SQL Server version, Different versions have different connection drivers"
msgstr "SQL Server 版本,不同版本有不同的连接驱动"
-#: assets/const/protocol.py:220
+#: assets/const/protocol.py:226
msgid "Auth source"
msgstr "认证数据库"
-#: assets/const/protocol.py:221
+#: assets/const/protocol.py:227
msgid "The database to authenticate against"
msgstr "要进行身份验证的数据库"
-#: assets/const/protocol.py:226 authentication/models/connection_token.py:43
+#: assets/const/protocol.py:232 authentication/models/connection_token.py:43
msgid "Connect options"
msgstr "连接项"
-#: assets/const/protocol.py:227
+#: assets/const/protocol.py:233
msgid "The connection specific options eg. retryWrites=false&retryReads=false"
msgstr "连接特定选项,例如 retryWrites=false&retryReads=false"
-#: assets/const/protocol.py:239
+#: assets/const/protocol.py:245
msgid "Auth username"
msgstr "使用用户名认证"
-#: assets/const/protocol.py:262
+#: assets/const/protocol.py:268
msgid "Safe mode"
msgstr "安全模式"
-#: assets/const/protocol.py:264
+#: assets/const/protocol.py:270
msgid ""
"When safe mode is enabled, some operations will be disabled, such as: New "
"tab, right click, visit other website, etc."
msgstr ""
"当安全模式启用时,一些操作将被禁用,例如:新建标签页、右键、访问其它网站 等"
-#: assets/const/protocol.py:269 assets/models/asset/web.py:9
+#: assets/const/protocol.py:275 assets/models/asset/web.py:9
#: assets/serializers/asset/info/spec.py:16
msgid "Autofill"
msgstr "自动代填"
-#: assets/const/protocol.py:277 assets/models/asset/web.py:10
+#: assets/const/protocol.py:283 assets/models/asset/web.py:10
msgid "Username selector"
msgstr "用户名选择器"
-#: assets/const/protocol.py:282 assets/models/asset/web.py:11
+#: assets/const/protocol.py:288 assets/models/asset/web.py:11
msgid "Password selector"
msgstr "密码选择器"
-#: assets/const/protocol.py:287 assets/models/asset/web.py:12
+#: assets/const/protocol.py:293 assets/models/asset/web.py:12
msgid "Submit selector"
msgstr "确认按钮选择器"
-#: assets/const/protocol.py:310
+#: assets/const/protocol.py:316
msgid "API mode"
msgstr "API 模式"
@@ -1819,51 +1949,51 @@ msgstr "暂时不支持此功能"
msgid "Cloud"
msgstr "云服务"
-#: assets/models/asset/common.py:94 assets/models/platform.py:16
+#: assets/models/asset/common.py:101 assets/models/platform.py:16
#: settings/serializers/auth/radius.py:18 settings/serializers/auth/sms.py:77
#: settings/serializers/msg.py:31 terminal/serializers/storage.py:133
#: xpack/plugins/cloud/serializers/account_attrs.py:73
msgid "Port"
msgstr "端口"
-#: assets/models/asset/common.py:160 assets/serializers/asset/common.py:150
+#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:170
#: settings/serializers/terminal.py:10
msgid "Address"
msgstr "地址"
-#: assets/models/asset/common.py:161 assets/models/platform.py:149
+#: assets/models/asset/common.py:169 assets/models/platform.py:149
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:118
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:385
msgid "Platform"
msgstr "平台"
-#: assets/models/asset/common.py:163 assets/models/domain.py:22
+#: assets/models/asset/common.py:173 assets/models/domain.py:22
msgid "Zone"
msgstr "网域"
-#: assets/models/asset/common.py:166 assets/serializers/asset/common.py:388
+#: assets/models/asset/common.py:179 assets/serializers/asset/common.py:408
#: assets/serializers/asset/host.py:11
msgid "Gathered info"
msgstr "收集资产硬件信息"
-#: assets/models/asset/common.py:167 assets/serializers/asset/custom.py:14
+#: assets/models/asset/common.py:180 assets/serializers/asset/custom.py:14
msgid "Custom info"
msgstr "自定义属性"
-#: assets/models/asset/common.py:352
+#: assets/models/asset/common.py:365
msgid "Can refresh asset hardware info"
msgstr "可以更新资产硬件信息"
-#: assets/models/asset/common.py:353
+#: assets/models/asset/common.py:366
msgid "Can test asset connectivity"
msgstr "可以测试资产连接性"
-#: assets/models/asset/common.py:354
+#: assets/models/asset/common.py:367
msgid "Can match asset"
msgstr "可以匹配资产"
-#: assets/models/asset/common.py:355
+#: assets/models/asset/common.py:368
msgid "Can change asset nodes"
msgstr "可以修改资产节点"
@@ -1871,23 +2001,27 @@ msgstr "可以修改资产节点"
msgid "Custom asset"
msgstr "自定义资产"
-#: assets/models/asset/database.py:11
+#: assets/models/asset/database.py:12
msgid "CA cert"
msgstr "CA 证书"
-#: assets/models/asset/database.py:12
+#: assets/models/asset/database.py:13
msgid "Client cert"
msgstr "客户端证书"
-#: assets/models/asset/database.py:13
+#: assets/models/asset/database.py:14
msgid "Client key"
msgstr "客户端密钥"
-#: assets/models/asset/database.py:14
+#: assets/models/asset/database.py:15
msgid "Allow invalid cert"
msgstr "忽略证书校验"
-#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:89
+#: assets/models/asset/database.py:18
+msgid "Postgresql SSL mode"
+msgstr "PostgreSQL SSL 模式"
+
+#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:92
msgid "Proxy"
msgstr "代理"
@@ -1991,14 +2125,14 @@ msgstr "系统"
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:29
#: authentication/serializers/connect_token_secret.py:125
-#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:35
+#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:36
#: users/models/preference.py:13
msgid "Value"
msgstr "值"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
-#: assets/serializers/platform.py:154
+#: assets/serializers/platform.py:158
#: authentication/serializers/connect_token_secret.py:124
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90
@@ -2049,7 +2183,7 @@ msgstr "主要的"
msgid "Required"
msgstr "必须的"
-#: assets/models/platform.py:19 assets/serializers/platform.py:156
+#: assets/models/platform.py:19 assets/serializers/platform.py:160
#: terminal/models/component/storage.py:28
#: xpack/plugins/cloud/providers/nutanix.py:30
msgid "Default"
@@ -2066,7 +2200,7 @@ msgid "Setting"
msgstr "设置"
#: assets/models/platform.py:38 audits/const.py:59
-#: authentication/backends/passkey/models.py:11 settings/models.py:38
+#: authentication/backends/passkey/models.py:11 settings/models.py:39
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:33
msgid "Enabled"
msgstr "启用"
@@ -2157,23 +2291,23 @@ msgstr "元数据"
msgid "Internal"
msgstr "内置"
-#: assets/models/platform.py:102 assets/serializers/platform.py:166
+#: assets/models/platform.py:102 assets/serializers/platform.py:170
msgid "Charset"
msgstr "编码"
-#: assets/models/platform.py:104 assets/serializers/platform.py:204
+#: assets/models/platform.py:104 assets/serializers/platform.py:208
msgid "Gateway enabled"
msgstr "启用网域"
-#: assets/models/platform.py:106 assets/serializers/platform.py:197
+#: assets/models/platform.py:106 assets/serializers/platform.py:201
msgid "Su enabled"
msgstr "启用账号切换"
-#: assets/models/platform.py:107 assets/serializers/platform.py:172
+#: assets/models/platform.py:107 assets/serializers/platform.py:176
msgid "Su method"
msgstr "账号切换方式"
-#: assets/models/platform.py:108 assets/serializers/platform.py:175
+#: assets/models/platform.py:108 assets/serializers/platform.py:179
msgid "Custom fields"
msgstr "自定义属性"
@@ -2188,38 +2322,60 @@ msgid ""
"type"
msgstr "资产中批量更新平台,不符合平台类型跳过的资产"
-#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:169
+#: assets/serializers/asset/common.py:36 assets/serializers/platform.py:152
+msgid "Protocols, format is [\"protocol/port\"]"
+msgstr "协议,格式为 [\"协议/端口\"]"
+
+#: assets/serializers/asset/common.py:38
+msgid "Protocol, format is name/port"
+msgstr "协议,格式为 名称/端口"
+
+#: assets/serializers/asset/common.py:107
+msgid ""
+"Accounts, format [{\"name\": \"x\", \"username\": \"x\", \"secret\": \"x\", "
+"\"secret_type\": \"password\"}]"
+msgstr ""
+"账号,格式为 [{\"name\": \"x\", \"username\": \"x\", \"secret\": \"x\", "
+"\"secret_type\": \"password\"}]"
+
+#: assets/serializers/asset/common.py:135
+msgid ""
+"Node path, format [\"/org_name/node_name\"], if node not exist, will create "
+"it"
+msgstr "节点路径,格式为 [\"/组织/节点名\"], 如果节点不存在,将创建它"
+
+#: assets/serializers/asset/common.py:147 assets/serializers/platform.py:173
#: authentication/serializers/connect_token_secret.py:30
#: authentication/serializers/connect_token_secret.py:75
-#: perms/models/asset_permission.py:76 perms/serializers/permission.py:45
+#: perms/models/asset_permission.py:76 perms/serializers/permission.py:56
#: perms/serializers/user_permission.py:74 xpack/plugins/cloud/models.py:388
#: xpack/plugins/cloud/serializers/task.py:35
msgid "Protocols"
msgstr "协议组"
-#: assets/serializers/asset/common.py:129
-#: assets/serializers/asset/common.py:151
+#: assets/serializers/asset/common.py:149
+#: assets/serializers/asset/common.py:171
msgid "Node path"
msgstr "节点路径"
-#: assets/serializers/asset/common.py:148
-#: assets/serializers/asset/common.py:389
+#: assets/serializers/asset/common.py:168
+#: assets/serializers/asset/common.py:409
msgid "Auto info"
msgstr "自动化信息"
-#: assets/serializers/asset/common.py:245
+#: assets/serializers/asset/common.py:265
msgid "Platform not exist"
msgstr "平台不存在"
-#: assets/serializers/asset/common.py:281
+#: assets/serializers/asset/common.py:301
msgid "port out of range (0-65535)"
msgstr "端口超出范围 (0-65535)"
-#: assets/serializers/asset/common.py:288
+#: assets/serializers/asset/common.py:308
msgid "Protocol is required: {}"
msgstr "协议是必填的: {}"
-#: assets/serializers/asset/common.py:316
+#: assets/serializers/asset/common.py:336
msgid "Invalid data"
msgstr "无效的数据"
@@ -2227,6 +2383,21 @@ msgstr "无效的数据"
msgid "Default database"
msgstr "默认数据库"
+#: assets/serializers/asset/database.py:23
+msgid "CA cert help text"
+msgstr ""
+"Common Name (CN) 字段已被弃用,请根据 RFC 5280 使用 Subject Alternative Name "
+"(SAN) 字段来验证域名,以提高安全性。"
+
+#: assets/serializers/asset/database.py:24
+msgid "Postgresql ssl model help text"
+msgstr ""
+"Prefer:我不关心加密,但如果服务器支持加密,我愿意支付加密的开销。Require:我"
+"希望我的数据被加密,我接受开销。我相信网络将确保我始终连接到我想要的服务器。"
+"Verify CA:我希望我的数据被加密,我接受开销。我想确保我连接到我信任的服务器。"
+"Verify Full:我希望我的数据被加密,我接受开销。我想确保我连接到我信任的服务"
+"器,并且它是我指定的服务器。"
+
#: assets/serializers/asset/gpt.py:20
msgid ""
"If the server cannot directly connect to the API address, you need set up an "
@@ -2307,12 +2478,16 @@ msgid ""
"the zone, the connection is routed through the gateway."
msgstr "网关是网域的网络代理,当连接网域内的资产时,连接将通过网关进行路由。"
-#: assets/serializers/domain.py:24 assets/serializers/platform.py:177
-#: orgs/serializers.py:13 perms/serializers/permission.py:39
+#: assets/serializers/domain.py:24 assets/serializers/platform.py:181
+#: orgs/serializers.py:13 perms/serializers/permission.py:50
msgid "Assets amount"
msgstr "资产数量"
-#: assets/serializers/gateway.py:23 common/validators.py:34
+#: assets/serializers/gateway.py:19
+msgid "The platform must start with Gateway"
+msgstr ""
+
+#: assets/serializers/gateway.py:28 common/validators.py:34
msgid "This field must be unique."
msgstr "字段必须唯一"
@@ -2388,19 +2563,19 @@ msgstr "该协议是默认的,添加资产时,将默认显示"
msgid "This protocol is public, asset will show this protocol to user"
msgstr "该协议是公开的,资产将向用户显示该协议并可以连接使用"
-#: assets/serializers/platform.py:157
+#: assets/serializers/platform.py:161
msgid "Help text"
msgstr "帮助"
-#: assets/serializers/platform.py:158
+#: assets/serializers/platform.py:162
msgid "Choices"
msgstr "选择"
-#: assets/serializers/platform.py:170
+#: assets/serializers/platform.py:174
msgid "Automation"
msgstr "自动化"
-#: assets/serializers/platform.py:199
+#: assets/serializers/platform.py:203
msgid ""
"Login with account when accessing assets, then automatically switch to "
"another, similar to logging in with a regular account and then switching to "
@@ -2409,23 +2584,23 @@ msgstr ""
"在访问资产时使用账户登录,然后自动切换到另一个账户,就像用普通账户登录然后切"
"换到 root 一样"
-#: assets/serializers/platform.py:205
+#: assets/serializers/platform.py:209
msgid "Assets can be connected using a zone gateway"
msgstr "资产可以使用区域网关进行连接"
-#: assets/serializers/platform.py:207
+#: assets/serializers/platform.py:211
msgid "Default Domain"
msgstr "默认网域"
-#: assets/serializers/platform.py:229
+#: assets/serializers/platform.py:233
msgid "type is required"
msgstr "类型 该字段是必填项。"
-#: assets/serializers/platform.py:244
+#: assets/serializers/platform.py:248
msgid "Protocols is required"
msgstr "协议是必填的"
-#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:35
+#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:39
msgid "Test assets connectivity "
msgstr "测试资产可连接性"
@@ -2433,19 +2608,26 @@ msgstr "测试资产可连接性"
msgid "Gather asset hardware info"
msgstr "收集资产硬件信息"
-#: assets/tasks/automation.py:24
+#: assets/tasks/automation.py:25
msgid "Asset execute automation"
msgstr "资产执行自动化"
-#: assets/tasks/gather_facts.py:21 assets/tasks/gather_facts.py:27
+#: assets/tasks/gather_facts.py:22 assets/tasks/gather_facts.py:32
msgid "Gather assets facts"
msgstr "收集资产信息"
-#: assets/tasks/gather_facts.py:39
+#: assets/tasks/gather_facts.py:25
+msgid ""
+"When clicking 'Refresh hardware info' in 'Console - Asset Details - Basic' "
+"this task \n"
+" will be executed"
+msgstr "当在控制台资产详情-基本设置点击更新硬件信息执行该任务"
+
+#: assets/tasks/gather_facts.py:44
msgid "Update assets hardware info: "
msgstr "更新资产硬件信息"
-#: assets/tasks/gather_facts.py:47
+#: assets/tasks/gather_facts.py:52
msgid "Update node asset hardware information: "
msgstr "更新节点资产硬件信息: "
@@ -2453,28 +2635,59 @@ msgstr "更新节点资产硬件信息: "
msgid "Check the amount of assets under the node"
msgstr "检查节点下资产数量"
-#: assets/tasks/nodes_amount.py:28
+#: assets/tasks/nodes_amount.py:18
+msgid ""
+"Manually verifying asset quantities updates the asset count for nodes under "
+"the \n"
+" current organization. This task will be called in the following two "
+"cases: when updating \n"
+" nodes and when the number of nodes exceeds 100"
+msgstr ""
+"手动校对资产数量更新当前组织下的节点资产数量;更新节点,当节点数大于100这两种"
+"情况会调用该任务"
+
+#: assets/tasks/nodes_amount.py:34
msgid ""
"The task of self-checking is already running and cannot be started repeatedly"
msgstr "自检程序已经在运行,不能重复启动"
-#: assets/tasks/nodes_amount.py:33
+#: assets/tasks/nodes_amount.py:40
msgid "Periodic check the amount of assets under the node"
msgstr "周期性检查节点下资产数量"
-#: assets/tasks/ping.py:20 assets/tasks/ping.py:26
+#: assets/tasks/nodes_amount.py:42
+msgid ""
+"Schedule the check_node_assets_amount_task to periodically update the asset "
+"count of \n"
+" all nodes under all organizations"
+msgstr ""
+"定时调用check_node_assets_amount_task任务,更新所有组织下所有节点的资产数量"
+
+#: assets/tasks/ping.py:20 assets/tasks/ping.py:30
msgid "Test assets connectivity"
msgstr "测试资产可连接性"
-#: assets/tasks/ping.py:42
+#: assets/tasks/ping.py:24
+msgid ""
+"When clicking 'Test Asset Connectivity' in 'Asset Details - Basic Settings' "
+"this task will be executed"
+msgstr "当资产详情-基本设置点击测试资产可连接性 执行该任务"
+
+#: assets/tasks/ping.py:46
msgid "Test if the assets under the node are connectable "
msgstr "测试节点下资产是否可连接"
-#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:25
-#: assets/tasks/ping_gateway.py:34
+#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:29
+#: assets/tasks/ping_gateway.py:38
msgid "Test gateways connectivity"
msgstr "测试网关可连接性"
+#: assets/tasks/ping_gateway.py:23
+msgid ""
+"When clicking 'Test Connection' in 'Domain Details - Gateway' this task will "
+"be executed"
+msgstr "当在网域详情-网关-测试连接时执行该任务"
+
#: assets/tasks/utils.py:16
msgid "Asset has been disabled, skipped: {}"
msgstr "资产已经被禁用, 跳过: {}"
@@ -2531,7 +2744,7 @@ msgstr "建立软链接"
#: audits/const.py:18 audits/const.py:28
#: ops/templates/ops/celery_task_log.html:86
-#: terminal/api/session/session.py:149
+#: terminal/api/session/session.py:153
msgid "Download"
msgstr "下载"
@@ -2539,7 +2752,7 @@ msgstr "下载"
msgid "Rename dir"
msgstr "映射目录"
-#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:274
+#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:281
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:84
@@ -2581,16 +2794,16 @@ msgstr "同意"
msgid "Close"
msgstr "关闭"
-#: audits/const.py:41 ops/models/celery.py:84
+#: audits/const.py:41 ops/models/celery.py:85
#: terminal/models/session/sharing.py:128 tickets/const.py:25
#: xpack/plugins/cloud/const.py:67
msgid "Finished"
msgstr "结束"
#: audits/const.py:46 settings/serializers/terminal.py:6
-#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:175
+#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:174
#: terminal/models/virtualapp/provider.py:14 terminal/serializers/session.py:55
-#: terminal/serializers/session.py:69
+#: terminal/serializers/session.py:79
msgid "Terminal"
msgstr "终端"
@@ -2742,9 +2955,9 @@ msgstr "用户会话"
msgid "Offline user session"
msgstr "下线用户会话"
-#: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16
-#: ops/models/base.py:53 ops/models/celery.py:86 ops/models/job.py:151
-#: ops/models/job.py:240 ops/models/playbook.py:30
+#: audits/serializers.py:33 ops/models/adhoc.py:24 ops/models/base.py:16
+#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:151
+#: ops/models/job.py:240 ops/models/playbook.py:32
#: terminal/models/session/sharing.py:25
msgid "Creator"
msgstr "创建者"
@@ -2799,7 +3012,7 @@ msgstr "认证令牌"
#: audits/signal_handlers/login_log.py:37 authentication/notifications.py:73
#: authentication/views/login.py:78 notifications/backends/__init__.py:11
#: settings/serializers/auth/wecom.py:11 settings/serializers/auth/wecom.py:16
-#: users/models/user/__init__.py:122 users/models/user/_source.py:18
+#: users/models/user/__init__.py:122 users/models/user/_source.py:19
msgid "WeCom"
msgstr "企业微信"
@@ -2807,21 +3020,21 @@ msgstr "企业微信"
#: authentication/views/login.py:90 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:12
#: settings/serializers/auth/feishu.py:14 users/models/user/__init__.py:128
-#: users/models/user/_source.py:20
+#: users/models/user/_source.py:21
msgid "FeiShu"
msgstr "飞书"
#: audits/signal_handlers/login_log.py:40 authentication/views/login.py:102
#: authentication/views/slack.py:79 notifications/backends/__init__.py:16
#: settings/serializers/auth/slack.py:11 settings/serializers/auth/slack.py:13
-#: users/models/user/__init__.py:134 users/models/user/_source.py:22
+#: users/models/user/__init__.py:134 users/models/user/_source.py:23
msgid "Slack"
msgstr "Slack"
#: audits/signal_handlers/login_log.py:41 authentication/views/dingtalk.py:151
#: authentication/views/login.py:84 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:11 users/models/user/__init__.py:125
-#: users/models/user/_source.py:19
+#: users/models/user/_source.py:20
msgid "DingTalk"
msgstr "钉钉"
@@ -2836,14 +3049,36 @@ msgstr "临时密码"
msgid "Passkey"
msgstr "Passkey"
-#: audits/tasks.py:131
+#: audits/tasks.py:132
msgid "Clean audits session task log"
msgstr "清理资产审计会话任务日志"
-#: audits/tasks.py:145
+#: audits/tasks.py:134
+msgid ""
+"Since the system generates login logs, operation logs, file upload logs, "
+"activity \n"
+" logs, Celery execution logs, session recordings, command records, "
+"and password change \n"
+" logs, it will perform cleanup of records that exceed the time limit "
+"according to the \n"
+" 'Tasks - Regular clean-up' in the system settings at 2 a.m daily"
+msgstr ""
+"由于系统会产生登录日志,操作日志,文件上传日志,活动日志,celery执行日志,会"
+"话录像和命令记录,改密日志,所以系统会根据系统设置-任务列表-定期清理配置,对"
+"于超出时间的于每天凌晨2点进行清理"
+
+#: audits/tasks.py:154
msgid "Upload FTP file to external storage"
msgstr "上传 FTP 文件到外部存储"
+#: audits/tasks.py:156
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured, files uploaded through file "
+"management will be \n"
+" synchronized to external storage"
+msgstr ""
+"如果设置了SERVER_REPLAY_STORAGE,将通过文件管理上传的文件同步到外部存储"
+
#: authentication/api/access_key.py:39
msgid "Access keys can be created at most 10"
msgstr "最多可以创建10个访问密钥"
@@ -2885,7 +3120,7 @@ msgstr "ACL 动作是复核"
msgid "Current user not support mfa type: {}"
msgstr "当前用户不支持 MFA 类型: {}"
-#: authentication/api/password.py:33 terminal/api/session/session.py:322
+#: authentication/api/password.py:33 terminal/api/session/session.py:334
#: users/views/profile/reset.py:63
msgid "User does not exist: {}"
msgstr "用户不存在: {}"
@@ -2935,6 +3170,15 @@ msgstr "无效的令牌头。符号字符串不应包含无效字符。"
msgid "Invalid token or cache refreshed."
msgstr "刷新的令牌或缓存无效。"
+#: authentication/backends/oidc/views.py:174
+msgid "OpenID Error"
+msgstr "OpenID 错误"
+
+#: authentication/backends/oidc/views.py:175
+#: authentication/backends/saml2/views.py:282
+msgid "Please check if a user with the same username or email already exists"
+msgstr "请检查是否已经存在相同用户名或邮箱的用户"
+
#: authentication/backends/passkey/api.py:37
msgid "Only register passkey for local user"
msgstr "仅为本地用户注册密钥"
@@ -2961,6 +3205,10 @@ msgstr "最后使用日期"
msgid "Credential ID"
msgstr "凭证 ID"
+#: authentication/backends/saml2/views.py:281
+msgid "SAML2 Error"
+msgstr "SAML2 错误"
+
#: authentication/confirm/password.py:16
msgid "Authentication failed password incorrect"
msgstr "认证失败 (用户名或密码不正确)"
@@ -3126,15 +3374,15 @@ msgstr "您的密码无效"
msgid "Please wait for %s seconds before retry"
msgstr "请在 %s 秒后重试"
-#: authentication/errors/redirect.py:85 authentication/mixins.py:323
+#: authentication/errors/redirect.py:85 authentication/mixins.py:326
msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改"
-#: authentication/errors/redirect.py:93 authentication/mixins.py:330
+#: authentication/errors/redirect.py:93 authentication/mixins.py:335
msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码"
-#: authentication/errors/redirect.py:101 authentication/mixins.py:337
+#: authentication/errors/redirect.py:101 authentication/mixins.py:344
msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录"
@@ -3232,7 +3480,7 @@ msgstr "设置手机号码启用"
msgid "Clear phone number to disable"
msgstr "清空手机号码禁用"
-#: authentication/middleware.py:94 settings/utils/ldap.py:679
+#: authentication/middleware.py:94 settings/utils/ldap.py:691
msgid "Authentication failed (before login check failed): {}"
msgstr "认证失败 (登录前检查失败): {}"
@@ -3250,7 +3498,7 @@ msgstr "管理员已开启'仅允许从用户来源登录',当前用户来源
msgid "The MFA type ({}) is not enabled"
msgstr "该 MFA ({}) 方式没有启用"
-#: authentication/mixins.py:313
+#: authentication/mixins.py:314
msgid "Please change your password"
msgstr "请修改密码"
@@ -3428,7 +3676,7 @@ msgid "Actions"
msgstr "动作"
#: authentication/serializers/connection_token.py:42
-#: perms/serializers/permission.py:43 perms/serializers/permission.py:64
+#: perms/serializers/permission.py:54 perms/serializers/permission.py:75
#: users/serializers/user.py:127 users/serializers/user.py:273
msgid "Is expired"
msgstr "已过期"
@@ -3470,16 +3718,22 @@ msgstr "SSH密钥不合法"
msgid "Access IP"
msgstr "IP 白名单"
-#: authentication/serializers/token.py:92 perms/serializers/permission.py:42
-#: perms/serializers/permission.py:65 users/serializers/user.py:128
+#: authentication/serializers/token.py:92 perms/serializers/permission.py:53
+#: perms/serializers/permission.py:76 users/serializers/user.py:128
#: users/serializers/user.py:270
msgid "Is valid"
msgstr "是否有效"
-#: authentication/tasks.py:11
+#: authentication/tasks.py:13
msgid "Clean expired session"
msgstr "清除过期会话"
+#: authentication/tasks.py:15
+msgid ""
+"Since user logins create sessions, the system will clean up expired sessions "
+"every 24 hours"
+msgstr "由于用户登录系统会产生会话,系统会每24小时清理已经过期的会话"
+
#: authentication/templates/authentication/_access_key_modal.html:6
msgid "API key list"
msgstr "API Key列表"
@@ -3539,7 +3793,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_oauth_bind.html:3
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_reset_password_code.html:9
-#: jumpserver/conf.py:502
+#: jumpserver/conf.py:522
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:32
#: users/templates/users/_msg_account_expire_reminder.html:4
@@ -3853,7 +4107,7 @@ msgstr "从企业微信获取用户失败"
msgid "Please login with a password and then bind the WeCom"
msgstr "请使用密码登录,然后绑定企业微信"
-#: common/api/action.py:51
+#: common/api/action.py:57
msgid "Request file format may be wrong"
msgstr "上传的文件格式错误 或 其它类型资源的文件"
@@ -3881,7 +4135,7 @@ msgstr "运行中"
msgid "Canceled"
msgstr "取消"
-#: common/const/common.py:5 xpack/plugins/cloud/manager.py:412
+#: common/const/common.py:5 xpack/plugins/cloud/manager.py:411
#, python-format
msgid "%(name)s was created successfully"
msgstr "%(name)s 创建成功"
@@ -3985,7 +4239,7 @@ msgstr "组织 ID"
msgid "The file content overflowed (The maximum length `{}` bytes)"
msgstr "文件内容太大 (最大长度 `{}` 字节)"
-#: common/drf/parsers/base.py:199
+#: common/drf/parsers/base.py:207
msgid "Parse file error: {}"
msgstr "解析文件错误: {}"
@@ -3993,7 +4247,70 @@ msgstr "解析文件错误: {}"
msgid "Invalid excel file"
msgstr "无效的 excel 文件"
-#: common/drf/renders/base.py:208
+#: common/drf/renders/base.py:138
+msgid "Yes/No"
+msgstr ""
+
+#: common/drf/renders/base.py:141
+msgid "Text, max length {}"
+msgstr "文本,最大长度 {}"
+
+#: common/drf/renders/base.py:143
+msgid "Long text, no length limit"
+msgstr "长文本,无长度限制"
+
+#: common/drf/renders/base.py:145
+msgid "Number, min {} max {}"
+msgstr "数字,最小 {} 最大 {}"
+
+#: common/drf/renders/base.py:148
+msgid "Datetime format {}"
+msgstr "日期时间格式 {}"
+
+#: common/drf/renders/base.py:154
+msgid ""
+"Choices, format name(value), name is optional for human read, value is "
+"requisite, options {}"
+msgstr "选项,格式: 名称(值),名称是可选的,方便阅读,值是必填的,可选项有 {}"
+
+#: common/drf/renders/base.py:157
+msgid "Choices, options {}"
+msgstr "选项,可选项有 {}"
+
+#: common/drf/renders/base.py:159
+msgid "Phone number, format +8612345678901"
+msgstr "手机号,格式 +8612345678901"
+
+#: common/drf/renders/base.py:161
+msgid "Label, format [\"key:value\"]"
+msgstr "标签,格式: [\"键:值\"]"
+
+#: common/drf/renders/base.py:163
+msgid ""
+"Object, format name(id), name is optional for human read, id is requisite"
+msgstr "关联项,格式: 名称(id), 名称是可选的,方便阅读,id 是必填的"
+
+#: common/drf/renders/base.py:165
+msgid "Object, format id"
+msgstr "关联项,格式是 id"
+
+#: common/drf/renders/base.py:169
+msgid ""
+"Objects, format [\"name(id)\", ...], name is optional for human read, id is "
+"requisite"
+msgstr ""
+"多关联项,格式: [\"名称(id)\", ...], 名称是可选的,方便阅读,id 是必填的"
+
+#: common/drf/renders/base.py:171
+msgid ""
+"Labels, format [\"key:value\", ...], if label not exists, will create it"
+msgstr "标签,格式: [\"键:值\", ...], 如果标签不存在,将创建它"
+
+#: common/drf/renders/base.py:173
+msgid "Objects, format [\"id\", ...]"
+msgstr "多关联项,格式是 [\"id\", ...]"
+
+#: common/drf/renders/base.py:271
msgid ""
"{} - The encryption password has not been set - please go to personal "
"information -> file encryption password to set the encryption password"
@@ -4152,18 +4469,36 @@ msgstr "标签"
# msgid "Labels"
# msgstr "标签管理"
-#: common/tasks.py:31
+#: common/tasks.py:32
msgid "Send email"
msgstr "发件邮件"
-#: common/tasks.py:58
+#: common/tasks.py:35
+msgid "This task will be executed when sending email notifications"
+msgstr "发送邮件消息时执行该任务"
+
+#: common/tasks.py:65
msgid "Send email attachment"
msgstr "发送邮件附件"
-#: common/tasks.py:80 terminal/tasks.py:58
-msgid "Upload session replay to external storage"
+#: common/tasks.py:68
+msgid ""
+"When an account password is changed or an account backup generates "
+"attachments, \n"
+" this task needs to be executed for sending emails and handling "
+"attachments"
+msgstr "当账号改密,账号备份产生附件,需要对发送邮件及附件,执行该任务"
+
+#: common/tasks.py:94
+msgid "Upload account backup to external storage"
msgstr "上传会话录像到外部存储"
+#: common/tasks.py:96
+msgid ""
+"When performing an account backup, this task needs to be executed to "
+"external storage (SFTP)"
+msgstr "当执行账号备份,需要到外部存储(sftp),执行该任务"
+
#: common/utils/ip/geoip/utils.py:26
msgid "Invalid ip"
msgstr "无效 IP"
@@ -4177,10 +4512,17 @@ msgstr "无效地址"
msgid "Hello %s"
msgstr "你好 %s"
-#: common/utils/verify_code.py:16
+#: common/utils/verify_code.py:17
msgid "Send SMS code"
msgstr "发送短信验证码"
+#: common/utils/verify_code.py:19
+msgid ""
+"When resetting a password, forgetting a password, or verifying MFA, this "
+"task needs to \n"
+" be executed to send SMS messages"
+msgstr "当重置密码,忘记密码,验证mfa时,需要发送短信时执行该任务"
+
#: common/validators.py:16
msgid "Special char not allowed"
msgstr "不能包含特殊字符"
@@ -4193,16 +4535,16 @@ msgstr "不能包含特殊字符"
msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确"
-#: jumpserver/conf.py:496
+#: jumpserver/conf.py:516
#, python-brace-format
msgid "The verification code is: {code}"
msgstr "验证码为: {code}"
-#: jumpserver/conf.py:501
+#: jumpserver/conf.py:521
msgid "Create account successfully"
msgstr "创建账号成功"
-#: jumpserver/conf.py:503
+#: jumpserver/conf.py:523
msgid "Your account has been created successfully"
msgstr "你的账号已创建成功"
@@ -4297,15 +4639,22 @@ msgstr "系统信息"
msgid "Publish the station message"
msgstr "发布站内消息"
-#: ops/ansible/inventory.py:106 ops/models/job.py:65
+#: notifications/notifications.py:48
+msgid ""
+"This task needs to be executed for sending internal messages for system "
+"alerts, \n"
+" work orders, and other notifications"
+msgstr "系统一些告警,工单等需要发送站内信时执行该任务"
+
+#: ops/ansible/inventory.py:116 ops/models/job.py:65
msgid "No account available"
msgstr "无可用账号"
-#: ops/ansible/inventory.py:285
+#: ops/ansible/inventory.py:296
msgid "Ansible disabled"
msgstr "Ansible 已禁用"
-#: ops/ansible/inventory.py:301
+#: ops/ansible/inventory.py:312
msgid "Skip hosts below:"
msgstr "跳过以下主机: "
@@ -4353,31 +4702,31 @@ msgid ""
"The task is being created and cannot be interrupted. Please try again later."
msgstr "正在创建任务,无法中断,请稍后重试。"
-#: ops/api/playbook.py:39
+#: ops/api/playbook.py:50
msgid "Currently playbook is being used in a job"
msgstr "当前 playbook 正在作业中使用"
-#: ops/api/playbook.py:97
+#: ops/api/playbook.py:113
msgid "Unsupported file content"
msgstr "不支持的文件内容"
-#: ops/api/playbook.py:99 ops/api/playbook.py:145 ops/api/playbook.py:193
+#: ops/api/playbook.py:115 ops/api/playbook.py:161 ops/api/playbook.py:209
msgid "Invalid file path"
msgstr "无效的文件路径"
-#: ops/api/playbook.py:171
+#: ops/api/playbook.py:187
msgid "This file can not be rename"
msgstr "该文件不能重命名"
-#: ops/api/playbook.py:190
+#: ops/api/playbook.py:206
msgid "File already exists"
msgstr "文件已存在"
-#: ops/api/playbook.py:208
+#: ops/api/playbook.py:224
msgid "File key is required"
msgstr "文件密钥该字段是必填项。"
-#: ops/api/playbook.py:211
+#: ops/api/playbook.py:227
msgid "This file can not be delete"
msgstr "无法删除此文件"
@@ -4417,11 +4766,11 @@ msgstr "空白"
msgid "VCS"
msgstr "VCS"
-#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:120
+#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:123
msgid "Adhoc"
msgstr "命令"
-#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:88
+#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:91
msgid "Playbook"
msgstr "Playbook"
@@ -4485,53 +4834,69 @@ msgstr "超时"
msgid "Command execution disabled"
msgstr "命令执行已禁用"
+#: ops/const.py:86
+msgctxt "scope"
+msgid "Public"
+msgstr "公有"
+
+#: ops/const.py:87
+msgid "Private"
+msgstr "私有"
+
#: ops/exception.py:6
msgid "no valid program entry found."
msgstr "没有可用程序入口"
-#: ops/mixin.py:23 ops/mixin.py:102 settings/serializers/auth/ldap.py:72
+#: ops/mixin.py:30 ops/mixin.py:110 settings/serializers/auth/ldap.py:73
+#: settings/serializers/auth/ldap_ha.py:55
msgid "Periodic run"
msgstr "周期执行"
-#: ops/mixin.py:25 ops/mixin.py:88 ops/mixin.py:108
-#: settings/serializers/auth/ldap.py:79
+#: ops/mixin.py:32 ops/mixin.py:96 ops/mixin.py:116
+#: settings/serializers/auth/ldap.py:80 settings/serializers/auth/ldap_ha.py:62
msgid "Interval"
msgstr "间隔"
-#: ops/mixin.py:28 ops/mixin.py:86 ops/mixin.py:105
-#: settings/serializers/auth/ldap.py:76
+#: ops/mixin.py:35 ops/mixin.py:94 ops/mixin.py:113
+#: settings/serializers/auth/ldap.py:77 settings/serializers/auth/ldap_ha.py:59
msgid "Crontab"
msgstr "Crontab"
-#: ops/mixin.py:110
+#: ops/mixin.py:118
msgid "Run period"
msgstr "执行周期"
-#: ops/mixin.py:119
+#: ops/mixin.py:127
msgid "* Please enter a valid crontab expression"
msgstr "* 请输入有效的 crontab 表达式"
-#: ops/mixin.py:126
+#: ops/mixin.py:134
msgid "Range {} to {}"
msgstr "输入在 {} - {} 范围之间"
-#: ops/mixin.py:137
+#: ops/mixin.py:145
msgid "Require interval or crontab setting"
msgstr "需要周期或定期设置"
-#: ops/models/adhoc.py:21
+#: ops/models/adhoc.py:20
msgid "Pattern"
msgstr "模式"
-#: ops/models/adhoc.py:23 ops/models/job.py:146
+#: ops/models/adhoc.py:22 ops/models/job.py:146
msgid "Module"
msgstr "模块"
-#: ops/models/adhoc.py:24 ops/models/celery.py:81 ops/models/job.py:144
+#: ops/models/adhoc.py:23 ops/models/celery.py:82 ops/models/job.py:144
#: terminal/models/component/task.py:14
msgid "Args"
msgstr "参数"
+#: ops/models/adhoc.py:26 ops/models/playbook.py:36 ops/serializers/mixin.py:10
+#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
+#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
+msgid "Scope"
+msgstr "范围"
+
#: ops/models/base.py:19
msgid "Account policy"
msgstr "账号策略"
@@ -4558,23 +4923,23 @@ msgstr "汇总"
msgid "Date last publish"
msgstr "发布日期"
-#: ops/models/celery.py:70
+#: ops/models/celery.py:71
msgid "Celery Task"
msgstr "Celery 任务"
-#: ops/models/celery.py:73
+#: ops/models/celery.py:74
msgid "Can view task monitor"
msgstr "可以查看任务监控"
-#: ops/models/celery.py:82 terminal/models/component/task.py:15
+#: ops/models/celery.py:83 terminal/models/component/task.py:15
msgid "Kwargs"
msgstr "其它参数"
-#: ops/models/celery.py:87
+#: ops/models/celery.py:88
msgid "Date published"
msgstr "发布日期"
-#: ops/models/celery.py:112
+#: ops/models/celery.py:113
msgid "Celery Task Execution"
msgstr "Celery 任务执行"
@@ -4619,11 +4984,11 @@ msgstr "Material 类型"
msgid "Job Execution"
msgstr "作业执行"
-#: ops/models/playbook.py:33
+#: ops/models/playbook.py:35
msgid "CreateMethod"
msgstr "创建方式"
-#: ops/models/playbook.py:34
+#: ops/models/playbook.py:37
msgid "VCS URL"
msgstr "VCS URL"
@@ -4687,34 +5052,95 @@ msgstr "任务 ID"
msgid "You do not have permission for the current job."
msgstr "你没有当前作业的权限。"
-#: ops/tasks.py:38
+#: ops/tasks.py:51
msgid "Run ansible task"
msgstr "运行 Ansible 任务"
-#: ops/tasks.py:72
+#: ops/tasks.py:54
+msgid ""
+"Execute scheduled adhoc and playbooks, periodically invoking the task for "
+"execution"
+msgstr "当执行定时的快捷命令,playbook,定时调用该任务执行"
+
+#: ops/tasks.py:82
msgid "Run ansible task execution"
msgstr "开始执行 Ansible 任务"
-#: ops/tasks.py:94
+#: ops/tasks.py:85
+msgid "Execute the task when manually adhoc or playbooks"
+msgstr "手动执行快捷命令,playbook时执行该任务"
+
+#: ops/tasks.py:99
msgid "Clear celery periodic tasks"
msgstr "清理周期任务"
-#: ops/tasks.py:115
+#: ops/tasks.py:101
+msgid "At system startup, clean up celery tasks that no longer exist"
+msgstr "系统启动时,清理已经不存在的celery任务"
+
+#: ops/tasks.py:125
msgid "Create or update periodic tasks"
msgstr "创建或更新周期任务"
-#: ops/tasks.py:123
+#: ops/tasks.py:127
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, tasks will be "
+"registered or the parameters \n"
+" of scheduled tasks will be updated"
+msgstr ""
+"随着版本迭代,可能会新增任务或者修改任务的名称,执行时间,所以在系统启动时,"
+"将会注册任务或者更新定时任务参数"
+
+#: ops/tasks.py:140
msgid "Periodic check service performance"
msgstr "周期检测服务性能"
-#: ops/tasks.py:129
+#: ops/tasks.py:142
+msgid ""
+"Check every hour whether each component is offline and whether the CPU, "
+"memory, \n"
+" and disk usage exceed the thresholds, and send an alert message to "
+"the administrator"
+msgstr ""
+"每小时检测各组件是否离线,cpu,内存,硬盘使用率是否超过阈值,向管理员发送消息"
+"预警"
+
+#: ops/tasks.py:152
msgid "Clean up unexpected jobs"
msgstr "清理异常作业"
-#: ops/tasks.py:136
+#: ops/tasks.py:154
+msgid ""
+"Due to exceptions caused by executing adhoc and playbooks in the Job "
+"Center, \n"
+" which result in the task status not being updated, the system will "
+"clean up abnormal jobs \n"
+" that have not been completed for more than 3 hours every hour and "
+"mark these tasks as \n"
+" failed"
+msgstr ""
+"由于作业中心执行快捷命令,playbook会产生异常,任务状态未更新完成,系统将每小"
+"时执行清理超3小时未完成的异常作业,并将任务标记失败"
+
+#: ops/tasks.py:167
msgid "Clean job_execution db record"
msgstr "清理作业中心执行历史"
+#: ops/tasks.py:169
+msgid ""
+"Due to the execution of adhoc and playbooks in the Job Center, execution "
+"records will \n"
+" be generated. The system will clean up records that exceed the "
+"retention period every day \n"
+" at 2 a.m., based on the configuration of 'System Settings - Tasks - "
+"Regular clean-up - \n"
+" Job execution retention days'"
+msgstr ""
+"由于作业中心执行快捷命令,playbook,会产生执行记录,系统会根据系统设置-任务列"
+"表-定期清理-作业中心执行历史配置,每天凌晨2点对超出保存时间的记录进行清理"
+
#: ops/templates/ops/celery_task_log.html:4
msgid "Task log"
msgstr "任务列表"
@@ -4755,17 +5181,17 @@ msgstr "Job ID"
msgid "Name of the job"
msgstr "Job 名称"
-#: orgs/api.py:61
+#: orgs/api.py:60
msgid "The current organization ({}) cannot be deleted"
msgstr "当前组织 ({}) 不能被删除"
-#: orgs/api.py:66
+#: orgs/api.py:65
msgid ""
"LDAP synchronization is set to the current organization. Please switch to "
"another organization before deleting"
msgstr "LDAP 同步设置组织为当前组织,请切换其他组织后再进行删除操作"
-#: orgs/api.py:76
+#: orgs/api.py:75
msgid "The organization have resource ({}) cannot be deleted"
msgstr "组织存在资源 ({}) 不能被删除"
@@ -4779,7 +5205,7 @@ msgstr "请选择一个组织后再保存"
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91
#: rbac/const.py:7 rbac/models/rolebinding.py:56
-#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:52
+#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:53
#: terminal/templates/terminal/_msg_command_warning.html:21
#: terminal/templates/terminal/_msg_session_sharing.html:14
#: tickets/models/ticket/general.py:303 tickets/serializers/ticket/ticket.py:60
@@ -4798,7 +5224,7 @@ msgstr "默认组织"
msgid "SYSTEM"
msgstr "系统组织"
-#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:185
+#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:186
#: terminal/models/applet/applet.py:42
msgid "Builtin"
msgstr "内置的"
@@ -4815,7 +5241,7 @@ msgstr "可以查看所有加入的组织"
msgid "Can not delete virtual org"
msgstr "无法删除虚拟组织"
-#: orgs/serializers.py:10 perms/serializers/permission.py:37
+#: orgs/serializers.py:10 perms/serializers/permission.py:48
#: rbac/serializers/role.py:27 users/serializers/group.py:54
msgid "Users amount"
msgstr "用户数量"
@@ -4824,7 +5250,7 @@ msgstr "用户数量"
msgid "User groups amount"
msgstr "用户组数量"
-#: orgs/serializers.py:14 perms/serializers/permission.py:40
+#: orgs/serializers.py:14 perms/serializers/permission.py:51
msgid "Nodes amount"
msgstr "节点数量"
@@ -4840,7 +5266,7 @@ msgstr "网关数量"
msgid "Asset permissions amount"
msgstr "资产授权数量"
-#: orgs/tasks.py:9
+#: orgs/tasks.py:10
msgid "Refresh organization cache"
msgstr "刷新组织缓存"
@@ -4921,7 +5347,7 @@ msgid "today"
msgstr "今天"
#: perms/notifications.py:12 perms/notifications.py:44
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "day"
msgstr "天"
@@ -4941,22 +5367,62 @@ msgstr "资产授权规则将要过期"
msgid "asset permissions of organization {}"
msgstr "组织 ({}) 的资产授权"
-#: perms/serializers/permission.py:33 users/serializers/user.py:257
+#: perms/serializers/permission.py:32
+msgid ""
+"Accounts, format [\"@virtual\", \"root\", \"%template_id\"], virtual "
+"choices: @ALL, @SPEC, @USER, @ANON, @INPUT"
+msgstr ""
+"账号,格式 [\"@虚拟账号\", \"root\", \"%模版id\"], 虚拟选项: @ALL, @SPEC, "
+"@USER, @ANON, @INPUT"
+
+#: perms/serializers/permission.py:38
+msgid "Protocols, format [\"ssh\", \"rdp\", \"vnc\"] or [\"all\"]"
+msgstr "协议,格式为 [\"ssh\", \"rdp\", \"vnc\"] 或 [\"all\"]"
+
+#: perms/serializers/permission.py:44 users/serializers/user.py:257
msgid "Groups"
msgstr "用户组"
-#: perms/serializers/permission.py:38
+#: perms/serializers/permission.py:49
msgid "Groups amount"
msgstr "用户组数量"
-#: perms/tasks.py:27
+#: perms/tasks.py:28
msgid "Check asset permission expired"
msgstr "校验资产授权规则已过期"
-#: perms/tasks.py:40
+#: perms/tasks.py:30
+msgid ""
+"The cache of organizational collections, which have completed user "
+"authorization tree \n"
+" construction, will expire. Therefore, expired collections need to be "
+"cleared from the \n"
+" cache, and this task will be executed periodically based on the time "
+"interval specified \n"
+" by PERM_EXPIRED_CHECK_PERIODIC in the system configuration file "
+"config.txt"
+msgstr ""
+"用户授权树已经构建完成的组织集合缓存会过期,所以需要将过期的集合从缓存中清理"
+"掉,根据系统配置文件 config.txt 中 PERM_EXPIRED_CHECK_PERIODIC 的时间间隔定时"
+"执行该任务"
+
+#: perms/tasks.py:49
msgid "Send asset permission expired notification"
msgstr "发送资产权限过期通知"
+#: perms/tasks.py:51
+msgid ""
+"Check every day at 10 a.m. and send a notification message to users "
+"associated with \n"
+" assets whose authorization is about to expire, as well as to the "
+"organization's \n"
+" administrators, 3 days in advance, to remind them that the asset "
+"authorization will \n"
+" expire in a few days"
+msgstr ""
+"每天上午10点检查,对资产授权即将过期的所关联的用户及该组织管理员提前三天发送"
+"消息通知,提示资产还有几天即将过期"
+
#: perms/templates/perms/_msg_item_permissions_expire.html:7
#: perms/templates/perms/_msg_permed_items_expire.html:7
#, python-format
@@ -4989,27 +5455,27 @@ msgstr "{} 至少有一个系统角色"
msgid "App RBAC"
msgstr "RBAC"
-#: rbac/builtin.py:115
+#: rbac/builtin.py:116
msgid "SystemAdmin"
msgstr "系统管理员"
-#: rbac/builtin.py:118
+#: rbac/builtin.py:119
msgid "SystemAuditor"
msgstr "系统审计员"
-#: rbac/builtin.py:121
+#: rbac/builtin.py:122
msgid "SystemComponent"
msgstr "系统组件"
-#: rbac/builtin.py:127
+#: rbac/builtin.py:128
msgid "OrgAdmin"
msgstr "组织管理员"
-#: rbac/builtin.py:130
+#: rbac/builtin.py:131
msgid "OrgAuditor"
msgstr "组织审计员"
-#: rbac/builtin.py:133
+#: rbac/builtin.py:134
msgid "OrgUser"
msgstr "组织用户"
@@ -5049,11 +5515,6 @@ msgstr "内容类型"
msgid "Permissions"
msgstr "授权"
-#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
-#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
-msgid "Scope"
-msgstr "范围"
-
#: rbac/models/role.py:46 rbac/models/rolebinding.py:52
#: users/models/user/__init__.py:66
msgid "Role"
@@ -5113,7 +5574,7 @@ msgstr "工作台"
msgid "Audit view"
msgstr "审计台"
-#: rbac/tree.py:27 settings/models.py:161
+#: rbac/tree.py:27 settings/models.py:162
msgid "System setting"
msgstr "系统设置"
@@ -5141,7 +5602,7 @@ msgstr "账号改密"
msgid "App ops"
msgstr "作业中心"
-#: rbac/tree.py:57 settings/serializers/feature.py:117
+#: rbac/tree.py:57 settings/serializers/feature.py:120
msgid "Feature"
msgstr "功能"
@@ -5176,8 +5637,8 @@ msgstr "组织管理"
msgid "Ticket comment"
msgstr "工单评论"
-#: rbac/tree.py:159 settings/serializers/feature.py:98
-#: settings/serializers/feature.py:100 tickets/models/ticket/general.py:308
+#: rbac/tree.py:159 settings/serializers/feature.py:101
+#: settings/serializers/feature.py:103 tickets/models/ticket/general.py:308
msgid "Ticket"
msgstr "工单"
@@ -5207,7 +5668,7 @@ msgstr "邮件已经发送{}, 请检查"
msgid "Test smtp setting"
msgstr "测试 smtp 设置"
-#: settings/api/ldap.py:89
+#: settings/api/ldap.py:92
msgid ""
"Users are not synchronized, please click the user synchronization button"
msgstr "用户未同步,请点击同步用户按钮"
@@ -5224,75 +5685,75 @@ msgstr "测试手机号 该字段是必填项。"
msgid "App Settings"
msgstr "系统设置"
-#: settings/models.py:37 users/models/preference.py:14
+#: settings/models.py:38 users/models/preference.py:14
msgid "Encrypted"
msgstr "加密的"
-#: settings/models.py:163
+#: settings/models.py:164
msgid "Can change email setting"
msgstr "邮件设置"
-#: settings/models.py:164
+#: settings/models.py:165
msgid "Can change auth setting"
msgstr "认证设置"
-#: settings/models.py:165
+#: settings/models.py:166
msgid "Can change auth ops"
msgstr "任务中心设置"
-#: settings/models.py:166
+#: settings/models.py:167
msgid "Can change auth ticket"
msgstr "工单设置"
-#: settings/models.py:167
+#: settings/models.py:168
msgid "Can change virtual app setting"
msgstr "可以更改虚拟应用设置"
-#: settings/models.py:168
+#: settings/models.py:169
msgid "Can change auth announcement"
msgstr "公告设置"
-#: settings/models.py:169
+#: settings/models.py:170
msgid "Can change vault setting"
msgstr "可以更改 vault 设置"
-#: settings/models.py:170
+#: settings/models.py:171
msgid "Can change chat ai setting"
msgstr "可以修改聊天 AI 设置"
-#: settings/models.py:171
+#: settings/models.py:172
msgid "Can change system msg sub setting"
msgstr "消息订阅设置"
-#: settings/models.py:172
+#: settings/models.py:173
msgid "Can change sms setting"
msgstr "短信设置"
-#: settings/models.py:173
+#: settings/models.py:174
msgid "Can change security setting"
msgstr "安全设置"
-#: settings/models.py:174
+#: settings/models.py:175
msgid "Can change clean setting"
msgstr "定期清理"
-#: settings/models.py:175
+#: settings/models.py:176
msgid "Can change interface setting"
msgstr "界面设置"
-#: settings/models.py:176
+#: settings/models.py:177
msgid "Can change license setting"
msgstr "许可证设置"
-#: settings/models.py:177
+#: settings/models.py:178
msgid "Can change terminal setting"
msgstr "终端设置"
-#: settings/models.py:178
+#: settings/models.py:179
msgid "Can change other setting"
msgstr "其它设置"
-#: settings/models.py:188
+#: settings/models.py:189
msgid "Chat prompt"
msgstr "聊天提示"
@@ -5305,58 +5766,62 @@ msgid "LDAP Auth"
msgstr "LDAP 认证"
#: settings/serializers/auth/base.py:14
+msgid "LDAP Auth HA"
+msgstr "LDAP HA 认证"
+
+#: settings/serializers/auth/base.py:15
msgid "CAS Auth"
msgstr "CAS 认证"
-#: settings/serializers/auth/base.py:15
+#: settings/serializers/auth/base.py:16
msgid "OPENID Auth"
msgstr "OIDC 认证"
-#: settings/serializers/auth/base.py:16
+#: settings/serializers/auth/base.py:17
msgid "SAML2 Auth"
msgstr "SAML2 认证"
-#: settings/serializers/auth/base.py:17
+#: settings/serializers/auth/base.py:18
msgid "OAuth2 Auth"
msgstr "OAuth2 认证"
-#: settings/serializers/auth/base.py:18
+#: settings/serializers/auth/base.py:19
msgid "RADIUS Auth"
msgstr "RADIUS 认证"
-#: settings/serializers/auth/base.py:19
+#: settings/serializers/auth/base.py:20
msgid "DingTalk Auth"
msgstr "钉钉 认证"
-#: settings/serializers/auth/base.py:20
+#: settings/serializers/auth/base.py:21
msgid "FeiShu Auth"
msgstr "飞书 认证"
-#: settings/serializers/auth/base.py:21
+#: settings/serializers/auth/base.py:22
msgid "Lark Auth"
msgstr "Lark 认证"
-#: settings/serializers/auth/base.py:22
+#: settings/serializers/auth/base.py:23
msgid "Slack Auth"
msgstr "Slack 认证"
-#: settings/serializers/auth/base.py:23
+#: settings/serializers/auth/base.py:24
msgid "WeCom Auth"
msgstr "企业微信 认证"
-#: settings/serializers/auth/base.py:24
+#: settings/serializers/auth/base.py:25
msgid "SSO Auth"
msgstr "SSO 令牌认证"
-#: settings/serializers/auth/base.py:25
+#: settings/serializers/auth/base.py:26
msgid "Passkey Auth"
msgstr "Passkey 认证"
-#: settings/serializers/auth/base.py:27
+#: settings/serializers/auth/base.py:28
msgid "Email suffix"
msgstr "邮件后缀"
-#: settings/serializers/auth/base.py:29
+#: settings/serializers/auth/base.py:30
msgid ""
"After third-party user authentication is successful, if the third-party "
"authentication service platform does not return the user's email "
@@ -5366,19 +5831,19 @@ msgstr ""
"第三方用户认证成功后,若第三方认证服务平台未返回该用户的邮箱信息,系统将自动"
"以此邮箱后缀创建用户"
-#: settings/serializers/auth/base.py:36
+#: settings/serializers/auth/base.py:37
msgid "Forgot Password URL"
msgstr "忘记密码链接"
-#: settings/serializers/auth/base.py:37
+#: settings/serializers/auth/base.py:38
msgid "The URL for Forgotten Password on the user login page"
msgstr "用户登录页面忘记密码的 URL"
-#: settings/serializers/auth/base.py:40
+#: settings/serializers/auth/base.py:41
msgid "Login redirection"
msgstr "启用登录跳转提示"
-#: settings/serializers/auth/base.py:42
+#: settings/serializers/auth/base.py:43
msgid ""
"Should an flash page be displayed before the user is redirected to third-"
"party authentication when the administrator enables third-party redirect "
@@ -5387,7 +5852,7 @@ msgstr ""
"当管理员启用第三方重定向身份验证时,在用户重定向到第三方身份验证之前是否显示 "
"Flash 页面"
-#: settings/serializers/auth/base.py:54
+#: settings/serializers/auth/base.py:55
msgid ""
"When you create a user, you associate the user to the organization of your "
"choice. Users always belong to the Default organization."
@@ -5398,8 +5863,8 @@ msgstr ""
msgid "CAS"
msgstr "CAS"
-#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:43
-#: settings/serializers/auth/oidc.py:61
+#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap_ha.py:26 settings/serializers/auth/oidc.py:61
msgid "Server"
msgstr "服务端地址"
@@ -5426,9 +5891,10 @@ msgstr "启用属性映射"
#: settings/serializers/auth/cas.py:34 settings/serializers/auth/dingtalk.py:18
#: settings/serializers/auth/feishu.py:18 settings/serializers/auth/lark.py:17
-#: settings/serializers/auth/ldap.py:65 settings/serializers/auth/oauth2.py:60
-#: settings/serializers/auth/oidc.py:39 settings/serializers/auth/saml2.py:35
-#: settings/serializers/auth/slack.py:18 settings/serializers/auth/wecom.py:18
+#: settings/serializers/auth/ldap.py:66 settings/serializers/auth/ldap_ha.py:48
+#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:39
+#: settings/serializers/auth/saml2.py:35 settings/serializers/auth/slack.py:18
+#: settings/serializers/auth/wecom.py:18
msgid "User attribute"
msgstr "映射属性"
@@ -5470,7 +5936,7 @@ msgstr ""
"用户属性映射,其中 `key` 是 JumpServer 用户属性名称,`value` 是飞书服务用户属"
"性名称"
-#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:21
+#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:22
msgid "Lark"
msgstr ""
@@ -5482,46 +5948,46 @@ msgstr ""
"用户属性映射,其中 `key` 是 JumpServer 用户属性名称,`value` 是 Lark 服务用户"
"属性名称"
-#: settings/serializers/auth/ldap.py:40 settings/serializers/auth/ldap.py:102
+#: settings/serializers/auth/ldap.py:41 settings/serializers/auth/ldap.py:103
msgid "LDAP"
msgstr "LDAP"
-#: settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap.py:45
msgid "LDAP server URI"
msgstr "LDAP 服务域名"
-#: settings/serializers/auth/ldap.py:47
+#: settings/serializers/auth/ldap.py:48 settings/serializers/auth/ldap_ha.py:30
msgid "Bind DN"
msgstr "绑定 DN"
-#: settings/serializers/auth/ldap.py:48
+#: settings/serializers/auth/ldap.py:49 settings/serializers/auth/ldap_ha.py:31
msgid "Binding Distinguished Name"
msgstr "绑定目录管理员"
-#: settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap.py:53 settings/serializers/auth/ldap_ha.py:35
msgid "Binding password"
msgstr "绑定密码"
-#: settings/serializers/auth/ldap.py:55
+#: settings/serializers/auth/ldap.py:56 settings/serializers/auth/ldap_ha.py:38
msgid "Search OU"
msgstr "用户 OU"
-#: settings/serializers/auth/ldap.py:57
+#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/ldap_ha.py:40
msgid ""
"User Search Base, if there are multiple OUs, you can separate them with the "
"`|` symbol"
msgstr "用户搜索库,如果有多个OU,可以用`|`符号分隔"
-#: settings/serializers/auth/ldap.py:61
+#: settings/serializers/auth/ldap.py:62 settings/serializers/auth/ldap_ha.py:44
msgid "Search filter"
msgstr "用户过滤器"
-#: settings/serializers/auth/ldap.py:62
+#: settings/serializers/auth/ldap.py:63 settings/serializers/auth/ldap_ha.py:45
#, python-format
msgid "Selection could include (cn|uid|sAMAccountName=%(user)s)"
msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)"
-#: settings/serializers/auth/ldap.py:67
+#: settings/serializers/auth/ldap.py:68 settings/serializers/auth/ldap_ha.py:50
msgid ""
"User attribute mapping, where the `key` is the JumpServer user attribute "
"name and the `value` is the LDAP service user attribute name"
@@ -5529,28 +5995,47 @@ msgstr ""
"用户属性映射,其中 `key` 是 JumpServer 用户属性名称,`value` 是 LDAP 服务用户"
"属性名称"
-#: settings/serializers/auth/ldap.py:83
+#: settings/serializers/auth/ldap.py:84 settings/serializers/auth/ldap_ha.py:66
msgid "Connect timeout (s)"
msgstr "连接超时时间 (秒)"
-#: settings/serializers/auth/ldap.py:88
+#: settings/serializers/auth/ldap.py:89 settings/serializers/auth/ldap_ha.py:71
msgid "User DN cache timeout (s)"
msgstr "User DN 缓存超时时间 (秒)"
-#: settings/serializers/auth/ldap.py:90
+#: settings/serializers/auth/ldap.py:91
msgid ""
"Caching the User DN obtained during user login authentication can "
-"effectivelyimprove the speed of user authentication., 0 means no cache
If "
-"the user OU structure has been adjusted, click Submit to clear the user DN "
-"cache"
+"effectively improve the speed of user authentication., 0 means no "
+"cache
If the user OU structure has been adjusted, click Submit to clear "
+"the user DN cache"
msgstr ""
"对用户登录认证时查询出的 User DN 进行缓存,可以有效提高用户认证的速度
如果"
"用户 OU 架构有调整,点击提交即可清除用户 DN 缓存"
-#: settings/serializers/auth/ldap.py:96
+#: settings/serializers/auth/ldap.py:97 settings/serializers/auth/ldap_ha.py:79
msgid "Search paged size (piece)"
msgstr "搜索分页数量 (条)"
+#: settings/serializers/auth/ldap_ha.py:23
+#: settings/serializers/auth/ldap_ha.py:85
+msgid "LDAP HA"
+msgstr "LDAP 认证"
+
+#: settings/serializers/auth/ldap_ha.py:27
+msgid "LDAP HA server URI"
+msgstr "LDAP HA 服务域名"
+
+#: settings/serializers/auth/ldap_ha.py:73
+msgid ""
+"Caching the User DN obtained during user login authentication can "
+"effectivelyimprove the speed of user authentication., 0 means no cache
If "
+"the user OU structure has been adjusted, click Submit to clear the user DN "
+"cache"
+msgstr ""
+"对用户登录认证时查询出的 User DN 进行缓存,可以有效提高用户认证的速度
如果"
+"用户 OU 架构有调整,点击提交即可清除用户 DN 缓存"
+
#: settings/serializers/auth/oauth2.py:19
#: settings/serializers/auth/oauth2.py:22
msgid "OAuth2"
@@ -5987,32 +6472,42 @@ msgid ""
msgstr ""
"会话、录像,命令记录超过该时长将会被清除 (影响数据库存储,OSS 等不受影响)"
-#: settings/serializers/feature.py:18 settings/serializers/msg.py:68
+#: settings/serializers/cleaning.py:53
+msgid "Change secret and push record retention days (day)"
+msgstr "改密推送记录保留天数 (天)"
+
+#: settings/serializers/feature.py:19 settings/serializers/msg.py:68
msgid "Subject"
msgstr "主题"
-#: settings/serializers/feature.py:22
+#: settings/serializers/feature.py:23
msgid "More Link"
msgstr "更多信息 URL"
-#: settings/serializers/feature.py:36 settings/serializers/feature.py:38
-#: settings/serializers/feature.py:39
+#: settings/serializers/feature.py:26
+#: settings/templates/ldap/_msg_import_ldap_user.html:6
+#: terminal/models/session/session.py:46
+msgid "Date end"
+msgstr "结束日期"
+
+#: settings/serializers/feature.py:39 settings/serializers/feature.py:41
+#: settings/serializers/feature.py:42
msgid "Announcement"
msgstr "公告"
-#: settings/serializers/feature.py:46
+#: settings/serializers/feature.py:49
msgid "Vault"
msgstr "启用 Vault"
-#: settings/serializers/feature.py:55
+#: settings/serializers/feature.py:58
msgid "Mount Point"
msgstr "挂载点"
-#: settings/serializers/feature.py:61
+#: settings/serializers/feature.py:64
msgid "Record limit"
msgstr "记录限制"
-#: settings/serializers/feature.py:63
+#: settings/serializers/feature.py:66
msgid ""
"If the specific value is less than 999 (default), the system will "
"automatically perform a task every night: check and delete historical "
@@ -6022,74 +6517,74 @@ msgstr ""
"若特定数值小于999,系统将在每日晚间自动执行任务:检查并删除超出预定数量的历史"
"账号。如果该数值达到或超过999,则不进行任何历史账号的删除操作。"
-#: settings/serializers/feature.py:73 settings/serializers/feature.py:79
+#: settings/serializers/feature.py:76 settings/serializers/feature.py:82
msgid "Chat AI"
msgstr "聊天 AI"
-#: settings/serializers/feature.py:82
+#: settings/serializers/feature.py:85
msgid "GPT Base URL"
msgstr "GPT 地址"
-#: settings/serializers/feature.py:83
+#: settings/serializers/feature.py:86
msgid "The base URL of the GPT service. For example: https://api.openai.com/v1"
msgstr "GPT 服务的基本 URL。例如:https://api.openai.com/v1"
-#: settings/serializers/feature.py:86 templates/_header_bar.html:96
+#: settings/serializers/feature.py:89 templates/_header_bar.html:96
msgid "API Key"
msgstr "API Key"
-#: settings/serializers/feature.py:90
+#: settings/serializers/feature.py:93
msgid ""
"The proxy server address of the GPT service. For example: http://ip:port"
msgstr "GPT 服务的代理服务器地址。例如:http://ip:port"
-#: settings/serializers/feature.py:93
+#: settings/serializers/feature.py:96
msgid "GPT Model"
msgstr "GPT 模型"
-#: settings/serializers/feature.py:102
+#: settings/serializers/feature.py:105
msgid "Approval without login"
msgstr "免登录审批"
-#: settings/serializers/feature.py:103
+#: settings/serializers/feature.py:106
msgid "Allow direct approval ticket without login"
msgstr "允许无需登录直接批准工单"
-#: settings/serializers/feature.py:107
+#: settings/serializers/feature.py:110
msgid "Period"
msgstr "时段"
-#: settings/serializers/feature.py:108
+#: settings/serializers/feature.py:111
msgid ""
"The default authorization time period when applying for assets via a ticket"
msgstr "工单申请资产的默认授权时间段"
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "hour"
msgstr "时"
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "Unit"
msgstr "单位"
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "The unit of period"
msgstr "执行周期"
-#: settings/serializers/feature.py:121
+#: settings/serializers/feature.py:124
msgid ""
"Allow users to execute batch commands in the Workbench - Job Center - Adhoc"
msgstr "允许用户在工作台 - 作业中心 - Adhoc 中执行批量命令"
-#: settings/serializers/feature.py:125
+#: settings/serializers/feature.py:128
msgid "Command blacklist"
msgstr "作业中心命令黑名单"
-#: settings/serializers/feature.py:126
+#: settings/serializers/feature.py:129
msgid "Command blacklist in Adhoc"
msgstr "作业中心命令黑名单"
-#: settings/serializers/feature.py:131
+#: settings/serializers/feature.py:134
#: terminal/models/virtualapp/provider.py:17
#: terminal/models/virtualapp/virtualapp.py:36
#: terminal/models/virtualapp/virtualapp.py:97
@@ -6097,11 +6592,11 @@ msgstr "作业中心命令黑名单"
msgid "Virtual app"
msgstr "虚拟应用"
-#: settings/serializers/feature.py:134
+#: settings/serializers/feature.py:137
msgid "Virtual App"
msgstr "虚拟应用"
-#: settings/serializers/feature.py:136
+#: settings/serializers/feature.py:139
msgid ""
"Virtual applications, you can use the Linux operating system as an "
"application server in remote applications."
@@ -6546,23 +7041,50 @@ msgid ""
"in the workbench"
msgstr "*! 如果启用,具有 RBAC 权限的用户将能够使用工作台中的所有工具"
-#: settings/tasks/ldap.py:28
+#: settings/tasks/ldap.py:73
msgid "Periodic import ldap user"
msgstr "周期导入 LDAP 用户"
-#: settings/tasks/ldap.py:66
+#: settings/tasks/ldap.py:75 settings/tasks/ldap.py:85
+msgid ""
+"When LDAP auto-sync is configured, this task will be invoked to synchronize "
+"users"
+msgstr "当设置了LDAP自动同步,将调用该任务进行用户同步"
+
+#: settings/tasks/ldap.py:83
+msgid "Periodic import ldap ha user"
+msgstr "周期导入 LDAP HA 用户"
+
+#: settings/tasks/ldap.py:117
msgid "Registration periodic import ldap user task"
msgstr "注册周期导入 LDAP 用户 任务"
+#: settings/tasks/ldap.py:119
+msgid ""
+"When LDAP auto-sync parameters change, such as Crontab parameters, the LDAP "
+"sync task \n"
+" will be re-registered or updated, and this task will be invoked"
+msgstr ""
+"当设置了LDAP自动同步参数发生变化时,比如Crontab参数,重新注册或更新ldap同步任"
+"务将调用该任务"
+
+#: settings/tasks/ldap.py:133
+msgid "Registration periodic import ldap ha user task"
+msgstr "注册周期导入 LDAP HA 用户 任务"
+
+#: settings/tasks/ldap.py:135
+msgid ""
+"When LDAP HA auto-sync parameters change, such as Crontab parameters, the "
+"LDAP HA sync task \n"
+" will be re-registered or updated, and this task will be invoked"
+msgstr ""
+"当设置了LDAP HA 自动同步参数发生变化时,比如Crontab参数,重新注册或更新ldap "
+"ha 同步任务将调用该任务"
+
#: settings/templates/ldap/_msg_import_ldap_user.html:2
msgid "Sync task finish"
msgstr "同步任务完成"
-#: settings/templates/ldap/_msg_import_ldap_user.html:6
-#: terminal/models/session/session.py:46
-msgid "Date end"
-msgstr "结束日期"
-
#: settings/templates/ldap/_msg_import_ldap_user.html:9
msgid "Synced Organization"
msgstr "已同步组织"
@@ -6575,108 +7097,108 @@ msgstr "已同步用户"
msgid "No user synchronization required"
msgstr "没有用户需要同步"
-#: settings/utils/ldap.py:494
+#: settings/utils/ldap.py:509
msgid "ldap:// or ldaps:// protocol is used."
msgstr "使用 ldap:// 或 ldaps:// 协议"
-#: settings/utils/ldap.py:505
+#: settings/utils/ldap.py:520
msgid "Host or port is disconnected: {}"
msgstr "主机或端口不可连接: {}"
-#: settings/utils/ldap.py:507
+#: settings/utils/ldap.py:522
msgid "The port is not the port of the LDAP service: {}"
msgstr "端口不是LDAP服务端口: {}"
-#: settings/utils/ldap.py:509
+#: settings/utils/ldap.py:524
msgid "Please add certificate: {}"
msgstr "请添加证书"
-#: settings/utils/ldap.py:513 settings/utils/ldap.py:540
-#: settings/utils/ldap.py:570 settings/utils/ldap.py:598
+#: settings/utils/ldap.py:528 settings/utils/ldap.py:555
+#: settings/utils/ldap.py:585 settings/utils/ldap.py:613
msgid "Unknown error: {}"
msgstr "未知错误: {}"
-#: settings/utils/ldap.py:527
+#: settings/utils/ldap.py:542
msgid "Bind DN or Password incorrect"
msgstr "绑定DN或密码错误"
-#: settings/utils/ldap.py:534
+#: settings/utils/ldap.py:549
msgid "Please enter Bind DN: {}"
msgstr "请输入绑定DN: {}"
-#: settings/utils/ldap.py:536
+#: settings/utils/ldap.py:551
msgid "Please enter Password: {}"
msgstr "请输入密码: {}"
-#: settings/utils/ldap.py:538
+#: settings/utils/ldap.py:553
msgid "Please enter correct Bind DN and Password: {}"
msgstr "请输入正确的绑定DN和密码: {}"
-#: settings/utils/ldap.py:556
+#: settings/utils/ldap.py:571
msgid "Invalid User OU or User search filter: {}"
msgstr "不合法的用户OU或用户过滤器: {}"
-#: settings/utils/ldap.py:587
+#: settings/utils/ldap.py:602
msgid "LDAP User attr map not include: {}"
msgstr "LDAP属性映射没有包含: {}"
-#: settings/utils/ldap.py:594
+#: settings/utils/ldap.py:609
msgid "LDAP User attr map is not dict"
msgstr "LDAP属性映射不合法"
-#: settings/utils/ldap.py:613
+#: settings/utils/ldap.py:628
msgid "LDAP authentication is not enabled"
msgstr "LDAP认证没有启用"
-#: settings/utils/ldap.py:631
+#: settings/utils/ldap.py:646
msgid "Error (Invalid LDAP server): {}"
msgstr "错误 (不合法的LDAP服务器地址): {}"
-#: settings/utils/ldap.py:633
+#: settings/utils/ldap.py:648
msgid "Error (Invalid Bind DN): {}"
msgstr "错误 (不合法的绑定DN): {}"
-#: settings/utils/ldap.py:635
+#: settings/utils/ldap.py:650
msgid "Error (Invalid LDAP User attr map): {}"
msgstr "错误 (不合法的LDAP属性映射): {}"
-#: settings/utils/ldap.py:637
+#: settings/utils/ldap.py:652
msgid "Error (Invalid User OU or User search filter): {}"
msgstr "错误 (不合法的用户OU或用户过滤器): {}"
-#: settings/utils/ldap.py:639
+#: settings/utils/ldap.py:654
msgid "Error (Not enabled LDAP authentication): {}"
msgstr "错误 (没有启用LDAP认证): {}"
-#: settings/utils/ldap.py:641
+#: settings/utils/ldap.py:656
msgid "Error (Unknown): {}"
msgstr "错误 (未知): {}"
-#: settings/utils/ldap.py:644
+#: settings/utils/ldap.py:659
msgid "Succeed: Match {} users"
msgstr "成功匹配 {} 个用户"
-#: settings/utils/ldap.py:677
+#: settings/utils/ldap.py:689
msgid "Authentication failed (configuration incorrect): {}"
msgstr "认证失败 (配置错误): {}"
-#: settings/utils/ldap.py:681
+#: settings/utils/ldap.py:693
msgid "Authentication failed (username or password incorrect): {}"
msgstr "认证失败 (用户名或密码不正确): {}"
-#: settings/utils/ldap.py:683
+#: settings/utils/ldap.py:695
msgid "Authentication failed (Unknown): {}"
msgstr "认证失败: (未知): {}"
-#: settings/utils/ldap.py:686
+#: settings/utils/ldap.py:698
msgid "Authentication success: {}"
msgstr "认证成功: {}"
-#: settings/ws.py:198
+#: settings/ws.py:199
msgid "No LDAP user was found"
msgstr "没有获取到 LDAP 用户"
-#: settings/ws.py:204
+#: settings/ws.py:205
msgid "Total {}, success {}, failure {}"
msgstr "总共 {},成功 {},失败 {}"
@@ -6896,27 +7418,27 @@ msgstr "未发现 protocol 查询参数"
msgid "Deleting the default storage is not allowed"
msgstr "不允许删除默认存储配置"
-#: terminal/api/component/storage.py:34
-msgid "Cannot delete storage that is being used"
-msgstr "不允许删除正在使用的存储配置"
+#: terminal/api/component/storage.py:36
+msgid "Cannot delete storage that is being used: {}"
+msgstr "无法删除正在使用的存储: {}"
-#: terminal/api/component/storage.py:75 terminal/api/component/storage.py:76
+#: terminal/api/component/storage.py:77 terminal/api/component/storage.py:78
msgid "Command storages"
msgstr "命令存储"
-#: terminal/api/component/storage.py:82
+#: terminal/api/component/storage.py:84
msgid "Invalid"
msgstr "无效"
-#: terminal/api/component/storage.py:130 terminal/tasks.py:149
+#: terminal/api/component/storage.py:132 terminal/tasks.py:208
msgid "Test failure: {}"
msgstr "测试失败: {}"
-#: terminal/api/component/storage.py:133
+#: terminal/api/component/storage.py:135
msgid "Test successful"
msgstr "测试成功"
-#: terminal/api/component/storage.py:135
+#: terminal/api/component/storage.py:137
msgid "Test failure: Please check configuration"
msgstr "测试失败:请检查配置"
@@ -6929,15 +7451,15 @@ msgstr "有在线会话"
msgid "User %s %s session %s replay"
msgstr "用户 %s %s 了会话 %s 的录像"
-#: terminal/api/session/session.py:314
+#: terminal/api/session/session.py:326
msgid "Session does not exist: {}"
msgstr "会话不存在: {}"
-#: terminal/api/session/session.py:317
+#: terminal/api/session/session.py:329
msgid "Session is finished or the protocol not supported"
msgstr "会话已经完成或协议不支持"
-#: terminal/api/session/session.py:330
+#: terminal/api/session/session.py:342
msgid "User does not have permission"
msgstr "用户没有权限"
@@ -7101,7 +7623,7 @@ msgstr "版本"
msgid "Can concurrent"
msgstr "可以并发"
-#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:167
+#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:179
#: terminal/serializers/storage.py:193
msgid "Hosts"
msgstr "主机"
@@ -7132,7 +7654,7 @@ msgstr "宿主机"
msgid "Applet Publication"
msgstr "应用发布"
-#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:69
+#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:81
msgid "Deploy options"
msgstr "部署参数"
@@ -7244,12 +7766,12 @@ msgstr "线程数"
msgid "Boot Time"
msgstr "运行时间"
-#: terminal/models/component/storage.py:146
+#: terminal/models/component/storage.py:144
#: terminal/models/component/terminal.py:91
msgid "Command storage"
msgstr "命令存储"
-#: terminal/models/component/storage.py:214
+#: terminal/models/component/storage.py:212
#: terminal/models/component/terminal.py:92
msgid "Replay storage"
msgstr "录像存储"
@@ -7266,7 +7788,7 @@ msgstr "远端地址"
msgid "Application User"
msgstr "应用用户"
-#: terminal/models/component/terminal.py:177
+#: terminal/models/component/terminal.py:176
msgid "Can view terminal config"
msgstr "可以查看终端配置"
@@ -7298,7 +7820,7 @@ msgstr "登录来源"
msgid "Replay"
msgstr "回放"
-#: terminal/models/session/session.py:48 terminal/serializers/session.py:68
+#: terminal/models/session/session.py:48 terminal/serializers/session.py:78
msgid "Command amount"
msgstr "命令数量"
@@ -7306,23 +7828,23 @@ msgstr "命令数量"
msgid "Error reason"
msgstr "错误原因"
-#: terminal/models/session/session.py:290
+#: terminal/models/session/session.py:308
msgid "Session record"
msgstr "会话记录"
-#: terminal/models/session/session.py:292
+#: terminal/models/session/session.py:310
msgid "Can monitor session"
msgstr "可以监控会话"
-#: terminal/models/session/session.py:293
+#: terminal/models/session/session.py:311
msgid "Can share session"
msgstr "可以分享会话"
-#: terminal/models/session/session.py:294
+#: terminal/models/session/session.py:312
msgid "Can terminate session"
msgstr "可以终断会话"
-#: terminal/models/session/session.py:295
+#: terminal/models/session/session.py:313
msgid "Can validate session action perm"
msgstr "可以验证会话动作权限"
@@ -7422,7 +7944,7 @@ msgstr "级别"
msgid "Command and replay storage"
msgstr "命令及录像存储"
-#: terminal/notifications.py:240 terminal/tasks.py:153
+#: terminal/notifications.py:240 terminal/tasks.py:212
#: xpack/plugins/cloud/api.py:160
#: xpack/plugins/cloud/serializers/account.py:121
#: xpack/plugins/cloud/serializers/account.py:123
@@ -7439,12 +7961,12 @@ msgid "Icon"
msgstr "图标"
#: terminal/serializers/applet_host.py:24
-msgid "Per Session"
-msgstr "每用户"
+msgid "Per Device (Device number limit)"
+msgstr "每用户 (限制设备数量)"
#: terminal/serializers/applet_host.py:25
-msgid "Per Device"
-msgstr "每设备"
+msgid "Per User (User number limit)"
+msgstr "每设备 (限制用户数量)"
#: terminal/serializers/applet_host.py:37
msgid "Core API"
@@ -7463,6 +7985,7 @@ msgid ""
" eg: https://172.16.10.110 or https://dev.jumpserver.com\n"
" "
msgstr ""
+" \n"
"提示:应用发布机和 Core 服务进行通信使用,如果发布机和 Core 服务在同一网段,"
"建议填写内网地址,否则填写当前站点 URL
例如:https://172.16.10.110 or "
"https://dev.jumpserver.com"
@@ -7471,27 +7994,45 @@ msgstr ""
msgid "Ignore Certificate Verification"
msgstr "忽略证书认证"
-#: terminal/serializers/applet_host.py:47
+#: terminal/serializers/applet_host.py:48
msgid "Existing RDS license"
msgstr "已有 RDS 许可证"
-#: terminal/serializers/applet_host.py:48
+#: terminal/serializers/applet_host.py:50
+msgid ""
+"If not exist, the RDS will be in trial mode, and the trial period is 120 "
+"days. Detail"
+msgstr ""
+"如果不存在,RDS 将处于试用模式,试用期为 120 天。详情"
+
+#: terminal/serializers/applet_host.py:55
msgid "RDS License Server"
msgstr "RDS 许可服务器"
-#: terminal/serializers/applet_host.py:49
+#: terminal/serializers/applet_host.py:57
msgid "RDS Licensing Mode"
msgstr "RDS 授权模式"
-#: terminal/serializers/applet_host.py:51
+#: terminal/serializers/applet_host.py:60
msgid "RDS Single Session Per User"
msgstr "RDS 单用户单会话"
-#: terminal/serializers/applet_host.py:53
+#: terminal/serializers/applet_host.py:61
+msgid ""
+"Tips: A RDS user can have only one session at a time. If set, when next "
+"login connected, previous session will be disconnected."
+msgstr ""
+"提示:RDS 用户一次只能有一个会话。如果设置了,当下一次登录连接时,之前的会话"
+"将会被断开"
+
+#: terminal/serializers/applet_host.py:65
msgid "RDS Max Disconnection Time (ms)"
msgstr "RDS 最大断开时间(毫秒)"
-#: terminal/serializers/applet_host.py:55
+#: terminal/serializers/applet_host.py:67
msgid ""
"Tips: Set the maximum duration for keeping a disconnected session active on "
"the server (log off the session after 60000 milliseconds)."
@@ -7499,11 +8040,11 @@ msgstr ""
"提示:设置某个已断开连接的会话在服务器上能保持活动状态的最长时间(60000 毫秒"
"后注销会话)"
-#: terminal/serializers/applet_host.py:60
+#: terminal/serializers/applet_host.py:72
msgid "RDS Remote App Logoff Time Limit (ms)"
msgstr "RDS 远程应用注销时间限制(毫秒)"
-#: terminal/serializers/applet_host.py:62
+#: terminal/serializers/applet_host.py:74
msgid ""
"Tips: Set the logoff time for RemoteApp sessions after closing all RemoteApp "
"programs (0 milliseconds, log off the session immediately)."
@@ -7511,12 +8052,12 @@ msgstr ""
"提示:关闭所有 RemoteApp 程序之后设置 RemoteAPP 会话的注销时间(0 毫秒,立即"
"注销会话)"
-#: terminal/serializers/applet_host.py:71 terminal/serializers/terminal.py:47
+#: terminal/serializers/applet_host.py:83 terminal/serializers/terminal.py:47
#: terminal/serializers/virtualapp_provider.py:13
msgid "Load status"
msgstr "负载状态"
-#: terminal/serializers/applet_host.py:85
+#: terminal/serializers/applet_host.py:97
msgid ""
"These accounts are used to connect to the published application, the account "
"is now divided into two types, one is dedicated to each account, each user "
@@ -7529,11 +8070,11 @@ msgstr ""
"使用公共账号连接;
注意: 如果不开启自动创建账号, 当前发布机仅能被指定标"
"签的资产调度到,默认不会放到调度池中,且需要手动维护账号"
-#: terminal/serializers/applet_host.py:92
+#: terminal/serializers/applet_host.py:104
msgid "The number of public accounts created automatically"
msgstr "公用账号自动创建的数量"
-#: terminal/serializers/applet_host.py:95
+#: terminal/serializers/applet_host.py:107
msgid ""
"Connect to the host using the same account first. For security reasons, "
"please set the configuration item CACHE_LOGIN_PASSWORD_ENABLED=true and "
@@ -7542,15 +8083,15 @@ msgstr ""
"优先使用同名账号连接发布机。为了安全,需配置文件中开启配置 "
"CACHE_LOGIN_PASSWORD_ENABLED=true, 修改后重启服务"
-#: terminal/serializers/applet_host.py:137
+#: terminal/serializers/applet_host.py:149
msgid "Install applets"
msgstr "安装应用"
-#: terminal/serializers/applet_host.py:167
+#: terminal/serializers/applet_host.py:179
msgid "Host ID"
msgstr "主机 ID"
-#: terminal/serializers/applet_host.py:168
+#: terminal/serializers/applet_host.py:180
msgid "Applet ID"
msgstr "远程应用 ID"
@@ -7875,39 +8416,102 @@ msgstr "授权已过期"
msgid "storage is null"
msgstr "存储为空"
-#: terminal/tasks.py:31
+#: terminal/tasks.py:32
msgid "Periodic delete terminal status"
msgstr "周期清理终端状态"
-#: terminal/tasks.py:39
+#: terminal/tasks.py:43
msgid "Clean orphan session"
msgstr "清除离线会话"
-#: terminal/tasks.py:87
+#: terminal/tasks.py:45
+msgid ""
+"Check every 10 minutes for asset connection sessions that have been inactive "
+"for 3 \n"
+" minutes and mark these sessions as completed"
+msgstr "每10分钟检查3分钟未活跃的资产连接会话,将这些会话标记未已完成"
+
+#: terminal/tasks.py:68
+msgid "Upload session replay to external storage"
+msgstr "上传会话录像到外部存储"
+
+#: terminal/tasks.py:70 terminal/tasks.py:104
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured in the config.txt, session commands "
+"and \n"
+" recordings will be uploaded to external storage"
+msgstr ""
+"如果设置了SERVER_REPLAY_STORAGE,将通过文件管理上传的文件同步到外部存储"
+
+#: terminal/tasks.py:102
+msgid "Upload session replay part file to external storage"
+msgstr "将会话重播部分文件上传到外部存储"
+
+#: terminal/tasks.py:123
msgid "Run applet host deployment"
msgstr "运行应用机部署"
-#: terminal/tasks.py:97
+#: terminal/tasks.py:126
+msgid ""
+"When deploying from the remote application publisher details page, and the "
+"'Deploy' \n"
+" button is clicked, this task will be executed"
+msgstr "发布机部署,点击部署时,执行该任务"
+
+#: terminal/tasks.py:137
msgid "Install applet"
msgstr "安装应用"
-#: terminal/tasks.py:108
+#: terminal/tasks.py:140
+msgid ""
+"When the 'Deploy' button is clicked in the 'Remote Application' section of "
+"the remote \n"
+" application publisher details page, this task will be executed"
+msgstr "当远程应用发布机详情-远程应用,点击部署时,执行该任务"
+
+#: terminal/tasks.py:152
msgid "Uninstall applet"
msgstr "卸载应用"
-#: terminal/tasks.py:119
+#: terminal/tasks.py:155
+msgid ""
+"When the 'Uninstall' button is clicked in the 'Remote Application' section "
+"of the \n"
+" remote application publisher details page, this task will be executed"
+msgstr "当远程应用发布机详情-远程应用,点击卸载时,执行该任务"
+
+#: terminal/tasks.py:167
msgid "Generate applet host accounts"
msgstr "收集远程应用上的账号"
-#: terminal/tasks.py:131
+#: terminal/tasks.py:170
+msgid ""
+"When a remote publishing server is created and an account needs to be "
+"created \n"
+" automatically, this task will be executed"
+msgstr "当创建远程发布机后,需要自动创建账号时,执行该任务"
+
+#: terminal/tasks.py:184
msgid "Check command replay storage connectivity"
msgstr "检查命令及录像存储可连接性 "
+#: terminal/tasks.py:186
+msgid ""
+"Check every day at midnight whether the external storage for commands and "
+"recordings \n"
+" is accessible. If it is not accessible, send a notification to the "
+"recipients specified \n"
+" in 'System Settings - Notifications - Subscription - Storage - "
+"Connectivity'"
+msgstr ""
+"每天凌晨0点检查命令及录像外部存储是否可连接,不可连接发送给:系统设置-通知设"
+"置-消息订阅-命令及录像存储设置的接收人"
+
#: terminal/templates/terminal/_msg_command_alert.html:10
msgid "view"
msgstr "查看"
-#: terminal/utils/db_port_mapper.py:85
+#: terminal/utils/db_port_mapper.py:88
msgid ""
"No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the "
@@ -7916,13 +8520,13 @@ msgstr ""
"未匹配到可用端口,数据库的数量可能已经超过数据库代理服务开放的端口数量,请联"
"系管理员开放更多端口。"
-#: terminal/utils/db_port_mapper.py:113
+#: terminal/utils/db_port_mapper.py:116
msgid ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
msgstr "没有端口可以使用,检查并修改配置文件中 Magnus 监听的端口数量限制。"
-#: terminal/utils/db_port_mapper.py:115
+#: terminal/utils/db_port_mapper.py:118
msgid "All available port count: {}, Already use port count: {}"
msgstr "所有可用端口数量:{},已使用端口数量:{}"
@@ -7985,19 +8589,19 @@ msgid ""
msgstr ""
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Change field"
msgstr "变更字段"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Before change"
msgstr "变更前"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "After change"
msgstr "变更后"
-#: tickets/handlers/base.py:97
+#: tickets/handlers/base.py:96
msgid "{} {} the ticket"
msgstr "{} {} 工单"
@@ -8235,11 +8839,15 @@ msgstr "无效的审批动作"
msgid "This user is not authorized to approve this ticket"
msgstr "此用户无权审批此工单"
-#: users/api/user.py:155
+#: users/api/user.py:63
+msgid "Cannot delete the admin user. Please disable it instead."
+msgstr "无法删除管理员用户。请将其禁用。"
+
+#: users/api/user.py:161
msgid "Can not invite self"
msgstr "不能邀请自己"
-#: users/api/user.py:208
+#: users/api/user.py:214
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置"
@@ -8699,7 +9307,7 @@ msgstr "* 为安全起见,只显示部分用户。您可以搜索更多"
msgid "name not unique"
msgstr "名称重复"
-#: users/signal_handlers.py:39
+#: users/signal_handlers.py:41
msgid ""
"The administrator has enabled \"Only allow existing users to log in\", \n"
" and the current user is not in the user list. Please contact the "
@@ -8707,31 +9315,86 @@ msgid ""
msgstr ""
"管理员已开启'仅允许已存在用户登录',当前用户不在用户列表中,请联系管理员。"
-#: users/signal_handlers.py:183
+#: users/signal_handlers.py:196
msgid "Clean up expired user sessions"
msgstr "清除过期的用户会话"
-#: users/tasks.py:25
+#: users/signal_handlers.py:198
+msgid ""
+"After logging in via the web, a user session record is created. At 2 a.m. "
+"every day, \n"
+" the system cleans up inactive user devices"
+msgstr ""
+"使用web登录后,会产生用户会话在线记录,每天凌晨2点,清理未在线的用户设备"
+
+#: users/tasks.py:26
msgid "Check password expired"
msgstr "校验密码已过期"
-#: users/tasks.py:39
+#: users/tasks.py:28
+msgid ""
+"Check every day at 10 AM whether the passwords of users in the system are "
+"expired, \n"
+" and send a notification 5 days in advance"
+msgstr "每天上午10点检查,系统中用户的密码是否过期,提前5天发送通知"
+
+#: users/tasks.py:46
msgid "Periodic check password expired"
msgstr "周期校验密码过期"
-#: users/tasks.py:53
+#: users/tasks.py:48
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if passwords have expired"
+msgstr ""
+"随着版本迭代,可能会新增任务或者修改任务的名称,执行时间,所以在系统启动时,"
+"注册或者更新校验密码已过期任务的参数"
+
+#: users/tasks.py:67
msgid "Check user expired"
msgstr "校验用户已过期"
-#: users/tasks.py:70
+#: users/tasks.py:69
+msgid ""
+"Check every day at 2 p.m whether the users in the system are expired, and "
+"send a \n"
+" notification 5 days in advance"
+msgstr "每天下午2点检查,系统中的用户是否过期,提前5天发送通知"
+
+#: users/tasks.py:90
msgid "Periodic check user expired"
msgstr "周期检测用户过期"
-#: users/tasks.py:84
+#: users/tasks.py:92
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if users have expired"
+msgstr ""
+"随着版本迭代,可能会新增任务或者修改任务的名称,执行时间,所以在系统启动时,"
+"注册或者更新校验用户已过期任务的参数"
+
+#: users/tasks.py:111
msgid "Check unused users"
msgstr "检查未使用的用户"
-#: users/tasks.py:123
+#: users/tasks.py:113
+msgid ""
+"At 2 p.m. every day, according to the configuration in \"System Settings - "
+"Security - \n"
+" Auth security - Auto disable threshold\" users who have not logged "
+"in or whose API keys \n"
+" have not been used for a long time will be disabled"
+msgstr ""
+"每天下午2点,根据系统配置-安全设置-不活跃用户自动禁用配置,对长时间不登录或"
+"api_key不使用的用户进行禁用"
+
+#: users/tasks.py:157
msgid "The user has not logged in recently and has been disabled."
msgstr "该用户最近未登录,已被禁用。"
@@ -9178,49 +9841,49 @@ msgstr "获取区域 \"%s\" 的实例错误,错误:%s"
msgid "Failed to synchronize the instance \"%s\""
msgstr "无法同步实例 %s"
-#: xpack/plugins/cloud/manager.py:337
+#: xpack/plugins/cloud/manager.py:336
#, python-format
msgid ""
"The updated platform of asset \"%s\" is inconsistent with the original "
"platform type. Skip platform and protocol updates"
msgstr "资产“%s”的更新平台与原平台类型不一致。跳过平台和协议更新"
-#: xpack/plugins/cloud/manager.py:393
+#: xpack/plugins/cloud/manager.py:392
#, python-format
msgid "The asset \"%s\" already exists"
msgstr "资产 \"%s\" 已存在"
-#: xpack/plugins/cloud/manager.py:395
+#: xpack/plugins/cloud/manager.py:394
#, python-format
msgid "Update asset \"%s\""
msgstr "更新资产 \"%s\""
-#: xpack/plugins/cloud/manager.py:398
+#: xpack/plugins/cloud/manager.py:397
#, python-format
msgid "Asset \"%s\" has been updated"
msgstr "资产 \"%s\" 已更新"
-#: xpack/plugins/cloud/manager.py:408
+#: xpack/plugins/cloud/manager.py:407
#, python-format
msgid "Prepare to create asset \"%s\""
msgstr "准备创建资产 %s"
-#: xpack/plugins/cloud/manager.py:429
+#: xpack/plugins/cloud/manager.py:428
#, python-format
msgid "Set nodes \"%s\""
msgstr "删除节点: \"%s\""
-#: xpack/plugins/cloud/manager.py:455
+#: xpack/plugins/cloud/manager.py:454
#, python-format
msgid "Set accounts \"%s\""
msgstr "删除账号: %s"
-#: xpack/plugins/cloud/manager.py:471
+#: xpack/plugins/cloud/manager.py:470
#, python-format
msgid "Set protocols \"%s\""
msgstr "设置协议 \"%s\""
-#: xpack/plugins/cloud/manager.py:485 xpack/plugins/cloud/tasks.py:30
+#: xpack/plugins/cloud/manager.py:484 xpack/plugins/cloud/tasks.py:31
msgid "Run sync instance task"
msgstr "执行同步实例任务"
@@ -9695,10 +10358,34 @@ msgstr "执行次数"
msgid "Instance count"
msgstr "实例个数"
-#: xpack/plugins/cloud/tasks.py:44
+#: xpack/plugins/cloud/tasks.py:33
+msgid ""
+"\n"
+" Execute this task when manually or scheduled cloud synchronization "
+"tasks are performed\n"
+" "
+msgstr ""
+"\n"
+"手动,定时执行云同步任务时执行该任务"
+
+#: xpack/plugins/cloud/tasks.py:52
msgid "Period clean sync instance task execution"
msgstr "定期清除同步实例任务执行记录"
+#: xpack/plugins/cloud/tasks.py:54
+msgid ""
+"\n"
+" Every day, according to the configuration in \"System Settings - "
+"Tasks - Regular \n"
+" clean-up - Cloud sync task history retention days\" the system will "
+"clean up the execution \n"
+" records generated by cloud synchronization\n"
+" "
+msgstr ""
+"\n"
+"每天根据系统设置-任务列表-定期清理配置-云同步记录配置,对云同步产生的执行记录"
+"进行清理"
+
#: xpack/plugins/interface/api.py:52
msgid "Restore default successfully."
msgstr "恢复默认成功!"
diff --git a/apps/i18n/core/zh_Hant/LC_MESSAGES/django.po b/apps/i18n/core/zh_Hant/LC_MESSAGES/django.po
index e2de97732..fa41cfb03 100644
--- a/apps/i18n/core/zh_Hant/LC_MESSAGES/django.po
+++ b/apps/i18n/core/zh_Hant/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-15 14:04+0800\n"
+"POT-Creation-Date: 2024-09-19 16:31+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler \n"
"Language-Team: JumpServer team\n"
@@ -110,11 +110,11 @@ msgstr "帳號備份計劃正在執行"
msgid "Plan execution end"
msgstr "計劃執行結束"
-#: accounts/automations/change_secret/manager.py:100
+#: accounts/automations/change_secret/manager.py:97
msgid "No pending accounts found"
msgstr "未找到待處理帳戶"
-#: accounts/automations/change_secret/manager.py:227
+#: accounts/automations/change_secret/manager.py:225
#, python-format
msgid "Success: %s, Failed: %s, Total: %s"
msgstr "成功: %s, 失敗: %s, 總數: %s"
@@ -125,10 +125,11 @@ msgstr "成功: %s, 失敗: %s, 總數: %s"
#: authentication/confirm/password.py:24 authentication/confirm/password.py:26
#: authentication/forms.py:28
#: authentication/templates/authentication/login.html:362
-#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:51
-#: settings/serializers/msg.py:37 settings/serializers/terminal.py:28
-#: terminal/serializers/storage.py:123 terminal/serializers/storage.py:142
-#: users/forms/profile.py:21 users/serializers/user.py:144
+#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap_ha.py:34 settings/serializers/msg.py:37
+#: settings/serializers/terminal.py:28 terminal/serializers/storage.py:123
+#: terminal/serializers/storage.py:142 users/forms/profile.py:21
+#: users/serializers/user.py:144
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -146,7 +147,7 @@ msgid "Access key"
msgstr "Access key"
#: accounts/const/account.py:9 authentication/backends/passkey/models.py:16
-#: authentication/models/sso_token.py:14 settings/serializers/feature.py:52
+#: authentication/models/sso_token.py:14 settings/serializers/feature.py:55
msgid "Token"
msgstr "Token"
@@ -209,8 +210,8 @@ msgstr "更改密碼"
msgid "Verify account"
msgstr "驗證帳號"
-#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:24
-#: accounts/tasks/remove_account.py:33
+#: accounts/const/automation.py:27 accounts/tasks/remove_account.py:25
+#: accounts/tasks/remove_account.py:38
msgid "Remove account"
msgstr "移除帳號"
@@ -312,11 +313,11 @@ msgid "Pending"
msgstr "待定的"
#: accounts/const/vault.py:8 assets/const/category.py:12
-#: assets/models/asset/database.py:9 assets/models/asset/database.py:24
+#: assets/models/asset/database.py:10 assets/models/asset/database.py:29
msgid "Database"
msgstr "資料庫"
-#: accounts/const/vault.py:9 settings/serializers/feature.py:43
+#: accounts/const/vault.py:9 settings/serializers/feature.py:46
msgid "HCP Vault"
msgstr "HashiCorp Vault"
@@ -341,14 +342,14 @@ msgstr "用戶 %s 查看/匯出 了密碼"
#: accounts/models/account.py:49
#: accounts/models/automations/gather_account.py:16
#: accounts/serializers/account/account.py:226
-#: accounts/serializers/account/account.py:271
+#: accounts/serializers/account/account.py:272
#: accounts/serializers/account/gathered_account.py:10
#: accounts/serializers/automations/change_secret.py:111
#: accounts/serializers/automations/change_secret.py:143
#: accounts/templates/accounts/asset_account_change_info.html:7
#: accounts/templates/accounts/change_secret_failed_info.html:11
-#: acls/serializers/base.py:123 assets/models/asset/common.py:95
-#: assets/models/asset/common.py:349 assets/models/cmd_filter.py:36
+#: acls/serializers/base.py:123 assets/models/asset/common.py:102
+#: assets/models/asset/common.py:362 assets/models/cmd_filter.py:36
#: audits/models.py:58 authentication/models/connection_token.py:36
#: perms/models/asset_permission.py:69 terminal/backends/command/models.py:17
#: terminal/models/session/session.py:32 terminal/notifications.py:155
@@ -361,13 +362,13 @@ msgstr "資產"
#: accounts/models/account.py:53 accounts/models/template.py:16
#: accounts/serializers/account/account.py:233
-#: accounts/serializers/account/account.py:281
-#: accounts/serializers/account/template.py:27
+#: accounts/serializers/account/account.py:282
+#: accounts/serializers/account/template.py:37
#: authentication/serializers/connect_token_secret.py:50
msgid "Su from"
msgstr "切換自"
-#: accounts/models/account.py:55 assets/const/protocol.py:189
+#: accounts/models/account.py:55 assets/const/protocol.py:195
#: settings/serializers/auth/cas.py:25 terminal/models/applet/applet.py:36
#: terminal/models/virtualapp/virtualapp.py:21
msgid "Version"
@@ -388,7 +389,7 @@ msgstr "來源 ID"
#: accounts/templates/accounts/change_secret_failed_info.html:12
#: acls/serializers/base.py:124
#: acls/templates/acls/asset_login_reminder.html:10
-#: assets/serializers/gateway.py:28 audits/models.py:59
+#: assets/serializers/gateway.py:33 audits/models.py:59
#: authentication/api/connection_token.py:411 ops/models/base.py:18
#: perms/models/asset_permission.py:75 settings/serializers/msg.py:33
#: terminal/backends/command/models.py:18 terminal/models/session/session.py:34
@@ -460,9 +461,9 @@ msgstr "帳號備份計劃"
#: accounts/models/automations/backup_account.py:120
#: assets/models/automations/base.py:115 audits/models.py:65
-#: ops/models/base.py:55 ops/models/celery.py:88 ops/models/job.py:242
+#: ops/models/base.py:55 ops/models/celery.py:89 ops/models/job.py:242
#: ops/templates/ops/celery_task_log.html:101
-#: perms/models/asset_permission.py:78
+#: perms/models/asset_permission.py:78 settings/serializers/feature.py:25
#: settings/templates/ldap/_msg_import_ldap_user.html:5
#: terminal/models/applet/host.py:141 terminal/models/session/session.py:45
#: tickets/models/ticket/apply_application.py:30
@@ -472,7 +473,7 @@ msgstr "開始日期"
#: accounts/models/automations/backup_account.py:123
#: authentication/templates/authentication/_msg_oauth_bind.html:11
-#: notifications/notifications.py:194
+#: notifications/notifications.py:199
#: settings/templates/ldap/_msg_import_ldap_user.html:3
msgid "Time"
msgstr "時間"
@@ -551,7 +552,8 @@ msgstr "SSH 金鑰推送方式"
#: accounts/models/automations/gather_account.py:58
#: accounts/serializers/account/backup.py:40
#: accounts/serializers/automations/change_secret.py:58
-#: settings/serializers/auth/ldap.py:99 settings/serializers/msg.py:45
+#: settings/serializers/auth/ldap.py:100
+#: settings/serializers/auth/ldap_ha.py:82 settings/serializers/msg.py:45
msgid "Recipient"
msgstr "收件人"
@@ -573,7 +575,7 @@ msgstr "開始日期"
#: accounts/models/automations/change_secret.py:42
#: assets/models/automations/base.py:116 ops/models/base.py:56
-#: ops/models/celery.py:89 ops/models/job.py:243
+#: ops/models/celery.py:90 ops/models/job.py:243
#: terminal/models/applet/host.py:142
msgid "Date finished"
msgstr "結束日期"
@@ -585,7 +587,7 @@ msgstr "結束日期"
#: terminal/models/applet/applet.py:331 terminal/models/applet/host.py:140
#: terminal/models/component/status.py:30
#: terminal/models/virtualapp/virtualapp.py:99
-#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:136
+#: terminal/serializers/applet.py:18 terminal/serializers/applet_host.py:148
#: terminal/serializers/virtualapp.py:35 tickets/models/ticket/general.py:284
#: tickets/serializers/super_ticket.py:13
#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:225
@@ -593,8 +595,8 @@ msgstr "結束日期"
msgid "Status"
msgstr "狀態"
-#: accounts/models/automations/change_secret.py:47
-#: accounts/serializers/account/account.py:273
+#: accounts/models/automations/change_secret.py:46
+#: accounts/serializers/account/account.py:274
#: accounts/templates/accounts/change_secret_failed_info.html:13
#: assets/const/automation.py:8
#: authentication/templates/authentication/passkey.html:173
@@ -604,7 +606,7 @@ msgstr "狀態"
msgid "Error"
msgstr "錯誤"
-#: accounts/models/automations/change_secret.py:51
+#: accounts/models/automations/change_secret.py:50
msgid "Change secret record"
msgstr "改密記錄"
@@ -635,7 +637,7 @@ msgid "Address login"
msgstr "最後登入地址"
#: accounts/models/automations/gather_account.py:44
-#: accounts/tasks/gather_accounts.py:29
+#: accounts/tasks/gather_accounts.py:30
msgid "Gather asset accounts"
msgstr "收集帳號"
@@ -656,7 +658,7 @@ msgstr "觸發方式"
#: audits/models.py:92 audits/serializers.py:84
#: authentication/serializers/connect_token_secret.py:119
#: authentication/templates/authentication/_access_key_modal.html:34
-#: perms/serializers/permission.py:41 perms/serializers/permission.py:63
+#: perms/serializers/permission.py:52 perms/serializers/permission.py:74
#: tickets/serializers/ticket/ticket.py:21
msgid "Action"
msgstr "動作"
@@ -670,7 +672,7 @@ msgid "Verify asset account"
msgstr "帳號驗證"
#: accounts/models/base.py:37 accounts/models/base.py:67
-#: accounts/serializers/account/account.py:463
+#: accounts/serializers/account/account.py:464
#: accounts/serializers/account/base.py:17
#: accounts/serializers/automations/change_secret.py:47
#: authentication/serializers/connect_token_secret.py:42
@@ -692,28 +694,28 @@ msgstr "金鑰"
msgid "Secret strategy"
msgstr "密文策略"
-#: accounts/models/base.py:44 accounts/serializers/account/template.py:24
+#: accounts/models/base.py:44 accounts/serializers/account/template.py:34
#: accounts/serializers/automations/change_secret.py:46
msgid "Password rules"
msgstr "密碼規則"
#: accounts/models/base.py:64 accounts/serializers/account/virtual.py:20
#: acls/models/base.py:35 acls/models/base.py:96 acls/models/command_acl.py:21
-#: acls/serializers/base.py:35 assets/models/asset/common.py:93
-#: assets/models/asset/common.py:159 assets/models/cmd_filter.py:21
+#: acls/serializers/base.py:35 assets/models/asset/common.py:100
+#: assets/models/asset/common.py:166 assets/models/cmd_filter.py:21
#: assets/models/domain.py:19 assets/models/label.py:18
#: assets/models/platform.py:15 assets/models/platform.py:94
-#: assets/serializers/asset/common.py:149 assets/serializers/platform.py:153
-#: assets/serializers/platform.py:273
+#: assets/serializers/asset/common.py:169 assets/serializers/platform.py:157
+#: assets/serializers/platform.py:277
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:113
#: authentication/serializers/connect_token_secret.py:169 labels/models.py:11
-#: ops/mixin.py:21 ops/models/adhoc.py:20 ops/models/celery.py:15
-#: ops/models/celery.py:80 ops/models/job.py:142 ops/models/playbook.py:28
+#: ops/mixin.py:28 ops/models/adhoc.py:19 ops/models/celery.py:15
+#: ops/models/celery.py:81 ops/models/job.py:142 ops/models/playbook.py:30
#: ops/serializers/job.py:18 orgs/models.py:82
#: perms/models/asset_permission.py:61 rbac/models/role.py:29
-#: rbac/serializers/role.py:28 settings/models.py:34 settings/models.py:183
+#: rbac/serializers/role.py:28 settings/models.py:35 settings/models.py:184
#: settings/serializers/msg.py:89 settings/serializers/terminal.py:9
#: terminal/models/applet/applet.py:34 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:109
@@ -834,7 +836,6 @@ msgstr ""
"碼"
#: accounts/notifications.py:83
-#: accounts/templates/accounts/asset_account_change_info.html:3
msgid "Gather account change information"
msgstr "帳號變更資訊"
@@ -854,11 +855,16 @@ msgstr "參數"
msgid "Exist policy"
msgstr "帳號存在策略"
+#: accounts/serializers/account/account.py:181
+#: accounts/serializers/account/account.py:340
+msgid "Account already exists"
+msgstr "帳號已存在"
+
#: accounts/serializers/account/account.py:206 assets/models/label.py:21
-#: assets/models/platform.py:95 assets/serializers/asset/common.py:125
-#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:168
-#: assets/serializers/platform.py:274 perms/serializers/user_permission.py:26
-#: settings/models.py:36 tickets/models/ticket/apply_application.py:13
+#: assets/models/platform.py:95 assets/serializers/asset/common.py:145
+#: assets/serializers/cagegory.py:12 assets/serializers/platform.py:172
+#: assets/serializers/platform.py:278 perms/serializers/user_permission.py:26
+#: settings/models.py:37 tickets/models/ticket/apply_application.py:13
#: users/models/preference.py:12
msgid "Category"
msgstr "類別"
@@ -867,13 +873,13 @@ msgstr "類別"
#: accounts/serializers/automations/base.py:55 acls/models/command_acl.py:24
#: acls/serializers/command_acl.py:19 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:96
-#: assets/serializers/asset/common.py:126 assets/serializers/platform.py:155
-#: assets/serializers/platform.py:167 audits/serializers.py:53
+#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:159
+#: assets/serializers/platform.py:171 audits/serializers.py:53
#: audits/serializers.py:170
#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:150
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
#: terminal/models/component/storage.py:58
-#: terminal/models/component/storage.py:154 terminal/serializers/applet.py:29
+#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:29
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
@@ -886,62 +892,58 @@ msgstr "類型"
msgid "Asset not found"
msgstr "資產不存在"
-#: accounts/serializers/account/account.py:262
+#: accounts/serializers/account/account.py:263
msgid "Has secret"
msgstr "已託管密碼"
-#: accounts/serializers/account/account.py:272 ops/models/celery.py:83
+#: accounts/serializers/account/account.py:273 ops/models/celery.py:84
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:49
#: tickets/models/ticket/general.py:280 tickets/serializers/super_ticket.py:14
msgid "State"
msgstr "狀態"
-#: accounts/serializers/account/account.py:274
+#: accounts/serializers/account/account.py:275
msgid "Changed"
msgstr "已修改"
-#: accounts/serializers/account/account.py:284
+#: accounts/serializers/account/account.py:285
#: accounts/serializers/automations/base.py:22 acls/models/base.py:97
#: acls/templates/acls/asset_login_reminder.html:9
#: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:34
-#: assets/serializers/platform.py:176 assets/serializers/platform.py:208
+#: assets/serializers/platform.py:180 assets/serializers/platform.py:212
#: authentication/api/connection_token.py:410 ops/models/base.py:17
#: ops/models/job.py:152 ops/serializers/job.py:19
-#: perms/serializers/permission.py:35
+#: perms/serializers/permission.py:46
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
#: xpack/plugins/cloud/manager.py:83
msgid "Assets"
msgstr "資產"
-#: accounts/serializers/account/account.py:339
-msgid "Account already exists"
-msgstr "帳號已存在"
-
-#: accounts/serializers/account/account.py:389
+#: accounts/serializers/account/account.py:390
#, python-format
msgid "Asset does not support this secret type: %s"
msgstr "資產不支持帳號類型: %s"
-#: accounts/serializers/account/account.py:421
+#: accounts/serializers/account/account.py:422
msgid "Account has exist"
msgstr "帳號已存在"
-#: accounts/serializers/account/account.py:458
+#: accounts/serializers/account/account.py:459
#: accounts/serializers/account/base.py:93
-#: accounts/serializers/account/template.py:72
-#: assets/serializers/asset/common.py:387
+#: accounts/serializers/account/template.py:83
+#: assets/serializers/asset/common.py:407
msgid "Spec info"
msgstr "特殊資訊"
-#: accounts/serializers/account/account.py:464
+#: accounts/serializers/account/account.py:465
#: authentication/serializers/connect_token_secret.py:159
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
msgstr "ID"
-#: accounts/serializers/account/account.py:474 acls/serializers/base.py:116
+#: accounts/serializers/account/account.py:475 acls/serializers/base.py:116
#: acls/templates/acls/asset_login_reminder.html:8
#: acls/templates/acls/user_login_reminder.html:8
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:54
@@ -950,7 +952,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:63
-#: rbac/builtin.py:124 rbac/models/rolebinding.py:49
+#: rbac/builtin.py:125 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:34
#: terminal/notifications.py:156 terminal/notifications.py:205
@@ -963,7 +965,7 @@ msgstr "ID"
msgid "User"
msgstr "用戶"
-#: accounts/serializers/account/account.py:475
+#: accounts/serializers/account/account.py:476
#: authentication/templates/authentication/_access_key_modal.html:33
#: terminal/notifications.py:158 terminal/notifications.py:207
msgid "Date"
@@ -1025,24 +1027,43 @@ msgstr "特殊字元"
msgid "Exclude symbol"
msgstr "排除字元"
-#: accounts/serializers/account/template.py:39
+#: accounts/serializers/account/template.py:24
+msgid ""
+"length is the length of the password, and the range is 8 to 30.\n"
+"lowercase indicates whether the password contains lowercase letters, \n"
+"uppercase indicates whether it contains uppercase letters,\n"
+"digit indicates whether it contains numbers, and symbol indicates whether it "
+"contains special symbols.\n"
+"exclude_symbols is used to exclude specific symbols. You can fill in the "
+"symbol characters to be excluded (up to 16). \n"
+"If you do not need to exclude symbols, you can leave it blank.\n"
+"default: {\"length\": 16, \"lowercase\": true, \"uppercase\": true, "
+"\"digit\": true, \"symbol\": true, \"exclude_symbols\": \"\"}"
+msgstr ""
+"length 是密碼的長度,填入範圍為 8 到 30。"
+"lowercase 表示密碼中是否包含小寫字母,uppercase 表示是否包含大寫字母,"
+"digit 表示是否包含數字,symbol 表示是否包含特殊符號。"
+"exclude_symbols 用於排除特定符號,您可以填寫要排除的符號字元(最多 16 個),如果無需排除符號,可以留空。"
+"預設: {\"length\": 16, \"lowercase\": true, \"uppercase\": true, \"digit\": true, \"symbol\": true, \"exclude_symbols\": \"\"}"
+
+#: accounts/serializers/account/template.py:49
msgid "Secret generation strategy for account creation"
msgstr "密碼生成策略,用於帳號創建時,設置密碼"
-#: accounts/serializers/account/template.py:40
+#: accounts/serializers/account/template.py:50
msgid "Whether to automatically push the account to the asset"
msgstr "是否自動推送帳號到資產"
-#: accounts/serializers/account/template.py:43
+#: accounts/serializers/account/template.py:53
msgid ""
"Associated platform, you can configure push parameters. If not associated, "
"default parameters will be used"
msgstr "關聯平台,可配置推送參數,如果不關聯,將使用默認參數"
#: accounts/serializers/account/virtual.py:19 assets/models/cmd_filter.py:40
-#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:26
-#: ops/models/job.py:158 ops/models/playbook.py:31 rbac/models/role.py:37
-#: settings/models.py:39 terminal/models/applet/applet.py:46
+#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:25
+#: ops/models/job.py:158 ops/models/playbook.py:33 rbac/models/role.py:37
+#: settings/models.py:40 terminal/models/applet/applet.py:46
#: terminal/models/applet/applet.py:332 terminal/models/applet/host.py:143
#: terminal/models/component/endpoint.py:25
#: terminal/models/component/endpoint.py:119
@@ -1064,8 +1085,8 @@ msgstr ""
"CACHE_LOGIN_PASSWORD_ENABLED=true,重啟服務才能開啟"
#: accounts/serializers/automations/base.py:23
-#: assets/models/asset/common.py:164 assets/serializers/asset/common.py:152
-#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:36
+#: assets/models/asset/common.py:176 assets/serializers/asset/common.py:172
+#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:47
msgid "Nodes"
msgstr "節點"
@@ -1125,31 +1146,110 @@ msgstr "添加帳號: %s"
msgid "Delete account: %s"
msgstr "刪除帳號: %s"
-#: accounts/tasks/automation.py:25
+#: accounts/tasks/automation.py:32
msgid "Account execute automation"
msgstr "帳號執行自動化"
-#: accounts/tasks/automation.py:51 accounts/tasks/automation.py:56
+#: accounts/tasks/automation.py:35
+msgid ""
+"Unified execution entry for account automation tasks: when the system "
+"performs tasks \n"
+" such as account push, password change, account verification, account "
+"collection, \n"
+" and gateway account verification, all tasks are executed through "
+"this unified entry"
+msgstr ""
+"帳號自動化任務統一執行入口,當系統執行帳號推送,更改密碼,驗證帳號,收集帳"
+"號,驗證網關帳號任務時,統一透過當前任務執行"
+
+#: accounts/tasks/automation.py:64 accounts/tasks/automation.py:72
msgid "Execute automation record"
msgstr "自動化執行記錄"
-#: accounts/tasks/backup_account.py:25
+#: accounts/tasks/automation.py:67
+msgid "When manually executing password change records, this task is used"
+msgstr "當手動執行改密記錄時,透過該任務執行"
+
+#: accounts/tasks/automation.py:96
+msgid "Clean change secret and push record period"
+msgstr "週期清理改密記錄和推送記錄"
+
+#: accounts/tasks/automation.py:98
+msgid ""
+"The system will periodically clean up unnecessary password change and push "
+"records, \n"
+" including their associated change tasks, execution logs, assets, and "
+"accounts. When any \n"
+" of these associated items are deleted, the corresponding password "
+"change and push records \n"
+" become invalid. Therefore, to maintain a clean and efficient "
+"database, the system will \n"
+" clean up expired records at 2 a.m daily, based on the interval "
+"specified by \n"
+" PERM_EXPIRED_CHECK_PERIODIC in the config.txt configuration file. "
+"This periodic cleanup \n"
+" mechanism helps free up storage space and enhances the security and "
+"overall performance \n"
+" of data management"
+msgstr ""
+"系統會定期清理不再需要的改密記錄和推送記錄,包括那些關聯的改密任務、執行記"
+"錄、資產和帳號。當這些關聯項中的任意一個被刪除時,對應的改密和推送記錄將變為"
+"無效。因此,為了保持資料庫的整潔和高效運行,根據系統配置文件 config.txt 中 "
+"PERM_EXPIRED_CHECK_PERIODIC 的時間間隔對於超出時間的於每天凌晨2點進行清理。這"
+"種定期清理機制不僅有助於釋放存儲空間,還能提高數據管理的安全和整體性能"
+
+#: accounts/tasks/backup_account.py:26
msgid "Execute account backup plan"
msgstr "執行帳號備份計劃"
-#: accounts/tasks/gather_accounts.py:34
+#: accounts/tasks/backup_account.py:29
+msgid "When performing scheduled or manual account backups, this task is used"
+msgstr "定時或手動執行帳號備份時,透過該任務執行"
+
+#: accounts/tasks/gather_accounts.py:32 assets/tasks/automation.py:27
+#: orgs/tasks.py:11 terminal/tasks.py:33
+msgid "Unused"
+msgstr "未使用"
+
+#: accounts/tasks/gather_accounts.py:36
msgid "Gather assets accounts"
msgstr "收集資產上的帳號"
-#: accounts/tasks/push_account.py:15 accounts/tasks/push_account.py:23
+#: accounts/tasks/push_account.py:16 accounts/tasks/push_account.py:27
msgid "Push accounts to assets"
msgstr "推送帳號到資產"
-#: accounts/tasks/remove_account.py:44
+#: accounts/tasks/push_account.py:19
+msgid ""
+"When creating or modifying an account requires account push, this task is "
+"executed"
+msgstr "當創建帳號,修改帳號時,需要帳號推送時執行該任務"
+
+#: accounts/tasks/remove_account.py:28
+msgid ""
+"When clicking \"Sync deletion\" in 'Console - Gather Account - Gathered "
+"accounts' this \n"
+" task will be executed"
+msgstr "當在控制台-自動化-帳號收集-收集的帳號-點擊同步刪除會執行該任務"
+
+#: accounts/tasks/remove_account.py:50
msgid "Clean historical accounts"
msgstr "清理歷史帳號"
-#: accounts/tasks/remove_account.py:76
+#: accounts/tasks/remove_account.py:52
+msgid ""
+"Each time an asset account is updated, a historical account is generated, so "
+"it is \n"
+" necessary to clean up the asset account history. The system will "
+"clean up excess account \n"
+" records at 2 a.m. daily based on the configuration in the \"System "
+"settings - Features - \n"
+" Account storage - Record limit"
+msgstr ""
+"由於每次更新資產帳號,都會產生歷史帳號,所以需要清理資產帳號的歷史。系統會根"
+"據帳號儲存-記錄限制的配置,每天凌晨2點對於超出的數量的帳號記錄進行清理"
+
+#: accounts/tasks/remove_account.py:89
msgid "Remove historical accounts that are out of range."
msgstr "刪除超出範圍的歷史帳戶。"
@@ -1157,23 +1257,42 @@ msgstr "刪除超出範圍的歷史帳戶。"
msgid "Template sync info to related accounts"
msgstr "同步資訊到關聯的帳號"
-#: accounts/tasks/vault.py:31
+#: accounts/tasks/template.py:14
+msgid ""
+"When clicking 'Sync new secret to accounts' in 'Console - Account - "
+"Templates - \n"
+" Accounts' this task will be executed"
+msgstr "當在控制台-帳號模板-帳號-同步更新帳號信息點擊同步時,執行該任務"
+
+#: accounts/tasks/vault.py:32
msgid "Sync secret to vault"
msgstr "同步密文到 vault"
+#: accounts/tasks/vault.py:34
+msgid ""
+"When clicking 'Sync' in 'System Settings - Features - Account Storage' this "
+"task will be executed"
+msgstr "在系統設定-功能設定-帳號儲存點擊同步時,執行該任務"
+
#: accounts/tasks/verify_account.py:49
msgid "Verify asset account availability"
msgstr "驗證資產帳號可用性"
-#: accounts/tasks/verify_account.py:55
+#: accounts/tasks/verify_account.py:52
+msgid ""
+"When clicking 'Test' in 'Console - Asset details - Accounts' this task will "
+"be executed"
+msgstr "當在控制台-資產詳情-帳號點擊測試執行該任務"
+
+#: accounts/tasks/verify_account.py:58
msgid "Verify accounts connectivity"
msgstr "測試帳號可連接性"
-#: accounts/templates/accounts/asset_account_change_info.html:8
+#: accounts/templates/accounts/asset_account_change_info.html:10
msgid "Added account"
msgstr "新增帳號"
-#: accounts/templates/accounts/asset_account_change_info.html:9
+#: accounts/templates/accounts/asset_account_change_info.html:13
msgid "Deleted account"
msgstr "刪除帳號"
@@ -1231,6 +1350,10 @@ msgstr "警報"
msgid "Notify"
msgstr "通知"
+#: acls/const.py:11
+msgid "Notify and warn"
+msgstr "提示並警告"
+
#: acls/models/base.py:37 assets/models/cmd_filter.py:76
#: terminal/models/component/endpoint.py:112 xpack/plugins/cloud/models.py:314
msgid "Priority"
@@ -1246,7 +1369,7 @@ msgstr "優先度可選範圍為 1-100 (數值越小越優先)"
msgid "Reviewers"
msgstr "審批人"
-#: acls/models/base.py:43 assets/models/asset/common.py:165
+#: acls/models/base.py:43 assets/models/asset/common.py:178
#: authentication/models/access_key.py:25
#: authentication/models/connection_token.py:53
#: authentication/models/ssh_key.py:13
@@ -1258,15 +1381,15 @@ msgstr "審批人"
msgid "Active"
msgstr "啟用中"
-#: acls/models/base.py:81 perms/serializers/permission.py:31
+#: acls/models/base.py:81 perms/serializers/permission.py:42
#: tickets/models/flow.py:23 users/models/preference.py:16
#: users/serializers/group.py:21 users/serializers/user.py:424
msgid "Users"
msgstr "用戶管理"
#: acls/models/base.py:98 assets/models/automations/base.py:17
-#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:128
-#: assets/serializers/asset/common.py:386 perms/serializers/permission.py:44
+#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:148
+#: assets/serializers/asset/common.py:406 perms/serializers/permission.py:55
#: perms/serializers/user_permission.py:75 rbac/tree.py:35
msgid "Accounts"
msgstr "帳號管理"
@@ -1286,7 +1409,7 @@ msgid "Regex"
msgstr "正則表達式"
#: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79
-#: settings/models.py:184 settings/serializers/feature.py:19
+#: settings/models.py:185 settings/serializers/feature.py:20
#: settings/serializers/msg.py:78 xpack/plugins/license/models.py:30
msgid "Content"
msgstr "內容"
@@ -1402,7 +1525,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
-#: xpack/plugins/cloud/models.py:390
+#: common/drf/renders/base.py:150 xpack/plugins/cloud/models.py:390
msgid "IP"
msgstr "IP"
@@ -1464,11 +1587,11 @@ msgstr "登錄城市"
msgid "User agent"
msgstr "用戶代理"
-#: assets/api/asset/asset.py:181
+#: assets/api/asset/asset.py:190
msgid "Cannot create asset directly, you should create a host or other"
msgstr "不能直接創建資產, 你應該創建主機或其他資產"
-#: assets/api/asset/asset.py:185
+#: assets/api/asset/asset.py:194
msgid "The number of assets exceeds the limit of 5000"
msgstr "資產數量超過 5000 的限制"
@@ -1496,32 +1619,32 @@ msgstr "同級別節點名字不能重複"
msgid "App Assets"
msgstr "資產管理"
-#: assets/automations/base/manager.py:187
+#: assets/automations/base/manager.py:188
msgid "{} disabled"
msgstr "{} 已禁用"
-#: assets/automations/base/manager.py:250
+#: assets/automations/base/manager.py:251
msgid " - Platform {} ansible disabled"
msgstr " - 平台 {} Ansible 已禁用, 無法執行任務"
-#: assets/automations/base/manager.py:323
+#: assets/automations/base/manager.py:324
msgid ">>> Task preparation phase"
msgstr ">>> 任務準備階段"
-#: assets/automations/base/manager.py:326
+#: assets/automations/base/manager.py:327
#, python-brace-format
msgid ">>> Executing tasks in batches, total {runner_count}"
msgstr ">>> 分次執行任務,總共 {runner_count}"
-#: assets/automations/base/manager.py:328
+#: assets/automations/base/manager.py:329
msgid ">>> Start executing tasks"
msgstr ">>> 開始執行任務"
-#: assets/automations/base/manager.py:330
+#: assets/automations/base/manager.py:331
msgid ">>> No tasks need to be executed"
msgstr ">>> 沒有需要執行的任務"
-#: assets/automations/base/manager.py:335
+#: assets/automations/base/manager.py:336
#, python-brace-format
msgid ">>> Begin executing batch {index} of tasks"
msgstr ">>> 開始執行第 {index} 批任務"
@@ -1558,7 +1681,7 @@ msgstr "未知"
#: assets/const/automation.py:7
msgid "OK"
-msgstr ""
+msgstr "Success"
#: assets/const/automation.py:12
msgid "Ping"
@@ -1583,14 +1706,14 @@ msgstr "禁用"
msgid "Basic"
msgstr "基本"
-#: assets/const/base.py:34 assets/const/protocol.py:292
+#: assets/const/base.py:34 assets/const/protocol.py:298
#: assets/models/asset/web.py:13
msgid "Script"
msgstr "腳本"
#: assets/const/category.py:10 assets/models/asset/host.py:8
#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:76
-#: settings/serializers/feature.py:49 settings/serializers/msg.py:30
+#: settings/serializers/feature.py:52 settings/serializers/msg.py:30
#: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17
#: xpack/plugins/cloud/manager.py:83
#: xpack/plugins/cloud/serializers/account_attrs.py:72
@@ -1655,17 +1778,23 @@ msgstr "其它"
#: assets/const/protocol.py:46
msgid "Old SSH version"
-msgstr "Old SSH version"
+msgstr "舊的 SSH 版本"
#: assets/const/protocol.py:47
msgid "Old SSH version like openssh 5.x or 6.x"
msgstr "舊的 SSH 版本,例如 openssh 5.x 或 6.x"
-#: assets/const/protocol.py:58
+#: assets/const/protocol.py:53
+msgid "Netcat help text"
+msgstr ""
+"使用 netcat (nc) 作為代理工具,將連線從代理伺服器轉送到目標主機。適用於不支"
+"援 SSH 原生代理選項 (-W) 的環境,或需要更多靈活性和逾時控制的場景。"
+
+#: assets/const/protocol.py:64
msgid "SFTP root"
msgstr "SFTP 根路徑"
-#: assets/const/protocol.py:60
+#: assets/const/protocol.py:66
#, python-brace-format
msgid ""
"SFTP root directory, Support variable:
- ${ACCOUNT} The connected "
@@ -1675,24 +1804,24 @@ msgstr ""
"SFTP根目錄,支持變數:
-${ACCOUNT}已連接帳戶使用者名稱
-${HOME}連接帳戶"
"的主目錄
-${USER}用戶的使用者名稱"
-#: assets/const/protocol.py:75
+#: assets/const/protocol.py:81
msgid "Console"
msgstr "控制台"
-#: assets/const/protocol.py:76
+#: assets/const/protocol.py:82
msgid "Connect to console session"
msgstr "連接到控制台會話"
-#: assets/const/protocol.py:80
+#: assets/const/protocol.py:86
msgid "Any"
msgstr "任意"
-#: assets/const/protocol.py:82 rbac/tree.py:62
+#: assets/const/protocol.py:88 rbac/tree.py:62
#: settings/serializers/security.py:232
msgid "Security"
msgstr "安全"
-#: assets/const/protocol.py:83
+#: assets/const/protocol.py:89
msgid ""
"Security layer to use for the connection:
Any
Automatically select the "
"security mode based on the security protocols supported by both the client "
@@ -1707,101 +1836,101 @@ msgstr ""
"Windows 登入螢幕的情況
TLS
通過 TLS 實現的 RDP 認證和加密
NLA
此"
"模式使用 TLS 加密,並要求提前提供用戶名和密碼
"
-#: assets/const/protocol.py:100
+#: assets/const/protocol.py:106
msgid "AD domain"
msgstr "AD 網域"
-#: assets/const/protocol.py:115
+#: assets/const/protocol.py:121
msgid "Username prompt"
msgstr "使用者名稱提示"
-#: assets/const/protocol.py:116
+#: assets/const/protocol.py:122
msgid "We will send username when we see this prompt"
msgstr "當我們看到這個提示時,我們將發送使用者名稱"
-#: assets/const/protocol.py:121
+#: assets/const/protocol.py:127
msgid "Password prompt"
msgstr "密碼提示"
-#: assets/const/protocol.py:122
+#: assets/const/protocol.py:128
msgid "We will send password when we see this prompt"
msgstr "當我們看到這個提示時,我們將發送密碼"
-#: assets/const/protocol.py:127
+#: assets/const/protocol.py:133
msgid "Success prompt"
msgstr "成功提示"
-#: assets/const/protocol.py:128
+#: assets/const/protocol.py:134
msgid "We will consider login success when we see this prompt"
msgstr "當我們看到這個提示時,我們將認為登錄成功"
-#: assets/const/protocol.py:139 assets/models/asset/database.py:10
+#: assets/const/protocol.py:145 assets/models/asset/database.py:11
#: settings/serializers/msg.py:49
msgid "Use SSL"
msgstr "使用 SSL"
-#: assets/const/protocol.py:174
+#: assets/const/protocol.py:180
msgid "SYSDBA"
msgstr "SYSDBA"
-#: assets/const/protocol.py:175
+#: assets/const/protocol.py:181
msgid "Connect as SYSDBA"
msgstr "以 SYSDBA 角色連接"
-#: assets/const/protocol.py:190
+#: assets/const/protocol.py:196
msgid ""
"SQL Server version, Different versions have different connection drivers"
msgstr "SQL Server 版本,不同版本有不同的連接驅動"
-#: assets/const/protocol.py:220
+#: assets/const/protocol.py:226
msgid "Auth source"
msgstr "認證資料庫"
-#: assets/const/protocol.py:221
+#: assets/const/protocol.py:227
msgid "The database to authenticate against"
msgstr "要進行身份驗證的資料庫"
-#: assets/const/protocol.py:226 authentication/models/connection_token.py:43
+#: assets/const/protocol.py:232 authentication/models/connection_token.py:43
msgid "Connect options"
msgstr "連接項"
-#: assets/const/protocol.py:227
+#: assets/const/protocol.py:233
msgid "The connection specific options eg. retryWrites=false&retryReads=false"
msgstr "連接特定選項,例如。重試寫入=假&重試讀取=假"
-#: assets/const/protocol.py:239
+#: assets/const/protocol.py:245
msgid "Auth username"
msgstr "使用使用者名稱認證"
-#: assets/const/protocol.py:262
+#: assets/const/protocol.py:268
msgid "Safe mode"
msgstr "安全模式"
-#: assets/const/protocol.py:264
+#: assets/const/protocol.py:270
msgid ""
"When safe mode is enabled, some operations will be disabled, such as: New "
"tab, right click, visit other website, etc."
msgstr ""
"當安全模式啟用時,一些操作將被禁用,例如:新建標籤頁、右鍵、訪問其它網站 等"
-#: assets/const/protocol.py:269 assets/models/asset/web.py:9
+#: assets/const/protocol.py:275 assets/models/asset/web.py:9
#: assets/serializers/asset/info/spec.py:16
msgid "Autofill"
msgstr "自動代填"
-#: assets/const/protocol.py:277 assets/models/asset/web.py:10
+#: assets/const/protocol.py:283 assets/models/asset/web.py:10
msgid "Username selector"
msgstr "使用者名稱選擇器"
-#: assets/const/protocol.py:282 assets/models/asset/web.py:11
+#: assets/const/protocol.py:288 assets/models/asset/web.py:11
msgid "Password selector"
msgstr "密碼選擇器"
-#: assets/const/protocol.py:287 assets/models/asset/web.py:12
+#: assets/const/protocol.py:293 assets/models/asset/web.py:12
msgid "Submit selector"
msgstr "確認按鈕選擇器"
-#: assets/const/protocol.py:310
+#: assets/const/protocol.py:316
msgid "API mode"
msgstr "API 模式"
@@ -1821,51 +1950,51 @@ msgstr "暫時不支持此功能"
msgid "Cloud"
msgstr "雲服務"
-#: assets/models/asset/common.py:94 assets/models/platform.py:16
+#: assets/models/asset/common.py:101 assets/models/platform.py:16
#: settings/serializers/auth/radius.py:18 settings/serializers/auth/sms.py:77
#: settings/serializers/msg.py:31 terminal/serializers/storage.py:133
#: xpack/plugins/cloud/serializers/account_attrs.py:73
msgid "Port"
msgstr "埠"
-#: assets/models/asset/common.py:160 assets/serializers/asset/common.py:150
+#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:170
#: settings/serializers/terminal.py:10
msgid "Address"
msgstr "地址"
-#: assets/models/asset/common.py:161 assets/models/platform.py:149
+#: assets/models/asset/common.py:169 assets/models/platform.py:149
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:118
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:385
msgid "Platform"
msgstr "系統平台"
-#: assets/models/asset/common.py:163 assets/models/domain.py:22
+#: assets/models/asset/common.py:173 assets/models/domain.py:22
msgid "Zone"
msgstr "網域"
-#: assets/models/asset/common.py:166 assets/serializers/asset/common.py:388
+#: assets/models/asset/common.py:179 assets/serializers/asset/common.py:408
#: assets/serializers/asset/host.py:11
msgid "Gathered info"
msgstr "收集資產硬體資訊"
-#: assets/models/asset/common.py:167 assets/serializers/asset/custom.py:14
+#: assets/models/asset/common.py:180 assets/serializers/asset/custom.py:14
msgid "Custom info"
msgstr "自訂屬性"
-#: assets/models/asset/common.py:352
+#: assets/models/asset/common.py:365
msgid "Can refresh asset hardware info"
msgstr "可以更新資產硬體資訊"
-#: assets/models/asset/common.py:353
+#: assets/models/asset/common.py:366
msgid "Can test asset connectivity"
msgstr "可以測試資產連接性"
-#: assets/models/asset/common.py:354
+#: assets/models/asset/common.py:367
msgid "Can match asset"
msgstr "可以匹配資產"
-#: assets/models/asset/common.py:355
+#: assets/models/asset/common.py:368
msgid "Can change asset nodes"
msgstr "可以修改資產節點"
@@ -1873,23 +2002,27 @@ msgstr "可以修改資產節點"
msgid "Custom asset"
msgstr "自訂資產"
-#: assets/models/asset/database.py:11
+#: assets/models/asset/database.py:12
msgid "CA cert"
msgstr "CA 證書"
-#: assets/models/asset/database.py:12
+#: assets/models/asset/database.py:13
msgid "Client cert"
msgstr "用戶端證書"
-#: assets/models/asset/database.py:13
+#: assets/models/asset/database.py:14
msgid "Client key"
msgstr "用戶端金鑰"
-#: assets/models/asset/database.py:14
+#: assets/models/asset/database.py:15
msgid "Allow invalid cert"
msgstr "忽略證書校驗"
-#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:89
+#: assets/models/asset/database.py:18
+msgid "Postgresql SSL mode"
+msgstr "PostgreSQL SSL 模式"
+
+#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:92
msgid "Proxy"
msgstr "代理"
@@ -1993,14 +2126,14 @@ msgstr "系統"
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:29
#: authentication/serializers/connect_token_secret.py:125
-#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:35
+#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:36
#: users/models/preference.py:13
msgid "Value"
msgstr "值"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
-#: assets/serializers/platform.py:154
+#: assets/serializers/platform.py:158
#: authentication/serializers/connect_token_secret.py:124
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90
@@ -2051,7 +2184,7 @@ msgstr "主要的"
msgid "Required"
msgstr "必須的"
-#: assets/models/platform.py:19 assets/serializers/platform.py:156
+#: assets/models/platform.py:19 assets/serializers/platform.py:160
#: terminal/models/component/storage.py:28
#: xpack/plugins/cloud/providers/nutanix.py:30
msgid "Default"
@@ -2068,7 +2201,7 @@ msgid "Setting"
msgstr "設置"
#: assets/models/platform.py:38 audits/const.py:59
-#: authentication/backends/passkey/models.py:11 settings/models.py:38
+#: authentication/backends/passkey/models.py:11 settings/models.py:39
#: terminal/serializers/applet_host.py:33 users/models/user/_auth.py:33
msgid "Enabled"
msgstr "啟用"
@@ -2159,23 +2292,23 @@ msgstr "元數據"
msgid "Internal"
msgstr "內建"
-#: assets/models/platform.py:102 assets/serializers/platform.py:166
+#: assets/models/platform.py:102 assets/serializers/platform.py:170
msgid "Charset"
msgstr "編碼"
-#: assets/models/platform.py:104 assets/serializers/platform.py:204
+#: assets/models/platform.py:104 assets/serializers/platform.py:208
msgid "Gateway enabled"
msgstr "啟用網域"
-#: assets/models/platform.py:106 assets/serializers/platform.py:197
+#: assets/models/platform.py:106 assets/serializers/platform.py:201
msgid "Su enabled"
msgstr "啟用帳號切換"
-#: assets/models/platform.py:107 assets/serializers/platform.py:172
+#: assets/models/platform.py:107 assets/serializers/platform.py:176
msgid "Su method"
msgstr "帳號切換方式"
-#: assets/models/platform.py:108 assets/serializers/platform.py:175
+#: assets/models/platform.py:108 assets/serializers/platform.py:179
msgid "Custom fields"
msgstr "自訂屬性"
@@ -2190,38 +2323,60 @@ msgid ""
"type"
msgstr "資產中批次更新平台,不符合平台類型跳過的資產"
-#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:169
+#: assets/serializers/asset/common.py:36 assets/serializers/platform.py:152
+msgid "Protocols, format is [\"protocol/port\"]"
+msgstr "協定,格式為 [\"協定/連接埠\"]"
+
+#: assets/serializers/asset/common.py:38
+msgid "Protocol, format is name/port"
+msgstr "協定,格式為 名稱/連接埠"
+
+#: assets/serializers/asset/common.py:107
+msgid ""
+"Accounts, format [{\"name\": \"x\", \"username\": \"x\", \"secret\": \"x\", "
+"\"secret_type\": \"password\"}]"
+msgstr ""
+"帳號,格式為 [{\"name\": \"x\", \"username\": \"x\", \"secret\": \"x\", "
+"\"secret_type\": \"password\"}]"
+
+#: assets/serializers/asset/common.py:135
+msgid ""
+"Node path, format [\"/org_name/node_name\"], if node not exist, will create "
+"it"
+msgstr "節點路徑,格式為 [\"/組織/節點名稱\"], 如果節點不存在,將創建它"
+
+#: assets/serializers/asset/common.py:147 assets/serializers/platform.py:173
#: authentication/serializers/connect_token_secret.py:30
#: authentication/serializers/connect_token_secret.py:75
-#: perms/models/asset_permission.py:76 perms/serializers/permission.py:45
+#: perms/models/asset_permission.py:76 perms/serializers/permission.py:56
#: perms/serializers/user_permission.py:74 xpack/plugins/cloud/models.py:388
#: xpack/plugins/cloud/serializers/task.py:35
msgid "Protocols"
msgstr "協議組"
-#: assets/serializers/asset/common.py:129
-#: assets/serializers/asset/common.py:151
+#: assets/serializers/asset/common.py:149
+#: assets/serializers/asset/common.py:171
msgid "Node path"
msgstr "節點路徑"
-#: assets/serializers/asset/common.py:148
-#: assets/serializers/asset/common.py:389
+#: assets/serializers/asset/common.py:168
+#: assets/serializers/asset/common.py:409
msgid "Auto info"
msgstr "自動化資訊"
-#: assets/serializers/asset/common.py:245
+#: assets/serializers/asset/common.py:265
msgid "Platform not exist"
msgstr "平台不存在"
-#: assets/serializers/asset/common.py:281
+#: assets/serializers/asset/common.py:301
msgid "port out of range (0-65535)"
msgstr "埠超出範圍 (0-65535)"
-#: assets/serializers/asset/common.py:288
+#: assets/serializers/asset/common.py:308
msgid "Protocol is required: {}"
msgstr "協議是必填的: {}"
-#: assets/serializers/asset/common.py:316
+#: assets/serializers/asset/common.py:336
msgid "Invalid data"
msgstr "無效的數據"
@@ -2229,6 +2384,21 @@ msgstr "無效的數據"
msgid "Default database"
msgstr "默認資料庫"
+#: assets/serializers/asset/database.py:23
+msgid "CA cert help text"
+msgstr ""
+"Common Name (CN) 字段已被棄用,請根據 RFC 5280 使用 Subject Alternative Name "
+"(SAN) 字段來驗證網域名,以提高安全性。"
+
+#: assets/serializers/asset/database.py:24
+msgid "Postgresql ssl model help text"
+msgstr ""
+"Prefer:我不在乎是否加密,但如果伺服器支持加密,我願意支付加密的費用。"
+"Require:我希望我的資料被加密,我可以承擔那個費用。我相信網路將確保我始終連接"
+"到我想要的伺服器。Verify CA:我希望我的資料被加密,我可以承擔那個費用。我想要"
+"確認我連接到我信任的伺服器。Verify Full:我希望我的資料被加密,我接受負擔。我"
+"想確保我連接到我信任的伺服器,並且它是我指定的伺服器。"
+
#: assets/serializers/asset/gpt.py:20
msgid ""
"If the server cannot directly connect to the API address, you need set up an "
@@ -2309,12 +2479,16 @@ msgid ""
"the zone, the connection is routed through the gateway."
msgstr "網關是網域的網路代理,當連接網域內的資產時,連接將由網關進行路由。"
-#: assets/serializers/domain.py:24 assets/serializers/platform.py:177
-#: orgs/serializers.py:13 perms/serializers/permission.py:39
+#: assets/serializers/domain.py:24 assets/serializers/platform.py:181
+#: orgs/serializers.py:13 perms/serializers/permission.py:50
msgid "Assets amount"
msgstr "資產數量"
-#: assets/serializers/gateway.py:23 common/validators.py:34
+#: assets/serializers/gateway.py:19
+msgid "The platform must start with Gateway"
+msgstr ""
+
+#: assets/serializers/gateway.py:28 common/validators.py:34
msgid "This field must be unique."
msgstr "欄位必須唯一"
@@ -2390,19 +2564,19 @@ msgstr "該協議是預設的,添加資產時,將默認顯示"
msgid "This protocol is public, asset will show this protocol to user"
msgstr "該協議是公開的,資產將向用戶顯示該協議並可以連接使用"
-#: assets/serializers/platform.py:157
+#: assets/serializers/platform.py:161
msgid "Help text"
msgstr "幫助"
-#: assets/serializers/platform.py:158
+#: assets/serializers/platform.py:162
msgid "Choices"
msgstr "選擇"
-#: assets/serializers/platform.py:170
+#: assets/serializers/platform.py:174
msgid "Automation"
msgstr "自動化"
-#: assets/serializers/platform.py:199
+#: assets/serializers/platform.py:203
msgid ""
"Login with account when accessing assets, then automatically switch to "
"another, similar to logging in with a regular account and then switching to "
@@ -2411,23 +2585,23 @@ msgstr ""
"在訪問資產時使用帳戶登入,然後自動切換到另一個帳戶,就像用普通帳戶登入然後切"
"換到 root 一樣"
-#: assets/serializers/platform.py:205
+#: assets/serializers/platform.py:209
msgid "Assets can be connected using a zone gateway"
msgstr "資產可以使用區域網關進行連接"
-#: assets/serializers/platform.py:207
+#: assets/serializers/platform.py:211
msgid "Default Domain"
msgstr "默認網域"
-#: assets/serializers/platform.py:229
+#: assets/serializers/platform.py:233
msgid "type is required"
msgstr "類型 該欄位是必填項。"
-#: assets/serializers/platform.py:244
+#: assets/serializers/platform.py:248
msgid "Protocols is required"
msgstr "協議是必填的"
-#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:35
+#: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:39
msgid "Test assets connectivity "
msgstr "測試資產可連接性"
@@ -2435,19 +2609,26 @@ msgstr "測試資產可連接性"
msgid "Gather asset hardware info"
msgstr "收集資產硬體資訊"
-#: assets/tasks/automation.py:24
+#: assets/tasks/automation.py:25
msgid "Asset execute automation"
msgstr "資產執行自動化"
-#: assets/tasks/gather_facts.py:21 assets/tasks/gather_facts.py:27
+#: assets/tasks/gather_facts.py:22 assets/tasks/gather_facts.py:32
msgid "Gather assets facts"
msgstr "收集資產資訊"
-#: assets/tasks/gather_facts.py:39
+#: assets/tasks/gather_facts.py:25
+msgid ""
+"When clicking 'Refresh hardware info' in 'Console - Asset Details - Basic' "
+"this task \n"
+" will be executed"
+msgstr "當在控制台資產詳情-基本設定點擊更新硬體資訊執行該任務"
+
+#: assets/tasks/gather_facts.py:44
msgid "Update assets hardware info: "
msgstr "更新資產硬體資訊"
-#: assets/tasks/gather_facts.py:47
+#: assets/tasks/gather_facts.py:52
msgid "Update node asset hardware information: "
msgstr "更新節點資產硬體資訊: "
@@ -2455,28 +2636,59 @@ msgstr "更新節點資產硬體資訊: "
msgid "Check the amount of assets under the node"
msgstr "檢查節點下資產數量"
-#: assets/tasks/nodes_amount.py:28
+#: assets/tasks/nodes_amount.py:18
+msgid ""
+"Manually verifying asset quantities updates the asset count for nodes under "
+"the \n"
+" current organization. This task will be called in the following two "
+"cases: when updating \n"
+" nodes and when the number of nodes exceeds 100"
+msgstr ""
+"手動校對資產數量更新當前組織下的節點資產數量;更新節點,當節點數大於100這兩種"
+"情況會呼叫該任務"
+
+#: assets/tasks/nodes_amount.py:34
msgid ""
"The task of self-checking is already running and cannot be started repeatedly"
msgstr "自檢程序已經在運行,不能重複啟動"
-#: assets/tasks/nodes_amount.py:33
+#: assets/tasks/nodes_amount.py:40
msgid "Periodic check the amount of assets under the node"
msgstr "週期性檢查節點下資產數量"
-#: assets/tasks/ping.py:20 assets/tasks/ping.py:26
+#: assets/tasks/nodes_amount.py:42
+msgid ""
+"Schedule the check_node_assets_amount_task to periodically update the asset "
+"count of \n"
+" all nodes under all organizations"
+msgstr ""
+"定時調用check_node_assets_amount_task任務,更新所有組織下所有節點的資產數量"
+
+#: assets/tasks/ping.py:20 assets/tasks/ping.py:30
msgid "Test assets connectivity"
msgstr "測試資產可連接性"
-#: assets/tasks/ping.py:42
+#: assets/tasks/ping.py:24
+msgid ""
+"When clicking 'Test Asset Connectivity' in 'Asset Details - Basic Settings' "
+"this task will be executed"
+msgstr "當資產詳情-基本設定點擊測試資產可連結性 執行該任務"
+
+#: assets/tasks/ping.py:46
msgid "Test if the assets under the node are connectable "
msgstr "測試節點下資產是否可連接"
-#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:25
-#: assets/tasks/ping_gateway.py:34
+#: assets/tasks/ping_gateway.py:19 assets/tasks/ping_gateway.py:29
+#: assets/tasks/ping_gateway.py:38
msgid "Test gateways connectivity"
msgstr "測試網關可連接性"
+#: assets/tasks/ping_gateway.py:23
+msgid ""
+"When clicking 'Test Connection' in 'Domain Details - Gateway' this task will "
+"be executed"
+msgstr "當在網域詳情-網關-測試連線時執行該任務"
+
#: assets/tasks/utils.py:16
msgid "Asset has been disabled, skipped: {}"
msgstr "資產已經被禁用, 跳過: {}"
@@ -2533,7 +2745,7 @@ msgstr "建立軟連結"
#: audits/const.py:18 audits/const.py:28
#: ops/templates/ops/celery_task_log.html:86
-#: terminal/api/session/session.py:149
+#: terminal/api/session/session.py:153
msgid "Download"
msgstr "下載"
@@ -2541,7 +2753,7 @@ msgstr "下載"
msgid "Rename dir"
msgstr "映射目錄"
-#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:274
+#: audits/const.py:23 rbac/tree.py:266 terminal/api/session/session.py:281
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:84
@@ -2583,16 +2795,16 @@ msgstr "同意"
msgid "Close"
msgstr "關閉"
-#: audits/const.py:41 ops/models/celery.py:84
+#: audits/const.py:41 ops/models/celery.py:85
#: terminal/models/session/sharing.py:128 tickets/const.py:25
#: xpack/plugins/cloud/const.py:67
msgid "Finished"
msgstr "結束"
#: audits/const.py:46 settings/serializers/terminal.py:6
-#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:175
+#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:174
#: terminal/models/virtualapp/provider.py:14 terminal/serializers/session.py:55
-#: terminal/serializers/session.py:69
+#: terminal/serializers/session.py:79
msgid "Terminal"
msgstr "終端"
@@ -2744,9 +2956,9 @@ msgstr "用戶會話"
msgid "Offline user session"
msgstr "下線用戶會話"
-#: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16
-#: ops/models/base.py:53 ops/models/celery.py:86 ops/models/job.py:151
-#: ops/models/job.py:240 ops/models/playbook.py:30
+#: audits/serializers.py:33 ops/models/adhoc.py:24 ops/models/base.py:16
+#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:151
+#: ops/models/job.py:240 ops/models/playbook.py:32
#: terminal/models/session/sharing.py:25
msgid "Creator"
msgstr "創建者"
@@ -2801,7 +3013,7 @@ msgstr "認證令牌"
#: audits/signal_handlers/login_log.py:37 authentication/notifications.py:73
#: authentication/views/login.py:78 notifications/backends/__init__.py:11
#: settings/serializers/auth/wecom.py:11 settings/serializers/auth/wecom.py:16
-#: users/models/user/__init__.py:122 users/models/user/_source.py:18
+#: users/models/user/__init__.py:122 users/models/user/_source.py:19
msgid "WeCom"
msgstr "企業微信"
@@ -2809,21 +3021,21 @@ msgstr "企業微信"
#: authentication/views/login.py:90 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:12
#: settings/serializers/auth/feishu.py:14 users/models/user/__init__.py:128
-#: users/models/user/_source.py:20
+#: users/models/user/_source.py:21
msgid "FeiShu"
msgstr "飛書"
#: audits/signal_handlers/login_log.py:40 authentication/views/login.py:102
#: authentication/views/slack.py:79 notifications/backends/__init__.py:16
#: settings/serializers/auth/slack.py:11 settings/serializers/auth/slack.py:13
-#: users/models/user/__init__.py:134 users/models/user/_source.py:22
+#: users/models/user/__init__.py:134 users/models/user/_source.py:23
msgid "Slack"
msgstr "Slack"
#: audits/signal_handlers/login_log.py:41 authentication/views/dingtalk.py:151
#: authentication/views/login.py:84 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:11 users/models/user/__init__.py:125
-#: users/models/user/_source.py:19
+#: users/models/user/_source.py:20
msgid "DingTalk"
msgstr "釘釘"
@@ -2838,14 +3050,36 @@ msgstr "臨時密碼"
msgid "Passkey"
msgstr "Passkey"
-#: audits/tasks.py:131
+#: audits/tasks.py:132
msgid "Clean audits session task log"
msgstr "清理資產審計會話任務日誌"
-#: audits/tasks.py:145
+#: audits/tasks.py:134
+msgid ""
+"Since the system generates login logs, operation logs, file upload logs, "
+"activity \n"
+" logs, Celery execution logs, session recordings, command records, "
+"and password change \n"
+" logs, it will perform cleanup of records that exceed the time limit "
+"according to the \n"
+" 'Tasks - Regular clean-up' in the system settings at 2 a.m daily"
+msgstr ""
+"由於系統會產生登錄日誌,操作日誌,文件上傳日誌,活動日誌,celery執行日誌,會"
+"話錄製和命令記錄,改密日誌,所以系統會根據系統設置-任務列表-定期清理配置,對"
+"於超出時間的於每天凌晨2點進行清理"
+
+#: audits/tasks.py:154
msgid "Upload FTP file to external storage"
msgstr "上傳 FTP 文件到外部儲存"
+#: audits/tasks.py:156
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured, files uploaded through file "
+"management will be \n"
+" synchronized to external storage"
+msgstr ""
+"如果設置了SERVER_REPLAY_STORAGE,將通過文件管理上傳的文件同步到外部儲存"
+
#: authentication/api/access_key.py:39
msgid "Access keys can be created at most 10"
msgstr "最多可以創建10個訪問金鑰"
@@ -2887,7 +3121,7 @@ msgstr "ACL 動作是覆核"
msgid "Current user not support mfa type: {}"
msgstr "當前用戶不支持 MFA 類型: {}"
-#: authentication/api/password.py:33 terminal/api/session/session.py:322
+#: authentication/api/password.py:33 terminal/api/session/session.py:334
#: users/views/profile/reset.py:63
msgid "User does not exist: {}"
msgstr "用戶不存在: {}"
@@ -2937,6 +3171,14 @@ msgstr "無效的令牌頭。符號字串不應包含無效字元。"
msgid "Invalid token or cache refreshed."
msgstr "刷新的令牌或快取無效。"
+#: authentication/backends/oidc/views.py:174
+msgid "OpenID Error"
+msgstr "OpenID 錯誤"
+
+#: authentication/backends/oidc/views.py:175
+msgid "Please check if a user with the same username or email already exists"
+msgstr "請檢查是否已經存在相同用戶名或電子郵箱的用戶"
+
#: authentication/backends/passkey/api.py:37
msgid "Only register passkey for local user"
msgstr "僅為本地用戶註冊金鑰"
@@ -3128,15 +3370,15 @@ msgstr "您的密碼無效"
msgid "Please wait for %s seconds before retry"
msgstr "請在 %s 秒後重試"
-#: authentication/errors/redirect.py:85 authentication/mixins.py:323
+#: authentication/errors/redirect.py:85 authentication/mixins.py:326
msgid "Your password is too simple, please change it for security"
msgstr "你的密碼過於簡單,為了安全,請修改"
-#: authentication/errors/redirect.py:93 authentication/mixins.py:330
+#: authentication/errors/redirect.py:93 authentication/mixins.py:335
msgid "You should to change your password before login"
msgstr "登錄完成前,請先修改密碼"
-#: authentication/errors/redirect.py:101 authentication/mixins.py:337
+#: authentication/errors/redirect.py:101 authentication/mixins.py:344
msgid "Your password has expired, please reset before logging in"
msgstr "您的密碼已過期,先修改再登錄"
@@ -3234,7 +3476,7 @@ msgstr "設置手機號碼啟用"
msgid "Clear phone number to disable"
msgstr "清空手機號碼禁用"
-#: authentication/middleware.py:94 settings/utils/ldap.py:679
+#: authentication/middleware.py:94 settings/utils/ldap.py:691
msgid "Authentication failed (before login check failed): {}"
msgstr "認證失敗 (登錄前檢查失敗): {}"
@@ -3252,7 +3494,7 @@ msgstr "管理員已開啟'僅允許從用戶來源登錄',當前用戶來源
msgid "The MFA type ({}) is not enabled"
msgstr "該 MFA ({}) 方式沒有啟用"
-#: authentication/mixins.py:313
+#: authentication/mixins.py:314
msgid "Please change your password"
msgstr "請修改密碼"
@@ -3430,7 +3672,7 @@ msgid "Actions"
msgstr "動作"
#: authentication/serializers/connection_token.py:42
-#: perms/serializers/permission.py:43 perms/serializers/permission.py:64
+#: perms/serializers/permission.py:54 perms/serializers/permission.py:75
#: users/serializers/user.py:127 users/serializers/user.py:273
msgid "Is expired"
msgstr "已過期"
@@ -3472,16 +3714,22 @@ msgstr "SSH金鑰不合法"
msgid "Access IP"
msgstr "IP 白名單"
-#: authentication/serializers/token.py:92 perms/serializers/permission.py:42
-#: perms/serializers/permission.py:65 users/serializers/user.py:128
+#: authentication/serializers/token.py:92 perms/serializers/permission.py:53
+#: perms/serializers/permission.py:76 users/serializers/user.py:128
#: users/serializers/user.py:270
msgid "Is valid"
msgstr "是否有效"
-#: authentication/tasks.py:11
+#: authentication/tasks.py:13
msgid "Clean expired session"
msgstr "清除過期會話"
+#: authentication/tasks.py:15
+msgid ""
+"Since user logins create sessions, the system will clean up expired sessions "
+"every 24 hours"
+msgstr "由於用戶登錄系統會產生會話,系統會每24小時清理已經過期的會話"
+
#: authentication/templates/authentication/_access_key_modal.html:6
msgid "API key list"
msgstr "API Key列表"
@@ -3541,7 +3789,7 @@ msgstr "代碼錯誤"
#: authentication/templates/authentication/_msg_oauth_bind.html:3
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_reset_password_code.html:9
-#: jumpserver/conf.py:502
+#: jumpserver/conf.py:522
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:32
#: users/templates/users/_msg_account_expire_reminder.html:4
@@ -3855,7 +4103,7 @@ msgstr "從企業微信獲取用戶失敗"
msgid "Please login with a password and then bind the WeCom"
msgstr "請使用密碼登錄,然後綁定企業微信"
-#: common/api/action.py:51
+#: common/api/action.py:57
msgid "Request file format may be wrong"
msgstr "上傳的檔案格式錯誤 或 其它類型資源的文件"
@@ -3883,7 +4131,7 @@ msgstr "運行中"
msgid "Canceled"
msgstr "取消"
-#: common/const/common.py:5 xpack/plugins/cloud/manager.py:412
+#: common/const/common.py:5 xpack/plugins/cloud/manager.py:411
#, python-format
msgid "%(name)s was created successfully"
msgstr "%(name)s 創建成功"
@@ -3987,7 +4235,7 @@ msgstr "組織 ID"
msgid "The file content overflowed (The maximum length `{}` bytes)"
msgstr "文件內容太大 (最大長度 `{}` 位元組)"
-#: common/drf/parsers/base.py:199
+#: common/drf/parsers/base.py:207
msgid "Parse file error: {}"
msgstr "解析文件錯誤: {}"
@@ -3995,7 +4243,70 @@ msgstr "解析文件錯誤: {}"
msgid "Invalid excel file"
msgstr "無效的 excel 文件"
-#: common/drf/renders/base.py:208
+#: common/drf/renders/base.py:138
+msgid "Yes/No"
+msgstr ""
+
+#: common/drf/renders/base.py:141
+msgid "Text, max length {}"
+msgstr "文字,最大長度 {}"
+
+#: common/drf/renders/base.py:143
+msgid "Long text, no length limit"
+msgstr "長文字,無長度限制"
+
+#: common/drf/renders/base.py:145
+msgid "Number, min {} max {}"
+msgstr "數字,最小 {} 最大 {}"
+
+#: common/drf/renders/base.py:148
+msgid "Datetime format {}"
+msgstr "日期時間格式 {}"
+
+#: common/drf/renders/base.py:154
+msgid ""
+"Choices, format name(value), name is optional for human read, value is "
+"requisite, options {}"
+msgstr "選項,格式: 名稱(值),名稱是可選的,方便閱讀,值是必填的,可選項有 {}"
+
+#: common/drf/renders/base.py:157
+msgid "Choices, options {}"
+msgstr "選項,可選項有 {}"
+
+#: common/drf/renders/base.py:159
+msgid "Phone number, format +8612345678901"
+msgstr "手機號碼,格式 +8612345678901"
+
+#: common/drf/renders/base.py:161
+msgid "Label, format [\"key:value\"]"
+msgstr "標籤,格式: [\"鍵:值\"]"
+
+#: common/drf/renders/base.py:163
+msgid ""
+"Object, format name(id), name is optional for human read, id is requisite"
+msgstr "關聯項,格式: 名稱(id), 名稱是可選的,方便閱讀,id 是必填的"
+
+#: common/drf/renders/base.py:165
+msgid "Object, format id"
+msgstr "關聯項,格式是 id"
+
+#: common/drf/renders/base.py:169
+msgid ""
+"Objects, format [\"name(id)\", ...], name is optional for human read, id is "
+"requisite"
+msgstr ""
+"多關聯項,格式: [\"名稱(id)\", ...], 名稱是可選的,方便閱讀,id 是必填的"
+
+#: common/drf/renders/base.py:171
+msgid ""
+"Labels, format [\"key:value\", ...], if label not exists, will create it"
+msgstr "標籤,格式: [\"鍵:值\", ...], 如果標籤不存在,將創建它"
+
+#: common/drf/renders/base.py:173
+msgid "Objects, format [\"id\", ...]"
+msgstr "多關聯項,格式是 [\"id\", ...]"
+
+#: common/drf/renders/base.py:271
msgid ""
"{} - The encryption password has not been set - please go to personal "
"information -> file encryption password to set the encryption password"
@@ -4154,18 +4465,36 @@ msgstr "標籤"
# msgid "Labels"
# msgstr "標籤管理"
-#: common/tasks.py:31
+#: common/tasks.py:32
msgid "Send email"
msgstr "發件郵件"
-#: common/tasks.py:58
+#: common/tasks.py:35
+msgid "This task will be executed when sending email notifications"
+msgstr "發送郵件訊息時執行該任務"
+
+#: common/tasks.py:65
msgid "Send email attachment"
msgstr "發送郵件附件"
-#: common/tasks.py:80 terminal/tasks.py:58
-msgid "Upload session replay to external storage"
+#: common/tasks.py:68
+msgid ""
+"When an account password is changed or an account backup generates "
+"attachments, \n"
+" this task needs to be executed for sending emails and handling "
+"attachments"
+msgstr "當帳號改密,帳號備份產生附件,需要對發送郵件及附件,執行該任務"
+
+#: common/tasks.py:94
+msgid "Upload account backup to external storage"
msgstr "上傳會話錄影到外部儲存"
+#: common/tasks.py:96
+msgid ""
+"When performing an account backup, this task needs to be executed to "
+"external storage (SFTP)"
+msgstr "當執行帳號備份,需要到外部儲存(sftp),執行該任務"
+
#: common/utils/ip/geoip/utils.py:26
msgid "Invalid ip"
msgstr "無效 IP"
@@ -4179,10 +4508,17 @@ msgstr "無效地址"
msgid "Hello %s"
msgstr "你好 %s"
-#: common/utils/verify_code.py:16
+#: common/utils/verify_code.py:17
msgid "Send SMS code"
msgstr "傳簡訊驗證碼"
+#: common/utils/verify_code.py:19
+msgid ""
+"When resetting a password, forgetting a password, or verifying MFA, this "
+"task needs to \n"
+" be executed to send SMS messages"
+msgstr "當重設密碼,忘記密碼,驗證mfa時,需要發送短信時執行該任務"
+
#: common/validators.py:16
msgid "Special char not allowed"
msgstr "不能包含特殊字元"
@@ -4195,16 +4531,16 @@ msgstr "不能包含特殊字元"
msgid "The mobile phone number format is incorrect"
msgstr "手機號碼格式不正確"
-#: jumpserver/conf.py:496
+#: jumpserver/conf.py:516
#, python-brace-format
msgid "The verification code is: {code}"
msgstr "驗證碼為: {code}"
-#: jumpserver/conf.py:501
+#: jumpserver/conf.py:521
msgid "Create account successfully"
msgstr "創建帳號成功"
-#: jumpserver/conf.py:503
+#: jumpserver/conf.py:523
msgid "Your account has been created successfully"
msgstr "你的帳號已創建成功"
@@ -4298,15 +4634,22 @@ msgstr "系統資訊"
msgid "Publish the station message"
msgstr "發布站內消息"
-#: ops/ansible/inventory.py:106 ops/models/job.py:65
+#: notifications/notifications.py:48
+msgid ""
+"This task needs to be executed for sending internal messages for system "
+"alerts, \n"
+" work orders, and other notifications"
+msgstr "系統一些告警,工單等需要發送站內信時執行該任務"
+
+#: ops/ansible/inventory.py:116 ops/models/job.py:65
msgid "No account available"
msgstr "無可用帳號"
-#: ops/ansible/inventory.py:285
+#: ops/ansible/inventory.py:296
msgid "Ansible disabled"
msgstr "Ansible 已禁用"
-#: ops/ansible/inventory.py:301
+#: ops/ansible/inventory.py:312
msgid "Skip hosts below:"
msgstr "跳過以下主機: "
@@ -4354,31 +4697,31 @@ msgid ""
"The task is being created and cannot be interrupted. Please try again later."
msgstr "正在創建任務,無法中斷,請稍後重試。"
-#: ops/api/playbook.py:39
+#: ops/api/playbook.py:50
msgid "Currently playbook is being used in a job"
msgstr "當前 playbook 正在作業中使用"
-#: ops/api/playbook.py:97
+#: ops/api/playbook.py:113
msgid "Unsupported file content"
msgstr "不支持的文件內容"
-#: ops/api/playbook.py:99 ops/api/playbook.py:145 ops/api/playbook.py:193
+#: ops/api/playbook.py:115 ops/api/playbook.py:161 ops/api/playbook.py:209
msgid "Invalid file path"
msgstr "無效的文件路徑"
-#: ops/api/playbook.py:171
+#: ops/api/playbook.py:187
msgid "This file can not be rename"
msgstr "該文件不能重命名"
-#: ops/api/playbook.py:190
+#: ops/api/playbook.py:206
msgid "File already exists"
msgstr "文件已存在"
-#: ops/api/playbook.py:208
+#: ops/api/playbook.py:224
msgid "File key is required"
msgstr "文件金鑰該欄位是必填項。"
-#: ops/api/playbook.py:211
+#: ops/api/playbook.py:227
msgid "This file can not be delete"
msgstr "無法刪除此文件"
@@ -4418,11 +4761,11 @@ msgstr "空白"
msgid "VCS"
msgstr "VCS"
-#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:120
+#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:123
msgid "Adhoc"
msgstr "命令"
-#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:88
+#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:91
msgid "Playbook"
msgstr "Playbook"
@@ -4486,53 +4829,69 @@ msgstr "超時"
msgid "Command execution disabled"
msgstr "命令執行禁用"
+#: ops/const.py:86
+msgctxt "scope"
+msgid "Public"
+msgstr "公有"
+
+#: ops/const.py:87
+msgid "Private"
+msgstr "私有"
+
#: ops/exception.py:6
msgid "no valid program entry found."
msgstr "沒有可用程序入口"
-#: ops/mixin.py:23 ops/mixin.py:102 settings/serializers/auth/ldap.py:72
+#: ops/mixin.py:30 ops/mixin.py:110 settings/serializers/auth/ldap.py:73
+#: settings/serializers/auth/ldap_ha.py:55
msgid "Periodic run"
msgstr "週期性執行"
-#: ops/mixin.py:25 ops/mixin.py:88 ops/mixin.py:108
-#: settings/serializers/auth/ldap.py:79
+#: ops/mixin.py:32 ops/mixin.py:96 ops/mixin.py:116
+#: settings/serializers/auth/ldap.py:80 settings/serializers/auth/ldap_ha.py:62
msgid "Interval"
msgstr "間隔"
-#: ops/mixin.py:28 ops/mixin.py:86 ops/mixin.py:105
-#: settings/serializers/auth/ldap.py:76
+#: ops/mixin.py:35 ops/mixin.py:94 ops/mixin.py:113
+#: settings/serializers/auth/ldap.py:77 settings/serializers/auth/ldap_ha.py:59
msgid "Crontab"
msgstr "Crontab"
-#: ops/mixin.py:110
+#: ops/mixin.py:118
msgid "Run period"
msgstr "執行週期"
-#: ops/mixin.py:119
+#: ops/mixin.py:127
msgid "* Please enter a valid crontab expression"
msgstr "* 請輸入有效的 crontab 表達式"
-#: ops/mixin.py:126
+#: ops/mixin.py:134
msgid "Range {} to {}"
msgstr "輸入在 {} - {} 範圍之間"
-#: ops/mixin.py:137
+#: ops/mixin.py:145
msgid "Require interval or crontab setting"
msgstr "需要週期或定期設定"
-#: ops/models/adhoc.py:21
+#: ops/models/adhoc.py:20
msgid "Pattern"
msgstr "模式"
-#: ops/models/adhoc.py:23 ops/models/job.py:146
+#: ops/models/adhoc.py:22 ops/models/job.py:146
msgid "Module"
msgstr "模組"
-#: ops/models/adhoc.py:24 ops/models/celery.py:81 ops/models/job.py:144
+#: ops/models/adhoc.py:23 ops/models/celery.py:82 ops/models/job.py:144
#: terminal/models/component/task.py:14
msgid "Args"
msgstr "參數"
+#: ops/models/adhoc.py:26 ops/models/playbook.py:36 ops/serializers/mixin.py:10
+#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
+#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
+msgid "Scope"
+msgstr "範圍"
+
# msgid "Creator"
# msgstr "創建者"
#: ops/models/base.py:19
@@ -4561,23 +4920,23 @@ msgstr "匯總"
msgid "Date last publish"
msgstr "發布日期"
-#: ops/models/celery.py:70
+#: ops/models/celery.py:71
msgid "Celery Task"
msgstr "Celery 任務"
-#: ops/models/celery.py:73
+#: ops/models/celery.py:74
msgid "Can view task monitor"
msgstr "可以查看任務監控"
-#: ops/models/celery.py:82 terminal/models/component/task.py:15
+#: ops/models/celery.py:83 terminal/models/component/task.py:15
msgid "Kwargs"
msgstr "其它參數"
-#: ops/models/celery.py:87
+#: ops/models/celery.py:88
msgid "Date published"
msgstr "發布日期"
-#: ops/models/celery.py:112
+#: ops/models/celery.py:113
msgid "Celery Task Execution"
msgstr "Celery 任務執行"
@@ -4622,11 +4981,11 @@ msgstr "Material 類型"
msgid "Job Execution"
msgstr "作業執行"
-#: ops/models/playbook.py:33
+#: ops/models/playbook.py:35
msgid "CreateMethod"
msgstr "創建方式"
-#: ops/models/playbook.py:34
+#: ops/models/playbook.py:37
msgid "VCS URL"
msgstr "VCS URL"
@@ -4690,34 +5049,95 @@ msgstr "任務 ID"
msgid "You do not have permission for the current job."
msgstr "你沒有當前作業的權限。"
-#: ops/tasks.py:38
+#: ops/tasks.py:51
msgid "Run ansible task"
msgstr "運行 Ansible 任務"
-#: ops/tasks.py:72
+#: ops/tasks.py:54
+msgid ""
+"Execute scheduled adhoc and playbooks, periodically invoking the task for "
+"execution"
+msgstr "當執行定時的快捷命令,playbook,定時呼叫該任務執行"
+
+#: ops/tasks.py:82
msgid "Run ansible task execution"
msgstr "開始執行 Ansible 任務"
-#: ops/tasks.py:94
+#: ops/tasks.py:85
+msgid "Execute the task when manually adhoc or playbooks"
+msgstr "手動執行快捷命令,playbook時執行該任務"
+
+#: ops/tasks.py:99
msgid "Clear celery periodic tasks"
msgstr "清理週期任務"
-#: ops/tasks.py:115
+#: ops/tasks.py:101
+msgid "At system startup, clean up celery tasks that no longer exist"
+msgstr "系統啟動時,清理已經不存在的celery任務"
+
+#: ops/tasks.py:125
msgid "Create or update periodic tasks"
msgstr "創建或更新週期任務"
-#: ops/tasks.py:123
+#: ops/tasks.py:127
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, tasks will be "
+"registered or the parameters \n"
+" of scheduled tasks will be updated"
+msgstr ""
+"隨著版本迭代,可能會新增任務或者修改任務的名稱,執行時間,所以在系統啟動"
+"時,,將會註冊任務或者更新定時任務參數"
+
+#: ops/tasks.py:140
msgid "Periodic check service performance"
msgstr "週期檢測服務性能"
-#: ops/tasks.py:129
+#: ops/tasks.py:142
+msgid ""
+"Check every hour whether each component is offline and whether the CPU, "
+"memory, \n"
+" and disk usage exceed the thresholds, and send an alert message to "
+"the administrator"
+msgstr ""
+"每小時檢測各組件是否離線,cpu,內存,硬盤使用率是否超過閾值,向管理員發送訊息"
+"預警"
+
+#: ops/tasks.py:152
msgid "Clean up unexpected jobs"
msgstr "清理異常作業"
-#: ops/tasks.py:136
+#: ops/tasks.py:154
+msgid ""
+"Due to exceptions caused by executing adhoc and playbooks in the Job "
+"Center, \n"
+" which result in the task status not being updated, the system will "
+"clean up abnormal jobs \n"
+" that have not been completed for more than 3 hours every hour and "
+"mark these tasks as \n"
+" failed"
+msgstr ""
+"由於作業中心執行快捷命令,playbook會產生異常,任務狀態未更新完成,系統將每小"
+"時執行清理超3小時未完成的異常作業,並將任務標記失敗"
+
+#: ops/tasks.py:167
msgid "Clean job_execution db record"
msgstr "清理作業中心執行歷史"
+#: ops/tasks.py:169
+msgid ""
+"Due to the execution of adhoc and playbooks in the Job Center, execution "
+"records will \n"
+" be generated. The system will clean up records that exceed the "
+"retention period every day \n"
+" at 2 a.m., based on the configuration of 'System Settings - Tasks - "
+"Regular clean-up - \n"
+" Job execution retention days'"
+msgstr ""
+"由於作業中心執行快捷命令,playbook,會產生j執行記錄,系統會根據系統設置-任務"
+"列表-定期清理-作業中心執行歷史配置,每天凌晨2點對超出保存時間的記錄進行清理"
+
#: ops/templates/ops/celery_task_log.html:4
msgid "Task log"
msgstr "任務列表"
@@ -4758,17 +5178,17 @@ msgstr "Job ID"
msgid "Name of the job"
msgstr "Job 名稱"
-#: orgs/api.py:61
+#: orgs/api.py:60
msgid "The current organization ({}) cannot be deleted"
msgstr "當前組織 ({}) 不能被刪除"
-#: orgs/api.py:66
+#: orgs/api.py:65
msgid ""
"LDAP synchronization is set to the current organization. Please switch to "
"another organization before deleting"
msgstr "LDAP 同步設定組織為當前組織,請切換其他組織後再進行刪除操作"
-#: orgs/api.py:76
+#: orgs/api.py:75
msgid "The organization have resource ({}) cannot be deleted"
msgstr "組織存在資源 ({}) 不能被刪除"
@@ -4782,7 +5202,7 @@ msgstr "請選擇一個組織後再保存"
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91
#: rbac/const.py:7 rbac/models/rolebinding.py:56
-#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:52
+#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:53
#: terminal/templates/terminal/_msg_command_warning.html:21
#: terminal/templates/terminal/_msg_session_sharing.html:14
#: tickets/models/ticket/general.py:303 tickets/serializers/ticket/ticket.py:60
@@ -4801,7 +5221,7 @@ msgstr "默認組織"
msgid "SYSTEM"
msgstr "系統組織"
-#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:185
+#: orgs/models.py:83 rbac/models/role.py:36 settings/models.py:186
#: terminal/models/applet/applet.py:42
msgid "Builtin"
msgstr "內建的"
@@ -4818,7 +5238,7 @@ msgstr "可以查看所有加入的組織"
msgid "Can not delete virtual org"
msgstr "無法刪除虛擬組織"
-#: orgs/serializers.py:10 perms/serializers/permission.py:37
+#: orgs/serializers.py:10 perms/serializers/permission.py:48
#: rbac/serializers/role.py:27 users/serializers/group.py:54
msgid "Users amount"
msgstr "用戶數量"
@@ -4827,7 +5247,7 @@ msgstr "用戶數量"
msgid "User groups amount"
msgstr "用戶組數量"
-#: orgs/serializers.py:14 perms/serializers/permission.py:40
+#: orgs/serializers.py:14 perms/serializers/permission.py:51
msgid "Nodes amount"
msgstr "節點數量"
@@ -4843,7 +5263,7 @@ msgstr "網關數量"
msgid "Asset permissions amount"
msgstr "資產授權數量"
-#: orgs/tasks.py:9
+#: orgs/tasks.py:10
msgid "Refresh organization cache"
msgstr "刷新組織快取"
@@ -4924,7 +5344,7 @@ msgid "today"
msgstr "今天"
#: perms/notifications.py:12 perms/notifications.py:44
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "day"
msgstr "天"
@@ -4944,22 +5364,62 @@ msgstr "資產授權規則將要過期"
msgid "asset permissions of organization {}"
msgstr "組織 ({}) 的資產授權"
-#: perms/serializers/permission.py:33 users/serializers/user.py:257
+#: perms/serializers/permission.py:32
+msgid ""
+"Accounts, format [\"@virtual\", \"root\", \"%template_id\"], virtual "
+"choices: @ALL, @SPEC, @USER, @ANON, @INPUT"
+msgstr ""
+"帳號,格式為 [\"@虛擬帳號\", \"root\", \"%模板id\"], 虛擬選項: @ALL, @SPEC, "
+"@USER, @ANON, @INPUT"
+
+#: perms/serializers/permission.py:38
+msgid "Protocols, format [\"ssh\", \"rdp\", \"vnc\"] or [\"all\"]"
+msgstr "協定,格式為 [\"ssh\", \"rdp\", \"vnc\"] 或 [\"all\"]"
+
+#: perms/serializers/permission.py:44 users/serializers/user.py:257
msgid "Groups"
msgstr "使用者群組"
-#: perms/serializers/permission.py:38
+#: perms/serializers/permission.py:49
msgid "Groups amount"
msgstr "使用者組數量"
-#: perms/tasks.py:27
+#: perms/tasks.py:28
msgid "Check asset permission expired"
msgstr "校驗資產授權規則已過期"
-#: perms/tasks.py:40
+#: perms/tasks.py:30
+msgid ""
+"The cache of organizational collections, which have completed user "
+"authorization tree \n"
+" construction, will expire. Therefore, expired collections need to be "
+"cleared from the \n"
+" cache, and this task will be executed periodically based on the time "
+"interval specified \n"
+" by PERM_EXPIRED_CHECK_PERIODIC in the system configuration file "
+"config.txt"
+msgstr ""
+"使用者授權樹已經建製完成的組織集合快取會過期,因此需要將過期的集合從快取中清"
+"理掉,根據系統設定檔 config.txt 中的 PERM_EXPIRED_CHECK_PERIODIC 的時間間隔定"
+"時執行該Action"
+
+#: perms/tasks.py:49
msgid "Send asset permission expired notification"
msgstr "發送資產權限過期通知"
+#: perms/tasks.py:51
+msgid ""
+"Check every day at 10 a.m. and send a notification message to users "
+"associated with \n"
+" assets whose authorization is about to expire, as well as to the "
+"organization's \n"
+" administrators, 3 days in advance, to remind them that the asset "
+"authorization will \n"
+" expire in a few days"
+msgstr ""
+"每天早上10點檢查,對於即將過期的資產授權相關聯的使用者及該組織管理員提前三天"
+"發送訊息通知,提示資產還有幾天即將過期"
+
#: perms/templates/perms/_msg_item_permissions_expire.html:7
#: perms/templates/perms/_msg_permed_items_expire.html:7
#, python-format
@@ -4992,27 +5452,27 @@ msgstr "{} 至少有一個系統角色"
msgid "App RBAC"
msgstr "RBAC"
-#: rbac/builtin.py:115
+#: rbac/builtin.py:116
msgid "SystemAdmin"
msgstr "系統管理員"
-#: rbac/builtin.py:118
+#: rbac/builtin.py:119
msgid "SystemAuditor"
msgstr "系統審計員"
-#: rbac/builtin.py:121
+#: rbac/builtin.py:122
msgid "SystemComponent"
msgstr "系統組件"
-#: rbac/builtin.py:127
+#: rbac/builtin.py:128
msgid "OrgAdmin"
msgstr "組織管理員"
-#: rbac/builtin.py:130
+#: rbac/builtin.py:131
msgid "OrgAuditor"
msgstr "組織審計員"
-#: rbac/builtin.py:133
+#: rbac/builtin.py:134
msgid "OrgUser"
msgstr "組織用戶"
@@ -5052,11 +5512,6 @@ msgstr "內容類型"
msgid "Permissions"
msgstr "授權"
-#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
-#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
-msgid "Scope"
-msgstr "範圍"
-
#: rbac/models/role.py:46 rbac/models/rolebinding.py:52
#: users/models/user/__init__.py:66
msgid "Role"
@@ -5116,7 +5571,7 @@ msgstr "工作檯"
msgid "Audit view"
msgstr "審計台"
-#: rbac/tree.py:27 settings/models.py:161
+#: rbac/tree.py:27 settings/models.py:162
msgid "System setting"
msgstr "系統設置"
@@ -5144,7 +5599,7 @@ msgstr "帳號改密"
msgid "App ops"
msgstr "作業中心"
-#: rbac/tree.py:57 settings/serializers/feature.py:117
+#: rbac/tree.py:57 settings/serializers/feature.py:120
msgid "Feature"
msgstr "功能"
@@ -5179,8 +5634,8 @@ msgstr "組織管理"
msgid "Ticket comment"
msgstr "工單評論"
-#: rbac/tree.py:159 settings/serializers/feature.py:98
-#: settings/serializers/feature.py:100 tickets/models/ticket/general.py:308
+#: rbac/tree.py:159 settings/serializers/feature.py:101
+#: settings/serializers/feature.py:103 tickets/models/ticket/general.py:308
msgid "Ticket"
msgstr "工單管理"
@@ -5210,7 +5665,7 @@ msgstr "郵件已經發送{}, 請檢查"
msgid "Test smtp setting"
msgstr "測試 smtp 設定"
-#: settings/api/ldap.py:89
+#: settings/api/ldap.py:92
msgid ""
"Users are not synchronized, please click the user synchronization button"
msgstr "用戶未同步,請點擊同步用戶按鈕"
@@ -5227,75 +5682,75 @@ msgstr "測試手機號碼 該欄位是必填項。"
msgid "App Settings"
msgstr "System Settings"
-#: settings/models.py:37 users/models/preference.py:14
+#: settings/models.py:38 users/models/preference.py:14
msgid "Encrypted"
msgstr "加密的"
-#: settings/models.py:163
+#: settings/models.py:164
msgid "Can change email setting"
msgstr "郵件設置"
-#: settings/models.py:164
+#: settings/models.py:165
msgid "Can change auth setting"
msgstr "認證設置"
-#: settings/models.py:165
+#: settings/models.py:166
msgid "Can change auth ops"
msgstr "任務中心設置"
-#: settings/models.py:166
+#: settings/models.py:167
msgid "Can change auth ticket"
msgstr "工單設置"
-#: settings/models.py:167
+#: settings/models.py:168
msgid "Can change virtual app setting"
msgstr "可以更改虛擬應用設定"
-#: settings/models.py:168
+#: settings/models.py:169
msgid "Can change auth announcement"
msgstr "公告設置"
-#: settings/models.py:169
+#: settings/models.py:170
msgid "Can change vault setting"
msgstr "可以更改 vault 設置"
-#: settings/models.py:170
+#: settings/models.py:171
msgid "Can change chat ai setting"
msgstr "可以修改聊天 AI 設置"
-#: settings/models.py:171
+#: settings/models.py:172
msgid "Can change system msg sub setting"
msgstr "消息訂閱設置"
-#: settings/models.py:172
+#: settings/models.py:173
msgid "Can change sms setting"
msgstr "簡訊設置"
-#: settings/models.py:173
+#: settings/models.py:174
msgid "Can change security setting"
msgstr "安全設定"
-#: settings/models.py:174
+#: settings/models.py:175
msgid "Can change clean setting"
msgstr "定期清理"
-#: settings/models.py:175
+#: settings/models.py:176
msgid "Can change interface setting"
msgstr "界面設置"
-#: settings/models.py:176
+#: settings/models.py:177
msgid "Can change license setting"
msgstr "許可證設置"
-#: settings/models.py:177
+#: settings/models.py:178
msgid "Can change terminal setting"
msgstr "終端設置"
-#: settings/models.py:178
+#: settings/models.py:179
msgid "Can change other setting"
msgstr "其它設置"
-#: settings/models.py:188
+#: settings/models.py:189
msgid "Chat prompt"
msgstr "聊天提示"
@@ -5308,58 +5763,62 @@ msgid "LDAP Auth"
msgstr "LDAP 認證"
#: settings/serializers/auth/base.py:14
+msgid "LDAP Auth HA"
+msgstr "LDAP HA 認證"
+
+#: settings/serializers/auth/base.py:15
msgid "CAS Auth"
msgstr "CAS 認證"
-#: settings/serializers/auth/base.py:15
+#: settings/serializers/auth/base.py:16
msgid "OPENID Auth"
msgstr "OIDC 認證"
-#: settings/serializers/auth/base.py:16
+#: settings/serializers/auth/base.py:17
msgid "SAML2 Auth"
msgstr "SAML2 認證"
-#: settings/serializers/auth/base.py:17
+#: settings/serializers/auth/base.py:18
msgid "OAuth2 Auth"
msgstr "OAuth2 認證"
-#: settings/serializers/auth/base.py:18
+#: settings/serializers/auth/base.py:19
msgid "RADIUS Auth"
msgstr "RADIUS 認證"
-#: settings/serializers/auth/base.py:19
+#: settings/serializers/auth/base.py:20
msgid "DingTalk Auth"
msgstr "釘釘 認證"
-#: settings/serializers/auth/base.py:20
+#: settings/serializers/auth/base.py:21
msgid "FeiShu Auth"
msgstr "飛書 認證"
-#: settings/serializers/auth/base.py:21
+#: settings/serializers/auth/base.py:22
msgid "Lark Auth"
msgstr "Lark 認證"
-#: settings/serializers/auth/base.py:22
+#: settings/serializers/auth/base.py:23
msgid "Slack Auth"
msgstr "Slack 認證"
-#: settings/serializers/auth/base.py:23
+#: settings/serializers/auth/base.py:24
msgid "WeCom Auth"
msgstr "企業微信 認證"
-#: settings/serializers/auth/base.py:24
+#: settings/serializers/auth/base.py:25
msgid "SSO Auth"
msgstr "SSO 令牌認證"
-#: settings/serializers/auth/base.py:25
+#: settings/serializers/auth/base.py:26
msgid "Passkey Auth"
msgstr "Passkey 認證"
-#: settings/serializers/auth/base.py:27
+#: settings/serializers/auth/base.py:28
msgid "Email suffix"
msgstr "郵件後綴"
-#: settings/serializers/auth/base.py:29
+#: settings/serializers/auth/base.py:30
msgid ""
"After third-party user authentication is successful, if the third-party "
"authentication service platform does not return the user's email "
@@ -5369,19 +5828,19 @@ msgstr ""
"第三方使用者認證成功後,若第三方認證服務平台未回傳該使用者的電子信箱資訊,系"
"統將自動以此電子信箱後綴建立使用者"
-#: settings/serializers/auth/base.py:36
+#: settings/serializers/auth/base.py:37
msgid "Forgot Password URL"
msgstr "忘記密碼連結"
-#: settings/serializers/auth/base.py:37
+#: settings/serializers/auth/base.py:38
msgid "The URL for Forgotten Password on the user login page"
msgstr "使用者登入頁面忘記密碼的 URL"
-#: settings/serializers/auth/base.py:40
+#: settings/serializers/auth/base.py:41
msgid "Login redirection"
msgstr "啟用登入跳轉提示"
-#: settings/serializers/auth/base.py:42
+#: settings/serializers/auth/base.py:43
msgid ""
"Should an flash page be displayed before the user is redirected to third-"
"party authentication when the administrator enables third-party redirect "
@@ -5390,7 +5849,7 @@ msgstr ""
"Action管理員啟用第三方重新定向身份驗證時,在使用者重定向到第三方身份驗證之前"
"是否顯示 Flash 頁面"
-#: settings/serializers/auth/base.py:54
+#: settings/serializers/auth/base.py:55
msgid ""
"When you create a user, you associate the user to the organization of your "
"choice. Users always belong to the Default organization."
@@ -5401,8 +5860,8 @@ msgstr ""
msgid "CAS"
msgstr "CAS"
-#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:43
-#: settings/serializers/auth/oidc.py:61
+#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap_ha.py:26 settings/serializers/auth/oidc.py:61
msgid "Server"
msgstr "服務端地址"
@@ -5429,9 +5888,10 @@ msgstr "啟用屬性映射"
#: settings/serializers/auth/cas.py:34 settings/serializers/auth/dingtalk.py:18
#: settings/serializers/auth/feishu.py:18 settings/serializers/auth/lark.py:17
-#: settings/serializers/auth/ldap.py:65 settings/serializers/auth/oauth2.py:60
-#: settings/serializers/auth/oidc.py:39 settings/serializers/auth/saml2.py:35
-#: settings/serializers/auth/slack.py:18 settings/serializers/auth/wecom.py:18
+#: settings/serializers/auth/ldap.py:66 settings/serializers/auth/ldap_ha.py:48
+#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:39
+#: settings/serializers/auth/saml2.py:35 settings/serializers/auth/slack.py:18
+#: settings/serializers/auth/wecom.py:18
msgid "User attribute"
msgstr "映射屬性"
@@ -5473,7 +5933,7 @@ msgstr ""
"使用者屬性對照,其中 `key` 是 JumpServer 使用者屬性名稱,`value` 是飛書服務使"
"用者屬性名稱"
-#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:21
+#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:22
msgid "Lark"
msgstr ""
@@ -5485,46 +5945,46 @@ msgstr ""
"使用者屬性對照,其中 `key` 是 JumpServer 使用者屬性名稱,`value` 是 Lark 服務"
"使用者屬性名稱"
-#: settings/serializers/auth/ldap.py:40 settings/serializers/auth/ldap.py:102
+#: settings/serializers/auth/ldap.py:41 settings/serializers/auth/ldap.py:103
msgid "LDAP"
msgstr "LDAP"
-#: settings/serializers/auth/ldap.py:44
+#: settings/serializers/auth/ldap.py:45
msgid "LDAP server URI"
msgstr "LDAP 服務域名"
-#: settings/serializers/auth/ldap.py:47
+#: settings/serializers/auth/ldap.py:48 settings/serializers/auth/ldap_ha.py:30
msgid "Bind DN"
msgstr "綁定 DN"
-#: settings/serializers/auth/ldap.py:48
+#: settings/serializers/auth/ldap.py:49 settings/serializers/auth/ldap_ha.py:31
msgid "Binding Distinguished Name"
msgstr "綁定的 DN"
-#: settings/serializers/auth/ldap.py:52
+#: settings/serializers/auth/ldap.py:53 settings/serializers/auth/ldap_ha.py:35
msgid "Binding password"
msgstr "原來的密碼"
-#: settings/serializers/auth/ldap.py:55
+#: settings/serializers/auth/ldap.py:56 settings/serializers/auth/ldap_ha.py:38
msgid "Search OU"
msgstr "系統架構"
-#: settings/serializers/auth/ldap.py:57
+#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/ldap_ha.py:40
msgid ""
"User Search Base, if there are multiple OUs, you can separate them with the "
"`|` symbol"
msgstr "使用者搜尋庫,如果有多個OU,可以用`|`符號分隔"
-#: settings/serializers/auth/ldap.py:61
+#: settings/serializers/auth/ldap.py:62 settings/serializers/auth/ldap_ha.py:44
msgid "Search filter"
msgstr "用戶過濾器"
-#: settings/serializers/auth/ldap.py:62
+#: settings/serializers/auth/ldap.py:63 settings/serializers/auth/ldap_ha.py:45
#, python-format
msgid "Selection could include (cn|uid|sAMAccountName=%(user)s)"
msgstr "可能的選項是(cn或uid或sAMAccountName=%(user)s)"
-#: settings/serializers/auth/ldap.py:67
+#: settings/serializers/auth/ldap.py:68 settings/serializers/auth/ldap_ha.py:50
msgid ""
"User attribute mapping, where the `key` is the JumpServer user attribute "
"name and the `value` is the LDAP service user attribute name"
@@ -5532,28 +5992,47 @@ msgstr ""
"使用者屬性對照,其中 `key` 是 JumpServer 使用者屬性名稱,`value` 是 LDAP 服務"
"使用者屬性名稱"
-#: settings/serializers/auth/ldap.py:83
+#: settings/serializers/auth/ldap.py:84 settings/serializers/auth/ldap_ha.py:66
msgid "Connect timeout (s)"
msgstr "連接超時時間 (秒)"
-#: settings/serializers/auth/ldap.py:88
+#: settings/serializers/auth/ldap.py:89 settings/serializers/auth/ldap_ha.py:71
msgid "User DN cache timeout (s)"
msgstr "快取逾時時間 (秒)"
-#: settings/serializers/auth/ldap.py:90
+#: settings/serializers/auth/ldap.py:91
msgid ""
"Caching the User DN obtained during user login authentication can "
-"effectivelyimprove the speed of user authentication., 0 means no cache
If "
-"the user OU structure has been adjusted, click Submit to clear the user DN "
-"cache"
+"effectively improve the speed of user authentication., 0 means no "
+"cache
If the user OU structure has been adjusted, click Submit to clear "
+"the user DN cache"
msgstr ""
-"對於使用者登錄認證時查詢的使用者 DN 進行快取,可以有效提高使用者認證的速度"
-"
如果使用者 OU 架構已調整,請點擊提交以清除使用者 DN 快取"
+"對用戶登入驗證時查詢出的 User DN 進行緩存,可以有效提升用戶認證的速度
如果"
+"用戶 OU 架構有调整,點擊提交即可清除用戶 DN 緩存"
-#: settings/serializers/auth/ldap.py:96
+#: settings/serializers/auth/ldap.py:97 settings/serializers/auth/ldap_ha.py:79
msgid "Search paged size (piece)"
msgstr "搜索分頁數量 (條)"
+#: settings/serializers/auth/ldap_ha.py:23
+#: settings/serializers/auth/ldap_ha.py:85
+msgid "LDAP HA"
+msgstr "LDAP 認證"
+
+#: settings/serializers/auth/ldap_ha.py:27
+msgid "LDAP HA server URI"
+msgstr "LDAP HA 服務網域名"
+
+#: settings/serializers/auth/ldap_ha.py:73
+msgid ""
+"Caching the User DN obtained during user login authentication can "
+"effectivelyimprove the speed of user authentication., 0 means no cache
If "
+"the user OU structure has been adjusted, click Submit to clear the user DN "
+"cache"
+msgstr ""
+"對使用者登入驗證時查詢出的 User DN 進行快取,可以有效提升使用者驗證的速度
"
+"如果使用者 OU 架構有調整,點擊提交即可清除使用者 DN 快取"
+
#: settings/serializers/auth/oauth2.py:19
#: settings/serializers/auth/oauth2.py:22
msgid "OAuth2"
@@ -5990,32 +6469,42 @@ msgid ""
msgstr ""
"會話、錄影,命令記錄超過該時長將會被清除 (影響資料庫儲存,OSS 等不受影響)"
-#: settings/serializers/feature.py:18 settings/serializers/msg.py:68
+#: settings/serializers/cleaning.py:53
+msgid "Change secret and push record retention days (day)"
+msgstr "改密推送記錄保留天數 (天)"
+
+#: settings/serializers/feature.py:19 settings/serializers/msg.py:68
msgid "Subject"
msgstr "主題"
-#: settings/serializers/feature.py:22
+#: settings/serializers/feature.py:23
msgid "More Link"
msgstr "更多資訊 URL"
-#: settings/serializers/feature.py:36 settings/serializers/feature.py:38
-#: settings/serializers/feature.py:39
+#: settings/serializers/feature.py:26
+#: settings/templates/ldap/_msg_import_ldap_user.html:6
+#: terminal/models/session/session.py:46
+msgid "Date end"
+msgstr "結束日期"
+
+#: settings/serializers/feature.py:39 settings/serializers/feature.py:41
+#: settings/serializers/feature.py:42
msgid "Announcement"
msgstr "公告"
-#: settings/serializers/feature.py:46
+#: settings/serializers/feature.py:49
msgid "Vault"
msgstr "啟用 Vault"
-#: settings/serializers/feature.py:55
+#: settings/serializers/feature.py:58
msgid "Mount Point"
msgstr "掛載點"
-#: settings/serializers/feature.py:61
+#: settings/serializers/feature.py:64
msgid "Record limit"
msgstr "紀錄限制"
-#: settings/serializers/feature.py:63
+#: settings/serializers/feature.py:66
msgid ""
"If the specific value is less than 999 (default), the system will "
"automatically perform a task every night: check and delete historical "
@@ -6025,74 +6514,74 @@ msgstr ""
"如果特定數值小於999,系統將在每日晚間自動執行任務:檢查並刪除超出預定數量的歷"
"史帳號。如果該數值達到或超過999,則不進行任何歷史帳號的刪除操作。"
-#: settings/serializers/feature.py:73 settings/serializers/feature.py:79
+#: settings/serializers/feature.py:76 settings/serializers/feature.py:82
msgid "Chat AI"
msgstr "聊天 AI"
-#: settings/serializers/feature.py:82
+#: settings/serializers/feature.py:85
msgid "GPT Base URL"
msgstr "GPT 地址"
-#: settings/serializers/feature.py:83
+#: settings/serializers/feature.py:86
msgid "The base URL of the GPT service. For example: https://api.openai.com/v1"
msgstr "GPT 服務的基礎 URL。例如:https://api.openai.com/v1"
-#: settings/serializers/feature.py:86 templates/_header_bar.html:96
+#: settings/serializers/feature.py:89 templates/_header_bar.html:96
msgid "API Key"
msgstr "API Key"
-#: settings/serializers/feature.py:90
+#: settings/serializers/feature.py:93
msgid ""
"The proxy server address of the GPT service. For example: http://ip:port"
msgstr "GPT 服務的代理伺服器地址。例如:http://ip:port"
-#: settings/serializers/feature.py:93
+#: settings/serializers/feature.py:96
msgid "GPT Model"
msgstr "GPT 模型"
-#: settings/serializers/feature.py:102
+#: settings/serializers/feature.py:105
msgid "Approval without login"
msgstr "免登入審核"
-#: settings/serializers/feature.py:103
+#: settings/serializers/feature.py:106
msgid "Allow direct approval ticket without login"
msgstr "允許無需登入直接批准工單"
-#: settings/serializers/feature.py:107
+#: settings/serializers/feature.py:110
msgid "Period"
msgstr "時段"
-#: settings/serializers/feature.py:108
+#: settings/serializers/feature.py:111
msgid ""
"The default authorization time period when applying for assets via a ticket"
msgstr "工單申請資產的預設授權時間段"
-#: settings/serializers/feature.py:111
+#: settings/serializers/feature.py:114
msgid "hour"
msgstr "時"
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "Unit"
msgstr "單位"
-#: settings/serializers/feature.py:112
+#: settings/serializers/feature.py:115
msgid "The unit of period"
msgstr "執行週期"
-#: settings/serializers/feature.py:121
+#: settings/serializers/feature.py:124
msgid ""
"Allow users to execute batch commands in the Workbench - Job Center - Adhoc"
msgstr "允許使用者在工作台 - 作業中心 - Adhoc 中執行批量指令"
-#: settings/serializers/feature.py:125
+#: settings/serializers/feature.py:128
msgid "Command blacklist"
msgstr "作業中心命令黑名單"
-#: settings/serializers/feature.py:126
+#: settings/serializers/feature.py:129
msgid "Command blacklist in Adhoc"
msgstr "作業中心指令黑名單"
-#: settings/serializers/feature.py:131
+#: settings/serializers/feature.py:134
#: terminal/models/virtualapp/provider.py:17
#: terminal/models/virtualapp/virtualapp.py:36
#: terminal/models/virtualapp/virtualapp.py:97
@@ -6100,11 +6589,11 @@ msgstr "作業中心指令黑名單"
msgid "Virtual app"
msgstr "虛擬應用"
-#: settings/serializers/feature.py:134
+#: settings/serializers/feature.py:137
msgid "Virtual App"
msgstr "虛擬應用"
-#: settings/serializers/feature.py:136
+#: settings/serializers/feature.py:139
msgid ""
"Virtual applications, you can use the Linux operating system as an "
"application server in remote applications."
@@ -6549,23 +7038,50 @@ msgid ""
"in the workbench"
msgstr "*! 如果啟用,具有 RBAC 權限的用戶將能夠使用工作台中的所有工具"
-#: settings/tasks/ldap.py:28
+#: settings/tasks/ldap.py:73
msgid "Periodic import ldap user"
msgstr "週期匯入 LDAP 用戶"
-#: settings/tasks/ldap.py:66
+#: settings/tasks/ldap.py:75 settings/tasks/ldap.py:85
+msgid ""
+"When LDAP auto-sync is configured, this task will be invoked to synchronize "
+"users"
+msgstr "設置了LDAP自動同步後,將呼叫該Action進行使用者同步"
+
+#: settings/tasks/ldap.py:83
+msgid "Periodic import ldap ha user"
+msgstr "周期導入 LDAP HA 使用者"
+
+#: settings/tasks/ldap.py:117
msgid "Registration periodic import ldap user task"
msgstr "註冊週期匯入 LDAP 用戶 任務"
+#: settings/tasks/ldap.py:119
+msgid ""
+"When LDAP auto-sync parameters change, such as Crontab parameters, the LDAP "
+"sync task \n"
+" will be re-registered or updated, and this task will be invoked"
+msgstr ""
+"設置了LDAP自動同步參數變動時,像是Crontab參數,重新註冊或更新ldap同步Action將"
+"呼叫該Action"
+
+#: settings/tasks/ldap.py:133
+msgid "Registration periodic import ldap ha user task"
+msgstr "註冊周期導入 LDAP HA 使用者 Action"
+
+#: settings/tasks/ldap.py:135
+msgid ""
+"When LDAP HA auto-sync parameters change, such as Crontab parameters, the "
+"LDAP HA sync task \n"
+" will be re-registered or updated, and this task will be invoked"
+msgstr ""
+"設置了LDAP HA自動同步參數變動時,像是Crontab參數,重新註冊或更新ldap ha同步"
+"Action將呼叫該Action"
+
#: settings/templates/ldap/_msg_import_ldap_user.html:2
msgid "Sync task finish"
msgstr "同步動作完成"
-#: settings/templates/ldap/_msg_import_ldap_user.html:6
-#: terminal/models/session/session.py:46
-msgid "Date end"
-msgstr "結束日期"
-
#: settings/templates/ldap/_msg_import_ldap_user.html:9
msgid "Synced Organization"
msgstr "已同步組織"
@@ -6578,108 +7094,108 @@ msgstr "已同步用戶"
msgid "No user synchronization required"
msgstr "沒有用戶需要同步"
-#: settings/utils/ldap.py:494
+#: settings/utils/ldap.py:509
msgid "ldap:// or ldaps:// protocol is used."
msgstr "使用 ldap:// 或 ldaps:// 協議"
-#: settings/utils/ldap.py:505
+#: settings/utils/ldap.py:520
msgid "Host or port is disconnected: {}"
msgstr "主機或埠不可連接: {}"
-#: settings/utils/ldap.py:507
+#: settings/utils/ldap.py:522
msgid "The port is not the port of the LDAP service: {}"
msgstr "埠不是LDAP服務埠: {}"
-#: settings/utils/ldap.py:509
+#: settings/utils/ldap.py:524
msgid "Please add certificate: {}"
msgstr "請添加證書"
-#: settings/utils/ldap.py:513 settings/utils/ldap.py:540
-#: settings/utils/ldap.py:570 settings/utils/ldap.py:598
+#: settings/utils/ldap.py:528 settings/utils/ldap.py:555
+#: settings/utils/ldap.py:585 settings/utils/ldap.py:613
msgid "Unknown error: {}"
msgstr "未知錯誤: {}"
-#: settings/utils/ldap.py:527
+#: settings/utils/ldap.py:542
msgid "Bind DN or Password incorrect"
msgstr "綁定DN或密碼錯誤"
-#: settings/utils/ldap.py:534
+#: settings/utils/ldap.py:549
msgid "Please enter Bind DN: {}"
msgstr "請輸入綁定DN: {}"
-#: settings/utils/ldap.py:536
+#: settings/utils/ldap.py:551
msgid "Please enter Password: {}"
msgstr "請輸入密碼: {}"
-#: settings/utils/ldap.py:538
+#: settings/utils/ldap.py:553
msgid "Please enter correct Bind DN and Password: {}"
msgstr "請輸入正確的綁定DN和密碼: {}"
-#: settings/utils/ldap.py:556
+#: settings/utils/ldap.py:571
msgid "Invalid User OU or User search filter: {}"
msgstr "不合法的用戶OU或用戶過濾器: {}"
-#: settings/utils/ldap.py:587
+#: settings/utils/ldap.py:602
msgid "LDAP User attr map not include: {}"
msgstr "LDAP屬性映射沒有包含: {}"
-#: settings/utils/ldap.py:594
+#: settings/utils/ldap.py:609
msgid "LDAP User attr map is not dict"
msgstr "LDAP屬性映射不合法"
-#: settings/utils/ldap.py:613
+#: settings/utils/ldap.py:628
msgid "LDAP authentication is not enabled"
msgstr "LDAP認證沒有啟用"
-#: settings/utils/ldap.py:631
+#: settings/utils/ldap.py:646
msgid "Error (Invalid LDAP server): {}"
msgstr "錯誤 (不合法的LDAP伺服器地址): {}"
-#: settings/utils/ldap.py:633
+#: settings/utils/ldap.py:648
msgid "Error (Invalid Bind DN): {}"
msgstr "錯誤 (不合法的綁定DN): {}"
-#: settings/utils/ldap.py:635
+#: settings/utils/ldap.py:650
msgid "Error (Invalid LDAP User attr map): {}"
msgstr "錯誤 (不合法的LDAP屬性映射): {}"
-#: settings/utils/ldap.py:637
+#: settings/utils/ldap.py:652
msgid "Error (Invalid User OU or User search filter): {}"
msgstr "錯誤 (不合法的用戶OU或用戶過濾器): {}"
-#: settings/utils/ldap.py:639
+#: settings/utils/ldap.py:654
msgid "Error (Not enabled LDAP authentication): {}"
msgstr "錯誤 (沒有啟用LDAP認證): {}"
-#: settings/utils/ldap.py:641
+#: settings/utils/ldap.py:656
msgid "Error (Unknown): {}"
msgstr "錯誤 (未知): {}"
-#: settings/utils/ldap.py:644
+#: settings/utils/ldap.py:659
msgid "Succeed: Match {} users"
msgstr "成功配對 {} 個用戶"
-#: settings/utils/ldap.py:677
+#: settings/utils/ldap.py:689
msgid "Authentication failed (configuration incorrect): {}"
msgstr "認證失敗 (配置錯誤): {}"
-#: settings/utils/ldap.py:681
+#: settings/utils/ldap.py:693
msgid "Authentication failed (username or password incorrect): {}"
msgstr "認證失敗 (使用者名稱或密碼不正確): {}"
-#: settings/utils/ldap.py:683
+#: settings/utils/ldap.py:695
msgid "Authentication failed (Unknown): {}"
msgstr "認證失敗: (未知): {}"
-#: settings/utils/ldap.py:686
+#: settings/utils/ldap.py:698
msgid "Authentication success: {}"
msgstr "認證成功: {}"
-#: settings/ws.py:198
+#: settings/ws.py:199
msgid "No LDAP user was found"
msgstr "沒有取得到 LDAP 用戶"
-#: settings/ws.py:204
+#: settings/ws.py:205
msgid "Total {}, success {}, failure {}"
msgstr "總共 {},成功 {},失敗 {}"
@@ -6899,27 +7415,27 @@ msgstr "未發現 protocol 查詢參數"
msgid "Deleting the default storage is not allowed"
msgstr "不允許刪除默認儲存配置"
-#: terminal/api/component/storage.py:34
-msgid "Cannot delete storage that is being used"
-msgstr "不允許刪除正在使用的儲存配置"
+#: terminal/api/component/storage.py:36
+msgid "Cannot delete storage that is being used: {}"
+msgstr "無法刪除正在使用的儲存: {}"
-#: terminal/api/component/storage.py:75 terminal/api/component/storage.py:76
+#: terminal/api/component/storage.py:77 terminal/api/component/storage.py:78
msgid "Command storages"
msgstr "命令儲存"
-#: terminal/api/component/storage.py:82
+#: terminal/api/component/storage.py:84
msgid "Invalid"
msgstr "無效"
-#: terminal/api/component/storage.py:130 terminal/tasks.py:149
+#: terminal/api/component/storage.py:132 terminal/tasks.py:208
msgid "Test failure: {}"
msgstr "測試失敗: {}"
-#: terminal/api/component/storage.py:133
+#: terminal/api/component/storage.py:135
msgid "Test successful"
msgstr "測試成功"
-#: terminal/api/component/storage.py:135
+#: terminal/api/component/storage.py:137
msgid "Test failure: Please check configuration"
msgstr "測試失敗:請檢查配置"
@@ -6932,15 +7448,15 @@ msgstr "有在線會話"
msgid "User %s %s session %s replay"
msgstr "用戶 %s %s 了會話 %s 的錄影"
-#: terminal/api/session/session.py:314
+#: terminal/api/session/session.py:326
msgid "Session does not exist: {}"
msgstr "會話不存在: {}"
-#: terminal/api/session/session.py:317
+#: terminal/api/session/session.py:329
msgid "Session is finished or the protocol not supported"
msgstr "會話已經完成或協議不支持"
-#: terminal/api/session/session.py:330
+#: terminal/api/session/session.py:342
msgid "User does not have permission"
msgstr "用戶沒有權限"
@@ -7104,7 +7620,7 @@ msgstr "版本"
msgid "Can concurrent"
msgstr "可以並發"
-#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:167
+#: terminal/models/applet/applet.py:49 terminal/serializers/applet_host.py:179
#: terminal/serializers/storage.py:193
msgid "Hosts"
msgstr "主機"
@@ -7135,7 +7651,7 @@ msgstr "宿主機"
msgid "Applet Publication"
msgstr "應用發布"
-#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:69
+#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:81
msgid "Deploy options"
msgstr "部署參數"
@@ -7247,12 +7763,12 @@ msgstr "執行緒數"
msgid "Boot Time"
msgstr "運行時間"
-#: terminal/models/component/storage.py:146
+#: terminal/models/component/storage.py:144
#: terminal/models/component/terminal.py:91
msgid "Command storage"
msgstr "命令儲存"
-#: terminal/models/component/storage.py:214
+#: terminal/models/component/storage.py:212
#: terminal/models/component/terminal.py:92
msgid "Replay storage"
msgstr "錄影儲存"
@@ -7269,7 +7785,7 @@ msgstr "遠端地址"
msgid "Application User"
msgstr "應用用戶"
-#: terminal/models/component/terminal.py:177
+#: terminal/models/component/terminal.py:176
msgid "Can view terminal config"
msgstr "可以查看終端配置"
@@ -7301,7 +7817,7 @@ msgstr "登錄來源"
msgid "Replay"
msgstr "重播"
-#: terminal/models/session/session.py:48 terminal/serializers/session.py:68
+#: terminal/models/session/session.py:48 terminal/serializers/session.py:78
msgid "Command amount"
msgstr "命令數量"
@@ -7309,23 +7825,23 @@ msgstr "命令數量"
msgid "Error reason"
msgstr "錯誤原因"
-#: terminal/models/session/session.py:290
+#: terminal/models/session/session.py:308
msgid "Session record"
msgstr "會話記錄"
-#: terminal/models/session/session.py:292
+#: terminal/models/session/session.py:310
msgid "Can monitor session"
msgstr "可以監控會話"
-#: terminal/models/session/session.py:293
+#: terminal/models/session/session.py:311
msgid "Can share session"
msgstr "可以分享會話"
-#: terminal/models/session/session.py:294
+#: terminal/models/session/session.py:312
msgid "Can terminate session"
msgstr "可以終斷會話"
-#: terminal/models/session/session.py:295
+#: terminal/models/session/session.py:313
msgid "Can validate session action perm"
msgstr "可以驗證會話動作權限"
@@ -7425,7 +7941,7 @@ msgstr "級別"
msgid "Command and replay storage"
msgstr "命令及錄影儲存"
-#: terminal/notifications.py:240 terminal/tasks.py:153
+#: terminal/notifications.py:240 terminal/tasks.py:212
#: xpack/plugins/cloud/api.py:160
#: xpack/plugins/cloud/serializers/account.py:121
#: xpack/plugins/cloud/serializers/account.py:123
@@ -7442,12 +7958,12 @@ msgid "Icon"
msgstr "圖示"
#: terminal/serializers/applet_host.py:24
-msgid "Per Session"
-msgstr "每用戶"
+msgid "Per Device (Device number limit)"
+msgstr ""
#: terminal/serializers/applet_host.py:25
-msgid "Per Device"
-msgstr "每設備"
+msgid "Per User (User number limit)"
+msgstr ""
#: terminal/serializers/applet_host.py:37
msgid "Core API"
@@ -7474,27 +7990,40 @@ msgstr ""
msgid "Ignore Certificate Verification"
msgstr "忽略證書認證"
-#: terminal/serializers/applet_host.py:47
+#: terminal/serializers/applet_host.py:48
msgid "Existing RDS license"
msgstr "已有 RDS 許可證"
-#: terminal/serializers/applet_host.py:48
+#: terminal/serializers/applet_host.py:50
+msgid ""
+"If not exist, the RDS will be in trial mode, and the trial period is 120 "
+"days. Detail"
+msgstr ""
+
+#: terminal/serializers/applet_host.py:55
msgid "RDS License Server"
msgstr "RDS 許可伺服器"
-#: terminal/serializers/applet_host.py:49
+#: terminal/serializers/applet_host.py:57
msgid "RDS Licensing Mode"
msgstr "RDS 授權模式"
-#: terminal/serializers/applet_host.py:51
+#: terminal/serializers/applet_host.py:60
msgid "RDS Single Session Per User"
msgstr "RDS 單用戶單會話"
-#: terminal/serializers/applet_host.py:53
+#: terminal/serializers/applet_host.py:61
+msgid ""
+"Tips: A RDS user can have only one session at a time. If set, when next "
+"login connected, previous session will be disconnected."
+msgstr ""
+
+#: terminal/serializers/applet_host.py:65
msgid "RDS Max Disconnection Time (ms)"
msgstr "RDS 最大斷開時間(毫秒)"
-#: terminal/serializers/applet_host.py:55
+#: terminal/serializers/applet_host.py:67
msgid ""
"Tips: Set the maximum duration for keeping a disconnected session active on "
"the server (log off the session after 60000 milliseconds)."
@@ -7502,11 +8031,11 @@ msgstr ""
"提示:設置某個已斷開連接的會話在伺服器上能保持活動狀態的最長時間(60000 毫秒"
"後註銷會話)"
-#: terminal/serializers/applet_host.py:60
+#: terminal/serializers/applet_host.py:72
msgid "RDS Remote App Logoff Time Limit (ms)"
msgstr "RDS 遠程應用註銷時間限制(毫秒)"
-#: terminal/serializers/applet_host.py:62
+#: terminal/serializers/applet_host.py:74
msgid ""
"Tips: Set the logoff time for RemoteApp sessions after closing all RemoteApp "
"programs (0 milliseconds, log off the session immediately)."
@@ -7514,12 +8043,12 @@ msgstr ""
"提示:關閉所有 RemoteApp 程序之後設置 RemoteAPP 會話的註銷時間(0 毫秒,立即"
"註銷會話)"
-#: terminal/serializers/applet_host.py:71 terminal/serializers/terminal.py:47
+#: terminal/serializers/applet_host.py:83 terminal/serializers/terminal.py:47
#: terminal/serializers/virtualapp_provider.py:13
msgid "Load status"
msgstr "負載狀態"
-#: terminal/serializers/applet_host.py:85
+#: terminal/serializers/applet_host.py:97
msgid ""
"These accounts are used to connect to the published application, the account "
"is now divided into two types, one is dedicated to each account, each user "
@@ -7532,11 +8061,11 @@ msgstr ""
"使用公共帳號連接;
注意: 如果不開啟自動創建帳號, 當前發布機僅能被指定標"
"簽的資產調度到,默認不會放到調度池中,且需要手動維護帳號"
-#: terminal/serializers/applet_host.py:92
+#: terminal/serializers/applet_host.py:104
msgid "The number of public accounts created automatically"
msgstr "公用帳號自動創建的數量"
-#: terminal/serializers/applet_host.py:95
+#: terminal/serializers/applet_host.py:107
msgid ""
"Connect to the host using the same account first. For security reasons, "
"please set the configuration item CACHE_LOGIN_PASSWORD_ENABLED=true and "
@@ -7545,15 +8074,15 @@ msgstr ""
"優先使用同名帳號連接髮布機。為了安全,需配置文件中開啟配置 "
"CACHE_LOGIN_PASSWORD_ENABLED=true, 修改後重啟服務"
-#: terminal/serializers/applet_host.py:137
+#: terminal/serializers/applet_host.py:149
msgid "Install applets"
msgstr "安裝應用"
-#: terminal/serializers/applet_host.py:167
+#: terminal/serializers/applet_host.py:179
msgid "Host ID"
msgstr "主機 ID"
-#: terminal/serializers/applet_host.py:168
+#: terminal/serializers/applet_host.py:180
msgid "Applet ID"
msgstr "遠程應用 ID"
@@ -7878,39 +8407,102 @@ msgstr "授權已過期"
msgid "storage is null"
msgstr "儲存為空"
-#: terminal/tasks.py:31
+#: terminal/tasks.py:32
msgid "Periodic delete terminal status"
msgstr "週期清理終端狀態"
-#: terminal/tasks.py:39
+#: terminal/tasks.py:43
msgid "Clean orphan session"
msgstr "清除離線會話"
-#: terminal/tasks.py:87
+#: terminal/tasks.py:45
+msgid ""
+"Check every 10 minutes for asset connection sessions that have been inactive "
+"for 3 \n"
+" minutes and mark these sessions as completed"
+msgstr "每10分鐘檢查3分鐘未活躍的資產連結會話,將這些會話標記未已完成"
+
+#: terminal/tasks.py:68
+msgid "Upload session replay to external storage"
+msgstr "上傳會話錄影到外部儲存"
+
+#: terminal/tasks.py:70 terminal/tasks.py:104
+msgid ""
+"If SERVER_REPLAY_STORAGE is configured in the config.txt, session commands "
+"and \n"
+" recordings will be uploaded to external storage"
+msgstr ""
+"如果設置了SERVER_REPLAY_STORAGE,將透過檔案管理上傳的檔案同步到外部儲存"
+
+#: terminal/tasks.py:102
+msgid "Upload session replay part file to external storage"
+msgstr "將會話重播部分檔案上傳到外部存儲"
+
+#: terminal/tasks.py:123
msgid "Run applet host deployment"
msgstr "運行應用機部署"
-#: terminal/tasks.py:97
+#: terminal/tasks.py:126
+msgid ""
+"When deploying from the remote application publisher details page, and the "
+"'Deploy' \n"
+" button is clicked, this task will be executed"
+msgstr "發布機部署,點擊部署時,執行該Action"
+
+#: terminal/tasks.py:137
msgid "Install applet"
msgstr "安裝應用"
-#: terminal/tasks.py:108
+#: terminal/tasks.py:140
+msgid ""
+"When the 'Deploy' button is clicked in the 'Remote Application' section of "
+"the remote \n"
+" application publisher details page, this task will be executed"
+msgstr "當遠端應用發布機的詳細資訊-遠端應用,點擊部署時,執行該Action"
+
+#: terminal/tasks.py:152
msgid "Uninstall applet"
msgstr "卸載應用"
-#: terminal/tasks.py:119
+#: terminal/tasks.py:155
+msgid ""
+"When the 'Uninstall' button is clicked in the 'Remote Application' section "
+"of the \n"
+" remote application publisher details page, this task will be executed"
+msgstr "當遠端應用發布機的詳細資訊-遠端應用,點擊移除時,執行該Action"
+
+#: terminal/tasks.py:167
msgid "Generate applet host accounts"
msgstr "收集遠程應用上的帳號"
-#: terminal/tasks.py:131
+#: terminal/tasks.py:170
+msgid ""
+"When a remote publishing server is created and an account needs to be "
+"created \n"
+" automatically, this task will be executed"
+msgstr "當創建遠程發布機後,需要自動創建帳號時,執行該Action"
+
+#: terminal/tasks.py:184
msgid "Check command replay storage connectivity"
msgstr "檢查命令及錄影儲存可連接性 "
+#: terminal/tasks.py:186
+msgid ""
+"Check every day at midnight whether the external storage for commands and "
+"recordings \n"
+" is accessible. If it is not accessible, send a notification to the "
+"recipients specified \n"
+" in 'System Settings - Notifications - Subscription - Storage - "
+"Connectivity'"
+msgstr ""
+"每天凌晨0點檢查命令及錄像外部儲存是否能夠連接,無法連接時發送給:系統設定-通"
+"知設定-訊息訂閱-命令及錄像儲存設定的接收人"
+
#: terminal/templates/terminal/_msg_command_alert.html:10
msgid "view"
msgstr "查看"
-#: terminal/utils/db_port_mapper.py:85
+#: terminal/utils/db_port_mapper.py:88
msgid ""
"No available port is matched. The number of databases may have exceeded the "
"number of ports open to the database agent service, Contact the "
@@ -7919,13 +8511,13 @@ msgstr ""
"未匹配到可用埠,資料庫的數量可能已經超過資料庫代理服務開放的埠數量,請聯系管"
"理員開放更多埠。"
-#: terminal/utils/db_port_mapper.py:113
+#: terminal/utils/db_port_mapper.py:116
msgid ""
"No ports can be used, check and modify the limit on the number of ports that "
"Magnus listens on in the configuration file."
msgstr "沒有埠可以使用,檢查並修改配置文件中 Magnus 監聽的埠數量限制。"
-#: terminal/utils/db_port_mapper.py:115
+#: terminal/utils/db_port_mapper.py:118
msgid "All available port count: {}, Already use port count: {}"
msgstr "所有可用埠數量:{},已使用埠數量:{}"
@@ -7988,19 +8580,19 @@ msgid ""
msgstr ""
"通過工單創建, 工單標題: {}, 工單申請人: {}, 工單處理人: {}, 工單 ID: {}"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Change field"
msgstr "變更欄位"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "Before change"
msgstr "變更前"
-#: tickets/handlers/base.py:85
+#: tickets/handlers/base.py:84
msgid "After change"
msgstr "變更後"
-#: tickets/handlers/base.py:97
+#: tickets/handlers/base.py:96
msgid "{} {} the ticket"
msgstr "{} {} 工單"
@@ -8238,11 +8830,15 @@ msgstr "無效的審批動作"
msgid "This user is not authorized to approve this ticket"
msgstr "此用戶無權審批此工單"
-#: users/api/user.py:155
+#: users/api/user.py:63
+msgid "Cannot delete the admin user. Please disable it instead."
+msgstr "無法刪除管理員用戶。請將其禁用。"
+
+#: users/api/user.py:161
msgid "Can not invite self"
msgstr "不能邀請自己"
-#: users/api/user.py:208
+#: users/api/user.py:214
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在該頁面重設 MFA 多因子認證, 請去個人資訊頁面重設"
@@ -8709,7 +9305,7 @@ msgstr ""
msgid "name not unique"
msgstr "名稱重複"
-#: users/signal_handlers.py:39
+#: users/signal_handlers.py:41
msgid ""
"The administrator has enabled \"Only allow existing users to log in\", \n"
" and the current user is not in the user list. Please contact the "
@@ -8717,31 +9313,86 @@ msgid ""
msgstr ""
"管理員已開啟'僅允許已存在用戶登錄',當前用戶不在用戶列表中,請聯絡管理員。"
-#: users/signal_handlers.py:183
+#: users/signal_handlers.py:196
msgid "Clean up expired user sessions"
msgstr "清除過期的用戶會話"
-#: users/tasks.py:25
+#: users/signal_handlers.py:198
+msgid ""
+"After logging in via the web, a user session record is created. At 2 a.m. "
+"every day, \n"
+" the system cleans up inactive user devices"
+msgstr ""
+"使用web登入後,會產生使用者會話在線紀錄,每天凌晨2點,清理未在線的使用者設備"
+
+#: users/tasks.py:26
msgid "Check password expired"
msgstr "校驗密碼已過期"
-#: users/tasks.py:39
+#: users/tasks.py:28
+msgid ""
+"Check every day at 10 AM whether the passwords of users in the system are "
+"expired, \n"
+" and send a notification 5 days in advance"
+msgstr "每天早上10點檢查,系統中使用者的密碼是否過期,提前5天發送通知"
+
+#: users/tasks.py:46
msgid "Periodic check password expired"
msgstr "週期校驗密碼過期"
-#: users/tasks.py:53
+#: users/tasks.py:48
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if passwords have expired"
+msgstr ""
+"隨著版本迭代,可能會新增Action或者修改Action的名稱,執行時間,所以在系統啟動"
+"時,註冊或者更新檢驗密碼已過期Action的參數"
+
+#: users/tasks.py:67
msgid "Check user expired"
msgstr "校驗用戶已過期"
-#: users/tasks.py:70
+#: users/tasks.py:69
+msgid ""
+"Check every day at 2 p.m whether the users in the system are expired, and "
+"send a \n"
+" notification 5 days in advance"
+msgstr "每天上午10點檢查,系統中的使用者是否過期,提前5天發送通知"
+
+#: users/tasks.py:90
msgid "Periodic check user expired"
msgstr "週期檢測用戶過期"
-#: users/tasks.py:84
+#: users/tasks.py:92
+msgid ""
+"With version iterations, new tasks may be added, or task names and execution "
+"times may \n"
+" be modified. Therefore, upon system startup, it is necessary to "
+"register or update the \n"
+" parameters of the task that checks if users have expired"
+msgstr ""
+"隨著版本迭代,可能會新增任務或者修改任務的名稱,執行時間,所以在系統啟動時,"
+"註冊或者更新檢驗使用者已過期任務的參數"
+
+#: users/tasks.py:111
msgid "Check unused users"
msgstr "檢查未使用的用戶"
-#: users/tasks.py:123
+#: users/tasks.py:113
+msgid ""
+"At 2 p.m. every day, according to the configuration in \"System Settings - "
+"Security - \n"
+" Auth security - Auto disable threshold\" users who have not logged "
+"in or whose API keys \n"
+" have not been used for a long time will be disabled"
+msgstr ""
+"每天凌晨2點,根據系統配置-安全設置-不活躍使用者自動禁用配置,對長時間不登錄或"
+"api_key不使用的使用者進行禁用"
+
+#: users/tasks.py:157
msgid "The user has not logged in recently and has been disabled."
msgstr "該用戶最近未登錄,已被禁用。"
@@ -9192,7 +9843,7 @@ msgstr ""
msgid "Failed to synchronize the instance \"%s\""
msgstr "Unable to synchronize instance %s"
-#: xpack/plugins/cloud/manager.py:337
+#: xpack/plugins/cloud/manager.py:336
#, python-format
msgid ""
"The updated platform of asset \"%s\" is inconsistent with the original "
@@ -9201,42 +9852,42 @@ msgstr ""
"The update platform of asset \"%s\" is not consistent with the original "
"platform type. Skip platform and protocol updates"
-#: xpack/plugins/cloud/manager.py:393
+#: xpack/plugins/cloud/manager.py:392
#, python-format
msgid "The asset \"%s\" already exists"
msgstr "\"資產 \"%s\" 已存在"
-#: xpack/plugins/cloud/manager.py:395
+#: xpack/plugins/cloud/manager.py:394
#, python-format
msgid "Update asset \"%s\""
msgstr "更新資產 \"%s\""
-#: xpack/plugins/cloud/manager.py:398
+#: xpack/plugins/cloud/manager.py:397
#, python-format
msgid "Asset \"%s\" has been updated"
msgstr "資產 \"%s\" 已更新"
-#: xpack/plugins/cloud/manager.py:408
+#: xpack/plugins/cloud/manager.py:407
#, python-format
msgid "Prepare to create asset \"%s\""
msgstr "Preparing to create asset %s"
-#: xpack/plugins/cloud/manager.py:429
+#: xpack/plugins/cloud/manager.py:428
#, python-format
msgid "Set nodes \"%s\""
msgstr "Delete Node: \"%s\""
-#: xpack/plugins/cloud/manager.py:455
+#: xpack/plugins/cloud/manager.py:454
#, python-format
msgid "Set accounts \"%s\""
msgstr "刪除帳號: %s"
-#: xpack/plugins/cloud/manager.py:471
+#: xpack/plugins/cloud/manager.py:470
#, python-format
msgid "Set protocols \"%s\""
msgstr "設定協議 \"%s\""
-#: xpack/plugins/cloud/manager.py:485 xpack/plugins/cloud/tasks.py:30
+#: xpack/plugins/cloud/manager.py:484 xpack/plugins/cloud/tasks.py:31
msgid "Run sync instance task"
msgstr "執行同步實例任務"
@@ -9711,10 +10362,32 @@ msgstr "執行次數"
msgid "Instance count"
msgstr "實例個數"
-#: xpack/plugins/cloud/tasks.py:44
+#: xpack/plugins/cloud/tasks.py:33
+msgid ""
+"\n"
+" Execute this task when manually or scheduled cloud synchronization "
+"tasks are performed\n"
+" "
+msgstr "\n"
+"手動,定時執行雲同步任務時執行該任務"
+
+#: xpack/plugins/cloud/tasks.py:52
msgid "Period clean sync instance task execution"
msgstr "定期清除同步實例任務執行記錄"
+#: xpack/plugins/cloud/tasks.py:54
+msgid ""
+"\n"
+" Every day, according to the configuration in \"System Settings - "
+"Tasks - Regular \n"
+" clean-up - Cloud sync task history retention days\" the system will "
+"clean up the execution \n"
+" records generated by cloud synchronization\n"
+" "
+msgstr "\n"
+"每天根據系統設置-任務列表-定期清理配置-雲同步記錄配置,對雲同步產生的執行記錄"
+"進行清理"
+
#: xpack/plugins/interface/api.py:52
msgid "Restore default successfully."
msgstr "恢復默認成功!"
diff --git a/apps/i18n/koko/en.json b/apps/i18n/koko/en.json
index 136efdb4b..bd02311c7 100644
--- a/apps/i18n/koko/en.json
+++ b/apps/i18n/koko/en.json
@@ -1,20 +1,28 @@
{
"ActionPerm": "Actions",
"Cancel": "Cancel",
+ "Confirm": "Confirm",
"ConfirmBtn": "Confirm",
+ "Connect": "Connect",
"CopyLink": "Copy Link Address and Code",
"CopyShareURLSuccess": "Copy Share URL Success",
"CreateLink": "Create Share Link",
"CreateSuccess": "Success",
+ "DownArrow": "Down arrow",
"Download": "Download",
"DownloadSuccess": "Download success",
"EndFileTransfer": "File transfer end",
"ExceedTransferSize": "exceed max transfer size",
+ "Expand": "Expand",
"ExpiredTime": "Expired",
"GetShareUser": "Enter username",
+ "Hotkeys": "Hotkeys",
"InputVerifyCode": "Input Verify Code",
"JoinShare": "Join Session",
+ "JoinedWithSuccess": "Successfully joined",
+ "KubernetesManagement": "Kubernetes management",
"LeaveShare": "Leave Session",
+ "LeftArrow": "Left arrow",
"LinkAddr": "Link",
"Minute": "Minute",
"Minutes": "Minutes",
@@ -22,13 +30,16 @@
"MustSelectOneFile": "Must select one file",
"NoLink": "No Link",
"OnlineUsers": "Online Users",
+ "Paste": "Paste",
"PauseSession": "Pause Session",
"ReadOnly": "Read-Only",
+ "Refresh": "Refresh",
"Remove": "Remove",
- "Confirm": "Confirm",
"RemoveShareUser": "You have been removed from the shared session.",
"RemoveShareUserConfirm": "Are you sure to remove the user from the shared session?",
"ResumeSession": "Resume Session",
+ "RightArrow": "Right arrow",
+ "Search": "Search",
"SelectAction": "Select",
"SelectTheme": "Select Theme",
"Self": "Self",
@@ -42,6 +53,7 @@
"Theme": "Theme",
"ThemeColors": "Theme Colors",
"ThemeConfig": "Theme",
+ "UpArrow": "Up arrow",
"Upload": "Upload",
"UploadSuccess": "Upload success",
"UploadTips": "Drag file here or click to upload",
@@ -49,5 +61,11 @@
"User": "User",
"VerifyCode": "Verify Code",
"WaitFileTransfer": "Wait file transfer to finish",
- "Writable": "Writable"
-}
+ "WebSocketClosed": "WebSocket closed",
+ "Writable": "Writable",
+ "Reconnect": "Reconnect",
+ "Close Current Tab": "Close Current Tab",
+ "Close All Tabs": "Close All Tabs",
+ "Clone Connect": "Clone Connect",
+ "Custom Setting": "Custom Setting"
+}
\ No newline at end of file
diff --git a/apps/i18n/koko/ja.json b/apps/i18n/koko/ja.json
index 61b3438b9..47f426463 100644
--- a/apps/i18n/koko/ja.json
+++ b/apps/i18n/koko/ja.json
@@ -1,20 +1,32 @@
{
"ActionPerm": "アクション権限",
"Cancel": "キャンセル",
+ "Clone Connect": "ウィンドウをコピー",
+ "Close All Tabs": "すべてを閉じる",
+ "Close Current Tab": "現在を閉じる",
+ "Confirm": "確認",
"ConfirmBtn": "確定",
+ "Connect": "接続",
"CopyLink": "リンクと認証コードのコピー",
"CopyShareURLSuccess": "レプリケーション共有住所成功",
"CreateLink": "シェアリンクの作成",
"CreateSuccess": "作成に成功しました",
+ "Custom Setting": "カスタム設定",
+ "DownArrow": "下向き矢印",
"Download": "ダウンロード",
"DownloadSuccess": "ダウンロードに成功しました",
"EndFileTransfer": "ファイル転送終了",
"ExceedTransferSize": "最大転送サイズを超えています",
+ "Expand": "展開",
"ExpiredTime": "有効期限",
"GetShareUser": "ユーザー名の入力",
+ "Hotkeys": "ショートカットキー",
"InputVerifyCode": "認証コードを入力してください",
"JoinShare": "共有セッションに参加",
+ "JoinedWithSuccess": "正常に参加しました",
+ "KubernetesManagement": "Kubernetes 管理",
"LeaveShare": "共有セッションから退出",
+ "LeftArrow": "戻る矢印",
"LinkAddr": "リンク先",
"Minute": "分間",
"Minutes": "分間",
@@ -22,12 +34,17 @@
"MustSelectOneFile": "ファイルを選択する必要があります",
"NoLink": "住所なし",
"OnlineUsers": "オンラインスタッフ",
+ "Paste": "貼り付け",
"PauseSession": "セッションを一時停止",
"ReadOnly": "読み取り専用",
+ "Reconnect": "再接続",
+ "Refresh": "リフレッシュ",
"Remove": "削除",
"RemoveShareUser": "あなたはすでに共有セッションから削除されました」という意味です",
"RemoveShareUserConfirm": "共有セッションから削除してもよろしいですか?",
"ResumeSession": "セッションを再開",
+ "RightArrow": "進む矢印",
+ "Search": "検索",
"SelectAction": "選択してください",
"SelectTheme": "テーマを選択してください",
"Self": "自分",
@@ -41,6 +58,7 @@
"Theme": "テーマ",
"ThemeColors": "テーマカラー",
"ThemeConfig": "テーマ",
+ "UpArrow": "上向き矢印",
"Upload": "アップロード",
"UploadSuccess": "アップロード成功",
"UploadTips": "ファイルをここにドラッグするか、アップロードをクリックします",
@@ -48,5 +66,6 @@
"User": "ユーザー",
"VerifyCode": "認証コード",
"WaitFileTransfer": "ファイル転送終了待ち",
+ "WebSocketClosed": "WebSocket 閉店",
"Writable": "書き込み可能"
}
\ No newline at end of file
diff --git a/apps/i18n/koko/zh.json b/apps/i18n/koko/zh.json
index 072b524ad..db0cc86f2 100644
--- a/apps/i18n/koko/zh.json
+++ b/apps/i18n/koko/zh.json
@@ -1,21 +1,28 @@
{
"ActionPerm": "操作权限",
"Cancel": "取消",
+ "Confirm": "确认",
"ConfirmBtn": "确定",
+ "Connect": "连接",
"CopyLink": "复制链接及验证码",
"CopyShareURLSuccess": "复制分享地址成功",
"CreateLink": "创建分享链接",
"CreateSuccess": "创建成功",
- "Confirm": "确认",
+ "DownArrow": "向下箭头",
"Download": "下载",
"DownloadSuccess": "下载成功",
"EndFileTransfer": "文件传输结束",
"ExceedTransferSize": "超过最大传输大小",
+ "Expand": "展开",
"ExpiredTime": "有效期限",
"GetShareUser": "输入用户名",
+ "Hotkeys": "快捷键",
"InputVerifyCode": "请输入验证码",
"JoinShare": "加入共享",
+ "JoinedWithSuccess": "已成功加入",
+ "KubernetesManagement": "Kubernetes 管理",
"LeaveShare": "离开共享",
+ "LeftArrow": "后退箭头",
"LinkAddr": "链接地址",
"Minute": "分钟",
"Minutes": "分钟",
@@ -23,12 +30,16 @@
"MustSelectOneFile": "必须选择一个文件",
"NoLink": "无地址",
"OnlineUsers": "在线人员",
+ "Paste": "粘贴",
"PauseSession": "暂停此会话",
"ReadOnly": "只读",
+ "Refresh": "刷新",
"Remove": "移除",
"RemoveShareUser": "你已经被移除共享会话",
"RemoveShareUserConfirm": "确定要移除该用户吗?",
"ResumeSession": "恢复此会话",
+ "RightArrow": "前进箭头",
+ "Search": "搜索",
"SelectAction": "请选择",
"SelectTheme": "请选择主题",
"Self": "我",
@@ -42,6 +53,7 @@
"Theme": "主题",
"ThemeColors": "主题颜色",
"ThemeConfig": "主题",
+ "UpArrow": "向上箭头",
"Upload": "上传",
"UploadSuccess": "上传成功",
"UploadTips": "将文件拖到此处,或点击上传",
@@ -49,5 +61,11 @@
"User": "用户",
"VerifyCode": "验证码",
"WaitFileTransfer": "等待文件传输结束",
- "Writable": "读写"
-}
+ "WebSocketClosed": "WebSocket 已关闭",
+ "Reconnect": "重新连接",
+ "Writable": "可写",
+ "Close Current Tab": "关闭当前",
+ "Close All Tabs": "关闭所有",
+ "Clone Connect": "复制窗口",
+ "Custom Setting": "自定义设置"
+}
\ No newline at end of file
diff --git a/apps/i18n/koko/zh_hant.json b/apps/i18n/koko/zh_hant.json
index c233ae0c7..8fe8cfee0 100644
--- a/apps/i18n/koko/zh_hant.json
+++ b/apps/i18n/koko/zh_hant.json
@@ -1,20 +1,32 @@
{
"ActionPerm": "操作權限",
"Cancel": "取消",
+ "Clone Connect": "複製視窗",
+ "Close All Tabs": "關閉全部",
+ "Close Current Tab": "關閉當前",
+ "Confirm": "確認",
"ConfirmBtn": "確定",
+ "Connect": "連接",
"CopyLink": "複製連結及驗證碼",
"CopyShareURLSuccess": "複製分享地址成功",
"CreateLink": "創建分享連結",
"CreateSuccess": "創建成功",
+ "Custom Setting": "自訂設定",
+ "DownArrow": "向下箭頭",
"Download": "下載",
"DownloadSuccess": "下載成功",
"EndFileTransfer": "文件傳輸結束",
"ExceedTransferSize": "超過最大傳輸大小",
+ "Expand": "展開",
"ExpiredTime": "有效期限",
"GetShareUser": "輸入使用者名稱",
+ "Hotkeys": "快速鍵",
"InputVerifyCode": "請輸入驗證碼",
"JoinShare": "加入共享",
+ "JoinedWithSuccess": "已成功加入",
+ "KubernetesManagement": "Kubernetes 管理",
"LeaveShare": "離開共享",
+ "LeftArrow": "後退箭頭",
"LinkAddr": "連結地址",
"Minute": "分鐘",
"Minutes": "分鐘",
@@ -22,12 +34,17 @@
"MustSelectOneFile": "必須選擇一個文件",
"NoLink": "無地址",
"OnlineUsers": "在線人員",
+ "Paste": "貼上",
"PauseSession": "暫停此會話",
"ReadOnly": "只讀",
+ "Reconnect": "重新連線",
+ "Refresh": "刷新",
"Remove": "移除",
"RemoveShareUser": "你已經被移除共享會話",
"RemoveShareUserConfirm": "確定要移除該用戶嗎?",
"ResumeSession": "恢復此會話",
+ "RightArrow": "前進箭頭",
+ "Search": "搜尋",
"SelectAction": "請選擇",
"SelectTheme": "請選擇主題",
"Self": "我",
@@ -41,6 +58,7 @@
"Theme": "主題",
"ThemeColors": "主題顏色",
"ThemeConfig": "主題",
+ "UpArrow": "向上箭頭",
"Upload": "上傳",
"UploadSuccess": "上傳成功",
"UploadTips": "將文件拖到此處,或點擊上傳",
@@ -48,5 +66,6 @@
"User": "用戶",
"VerifyCode": "驗證碼",
"WaitFileTransfer": "等待文件傳輸結束",
+ "WebSocketClosed": "WebSocket 已關閉",
"Writable": "讀寫"
}
\ No newline at end of file
diff --git a/apps/i18n/lina/en.json b/apps/i18n/lina/en.json
index ce4363af4..4494ee93f 100644
--- a/apps/i18n/lina/en.json
+++ b/apps/i18n/lina/en.json
@@ -67,8 +67,9 @@
"AddUserGroupToThisPermission": "Add user groups",
"AddUserToThisPermission": "Add users",
"Address": "Address",
+ "AdhocCreate": "Create the command",
"AdhocDetail": "Command details",
- "AdhocManage": "Command",
+ "AdhocManage": "Script",
"AdhocUpdate": "Update the command",
"Advanced": "Advanced settings",
"AfterChange": "After changes",
@@ -116,6 +117,7 @@
"ApprovaLevel": "Approval information",
"ApprovalLevel": "Approval level",
"ApprovalProcess": "Approval process",
+ "ApprovalSelected": "Batch approval",
"Approved": "Agreed",
"ApproverNumbers": "Approvers",
"ApsaraStack": "Alibaba private cloud",
@@ -416,8 +418,8 @@
"DeleteConfirmMessage": "Deletion is irreversible, do you wish to continue?",
"DeleteErrorMsg": "Delete failed",
"DeleteNode": "Delete node",
- "DeleteOrgMsg": "User list, user group, asset list, network zone list, manage users, system users, tag management, asset authorization rules",
- "DeleteOrgTitle": "Please ensure the following information within the organization has been deleted",
+ "DeleteOrgMsg": "User, User group, Asset, Node, Label, Zone, Authorization",
+ "DeleteOrgTitle": "Please delete the following resources within the organization first",
"DeleteReleasedAssets": "Delete released assets",
"DeleteSelected": "Delete selected",
"DeleteSuccess": "Successfully deleted",
@@ -446,6 +448,7 @@
"DownloadReplay": "Download recording",
"DownloadUpdateTemplateMsg": "Download update template",
"DragUploadFileInfo": "Drag files here, or click to upload",
+ "DropConfirmMsg": "Do you want to move node: {src} to {dst}?",
"Duplicate": "Duplicate",
"DuplicateFileExists": "Uploading a file with the same name is not allowed, please delete the file with the same name",
"Duration": "Duration",
@@ -541,6 +544,7 @@
"Gateway": "Gateway",
"GatewayCreate": "Create gateway",
"GatewayList": "Gateways",
+ "GatewayPlatformHelpText": "Only platforms with names starting with ‘Gateway’ can be used as gateways.",
"GatewayUpdate": "Update the gateway",
"GatherAccounts": "Gather accounts",
"GatherAccountsHelpText": "Collect account information on assets. the collected account information can be imported into the system for centralized management.",
@@ -992,6 +996,7 @@
"Resume": "Recovery",
"ResumeTaskSendSuccessMsg": "Recovery task issued, please refresh later",
"Retry": "Retry",
+ "RetrySelected": "Retry selected",
"Reviewer": "Approvers",
"Role": "Role",
"RoleCreate": "Create role",
@@ -1014,10 +1019,11 @@
"RunCommand": "Run command",
"RunJob": "Run job",
"RunSucceed": "Task successfully completed",
- "RunTaskManually": "Manually execute",
+ "RunTaskManually": "Manually execution",
"RunasHelpText": "Enter username for running script",
"RunasPolicy": "Account policy",
"RunasPolicyHelpText": "When there are no users currently running on the asset, what account selection strategy should be adopted. skip: do not execute. prioritize privileged accounts: if there are privileged accounts, select them first; if not, select regular accounts. only privileged accounts: select only from privileged accounts; if none exist, do not execute.",
+ "Running": "Running",
"RunningPath": "Running path",
"RunningPathHelpText": "Enter the run path of the script, this setting only applies to shell scripts",
"RunningTimes": "Last 5 run times",
@@ -1034,7 +1040,6 @@
"SameAccount": "Same account",
"SameAccountTip": "Account with the same username as authorized users",
"SameTypeAccountTip": "An account with the same username and key type already exists",
- "Share": "Share",
"Saturday": "Sat",
"Save": "Save",
"SaveAdhoc": "Save command",
@@ -1084,6 +1089,7 @@
"SessionData": "Session data",
"SessionDetail": "Session details",
"SessionID": "Session id",
+ "SessionJoinRecords": "collaboration records",
"SessionList": "Asset sessions",
"SessionMonitor": "Monitor",
"SessionOffline": "Historical sessions",
@@ -1105,6 +1111,7 @@
"Setting": "Setting",
"SettingInEndpointHelpText": "Configure service address and port in system settings / component settings / server endpoints",
"Settings": "System settings",
+ "Share": "Share",
"Show": "Display",
"ShowAssetAllChildrenNode": "Show all sub-nodes assets",
"ShowAssetOnlyCurrentNode": "Only show current node assets",
@@ -1247,6 +1254,7 @@
"Timeout": "Timeout",
"TimeoutHelpText": "When this value is -1, no timeout is specified.",
"Timer": "Timer",
+ "TimerExecution": "Timer execution",
"Title": "Title",
"To": "To",
"Today": "Today",
@@ -1301,6 +1309,7 @@
"UploadCsvLth10MHelpText": "Only csv/xlsx can be uploaded, and no more than 10m",
"UploadDir": "Upload path",
"UploadFileLthHelpText": "Less than {limit}m supported",
+ "UploadHelpText": "Please upload a .zip file containing the following sample directory structure",
"UploadPlaybook": "Upload playbook",
"UploadSucceed": "Upload succeeded",
"UploadZipTips": "Please upload a file in zip format",
@@ -1386,12 +1395,8 @@
"ZoneHelpMessage": "The zone is the location where assets are located, which can be a data center, public cloud, or VPC. Gateways can be set up within the region. When the network cannot be directly accessed, users can utilize gateways to login to the assets.",
"ZoneList": "Zones",
"ZoneUpdate": "Update the zone",
+ "disallowSelfUpdateFields": "Not allowed to modify the current fields yourself",
"forceEnableMFAHelpText": "If force enable, user can not disable by themselves",
"removeWarningMsg": "Are you sure you want to remove",
- "RetrySelected": "Retry selected",
- "Running": "Running",
- "AdhocCreate": "Create the command",
- "UploadHelpText": "Please upload a .zip file containing the following sample directory structure",
- "SessionJoinRecords": "collaboration records",
- "ApprovalSelected": "Batch approval"
-}
+ "TaskPath": "Task path"
+}
\ No newline at end of file
diff --git a/apps/i18n/lina/ja.json b/apps/i18n/lina/ja.json
index d1527e0fa..a0f0af624 100644
--- a/apps/i18n/lina/ja.json
+++ b/apps/i18n/lina/ja.json
@@ -67,8 +67,9 @@
"AddUserGroupToThisPermission": "ユーザーグループを追加",
"AddUserToThisPermission": "ユーザーを追加する",
"Address": "アドレス",
+ "AdhocCreate": "アドホックコマンドを作成",
"AdhocDetail": "コマンド詳細",
- "AdhocManage": "コマンド",
+ "AdhocManage": "スクリプト管理",
"AdhocUpdate": "コマンドを更新",
"Advanced": "高度な設定",
"AfterChange": "変更後",
@@ -116,6 +117,7 @@
"ApprovaLevel": "承認情報",
"ApprovalLevel": "承認レベル",
"ApprovalProcess": "承認プロセス",
+ "ApprovalSelected": "大量承認です",
"Approved": "同意済み",
"ApproverNumbers": "アプルーバの数",
"ApsaraStack": "アリババクラウド専用クラウド",
@@ -330,7 +332,7 @@
"CommunityEdition": "コミュニティ版",
"Component": "コンポーネント",
"ComponentMonitor": "コンポーネントの監視",
- "Components": "コンポーネント設定",
+ "Components": "コンポーネントリスト",
"ConceptContent": "あなたにはPythonインタープリタのように行動してほしい。Pythonのコードを提供しますので、それを実行してください。説明は一切不要です。コードの出力以外では何も反応しないでください。",
"ConceptTitle": "🤔 Python インタープリター",
"Config": "設定",
@@ -431,7 +433,7 @@
"DeleteConfirmMessage": "一度削除すると復元はできません、続けますか?",
"DeleteErrorMsg": "削除に失敗",
"DeleteNode": "ノードを削除",
- "DeleteOrgMsg": "ユーザーリスト、ユーザーグループ、資産リスト、ネットワークリスト、ユーザー管理、システムユーザー、タグ管理、資産承認ルール",
+ "DeleteOrgMsg": "ユーザー、ユーザーグループ、アセット、ノード、ラベル、ドメイン、アセットの認可",
"DeleteOrgTitle": "以下の組織内の情報が削除されたことを確認してください",
"DeleteReleasedAssets": "リリースされたアセットの削除",
"DeleteSelected": "選択した項目を削除する",
@@ -461,6 +463,7 @@
"DownloadReplay": "ビデオのダウンロード",
"DownloadUpdateTemplateMsg": "更新テンプレートをダウンロード",
"DragUploadFileInfo": "ここにファイルをドラッグするか、ここをクリックしてアップロードしてください",
+ "DropConfirmMsg": "ノード: {src} を {dst} に移動しますか?",
"Duplicate": "ダブリケート",
"DuplicateFileExists": "同名のファイルのアップロードは許可されていません、同名のファイルを削除してください",
"Duration": "時間",
@@ -556,6 +559,7 @@
"Gateway": "ゲートウェイ",
"GatewayCreate": "ゲートウェイの作成",
"GatewayList": "ゲートウェイリスト",
+ "GatewayPlatformHelpText": "ゲートウェイプラットフォームは、Gatewayで始まるプラットフォームのみ選択可能です。",
"GatewayUpdate": "ゲートウェイの更新",
"GatherAccounts": "アカウント収集",
"GatherAccountsHelpText": "資産上のアカウント情報を収集します。収集したアカウント情報は、システムにインポートして一元管理が可能です",
@@ -1026,6 +1030,7 @@
"Resume": "回復",
"ResumeTaskSendSuccessMsg": "リカバリータスクが発行されました、しばらくしてから更新してご確認ください",
"Retry": "再試行",
+ "RetrySelected": "選択したものを再試行",
"Reviewer": "承認者",
"Role": "役割",
"RoleCreate": "ロール作成",
@@ -1052,6 +1057,7 @@
"RunasHelpText": "実行スクリプトのユーザー名を入力してください",
"RunasPolicy": "アカウント戦略",
"RunasPolicyHelpText": "現在の資産にはこの実行ユーザーがいない場合、どのアカウント選択戦略を採用するか。スキップ:実行しない。特権アカウントを優先:特権アカウントがあれば最初に特権アカウントを選び、なければ一般アカウントを選ぶ。特権アカウントのみ:特権アカウントからのみ選択し、なければ実行しない",
+ "Running": "実行中",
"RunningPath": "実行パス",
"RunningPathHelpText": "スクリプトの実行パスを記入してください、この設定はシェルスクリプトのみ有効です",
"RunningTimes": "最近5回の実行時間",
@@ -1060,7 +1066,7 @@
"SMSProvider": "メッセージサービスプロバイダ",
"SMTP": "メールサーバ",
"SPECIAL_CHAR_REQUIRED": "特別な文字を含む必要があります",
- "SSHKey": "SSH公開鍵",
+ "SSHKey": "SSHキー",
"SSHKeyOfProfileSSHUpdatePage": "下のボタンをクリックしてSSH公開鍵をリセットおよびダウンロードするか、あなたのSSH公開鍵をコピーして提出できます。",
"SSHPort": "SSH ポート",
"SSHSecretKey": "SSHキー",
@@ -1068,7 +1074,6 @@
"SameAccount": "同名アカウント",
"SameAccountTip": "権限を持つユーザーのユーザー名と同じアカウント",
"SameTypeAccountTip": "同じユーザー名、鍵の種類のアカウントがすでに存在しています",
- "Share": "共有",
"Saturday": "土曜日",
"Save": "保存",
"SaveAdhoc": "コマンドを保存する",
@@ -1119,6 +1124,7 @@
"SessionData": "セッションデータ",
"SessionDetail": "セッションの詳細",
"SessionID": "セッションID",
+ "SessionJoinRecords": "協力記録",
"SessionList": "セッション記録",
"SessionMonitor": "監視",
"SessionOffline": "過去のセッション",
@@ -1140,6 +1146,7 @@
"Setting": "設定",
"SettingInEndpointHelpText": "システム設定/コンポーネント設定/サーバーエンドポイントでサービスのアドレスとポートを設定してください",
"Settings": "システム設定",
+ "Share": "共有",
"Show": "表示",
"ShowAssetAllChildrenNode": "すべての子ノードの資産を表示",
"ShowAssetOnlyCurrentNode": "現在のノードアセットのみを表示",
@@ -1248,6 +1255,7 @@
"TaskID": "タスク ID",
"TaskList": "タスク一覧",
"TaskMonitor": "タスクモニタリング",
+ "TaskPath": "タスクパス",
"TechnologyConsult": "技術相談",
"TempPasswordTip": "一時的なパスワードの有効期間は300秒で、使用後すぐに無効になります",
"TempToken": "一時的なパスワード",
@@ -1342,6 +1350,7 @@
"UploadCsvLth10MHelpText": "アップロード可能なのは csv/xlsx のみで、10Mを超えないこと",
"UploadDir": "アップロードディレクトリ",
"UploadFileLthHelpText": "{limit}MB以下のファイルのみアップロード可能",
+ "UploadHelpText": "次のサンプル構造ディレクトリを含む .zip ファイルをアップロードしてください。",
"UploadPlaybook": "Playbookのアップロード",
"UploadSucceed": "アップロード成功",
"UploadZipTips": "zip形式のファイルをアップロードしてください",
@@ -1382,6 +1391,7 @@
"Valid": "有効",
"Variable": "変数",
"VariableHelpText": "コマンド中で {{ key }} を使用して内蔵変数を読み取ることができます",
+ "VaultHCPMountPoint": "Vault サーバのマウントポイント、デフォルトはjumpserver",
"VaultHelpText": "1. セキュリティ上の理由により、設定ファイルで Vault ストレージをオンにする必要があります。
2. オンにした後、他の設定を入力してテストを行います。
3. データ同期を行います。同期は一方向です。ローカルデータベースからリモートの Vault にのみ同期します。同期が終了すればローカルデータベースはパスワードを保管していませんので、データのバックアップをお願いします。
4. Vault の設定を二度変更した後はサービスを再起動する必要があります。",
"VerificationCodeSent": "認証コードが送信されました",
"VerifySignTmpl": "認証コードのSMSテンプレート",
@@ -1426,10 +1436,7 @@
"ZoneHelpMessage": "エリアとはアセットの位置で、データセンターやパブリッククラウド、あるいはVPCが該当します。エリアにはゲートウェイを設定でき、ネットワークが直接接続できない場合、ゲートウェイを経由してアセットにログインすることができます",
"ZoneList": "地域リスト",
"ZoneUpdate": "更新エリア",
+ "disallowSelfUpdateFields": "現在のフィールドを自分で変更することは許可されていません",
"forceEnableMFAHelpText": "強制的に有効化すると、ユーザーは自分で無効化することができません。",
- "removeWarningMsg": "削除してもよろしいですか",
- "AdhocCreate": "アドホックコマンドを作成",
- "UploadHelpText": "次のサンプル構造ディレクトリを含む .zip ファイルをアップロードしてください。",
- "SessionJoinRecords": "協力記録",
- "ApprovalSelected": "大量承認です"
-}
+ "removeWarningMsg": "削除してもよろしいですか"
+}
\ No newline at end of file
diff --git a/apps/i18n/lina/zh.json b/apps/i18n/lina/zh.json
index f73ba2b88..00884db00 100644
--- a/apps/i18n/lina/zh.json
+++ b/apps/i18n/lina/zh.json
@@ -67,8 +67,9 @@
"AddUserGroupToThisPermission": "添加用户组",
"AddUserToThisPermission": "添加用户",
"Address": "地址",
+ "AdhocCreate": "创建命令",
"AdhocDetail": "命令详情",
- "AdhocManage": "命令管理",
+ "AdhocManage": "脚本管理",
"AdhocUpdate": "更新命令",
"Advanced": "高级设置",
"AfterChange": "变更后",
@@ -116,6 +117,7 @@
"ApprovaLevel": "审批信息",
"ApprovalLevel": "审批级别",
"ApprovalProcess": "审批流程",
+ "ApprovalSelected": "批量审批",
"Approved": "已同意",
"ApproverNumbers": "审批人数量",
"ApsaraStack": "阿里云专有云",
@@ -315,7 +317,7 @@
"CommunityEdition": "社区版",
"Component": "组件",
"ComponentMonitor": "组件监控",
- "Components": "组件设置",
+ "Components": "组件列表",
"ConceptContent": "我想让你像一个 Python 解释器一样行事。我将给你 Python 代码,你将执行它。不要提供任何解释。除了代码的输出,不要用任何东西来回应。",
"ConceptTitle": "🤔 Python 解释器 ",
"Config": "配置",
@@ -416,8 +418,8 @@
"DeleteConfirmMessage": "删除后无法恢复,是否继续?",
"DeleteErrorMsg": "删除失败",
"DeleteNode": "删除节点",
- "DeleteOrgMsg": "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权规则",
- "DeleteOrgTitle": "请确保组织内的以下信息已删除",
+ "DeleteOrgMsg": "用户,用户组,资产,节点,标签,网域,资产授权",
+ "DeleteOrgTitle": "请确保组织内的以下资源已删除",
"DeleteReleasedAssets": "删除已释放资产",
"DeleteSelected": "删除所选",
"DeleteSuccess": "删除成功",
@@ -446,6 +448,7 @@
"DownloadReplay": "下载录像",
"DownloadUpdateTemplateMsg": "下载更新模板",
"DragUploadFileInfo": "将文件拖到此处,或点击此处上传",
+ "DropConfirmMsg": "你想移动节点: {src} 到 {dst} 下吗?",
"Duplicate": "副本",
"DuplicateFileExists": "不允许上传同名文件,请删除同名文件",
"Duration": "时长",
@@ -541,6 +544,7 @@
"Gateway": "网关",
"GatewayCreate": "创建网关",
"GatewayList": "网关列表",
+ "GatewayPlatformHelpText": "网关平台只能选择以 Gateway 开头的平台",
"GatewayUpdate": "更新网关",
"GatherAccounts": "账号收集",
"GatherAccountsHelpText": "收集资产上的账号信息。收集后的账号信息可以导入到系统中,方便统一管理",
@@ -995,6 +999,7 @@
"Resume": "恢复",
"ResumeTaskSendSuccessMsg": "恢复任务已下发,请稍后刷新查看",
"Retry": "重试",
+ "RetrySelected": "重试所选",
"Reviewer": "审批人",
"Role": "角色",
"RoleCreate": "创建角色",
@@ -1021,6 +1026,7 @@
"RunasHelpText": "填写运行脚本的用户名",
"RunasPolicy": "账号策略",
"RunasPolicyHelpText": "当前资产上没此运行用户时,采取什么账号选择策略。跳过:不执行。优先特权账号:如果有特权账号先选特权账号,如果没有就选普通账号。仅特权账号:只从特权账号中选择,如果没有则不执行",
+ "Running": "运行中",
"RunningPath": "运行路径",
"RunningPathHelpText": "填写脚本的运行路径,此设置仅 shell 脚本生效",
"RunningTimes": "最近5次运行时间",
@@ -1029,7 +1035,7 @@
"SMSProvider": "短信服务商",
"SMTP": "邮件服务器",
"SPECIAL_CHAR_REQUIRED": "必须包含特殊字符",
- "SSHKey": "SSH公钥",
+ "SSHKey": "SSH密钥",
"SSHKeyOfProfileSSHUpdatePage": "你可以点击下面的按钮重置并下载密钥,或者复制你的 SSH 公钥并提交。",
"SSHPort": "SSH 端口",
"SSHSecretKey": "SSH 密钥",
@@ -1037,7 +1043,6 @@
"SameAccount": "同名账号",
"SameAccountTip": "与被授权人用户名相同的账号",
"SameTypeAccountTip": "相同用户名、密钥类型的账号已存在",
- "Share": "分享",
"Saturday": "周六",
"Save": "保存",
"SaveAdhoc": "保存命令",
@@ -1087,6 +1092,7 @@
"SessionData": "会话数据",
"SessionDetail": "会话详情",
"SessionID": "会话ID",
+ "SessionJoinRecords": "协作记录",
"SessionList": "会话记录",
"SessionMonitor": "监控",
"SessionOffline": "历史会话",
@@ -1108,6 +1114,7 @@
"Setting": "设置",
"SettingInEndpointHelpText": "在 系统设置 / 组件设置 / 服务端点 中配置服务地址和端口",
"Settings": "系统设置",
+ "Share": "分享",
"Show": "显示",
"ShowAssetAllChildrenNode": "显示所有子节点资产",
"ShowAssetOnlyCurrentNode": "仅显示当前节点资产",
@@ -1249,7 +1256,8 @@
"TimeExpression": "时间表达式",
"Timeout": "超时",
"TimeoutHelpText": "当此值为-1时,不指定超时时间",
- "Timer": "定时执行",
+ "Timer": "定时",
+ "TimerExecution": "定时执行",
"Title": "标题",
"To": "至",
"Today": "今天",
@@ -1304,6 +1312,7 @@
"UploadCsvLth10MHelpText": "只能上传 csv/xlsx, 且不超过 10M",
"UploadDir": "上传目录",
"UploadFileLthHelpText": "只能上传小于{limit}MB文件",
+ "UploadHelpText": "请上传包含以下示例结构目录的 .zip 压缩文件",
"UploadPlaybook": "上传 Playbook",
"UploadSucceed": "上传成功",
"UploadZipTips": "请上传 zip 格式的文件",
@@ -1344,6 +1353,7 @@
"Valid": "有效",
"Variable": "变量",
"VariableHelpText": "您可以在命令中使用 {{ key }} 读取内置变量",
+ "VaultHCPMountPoint": "Vault 服务器的挂载点,默认为 jumpserver",
"VaultHelpText": "1. 由于安全原因,需要配置文件中开启 Vault 存储。
2. 开启后,填写其他配置,进行测试。
3. 进行数据同步,同步是单向的,只会从本地数据库同步到远端 Vault,同步完成本地数据库不再存储密码,请备份好数据。
4. 二次修改 Vault 配置后需重启服务。",
"VerificationCodeSent": "验证码已发送",
"VerifySignTmpl": "验证码短信模板",
@@ -1388,13 +1398,8 @@
"ZoneHelpMessage": "网域是资产所在的位置,可以是机房,公有云 或者 VPC。网域中可以设置网关,当网络不能直达的时候,可以使用网关跳转登录到资产",
"ZoneList": "网域列表",
"ZoneUpdate": "更新网域",
+ "disallowSelfUpdateFields": "不允许自己修改当前字段",
"forceEnableMFAHelpText": "如果强制启用,用户无法自行禁用",
"removeWarningMsg": "你确定要移除",
- "VaultHCPMountPoint": "Vault 服务器的挂载点,默认为 jumpserver",
- "RetrySelected": "重试所选",
- "Running": "运行中",
- "AdhocCreate": "创建命令",
- "UploadHelpText": "请上传包含以下示例结构目录的 .zip 压缩文件",
- "SessionJoinRecords": "协作记录",
- "ApprovalSelected": "批量审批"
-}
+ "TaskPath": "任务路径"
+}
\ No newline at end of file
diff --git a/apps/i18n/lina/zh_hant.json b/apps/i18n/lina/zh_hant.json
index f38256e0c..e4b00112a 100644
--- a/apps/i18n/lina/zh_hant.json
+++ b/apps/i18n/lina/zh_hant.json
@@ -85,8 +85,9 @@
"AddUserToThisPermission": "新增使用者",
"Address": "地址",
"Addressee": "收件人",
+ "AdhocCreate": "創建命令",
"AdhocDetail": "命令詳情",
- "AdhocManage": "命令管理",
+ "AdhocManage": "腳本管理",
"AdhocUpdate": "更新命令",
"Admin": "管理員",
"AdminUser": "特權用戶",
@@ -155,6 +156,7 @@
"ApprovaLevel": "審批資訊",
"ApprovalLevel": "審批級別",
"ApprovalProcess": "審批流程",
+ "ApprovalSelected": "批次審批",
"Approved": "已同意",
"ApproverNumbers": "審批人數量",
"ApsaraStack": "阿里雲專有雲",
@@ -423,7 +425,7 @@
"CommunityEdition": "社區版",
"Component": "組件",
"ComponentMonitor": "組件監控",
- "Components": "組件設置",
+ "Components": "組件列表",
"ConceptContent": "我想讓你像一個 Python 解釋器一樣行事。我將給你 Python 代碼,你將執行它。不要提供任何解釋。除了代碼的輸出,不要用任何東西來回應。",
"ConceptTitle": "🤔 Python 解釋器 ",
"Config": "配置",
@@ -553,7 +555,7 @@
"DeleteErrorMsg": "刪除失敗",
"DeleteFile": "刪除文件",
"DeleteNode": "刪除節點",
- "DeleteOrgMsg": "用戶列表、用戶組、資產列表、網域列表、管理用戶、系統用戶、標籤管理、資產授權規則",
+ "DeleteOrgMsg": "用戶,用戶組,資產,節點,標籤,網域,資產授權",
"DeleteOrgTitle": "請確保組織內的以下資訊已刪除",
"DeleteReleasedAssets": "刪除已釋放資產",
"DeleteSelected": "刪除所選",
@@ -595,7 +597,7 @@
"DownloadImportTemplateMsg": "下載創建模板",
"DownloadReplay": "下載錄影",
"DownloadUpdateTemplateMsg": "下載更新範本",
- "DragUploadFileInfo": " Drag files here, or click here to upload",
+ "DragUploadFileInfo": " 將檔案拖曳至此處,或點擊此處上傳",
"DropConfirmMsg": "你想移動節點: {src} 到 {dst} 下嗎?",
"DryRun": "測試運行",
"Duplicate": "Copy",
@@ -712,6 +714,7 @@
"Gateway": "網關",
"GatewayCreate": "創建網關",
"GatewayList": "網關列表",
+ "GatewayPlatformHelpText": "網關平台只能選擇以 Gateway 開頭的平台",
"GatewayProtocolHelpText": "SSH網關,支持代理SSH,RDP和VNC",
"GatewayUpdate": "更新網關",
"GatherAccounts": "帳號收集",
@@ -766,8 +769,8 @@
"IPLoginLimit": "IP 登入限制",
"IPMatch": "IP 匹配",
"IPNetworkSegment": "IP網段",
- "Icon": "圖示",
"IPType": "IP 類型",
+ "Icon": "圖示",
"Id": "ID",
"IdeaContent": "我想讓你充當一個 Linux 終端。我將輸入命令,你將回答終端應該顯示的內容。我希望你只在一個獨特的代碼塊內回復終端輸出,而不是其他。不要寫解釋。當我需要告訴你一些事情時,我會把文字放在大括號裡{備註文本}。",
"IdeaTitle": "🌱 Linux 終端",
@@ -1327,6 +1330,7 @@
"Resume": "恢復",
"ResumeTaskSendSuccessMsg": "恢復任務已下發,請稍後刷新查看",
"Retry": "重試",
+ "RetrySelected": "重新嘗試所選",
"Reviewer": "審批人",
"Revise": "修改",
"Role": "角色",
@@ -1357,6 +1361,7 @@
"RunasHelpText": "填寫運行腳本的使用者名稱",
"RunasPolicy": "帳號策略",
"RunasPolicyHelpText": "當前資產上沒此運行用戶時,採取什麼帳號選擇策略。跳過:不執行。優先特權帳號:如果有特權帳號先選特權帳號,如果沒有就選普通帳號。僅特權帳號:只從特權帳號中選擇,如果沒有則不執行",
+ "Running": "正在運行中的Vault 伺服器掛載點,預設為 jumpserver",
"RunningPath": "運行路徑",
"RunningPathHelpText": "填寫腳本的運行路徑,此設置僅 shell 腳本生效",
"RunningTimes": " Last 5 run times",
@@ -1367,7 +1372,7 @@
"SMSProvider": "簡訊服務商",
"SMTP": "郵件伺服器",
"SPECIAL_CHAR_REQUIRED": "須包含特殊字元",
- "SSHKey": "SSH公鑰",
+ "SSHKey": "SSH金鑰",
"SSHKeyOfProfileSSHUpdatePage": "複製你的公鑰到這裡",
"SSHKeySetting": "SSH公鑰設置",
"SSHPort": "SSH 埠",
@@ -1378,7 +1383,6 @@
"SameAccount": "同名帳號",
"SameAccountTip": "與被授權人使用者名稱相同的帳號",
"SameTypeAccountTip": "相同使用者名稱、金鑰類型的帳號已存在",
- "Share": "分享",
"Saturday": "週六",
"Save": "保存",
"SaveAdhoc": "保存命令",
@@ -1439,6 +1443,7 @@
"SessionData": "會話數據",
"SessionDetail": "會話詳情",
"SessionID": "會話ID",
+ "SessionJoinRecords": "協作記錄",
"SessionList": "會話記錄",
"SessionMonitor": "監控",
"SessionOffline": "歷史會話",
@@ -1463,6 +1468,7 @@
"Setting": "設置",
"SettingInEndpointHelpText": "在 系統設置 / 組件設置 / 服務端點 中配置服務地址和埠",
"Settings": "系統設置",
+ "Share": "分享",
"Show": "顯示",
"ShowAssetAllChildrenNode": "顯示所有子節點資產",
"ShowAssetOnlyCurrentNode": "僅顯示當前節點資產",
@@ -1594,6 +1600,7 @@
"TaskID": "任務 ID",
"TaskList": "工作列表",
"TaskMonitor": "任務監控",
+ "TaskPath": "任務路徑",
"TechnologyConsult": "技術諮詢",
"TempPassword": "臨時密碼有效期為 300 秒,使用後立刻失效",
"TempPasswordTip": "臨時密碼有效時間為 300 秒,使用後立即失效",
@@ -1716,6 +1723,7 @@
"UploadDir": "上傳目錄",
"UploadFailed": "上傳失敗",
"UploadFileLthHelpText": "只能上傳小於{limit}MB檔案",
+ "UploadHelpText": "請上傳包含以下範例結構目錄的 .zip 壓縮文件",
"UploadPlaybook": "上傳 Playbook",
"UploadSucceed": "上傳成功",
"UploadZipTips": "請上傳 zip 格式的文件",
@@ -1780,6 +1788,7 @@
"Variable": "變數",
"VariableHelpText": "您可以在命令中使用 {{ key }} 讀取內建變數",
"Vault": "密碼匣子",
+ "VaultHCPMountPoint": "重新嘗試所選",
"VaultHelpText": "1. 由於安全原因,需要配置文件中開啟 Vault 儲存。
2. 開啟後,填寫其他配置,進行測試。
3. 進行數據同步,同步是單向的,只會從本地資料庫同步到遠端 Vault,同步完成本地資料庫不再儲存密碼,請備份好數據。
4. 二次修改 Vault 配置後需重啟服務。",
"Vendor": "製造商",
"VerificationCodeSent": "驗證碼已發送",
@@ -1912,7 +1921,6 @@
"consult": "諮詢",
"containerName": "容器名稱",
"contents": "內容",
- "AdhocCreate": "創建命令",
"createBy": "創建者",
"createErrorMsg": "創建失敗",
"createSuccessMsg": "導入創建成功,總共:{count}",
@@ -2271,8 +2279,5 @@
"weComTest": "測試",
"week": "周",
"weekOf": "周的星期",
- "wildcardsAllowed": "允許的通配符",
- "UploadHelpText": "請上傳包含以下範例結構目錄的 .zip 壓縮文件",
- "SessionJoinRecords": "協作記錄",
- "ApprovalSelected": "批次審批"
-}
+ "wildcardsAllowed": "允許的通配符"
+}
\ No newline at end of file
diff --git a/apps/i18n/luna/en.json b/apps/i18n/luna/en.json
index dffcf3f0d..d5334f687 100644
--- a/apps/i18n/luna/en.json
+++ b/apps/i18n/luna/en.json
@@ -91,7 +91,7 @@
"Info": "Info",
"InstallClientMsg": "JumpServer client not found, Go to download and install?",
"Japanese keyboard layout": "Japanese (Qwerty)",
- "Keyboard keys": "Option + Left / Option + Right",
+ "Keyboard keys": "Option + Shift + Left / Right",
"Keyboard layout": "Keyboard layout",
"Keyboard switch session": "Switch session → Shortcut keys",
"Kubernetes": "Kubernetes",
@@ -123,6 +123,7 @@
"NoTabs": "No tabs",
"Not quick command": "Not quick command",
"Open in new window": "Open in new window",
+ "Operator": "Operator",
"Password": "Password",
"Password is token password on the table": "Password is token password on the table",
"Password is your password login to system": "Password is your password login to system",
@@ -200,6 +201,7 @@
"Users": "",
"Using token": "Using token",
"View": "View",
+ "Viewer": "Viewer",
"VirtualApp": "Virtual App",
"Web Terminal": "Web Terminal",
"Website": "Website",
@@ -209,16 +211,16 @@
"asset": "asset",
"cols": "cols",
"confirm": "confirm",
+ "connect info": "connect info",
+ "connectDisabledTipsMethodDisabled": "Tips: No valid remote application deployment machine found, current resource cannot be connected. Please contact the administrator for assistance",
"connectDisabledTipsNoAccount": "Tips: No valid authorization account found, current resource cannot be connected. Please contact the administrator for assistance",
"connectDisabledTipsNoConnectMethod": "Tips: No valid connection method found, current resource cannot be connected. Please contact the administrator for assistance",
- "connectDisabledTipsMethodDisabled": "Tips: No valid remote application deployment machine found, current resource cannot be connected. Please contact the administrator for assistance",
- "connect info": "connect info",
"download": "download",
"rows": "rows",
"start time": "start time",
"success": "success",
"system user": "system user",
"user": "user",
- "Viewer": "Viewer",
- "Operator": "Operator"
-}
+ "recordingIsBeingDownloaded": "Recording is being downloaded, please wait.",
+ "Play List": "Play List"
+}
\ No newline at end of file
diff --git a/apps/i18n/luna/ja.json b/apps/i18n/luna/ja.json
index 53e24ddc2..f18fd2b69 100644
--- a/apps/i18n/luna/ja.json
+++ b/apps/i18n/luna/ja.json
@@ -90,7 +90,7 @@
"Info": "ヒント",
"InstallClientMsg": "JumpServerクライアントがインストールされていない、今ダウンロードしてインストールしますか?",
"Japanese keyboard layout": "Japanese (Qwerty)",
- "Keyboard keys": "Option + Left / Option + Right",
+ "Keyboard keys": "Option + Shift + Left / Right",
"Keyboard layout": "キーボードレイアウト",
"Keyboard switch session": "セッションの切り替え → ショートカットキー",
"Kubernetes": "Kubernetes",
@@ -124,6 +124,7 @@
"Normal accounts": "通常のログインアカウント",
"Not quick command": "非高速コマンド",
"Open in new window": "新しいウィンドウが開きます",
+ "Operator": "オペレーター",
"Password": "パスワード",
"Password is token password on the table": "パスワードは、テーブルのトークンパスワードです",
"Password is your password login to system": "パスワードは、システムにログインするためのパスワードです",
@@ -205,6 +206,7 @@
"Users": "ユーザー",
"Using token": "トークンを使用する",
"View": "ビュー",
+ "Viewer": "ビューア",
"VirtualApp": "仮想アプリ",
"Web Terminal": "Web端末",
"Website": "公式サイト",
@@ -222,7 +224,5 @@
"start time": "開始時間",
"success": "成功",
"system user": "システムユーザー",
- "user": "ユーザー",
- "Viewer": "ビューア",
- "Operator": "オペレーター"
-}
+ "user": "ユーザー"
+}
\ No newline at end of file
diff --git a/apps/i18n/luna/zh.json b/apps/i18n/luna/zh.json
index a761a7713..005992ca4 100644
--- a/apps/i18n/luna/zh.json
+++ b/apps/i18n/luna/zh.json
@@ -89,7 +89,7 @@
"Info": "提示",
"InstallClientMsg": "JumpServer 客户端没有安装,现在去下载安装?",
"Japanese keyboard layout": "Japanese (Qwerty)",
- "Keyboard keys": "Option + Left / Option + Right",
+ "Keyboard keys": "Option + Shift + Left / Right",
"Keyboard layout": "键盘布局",
"Keyboard switch session": "切换会话 → 快捷键",
"Kubernetes": "Kubernetes",
@@ -122,6 +122,7 @@
"NoTabs": "没有窗口",
"Not quick command": "暂无快捷命令",
"Open in new window": "新窗口打开",
+ "Operator": "操作人",
"Password": "密码",
"Password is token password on the table": "密码是表格中的 Token 密码",
"Password is your password login to system": "密码是你登录系统的密码",
@@ -199,6 +200,7 @@
"Users": "用户",
"Using token": "使用 Token",
"View": "视图",
+ "Viewer": "查看人",
"VirtualApp": "虚拟应用",
"Web Terminal": "Web终端",
"Website": "官网",
@@ -207,16 +209,16 @@
"asset": "资产",
"cols": "列数",
"confirm": "确认",
+ "connect info": "连接信息",
+ "connectDisabledTipsMethodDisabled": "提示:未找到有效的远程应用发布机,当前资源无法连接,请联系管理员进行处理",
"connectDisabledTipsNoAccount": "提示:未找到有效的授权账号,当前资源无法连接,请联系管理员进行处理",
"connectDisabledTipsNoConnectMethod": "提示:未找到有效的连接方式,当前资源无法连接,请联系管理员进行处理",
- "connectDisabledTipsMethodDisabled": "提示:未找到有效的远程应用发布机,当前资源无法连接,请联系管理员进行处理",
- "connect info": "连接信息",
"download": "下载",
"rows": "行数",
"start time": "开始时间",
"success": "成功",
"system user": "系统用户",
"user": "用户",
- "Viewer": "查看人",
- "Operator": "操作人"
-}
+ "recordingIsBeingDownloaded": "录像正在下载中,请稍候",
+ "Play List": "播放列表"
+}
\ No newline at end of file
diff --git a/apps/i18n/luna/zh_hant.json b/apps/i18n/luna/zh_hant.json
index be0659e66..dcaae5d70 100644
--- a/apps/i18n/luna/zh_hant.json
+++ b/apps/i18n/luna/zh_hant.json
@@ -90,7 +90,7 @@
"Info": "提示",
"InstallClientMsg": "JumpServer 用戶端沒有安裝,現在去下載安裝?",
"Japanese keyboard layout": "Japanese (Qwerty)",
- "Keyboard keys": "Option + Left / Option + Right",
+ "Keyboard keys": "Option + Shift + Left / Right",
"Keyboard layout": "鍵盤布局",
"Keyboard switch session": "切換會話 → 快捷鍵",
"Kubernetes": "Kubernetes",
@@ -123,6 +123,7 @@
"NoTabs": "沒有視窗",
"Not quick command": "暫無快捷命令",
"Open in new window": "新窗口打開",
+ "Operator": "操作人",
"Password": "密碼",
"Password is token password on the table": "密碼是表格中的 Token 密碼",
"Password is your password login to system": "密碼是你登入系統的密碼",
@@ -203,6 +204,7 @@
"Users": "用戶",
"Using token": "使用 Token",
"View": "視圖",
+ "Viewer": "查看人",
"VirtualApp": "虛擬應用",
"Web Terminal": "Web終端",
"Website": "官網",
@@ -220,7 +222,5 @@
"start time": "開始時間",
"success": "成功",
"system user": "系統用戶",
- "user": "用戶",
- "Viewer": "查看人",
- "Operator": "操作人"
-}
+ "user": "用戶"
+}
\ No newline at end of file
diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py
index 2642c1da0..0e5843560 100644
--- a/apps/jumpserver/conf.py
+++ b/apps/jumpserver/conf.py
@@ -288,6 +288,26 @@ class Config(dict):
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1,
+ # Auth LDAP HA settings
+ 'AUTH_LDAP_HA': False,
+ 'AUTH_LDAP_HA_SERVER_URI': 'ldap://localhost:389',
+ 'AUTH_LDAP_HA_BIND_DN': 'cn=admin,dc=jumpserver,dc=org',
+ 'AUTH_LDAP_HA_BIND_PASSWORD': '',
+ 'AUTH_LDAP_HA_SEARCH_OU': 'ou=tech,dc=jumpserver,dc=org',
+ 'AUTH_LDAP_HA_SEARCH_FILTER': '(cn=%(user)s)',
+ 'AUTH_LDAP_HA_START_TLS': False,
+ 'AUTH_LDAP_HA_USER_ATTR_MAP': {"username": "cn", "name": "sn", "email": "mail"},
+ 'AUTH_LDAP_HA_CONNECT_TIMEOUT': 10,
+ 'AUTH_LDAP_HA_CACHE_TIMEOUT': 3600 * 24 * 30,
+ 'AUTH_LDAP_HA_SEARCH_PAGED_SIZE': 1000,
+ 'AUTH_LDAP_HA_SYNC_IS_PERIODIC': False,
+ 'AUTH_LDAP_HA_SYNC_INTERVAL': None,
+ 'AUTH_LDAP_HA_SYNC_CRONTAB': None,
+ 'AUTH_LDAP_HA_SYNC_ORG_IDS': [DEFAULT_ID],
+ 'AUTH_LDAP_HA_SYNC_RECEIVERS': [],
+ 'AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS': False,
+ 'AUTH_LDAP_HA_OPTIONS_OPT_REFERRALS': -1,
+
# OpenID 配置参数
# OpenID 公有配置参数 (version <= 1.5.8 或 version >= 1.5.8)
'AUTH_OPENID': False,
@@ -607,6 +627,7 @@ class Config(dict):
'CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS': 180,
'JOB_EXECUTION_KEEP_DAYS': 180,
'PASSWORD_CHANGE_LOG_KEEP_DAYS': 999,
+ 'ACCOUNT_CHANGE_SECRET_RECORD_KEEP_DAYS': 180,
'TICKETS_ENABLED': True,
'TICKETS_DIRECT_APPROVE': False,
diff --git a/apps/jumpserver/rewriting/logging.py b/apps/jumpserver/rewriting/logging.py
new file mode 100644
index 000000000..3a7d1c0ff
--- /dev/null
+++ b/apps/jumpserver/rewriting/logging.py
@@ -0,0 +1,20 @@
+import os
+from logging.handlers import TimedRotatingFileHandler
+from datetime import datetime, timedelta
+
+
+class DailyTimedRotatingFileHandler(TimedRotatingFileHandler):
+ def rotator(self, source, dest):
+ """ Override the original method to rotate the log file daily."""
+ dest = self._get_rotate_dest_filename(source)
+ if os.path.exists(source) and not os.path.exists(dest):
+ # 存在多个服务进程时, 保证只有一个进程成功 rotate
+ os.rename(source, dest)
+
+ @staticmethod
+ def _get_rotate_dest_filename(source):
+ date_yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
+ path = [os.path.dirname(source), date_yesterday, os.path.basename(source)]
+ filename = os.path.join(*path)
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
+ return filename
diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py
index c75a2f275..2385962b1 100644
--- a/apps/jumpserver/settings/auth.py
+++ b/apps/jumpserver/settings/auth.py
@@ -53,6 +53,44 @@ AUTH_LDAP_SYNC_ORG_IDS = CONFIG.AUTH_LDAP_SYNC_ORG_IDS
AUTH_LDAP_SYNC_RECEIVERS = CONFIG.AUTH_LDAP_SYNC_RECEIVERS
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
+# Auth LDAP HA settings
+AUTH_LDAP_HA = CONFIG.AUTH_LDAP_HA
+AUTH_LDAP_HA_SERVER_URI = CONFIG.AUTH_LDAP_HA_SERVER_URI
+AUTH_LDAP_HA_BIND_DN = CONFIG.AUTH_LDAP_HA_BIND_DN
+AUTH_LDAP_HA_BIND_PASSWORD = CONFIG.AUTH_LDAP_HA_BIND_PASSWORD
+AUTH_LDAP_HA_SEARCH_OU = CONFIG.AUTH_LDAP_HA_SEARCH_OU
+AUTH_LDAP_HA_SEARCH_FILTER = CONFIG.AUTH_LDAP_HA_SEARCH_FILTER
+AUTH_LDAP_HA_START_TLS = CONFIG.AUTH_LDAP_HA_START_TLS
+AUTH_LDAP_HA_USER_ATTR_MAP = CONFIG.AUTH_LDAP_HA_USER_ATTR_MAP
+AUTH_LDAP_HA_USER_QUERY_FIELD = 'username'
+AUTH_LDAP_HA_GLOBAL_OPTIONS = {
+ ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
+ ldap.OPT_REFERRALS: CONFIG.AUTH_LDAP_HA_OPTIONS_OPT_REFERRALS
+}
+LDAP_HA_CACERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ha_ca.pem")
+if os.path.isfile(LDAP_HA_CACERT_FILE):
+ AUTH_LDAP_HA_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CACERTFILE] = LDAP_CACERT_FILE
+LDAP_HA_CERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ha_cert.pem")
+if os.path.isfile(LDAP_HA_CERT_FILE):
+ AUTH_LDAP_HA_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CERTFILE] = LDAP_HA_CERT_FILE
+LDAP_HA_KEY_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ha_cert.key")
+if os.path.isfile(LDAP_HA_KEY_FILE):
+ AUTH_LDAP_HA_GLOBAL_OPTIONS[ldap.OPT_X_TLS_KEYFILE] = LDAP_HA_KEY_FILE
+AUTH_LDAP_HA_CONNECTION_OPTIONS = {
+ ldap.OPT_TIMEOUT: CONFIG.AUTH_LDAP_HA_CONNECT_TIMEOUT,
+ ldap.OPT_NETWORK_TIMEOUT: CONFIG.AUTH_LDAP_HA_CONNECT_TIMEOUT
+}
+AUTH_LDAP_HA_CACHE_TIMEOUT = CONFIG.AUTH_LDAP_HA_CACHE_TIMEOUT
+AUTH_LDAP_HA_ALWAYS_UPDATE_USER = True
+
+AUTH_LDAP_HA_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_HA_SEARCH_PAGED_SIZE
+AUTH_LDAP_HA_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_HA_SYNC_IS_PERIODIC
+AUTH_LDAP_HA_SYNC_INTERVAL = CONFIG.AUTH_LDAP_HA_SYNC_INTERVAL
+AUTH_LDAP_HA_SYNC_CRONTAB = CONFIG.AUTH_LDAP_HA_SYNC_CRONTAB
+AUTH_LDAP_HA_SYNC_ORG_IDS = CONFIG.AUTH_LDAP_HA_SYNC_ORG_IDS
+AUTH_LDAP_HA_SYNC_RECEIVERS = CONFIG.AUTH_LDAP_HA_SYNC_RECEIVERS
+AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS
+
# ==============================================================================
# 认证 OpenID 配置参数
# 参考: https://django-oidc-rp.readthedocs.io/en/stable/settings.html
@@ -212,6 +250,7 @@ RBAC_BACKEND = 'rbac.backends.RBACBackend'
AUTH_BACKEND_MODEL = 'authentication.backends.base.JMSModelBackend'
AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend'
AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend'
+AUTH_BACKEND_LDAP_HA = 'authentication.backends.ldap.LDAPHAAuthorizationBackend'
AUTH_BACKEND_OIDC_PASSWORD = 'authentication.backends.oidc.OIDCAuthPasswordBackend'
AUTH_BACKEND_OIDC_CODE = 'authentication.backends.oidc.OIDCAuthCodeBackend'
AUTH_BACKEND_RADIUS = 'authentication.backends.radius.RadiusBackend'
@@ -232,7 +271,7 @@ AUTHENTICATION_BACKENDS = [
# 只做权限校验
RBAC_BACKEND,
# 密码形式
- AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_LDAP, AUTH_BACKEND_RADIUS,
+ AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_LDAP, AUTH_BACKEND_LDAP_HA, AUTH_BACKEND_RADIUS,
# 跳转形式
AUTH_BACKEND_CAS, AUTH_BACKEND_OIDC_PASSWORD, AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_SAML2,
AUTH_BACKEND_OAUTH2,
diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py
index 821c209e1..9161e2252 100644
--- a/apps/jumpserver/settings/custom.py
+++ b/apps/jumpserver/settings/custom.py
@@ -128,6 +128,7 @@ ACTIVITY_LOG_KEEP_DAYS = CONFIG.ACTIVITY_LOG_KEEP_DAYS
FTP_LOG_KEEP_DAYS = CONFIG.FTP_LOG_KEEP_DAYS
CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = CONFIG.CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS
JOB_EXECUTION_KEEP_DAYS = CONFIG.JOB_EXECUTION_KEEP_DAYS
+ACCOUNT_CHANGE_SECRET_RECORD_KEEP_DAYS = CONFIG.ACCOUNT_CHANGE_SECRET_RECORD_KEEP_DAYS
ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL
WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD
diff --git a/apps/jumpserver/settings/logging.py b/apps/jumpserver/settings/logging.py
index 4fad5f641..23d15d970 100644
--- a/apps/jumpserver/settings/logging.py
+++ b/apps/jumpserver/settings/logging.py
@@ -8,7 +8,6 @@ LOG_DIR = os.path.join(PROJECT_DIR, 'data', 'logs')
JUMPSERVER_LOG_FILE = os.path.join(LOG_DIR, 'jumpserver.log')
DRF_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'drf_exception.log')
UNEXPECTED_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'unexpected_exception.log')
-ANSIBLE_LOG_FILE = os.path.join(LOG_DIR, 'ansible.log')
GUNICORN_LOG_FILE = os.path.join(LOG_DIR, 'gunicorn.log')
LOG_LEVEL = CONFIG.LOG_LEVEL
@@ -50,37 +49,25 @@ LOGGING = {
'file': {
'encoding': 'utf8',
'level': 'DEBUG',
- 'class': 'logging.handlers.RotatingFileHandler',
- 'maxBytes': 1024 * 1024 * 100,
- 'backupCount': 7,
+ 'class': 'jumpserver.rewriting.logging.DailyTimedRotatingFileHandler',
+ 'when': 'midnight',
'formatter': 'main',
'filename': JUMPSERVER_LOG_FILE,
},
- 'ansible_logs': {
- 'encoding': 'utf8',
- 'level': 'DEBUG',
- 'class': 'logging.handlers.RotatingFileHandler',
- 'formatter': 'main',
- 'maxBytes': 1024 * 1024 * 100,
- 'backupCount': 7,
- 'filename': ANSIBLE_LOG_FILE,
- },
'drf_exception': {
'encoding': 'utf8',
'level': 'DEBUG',
- 'class': 'logging.handlers.RotatingFileHandler',
+ 'class': 'jumpserver.rewriting.logging.DailyTimedRotatingFileHandler',
+ 'when': 'midnight',
'formatter': 'exception',
- 'maxBytes': 1024 * 1024 * 100,
- 'backupCount': 7,
'filename': DRF_EXCEPTION_LOG_FILE,
},
'unexpected_exception': {
'encoding': 'utf8',
'level': 'DEBUG',
- 'class': 'logging.handlers.RotatingFileHandler',
+ 'class': 'jumpserver.rewriting.logging.DailyTimedRotatingFileHandler',
+ 'when': 'midnight',
'formatter': 'exception',
- 'maxBytes': 1024 * 1024 * 100,
- 'backupCount': 7,
'filename': UNEXPECTED_EXCEPTION_LOG_FILE,
},
'syslog': {
@@ -117,10 +104,6 @@ LOGGING = {
'handlers': ['unexpected_exception'],
'level': LOG_LEVEL,
},
- 'ops.ansible_api': {
- 'handlers': ['console', 'ansible_logs'],
- 'level': LOG_LEVEL,
- },
'django_auth_ldap': {
'handlers': ['console', 'file'],
'level': "INFO",
@@ -155,3 +138,4 @@ if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
if not os.path.isdir(LOG_DIR):
os.makedirs(LOG_DIR, mode=0o755)
+
diff --git a/apps/libs/ansible/modules/rdp_ping.py b/apps/libs/ansible/modules/rdp_ping.py
index 6098f155d..6271fc859 100644
--- a/apps/libs/ansible/modules/rdp_ping.py
+++ b/apps/libs/ansible/modules/rdp_ping.py
@@ -36,7 +36,8 @@ conn_err_msg:
'''
import pyfreerdp
-from typing import NamedTuple
+import multiprocessing
+from sshtunnel import SSHTunnelForwarder
from ansible.module_utils.basic import AnsibleModule
@@ -44,12 +45,6 @@ from ansible.module_utils.basic import AnsibleModule
# Module execution.
#
-class Param(NamedTuple):
- hostname: str
- port: int
- username: str
- password: str
-
def common_argument_spec():
options = dict(
@@ -58,37 +53,102 @@ def common_argument_spec():
login_user=dict(type='str', required=False, default='root'),
login_password=dict(type='str', required=False, no_log=True),
login_secret_type=dict(type='str', required=False, default='password'),
- login_private_key_path=dict(type='str', required=False, no_log=True),
+ gateway_args=dict(type='dict', required=False, default=None),
)
return options
-def main():
- options = common_argument_spec()
- module = AnsibleModule(argument_spec=options, supports_check_mode=True)
- result = {'changed': False, 'is_available': False}
-
- secret_type = module.params['login_secret_type']
- if secret_type != 'password':
- module.fail_json(
- msg=f'The current ansible does not support \
- the verification method for {secret_type} types.'
+class RDPConnectionManager:
+
+ def __init__(self, module_params):
+ self.params = module_params
+ self.ssh_tunnel = None
+ self.connection_details = self.build_connection_details()
+ self.result_queue = multiprocessing.Queue()
+
+ def build_connection_details(self):
+ connection_details = {
+ 'hostname': self.params['login_host'],
+ 'port': self.params['login_port'],
+ 'username': self.params['login_user'],
+ 'password': self.params['login_password']
+ }
+ return connection_details
+
+ def setup_ssh_tunnel(self):
+ gateway_args = self.params['gateway_args'] or {}
+ if not gateway_args:
+ return
+
+ tunnel = SSHTunnelForwarder(
+ (gateway_args['address'], gateway_args['port']),
+ ssh_username=gateway_args['username'],
+ ssh_password=gateway_args['secret'],
+ ssh_pkey=gateway_args['private_key_path'],
+ remote_bind_address=(
+ self.connection_details['hostname'],
+ self.connection_details['port']
+ )
)
- return module.exit_json(**result)
- params = Param(
- hostname=module.params['login_host'],
- port=module.params['login_port'],
- username=module.params['login_user'],
- password=module.params['login_password']
- )
+ tunnel.start()
+ self.connection_details['hostname'] = '127.0.0.1'
+ self.connection_details['port'] = tunnel.local_bind_port
+ self.ssh_tunnel = tunnel
+
+ def close_ssh_tunnel(self):
+ if self.ssh_tunnel:
+ self.ssh_tunnel.stop()
+
+ def prepare_connection(self):
+ self.setup_ssh_tunnel()
+
+ def cleanup_connection(self):
+ self.close_ssh_tunnel()
+
+ def check_rdp_connectivity(self):
+ connect_params = list(self.connection_details.values()) + ['', 0]
+ is_reachable = pyfreerdp.check_connectivity(*connect_params)
+ self.result_queue.put(is_reachable)
+
+ def attempt_connection(self):
+ if self.params['login_secret_type'] != 'password':
+ error_message = f'unsupported authentication method: {self.params["login_secret_type"]}'
+ return False, error_message
- is_available = pyfreerdp.check_connectivity(*params, '', 0)
+ try:
+ self.prepare_connection()
+
+ connection_process = multiprocessing.Process(
+ target=self.check_rdp_connectivity
+ )
+ connection_process.start()
+ connection_process.join()
+
+ is_reachable = self.result_queue.get()
+ self.cleanup_connection()
+
+ if not is_reachable:
+ return False, 'RDP connection failed'
+ except Exception as ex:
+ return False, str(ex)
+ return True, ''
+
+
+def main():
+ argument_spec = common_argument_spec()
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ result = {'changed': False}
+ module_params = module.params
+ rdp_manager = RDPConnectionManager(module_params)
+ is_available, error_message = rdp_manager.attempt_connection()
result['is_available'] = is_available
+
if not is_available:
- module.fail_json(msg='Unable to connect to asset.')
+ module.fail_json(msg=f'Unable to connect to asset: {error_message}')
+
return module.exit_json(**result)
if __name__ == '__main__':
- main()
+ main()
\ No newline at end of file
diff --git a/apps/libs/ansible/modules_utils/custom_common.py b/apps/libs/ansible/modules_utils/custom_common.py
index f27446e9e..c70fba275 100644
--- a/apps/libs/ansible/modules_utils/custom_common.py
+++ b/apps/libs/ansible/modules_utils/custom_common.py
@@ -151,10 +151,8 @@ class SSHClient:
gateway_server = self.gateway_server
if not gateway_server:
return
- try:
- gateway_server.stop()
- except Exception:
- pass
+
+ gateway_server.stop()
def before_runner_start(self):
self.local_gateway_prepare()
diff --git a/apps/notifications/notifications.py b/apps/notifications/notifications.py
index 11db34f98..13df6932a 100644
--- a/apps/notifications/notifications.py
+++ b/apps/notifications/notifications.py
@@ -1,7 +1,6 @@
import textwrap
import traceback
from itertools import chain
-from typing import Iterable
from celery import shared_task
from django.utils.translation import gettext_lazy as _
@@ -43,7 +42,13 @@ class MessageType(type):
return clz
-@shared_task(verbose_name=_('Publish the station message'))
+@shared_task(
+ verbose_name=_('Publish the station message'),
+ description=_(
+ """This task needs to be executed for sending internal messages for system alerts,
+ work orders, and other notifications"""
+ )
+)
def publish_task(receive_user_ids, backends_msg_mapper):
Message.send_msg(receive_user_ids, backends_msg_mapper)
diff --git a/apps/notifications/serializers/site_msgs.py b/apps/notifications/serializers/site_msgs.py
index 351a04eac..199e4dac8 100644
--- a/apps/notifications/serializers/site_msgs.py
+++ b/apps/notifications/serializers/site_msgs.py
@@ -1,6 +1,7 @@
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer
+from common.utils import convert_html_to_markdown
from ..models import MessageContent
@@ -16,6 +17,8 @@ class SenderMixin(ModelSerializer):
class MessageContentSerializer(SenderMixin, ModelSerializer):
+ message = serializers.SerializerMethodField()
+
class Meta:
model = MessageContent
fields = [
@@ -24,6 +27,11 @@ class MessageContentSerializer(SenderMixin, ModelSerializer):
'sender',
]
+ @staticmethod
+ def get_message(site_msg):
+ markdown = convert_html_to_markdown(site_msg.message)
+ return markdown
+
class SiteMessageSerializer(SenderMixin, ModelSerializer):
content = MessageContentSerializer(read_only=True)
diff --git a/apps/ops/ansible/interface.py b/apps/ops/ansible/interface.py
index a11df97c7..cd6124fab 100644
--- a/apps/ops/ansible/interface.py
+++ b/apps/ops/ansible/interface.py
@@ -1,7 +1,7 @@
from django.conf import settings
from django.utils.functional import LazyObject
-from ops.ansible import AnsibleReceptorRunner, AnsibleNativeRunner
+from ops.ansible import AnsibleNativeRunner
from ops.ansible.runners.base import BaseRunner
__all__ = ['interface']
@@ -14,8 +14,7 @@ class _LazyRunnerInterface(LazyObject):
@staticmethod
def make_interface():
- runner_type = AnsibleReceptorRunner \
- if settings.RECEPTOR_ENABLED else AnsibleNativeRunner
+ runner_type = AnsibleNativeRunner
gateway_host = settings.ANSIBLE_RECEPTOR_GATEWAY_PROXY_HOST \
if settings.ANSIBLE_RECEPTOR_GATEWAY_PROXY_HOST else '127.0.0.1'
return RunnerInterface(runner_type=runner_type, gateway_proxy_host=gateway_host)
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index fde3f290b..bb9cf2060 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -45,24 +45,34 @@ class JMSInventory:
return groups
@staticmethod
- def make_proxy_command(gateway, path_dir):
+ def get_gateway_ssh_settings(gateway):
+ platform = gateway.platform
+ try:
+ protocol = platform.protocols.get(name='ssh')
+ except platform.protocols.model.DoesNotExist:
+ return {}
+ return protocol.setting
+
+ def make_proxy_command(self, gateway, path_dir):
proxy_command_list = [
"ssh", "-o", "Port={}".format(gateway.port),
"-o", "StrictHostKeyChecking=no",
- "{}@{}".format(gateway.username, gateway.address),
- "-W", "%h:%p", "-q",
+ f"{gateway.username}@{gateway.address}"
]
+ setting = self.get_gateway_ssh_settings(gateway)
+ if setting.get('nc', False):
+ proxy_command_list.extend(["nc", "-w", "10", "%h", "%p"])
+ else:
+ proxy_command_list.extend(["-W", "%h:%p", "-q"])
+
if gateway.password:
- proxy_command_list.insert(
- 0, "sshpass -p {}".format(gateway.password)
- )
+ proxy_command_list.insert(0, f"sshpass -p {gateway.password}")
+
if gateway.private_key:
- proxy_command_list.append("-i {}".format(gateway.get_private_key_path(path_dir)))
+ proxy_command_list.append(f"-i {gateway.get_private_key_path(path_dir)}")
- proxy_command = "-o ProxyCommand='{}'".format(
- " ".join(proxy_command_list)
- )
+ proxy_command = f"-o ProxyCommand='{' '.join(proxy_command_list)}'"
return {"ansible_ssh_common_args": proxy_command}
@staticmethod
@@ -187,6 +197,7 @@ class JMSInventory:
'protocol': protocol.name, 'port': protocol.port,
'spec_info': asset.spec_info, 'secret_info': secret_info,
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
+ 'origin_address': asset.address
},
'jms_account': {
'id': str(account.id), 'username': account.username,
diff --git a/apps/ops/ansible/runners/__init__.py b/apps/ops/ansible/runners/__init__.py
index 155e1b8e1..78ce5ed28 100644
--- a/apps/ops/ansible/runners/__init__.py
+++ b/apps/ops/ansible/runners/__init__.py
@@ -1,3 +1,2 @@
from .base import *
from .native import *
-from .receptor import *
diff --git a/apps/ops/ansible/runners/receptor.py b/apps/ops/ansible/runners/receptor.py
deleted file mode 100644
index 1cc1de12c..000000000
--- a/apps/ops/ansible/runners/receptor.py
+++ /dev/null
@@ -1,100 +0,0 @@
-import concurrent.futures
-import os
-import queue
-import socket
-
-import ansible_runner
-
-from ops.ansible.cleaner import cleanup_post_run
-from ops.ansible.runners.receptorctl.receptorctl import ReceptorCtl
-from ops.ansible.runners.base import BaseRunner
-
-__all__ = ['AnsibleReceptorRunner']
-
-receptor_ctl = ReceptorCtl()
-
-
-class AnsibleReceptorRunner(BaseRunner):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.unit_id = None
- self.stdout_queue = None
-
- @classmethod
- def kill_precess(cls, pid):
- return receptor_ctl.kill_process(pid)
-
- def write_unit_id(self):
- if not self.unit_id:
- return
- private_dir = self.runner_params.get("private_data_dir", "")
- with open(os.path.join(private_dir, "local.unitid"), "w") as f:
- f.write(self.unit_id)
- f.flush()
-
- @cleanup_post_run
- def run(self):
- input, output = socket.socketpair()
-
- with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
- transmitter_future = executor.submit(self.transmit, input)
- result = receptor_ctl.submit_work(payload=output.makefile('rb'),
- node='primary', worktype='ansible-runner')
-
- input.close()
- output.close()
-
- self.unit_id = result['unitid']
- self.write_unit_id()
-
- transmitter_future.result()
-
- result_file = receptor_ctl.get_work_results(self.unit_id, return_sockfile=True)
-
- self.stdout_queue = queue.Queue()
-
- with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
- processor_future = executor.submit(self.processor, result_file)
-
- while not processor_future.done() or \
- not self.stdout_queue.empty():
- msg = self.stdout_queue.get()
- if msg is None:
- break
- print(msg)
-
- return processor_future.result()
-
- def transmit(self, _socket):
- try:
- ansible_runner.run(
- streamer='transmit',
- _output=_socket.makefile('wb'),
- **self.runner_params
- )
- finally:
- _socket.shutdown(socket.SHUT_WR)
-
- def get_event_handler(self):
- _event_handler = super().get_event_handler()
-
- def _handler(data, **kwargs):
- stdout = data.get('stdout', '')
- if stdout:
- self.stdout_queue.put(stdout)
- _event_handler(data, **kwargs)
-
- return _handler
-
- def processor(self, _result_file):
- try:
- return ansible_runner.interface.run(
- quite=True,
- streamer='process',
- _input=_result_file,
- event_handler=self.get_event_handler(),
- status_handler=self.get_status_handler(),
- **self.runner_params,
- )
- finally:
- self.stdout_queue.put(None)
diff --git a/apps/ops/ansible/runners/receptorctl/__init__.py b/apps/ops/ansible/runners/receptorctl/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/apps/ops/ansible/runners/receptorctl/receptorctl.py b/apps/ops/ansible/runners/receptorctl/receptorctl.py
deleted file mode 100644
index 0c3d44c10..000000000
--- a/apps/ops/ansible/runners/receptorctl/receptorctl.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from django.conf import settings
-from receptorctl import ReceptorControl
-
-
-class ReceptorCtl:
- @property
- def ctl(self):
- return ReceptorControl("tcp://{}".format(settings.ANSIBLE_RECEPTOR_TCP_LISTEN_ADDRESS))
-
- def cancel(self, unit_id):
- return self.ctl.simple_command("work cancel {}".format(unit_id))
-
- def nodes(self):
- return self.ctl.simple_command("status").get("Advertisements", None)
-
- def submit_work(self,
- worktype,
- payload,
- node=None,
- tlsclient=None,
- ttl=None,
- signwork=False,
- params=None, ):
- return self.ctl.submit_work(worktype, payload, node, tlsclient, ttl, signwork, params)
-
- def get_work_results(self, unit_id, startpos=0, return_socket=False, return_sockfile=True):
- return self.ctl.get_work_results(unit_id, startpos, return_socket, return_sockfile)
-
- def kill_process(self, pid):
- submit_result = self.submit_work(worktype="kill", node="primary", payload=str(pid))
- unit_id = submit_result["unitid"]
- result_socket, result_file = self.get_work_results(unit_id=unit_id, return_sockfile=True,
- return_socket=True)
- while not result_socket.close():
- buf = result_file.read()
- if not buf:
- break
- print(buf.decode('utf8'))
diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py
index 70b74bed7..fdb82d8c1 100644
--- a/apps/ops/api/adhoc.py
+++ b/apps/ops/api/adhoc.py
@@ -1,22 +1,37 @@
# -*- coding: utf-8 -*-
-from orgs.mixins.api import OrgBulkModelViewSet
+from django.db.models import Q
+
+from common.api.generic import JMSBulkModelViewSet
+from common.utils.http import is_true
from rbac.permissions import RBACPermission
+from ..const import Scope
from ..models import AdHoc
-from ..serializers import (
- AdHocSerializer
-)
+from ..serializers import AdHocSerializer
__all__ = [
'AdHocViewSet'
]
-class AdHocViewSet(OrgBulkModelViewSet):
+class AdHocViewSet(JMSBulkModelViewSet):
+ queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer
permission_classes = (RBACPermission,)
search_fields = ('name', 'comment')
- model = AdHoc
+ filterset_fields = ['scope', 'creator']
+
+ def check_object_permissions(self, request, obj):
+ if request.method != 'GET' and obj.creator != request.user:
+ self.permission_denied(
+ request, message={"detail": "Deleting other people's script is not allowed"}
+ )
+ return super().check_object_permissions(request, obj)
def get_queryset(self):
queryset = super().get_queryset()
- return queryset.filter(creator=self.request.user)
+ user = self.request.user
+ if is_true(self.request.query_params.get('only_mine')):
+ queryset = queryset.filter(creator=user)
+ else:
+ queryset = queryset.filter(Q(creator=user) | Q(scope=Scope.public))
+ return queryset
diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py
index e75841ff1..6c3ed5a7c 100644
--- a/apps/ops/api/playbook.py
+++ b/apps/ops/api/playbook.py
@@ -4,13 +4,16 @@ import zipfile
from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
+from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from rest_framework import status
+from common.api.generic import JMSBulkModelViewSet
from common.exceptions import JMSException
-from orgs.mixins.api import OrgBulkModelViewSet
+from common.utils.http import is_true
from rbac.permissions import RBACPermission
+from ..const import Scope
from ..exception import PlaybookNoValidEntry
from ..models import Playbook
from ..serializers.playbook import PlaybookSerializer
@@ -28,11 +31,19 @@ def unzip_playbook(src, dist):
fz.extract(file, dist)
-class PlaybookViewSet(OrgBulkModelViewSet):
+class PlaybookViewSet(JMSBulkModelViewSet):
serializer_class = PlaybookSerializer
permission_classes = (RBACPermission,)
- model = Playbook
+ queryset = Playbook.objects.all()
search_fields = ('name', 'comment')
+ filterset_fields = ['scope', 'creator']
+
+ def check_object_permissions(self, request, obj):
+ if request.method != 'GET' and obj.creator != request.user:
+ self.permission_denied(
+ request, message={"detail": "Deleting other people's playbook is not allowed"}
+ )
+ return super().check_object_permissions(request, obj)
def perform_destroy(self, instance):
if instance.job_set.exists():
@@ -45,7 +56,11 @@ class PlaybookViewSet(OrgBulkModelViewSet):
def get_queryset(self):
queryset = super().get_queryset()
- queryset = queryset.filter(creator=self.request.user)
+ user = self.request.user
+ if is_true(self.request.query_params.get('only_mine')):
+ queryset = queryset.filter(creator=user)
+ else:
+ queryset = queryset.filter(Q(creator=user) | Q(scope=Scope.public))
return queryset
def perform_create(self, serializer):
@@ -85,7 +100,8 @@ class PlaybookFileBrowserAPIView(APIView):
def get(self, request, **kwargs):
playbook_id = kwargs.get('pk')
- playbook = self.get_playbook(playbook_id)
+ user = self.request.user
+ playbook = get_object_or_404(Playbook, Q(creator=user) | Q(scope=Scope.public), id=playbook_id)
work_path = playbook.work_dir
file_key = request.query_params.get('key', '')
if file_key:
diff --git a/apps/ops/const.py b/apps/ops/const.py
index 5676da4de..b2b1c63ca 100644
--- a/apps/ops/const.py
+++ b/apps/ops/const.py
@@ -1,5 +1,5 @@
from django.db import models
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import gettext_lazy as _, pgettext_lazy
class StrategyChoice(models.TextChoices):
@@ -80,3 +80,8 @@ class JobStatus(models.TextChoices):
CELERY_LOG_MAGIC_MARK = b'\x00\x00\x00\x00\x00'
COMMAND_EXECUTION_DISABLED = _('Command execution disabled')
+
+
+class Scope(models.TextChoices):
+ public = 'public', pgettext_lazy("scope", 'Public')
+ private = 'private', _('Private')
diff --git a/apps/ops/migrations/0003_alter_adhoc_unique_together_and_more.py b/apps/ops/migrations/0003_alter_adhoc_unique_together_and_more.py
new file mode 100644
index 000000000..98087c86a
--- /dev/null
+++ b/apps/ops/migrations/0003_alter_adhoc_unique_together_and_more.py
@@ -0,0 +1,61 @@
+# Generated by Django 4.1.13 on 2024-09-06 08:32
+
+from django.conf import settings
+from django.db import migrations, models
+from orgs.models import Organization
+
+
+def migrate_ops_adhoc_and_playbook_name(apps, schema_editor):
+ Adhoc = apps.get_model('ops', 'adhoc')
+ Playbook = apps.get_model('ops', 'playbook')
+ Organization = apps.get_model('orgs', 'Organization')
+ org_id_name_mapper = {str(org.id): org.name for org in Organization.objects.all()}
+
+ adhocs_to_update = Adhoc.objects.exclude(org_id=Organization.DEFAULT_ID)
+ for adhoc in adhocs_to_update:
+ suffix = org_id_name_mapper.get(str(adhoc.org_id), str(adhoc.id)[:6])
+ adhoc.name = f'{adhoc.name} ({suffix})'
+ Adhoc.objects.bulk_update(adhocs_to_update, ['name'])
+
+ playbooks_to_update = Playbook.objects.exclude(org_id=Organization.DEFAULT_ID)
+ for playbook in playbooks_to_update:
+ suffix = org_id_name_mapper.get(str(playbook.org_id), str(playbook.id)[:6])
+ playbook.name = f'{playbook.name} ({suffix})'
+ Playbook.objects.bulk_update(playbooks_to_update, ['name'])
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('ops', '0002_celerytask'),
+ ]
+
+ operations = [
+ migrations.RunPython(migrate_ops_adhoc_and_playbook_name),
+ migrations.AlterUniqueTogether(
+ name='adhoc',
+ unique_together={('name', 'creator')},
+ ),
+ migrations.AlterUniqueTogether(
+ name='playbook',
+ unique_together={('name', 'creator')},
+ ),
+ migrations.AddField(
+ model_name='adhoc',
+ name='scope',
+ field=models.CharField(default='public', max_length=64, verbose_name='Scope'),
+ ),
+ migrations.AddField(
+ model_name='playbook',
+ name='scope',
+ field=models.CharField(default='public', max_length=64, verbose_name='Scope'),
+ ),
+ migrations.RemoveField(
+ model_name='adhoc',
+ name='org_id',
+ ),
+ migrations.RemoveField(
+ model_name='playbook',
+ name='org_id',
+ ),
+ ]
diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py
index 9e884ee4b..ab312826a 100644
--- a/apps/ops/mixin.py
+++ b/apps/ops/mixin.py
@@ -16,6 +16,13 @@ __all__ = [
]
+class PeriodTaskModelQuerySet(models.QuerySet):
+ def delete(self, *args, **kwargs):
+ for obj in self:
+ obj.delete()
+ return super().delete(*args, **kwargs)
+
+
class PeriodTaskModelMixin(models.Model):
name = models.CharField(
max_length=128, unique=False, verbose_name=_("Name")
@@ -27,6 +34,7 @@ class PeriodTaskModelMixin(models.Model):
crontab = models.CharField(
blank=True, max_length=128, null=True, verbose_name=_("Crontab"),
)
+ objects = PeriodTaskModelQuerySet.as_manager()
@abc.abstractmethod
def get_register_task(self):
diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py
index 3ca2fa281..f7e13f18d 100644
--- a/apps/ops/models/adhoc.py
+++ b/apps/ops/models/adhoc.py
@@ -8,14 +8,13 @@ from common.utils import get_logger
__all__ = ["AdHoc"]
-from ops.const import AdHocModules
-
-from orgs.mixins.models import JMSOrgBaseModel
+from common.db.models import JMSBaseModel
+from ops.const import AdHocModules, Scope
logger = get_logger(__file__)
-class AdHoc(JMSOrgBaseModel):
+class AdHoc(JMSBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all')
@@ -24,6 +23,7 @@ class AdHoc(JMSOrgBaseModel):
args = models.CharField(max_length=8192, default='', verbose_name=_('Args'))
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
+ scope = models.CharField(max_length=64, default=Scope.public, verbose_name=_('Scope'))
@property
def row_count(self):
@@ -40,5 +40,5 @@ class AdHoc(JMSOrgBaseModel):
return "{}: {}".format(self.module, self.args)
class Meta:
- unique_together = [('name', 'org_id', 'creator')]
+ unique_together = [('name', 'creator')]
verbose_name = _("Adhoc")
diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py
index 9e40cd921..f92317eeb 100644
--- a/apps/ops/models/celery.py
+++ b/apps/ops/models/celery.py
@@ -23,6 +23,7 @@ class CeleryTask(models.Model):
task = app.tasks.get(self.name, None)
return {
"comment": getattr(task, 'verbose_name', None),
+ "description": getattr(task, 'description', None),
"queue": getattr(task, 'queue', 'default')
}
diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py
index 2b703916a..ccc7223d2 100644
--- a/apps/ops/models/playbook.py
+++ b/apps/ops/models/playbook.py
@@ -6,9 +6,9 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from private_storage.fields import PrivateFileField
-from ops.const import CreateMethods
+from common.db.models import JMSBaseModel
+from ops.const import CreateMethods, Scope
from ops.exception import PlaybookNoValidEntry
-from orgs.mixins.models import JMSOrgBaseModel
dangerous_keywords = (
'hosts:localhost',
@@ -23,7 +23,9 @@ dangerous_keywords = (
)
-class Playbook(JMSOrgBaseModel):
+
+
+class Playbook(JMSBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'), null=True)
path = PrivateFileField(upload_to='playbooks/')
@@ -31,6 +33,7 @@ class Playbook(JMSOrgBaseModel):
comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
create_method = models.CharField(max_length=128, choices=CreateMethods.choices, default=CreateMethods.blank,
verbose_name=_('CreateMethod'))
+ scope = models.CharField(max_length=64, default=Scope.public, verbose_name=_('Scope'))
vcs_url = models.CharField(max_length=1024, default='', verbose_name=_('VCS URL'), null=True, blank=True)
def __str__(self):
@@ -84,6 +87,6 @@ class Playbook(JMSOrgBaseModel):
return work_dir
class Meta:
- unique_together = [('name', 'org_id', 'creator')]
+ unique_together = [('name', 'creator')]
verbose_name = _("Playbook")
ordering = ['date_created']
diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py
index 58cbaad98..4b72860e5 100644
--- a/apps/ops/serializers/adhoc.py
+++ b/apps/ops/serializers/adhoc.py
@@ -1,17 +1,19 @@
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
-
+from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
-from common.serializers.fields import ReadableHiddenField
-from orgs.mixins.serializers import BulkOrgResourceModelSerializer
+from common.serializers.fields import ReadableHiddenField, LabeledChoiceField
+from common.serializers.mixin import CommonBulkModelSerializer
+from .mixin import ScopeSerializerMixin
+from ..const import Scope
from ..models import AdHoc
-class AdHocSerializer(BulkOrgResourceModelSerializer):
+class AdHocSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = AdHoc
read_only_field = ["id", "creator", "date_created", "date_updated"]
- fields = read_only_field + ["id", "name", "module", "args", "comment"]
+ fields = read_only_field + ["id", "name", "scope", "module", "args", "comment"]
diff --git a/apps/ops/serializers/mixin.py b/apps/ops/serializers/mixin.py
new file mode 100644
index 000000000..c1ee343ee
--- /dev/null
+++ b/apps/ops/serializers/mixin.py
@@ -0,0 +1,11 @@
+from django.utils.translation import gettext_lazy as _
+from rest_framework import serializers
+
+from common.serializers.fields import LabeledChoiceField
+from ..const import Scope
+
+
+class ScopeSerializerMixin(serializers.Serializer):
+ scope = LabeledChoiceField(
+ choices=Scope.choices, default=Scope.public, label=_("Scope")
+ )
diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py
index 1ff48906a..c157251a6 100644
--- a/apps/ops/serializers/playbook.py
+++ b/apps/ops/serializers/playbook.py
@@ -3,8 +3,9 @@ import os
from rest_framework import serializers
from common.serializers.fields import ReadableHiddenField
+from common.serializers.mixin import CommonBulkModelSerializer
from ops.models import Playbook
-from orgs.mixins.serializers import BulkOrgResourceModelSerializer
+from .mixin import ScopeSerializerMixin
def parse_playbook_name(path):
@@ -12,7 +13,7 @@ def parse_playbook_name(path):
return file_name.split(".")[-2]
-class PlaybookSerializer(BulkOrgResourceModelSerializer):
+class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
path = serializers.FileField(required=False)
@@ -26,6 +27,6 @@ class PlaybookSerializer(BulkOrgResourceModelSerializer):
model = Playbook
read_only_fields = ["id", "date_created", "date_updated"]
fields = read_only_fields + [
- "id", 'path', "name", "comment", "creator",
+ "id", 'path', 'scope', "name", "comment", "creator",
'create_method', 'vcs_url',
]
diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py
index 3fe956bf0..a3361e035 100644
--- a/apps/ops/signal_handlers.py
+++ b/apps/ops/signal_handlers.py
@@ -16,9 +16,9 @@ from common.signals import django_ready
from common.utils.connection import RedisPubSub
from jumpserver.utils import get_current_request
from orgs.utils import get_current_org_id, set_current_org
+from .ansible.runner import interface
from .celery import app
from .models import CeleryTaskExecution, CeleryTask, Job
-from .ansible.runner import interface
logger = get_logger(__name__)
@@ -63,6 +63,7 @@ def check_registered_tasks(*args, **kwargs):
'common.utils.verify_code.send_sms_async', 'assets.tasks.nodes_amount.check_node_assets_amount_period_task',
'users.tasks.check_user_expired', 'orgs.tasks.refresh_org_cache_task',
'terminal.tasks.upload_session_replay_to_external_storage', 'terminal.tasks.clean_orphan_session',
+ 'terminal.tasks.upload_session_replay_file_to_external_storage',
'audits.tasks.clean_audits_log_period', 'authentication.tasks.clean_django_sessions'
]
diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py
index 2d6bcb375..31d9ee38d 100644
--- a/apps/ops/tasks.py
+++ b/apps/ops/tasks.py
@@ -1,6 +1,5 @@
# coding: utf-8
import datetime
-import time
from celery import shared_task
from celery.exceptions import SoftTimeLimitExceeded
@@ -13,7 +12,7 @@ from common.utils import get_logger, get_object_or_none, get_log_keep_day
from ops.celery import app
from orgs.utils import tmp_to_org, tmp_to_root_org
from .celery.decorator import (
- register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic
+ register_as_period_task, after_app_ready_start
)
from .celery.utils import (
create_or_update_celery_periodic_tasks, get_celery_periodic_task,
@@ -47,8 +46,13 @@ def _run_ops_job_execution(execution):
@shared_task(
- soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task"),
- activity_callback=job_task_activity_callback
+ soft_time_limit=60,
+ queue="ansible",
+ verbose_name=_("Run ansible task"),
+ activity_callback=job_task_activity_callback,
+ description=_(
+ "Execute scheduled adhoc and playbooks, periodically invoking the task for execution"
+ )
)
def run_ops_job(job_id):
with tmp_to_root_org():
@@ -73,8 +77,13 @@ def job_execution_task_activity_callback(self, execution_id, *args, **kwargs):
@shared_task(
- soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task execution"),
- activity_callback=job_execution_task_activity_callback
+ soft_time_limit=60,
+ queue="ansible",
+ verbose_name=_("Run ansible task execution"),
+ activity_callback=job_execution_task_activity_callback,
+ description=_(
+ "Execute the task when manually adhoc or playbooks"
+ )
)
def run_ops_job_execution(execution_id, **kwargs):
with tmp_to_root_org():
@@ -86,7 +95,12 @@ def run_ops_job_execution(execution_id, **kwargs):
_run_ops_job_execution(execution)
-@shared_task(verbose_name=_('Clear celery periodic tasks'))
+@shared_task(
+ verbose_name=_('Clear celery periodic tasks'),
+ description=_(
+ "At system startup, clean up celery tasks that no longer exist"
+ )
+)
@after_app_ready_start
def clean_celery_periodic_tasks():
"""清除celery定时任务"""
@@ -107,7 +121,14 @@ def clean_celery_periodic_tasks():
logger.info('Clean task failure: {}'.format(task))
-@shared_task(verbose_name=_('Create or update periodic tasks'))
+@shared_task(
+ verbose_name=_('Create or update periodic tasks'),
+ description=_(
+ """With version iterations, new tasks may be added, or task names and execution times may
+ be modified. Therefore, upon system startup, tasks will be registered or the parameters
+ of scheduled tasks will be updated"""
+ )
+)
@after_app_ready_start
def create_or_update_registered_periodic_tasks():
from .celery.decorator import get_register_period_tasks
@@ -115,20 +136,42 @@ def create_or_update_registered_periodic_tasks():
create_or_update_celery_periodic_tasks(task)
-@shared_task(verbose_name=_("Periodic check service performance"))
+@shared_task(
+ verbose_name=_("Periodic check service performance"),
+ description=_(
+ """Check every hour whether each component is offline and whether the CPU, memory,
+ and disk usage exceed the thresholds, and send an alert message to the administrator"""
+ )
+)
@register_as_period_task(interval=3600)
def check_server_performance_period():
ServerPerformanceCheckUtil().check_and_publish()
-@shared_task(verbose_name=_("Clean up unexpected jobs"))
+@shared_task(
+ verbose_name=_("Clean up unexpected jobs"),
+ description=_(
+ """Due to exceptions caused by executing adhoc and playbooks in the Job Center,
+ which result in the task status not being updated, the system will clean up abnormal jobs
+ that have not been completed for more than 3 hours every hour and mark these tasks as
+ failed"""
+ )
+)
@register_as_period_task(interval=3600)
def clean_up_unexpected_jobs():
with tmp_to_root_org():
JobExecution.clean_unexpected_execution()
-@shared_task(verbose_name=_('Clean job_execution db record'))
+@shared_task(
+ verbose_name=_('Clean job_execution db record'),
+ description=_(
+ """Due to the execution of adhoc and playbooks in the Job Center, execution records will
+ be generated. The system will clean up records that exceed the retention period every day
+ at 2 a.m., based on the configuration of 'System Settings - Tasks - Regular clean-up -
+ Job execution retention days'"""
+ )
+)
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
def clean_job_execution_period():
logger.info("Start clean job_execution db record")
@@ -137,7 +180,8 @@ def clean_job_execution_period():
expired_day = now - datetime.timedelta(days=days)
with tmp_to_root_org():
del_res = JobExecution.objects.filter(date_created__lt=expired_day).delete()
- logger.info(f"clean job_execution db record success! delete {days} days {del_res[0]} records")
+ logger.info(
+ f"clean job_execution db record success! delete {days} days {del_res[0]} records")
# 测试使用,注释隐藏
# @shared_task
diff --git a/apps/orgs/api.py b/apps/orgs/api.py
index eb1a980d6..b93479c7e 100644
--- a/apps/orgs/api.py
+++ b/apps/orgs/api.py
@@ -24,8 +24,7 @@ logger = get_logger(__file__)
# 部分 org 相关的 model,需要清空这些数据之后才能删除该组织
org_related_models = [
- User, UserGroup, Asset, Label, Domain, Node, Label,
- AssetPermission,
+ User, UserGroup, Asset, Node, Label, Domain, AssetPermission
]
diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py
index 558dac282..5f60c1318 100644
--- a/apps/orgs/mixins/serializers.py
+++ b/apps/orgs/mixins/serializers.py
@@ -23,7 +23,7 @@ class OrgResourceSerializerMixin(serializers.Serializer):
但是coco需要资产的org_id字段,所以修改为CharField类型
"""
org_id = serializers.ReadOnlyField(default=get_current_org_id_for_serializer, label=_("Organization"))
- org_name = serializers.ReadOnlyField(label=_("Org name"))
+ org_name = serializers.CharField(label=_("Org name"), read_only=True)
add_org_fields = True
def get_validators(self):
diff --git a/apps/orgs/tasks.py b/apps/orgs/tasks.py
index 7e02a64a2..a23185362 100644
--- a/apps/orgs/tasks.py
+++ b/apps/orgs/tasks.py
@@ -6,7 +6,10 @@ from common.utils import get_logger
logger = get_logger(__file__)
-@shared_task(verbose_name=_("Refresh organization cache"))
+@shared_task(
+ verbose_name=_("Refresh organization cache"),
+ description=_("Unused")
+)
def refresh_org_cache_task(*fields):
from .caches import OrgResourceStatisticsCache
OrgResourceStatisticsCache.refresh(*fields)
diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py
index 114c5475e..dbba179f0 100644
--- a/apps/perms/api/user_permission/tree/node_with_asset.py
+++ b/apps/perms/api/user_permission/tree/node_with_asset.py
@@ -1,31 +1,22 @@
import abc
-from urllib.parse import parse_qsl
from django.conf import settings
from django.db.models import F, Value, CharField
-from rest_framework.exceptions import PermissionDenied, NotFound
from rest_framework.generics import ListAPIView
-from rest_framework.generics import get_object_or_404
-from rest_framework.request import Request
from rest_framework.response import Response
-from accounts.const import AliasAccount
from assets.api import SerializeToTreeNodeMixin
from assets.models import Asset
-from assets.utils import KubernetesTree
-from authentication.models import ConnectionToken
-from common.exceptions import JMSException
from common.utils import get_object_or_none, lazyproperty
from common.utils.common import timeit
from perms.hands import Node
from perms.models import PermNode
-from perms.utils import PermAssetDetailUtil, UserPermNodeUtil
from perms.utils import UserPermAssetUtil
+from perms.utils import UserPermNodeUtil
from .mixin import RebuildTreeMixin
from ..mixin import SelfOrPKUserMixin
__all__ = [
- 'UserGrantedK8sAsTreeApi',
'UserPermedNodesWithAssetsAsTreeApi',
'UserPermedNodeChildrenWithAssetsAsTreeApi',
'UserPermedNodeChildrenWithAssetsAsCategoryTreeApi',
@@ -218,55 +209,3 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi(BaseUserNodeWithAssetAsT
return self.get_assets()
else:
return []
-
-
-class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView):
- """ 用户授权的K8s树 """
-
- def get_token(self):
- token_id = self.request.query_params.get('token')
- token = get_object_or_404(ConnectionToken, pk=token_id)
- if token.is_expired:
- raise PermissionDenied('Token is expired')
- token.renewal()
- return token
-
- def get_account_secret(self, token: ConnectionToken):
- util = PermAssetDetailUtil(self.user, token.asset)
- accounts = util.get_permed_accounts_for_user()
- account_name = token.account
-
- if account_name in [AliasAccount.INPUT, AliasAccount.USER]:
- return token.input_secret
- else:
- accounts = filter(lambda x: x.name == account_name, accounts)
- accounts = list(accounts)
- if not accounts:
- raise NotFound('Account is not found')
- account = accounts[0]
- return account.secret
-
- @staticmethod
- def get_namespace_and_pod(key):
- namespace_and_pod = dict(parse_qsl(key))
- pod = namespace_and_pod.get('pod')
- namespace = namespace_and_pod.get('namespace')
- return namespace, pod
-
- def list(self, request: Request, *args, **kwargs):
- token = self.get_token()
- asset = token.asset
- secret = self.get_account_secret(token)
- key = self.request.query_params.get('key')
- namespace, pod = self.get_namespace_and_pod(key)
-
- tree = []
- k8s_tree_instance = KubernetesTree(asset, secret)
- if not any([namespace, pod]) and not key:
- asset_node = k8s_tree_instance.as_asset_tree_node()
- tree.append(asset_node)
- try:
- tree.extend(k8s_tree_instance.async_tree_node(namespace, pod))
- return Response(data=tree)
- except Exception as e:
- raise JMSException(e)
diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py
index 38a32a29d..de0421e66 100644
--- a/apps/perms/serializers/permission.py
+++ b/apps/perms/serializers/permission.py
@@ -27,6 +27,17 @@ class ActionChoicesField(BitChoicesField):
return data
+class PermAccountsSerializer(serializers.ListField):
+ def get_render_help_text(self):
+ return _('Accounts, format ["@virtual", "root", "%template_id"], '
+ 'virtual choices: @ALL, @SPEC, @USER, @ANON, @INPUT')
+
+
+class PermProtocolsSerializer(serializers.ListField):
+ def get_render_help_text(self):
+ return _('Protocols, format ["ssh", "rdp", "vnc"] or ["all"]')
+
+
class AssetPermissionSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
users = ObjectRelatedField(queryset=User.objects, many=True, required=False, label=_('Users'))
user_groups = ObjectRelatedField(
@@ -41,8 +52,8 @@ class AssetPermissionSerializer(ResourceLabelsMixin, BulkOrgResourceModelSeriali
actions = ActionChoicesField(required=False, allow_null=True, label=_("Action"))
is_valid = serializers.BooleanField(read_only=True, label=_("Is valid"))
is_expired = serializers.BooleanField(read_only=True, label=_("Is expired"))
- accounts = serializers.ListField(label=_("Accounts"), required=False)
- protocols = serializers.ListField(label=_("Protocols"), required=False)
+ accounts = PermAccountsSerializer(label=_("Accounts"), required=False)
+ protocols = PermProtocolsSerializer(label=_("Protocols"), required=False)
template_accounts = AccountTemplate.objects.none()
diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py
index 82f97bf8c..f52348060 100644
--- a/apps/perms/tasks.py
+++ b/apps/perms/tasks.py
@@ -24,7 +24,15 @@ from perms.utils import UserPermTreeExpireUtil
logger = get_logger(__file__)
-@shared_task(verbose_name=_('Check asset permission expired'))
+@shared_task(
+ verbose_name=_('Check asset permission expired'),
+ description=_(
+ """The cache of organizational collections, which have completed user authorization tree
+ construction, will expire. Therefore, expired collections need to be cleared from the
+ cache, and this task will be executed periodically based on the time interval specified
+ by PERM_EXPIRED_CHECK_PERIODIC in the system configuration file config.txt"""
+ )
+)
@register_as_period_task(interval=settings.PERM_EXPIRED_CHECK_PERIODIC)
@atomic()
@tmp_to_root_org()
@@ -37,7 +45,15 @@ def check_asset_permission_expired():
UserPermTreeExpireUtil().expire_perm_tree_for_perms(perm_ids)
-@shared_task(verbose_name=_('Send asset permission expired notification'))
+@shared_task(
+ verbose_name=_('Send asset permission expired notification'),
+ description=_(
+ """Check every day at 10 a.m. and send a notification message to users associated with
+ assets whose authorization is about to expire, as well as to the organization's
+ administrators, 3 days in advance, to remind them that the asset authorization will
+ expire in a few days"""
+ )
+)
@register_as_period_task(crontab=CRONTAB_AT_AM_TEN)
@atomic()
@tmp_to_root_org()
diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py
index 0ba99087e..e17ee3218 100644
--- a/apps/perms/urls/user_permission.py
+++ b/apps/perms/urls/user_permission.py
@@ -47,9 +47,6 @@ user_permission_urlpatterns = [
path('/nodes/all-with-assets/tree/',
api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
name='user-nodes-with-assets-as-tree'),
- path('/nodes/children-with-k8s/tree/',
- api.UserGrantedK8sAsTreeApi.as_view(),
- name='user-nodes-children-with-k8s-as-tree'),
]
user_group_permission_urlpatterns = [
diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py
index 8498f7776..946923878 100644
--- a/apps/rbac/builtin.py
+++ b/apps/rbac/builtin.py
@@ -30,6 +30,7 @@ system_user_perms = (
('authentication', 'temptoken', 'add,change,view', 'temptoken'),
('authentication', 'accesskey', '*', '*'),
('authentication', 'passkey', '*', '*'),
+ ('authentication', 'sshkey', '*', '*'),
('tickets', 'ticket', 'view', 'ticket'),
)
system_user_perms += (user_perms + _view_all_joined_org_perms)
diff --git a/apps/settings/api/ldap.py b/apps/settings/api/ldap.py
index f8b052a90..1f853060b 100644
--- a/apps/settings/api/ldap.py
+++ b/apps/settings/api/ldap.py
@@ -6,6 +6,7 @@ from rest_framework.views import Response
from common.utils import get_logger
from users.models import User
+from ..const import ImportStatus
from ..models import Setting
from ..serializers import LDAPUserSerializer
from ..utils import (
@@ -25,12 +26,14 @@ class LDAPUserListApi(generics.ListAPIView):
def get_queryset_from_cache(self):
search_value = self.request.query_params.get('search')
- users = LDAPCacheUtil().search(search_value=search_value)
+ category = self.request.query_params.get('category')
+ users = LDAPCacheUtil(category=category).search(search_value=search_value)
return users
def get_queryset_from_server(self):
search_value = self.request.query_params.get('search')
- users = LDAPServerUtil().search(search_value=search_value)
+ category = self.request.query_params.get('category')
+ users = LDAPServerUtil(category=category).search(search_value=search_value)
return users
def get_queryset(self):
diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py
index d543bbb2c..dfd5653d7 100644
--- a/apps/settings/api/settings.py
+++ b/apps/settings/api/settings.py
@@ -36,6 +36,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'security_password': serializers.SecurityPasswordRuleSerializer,
'security_login_limit': serializers.SecurityLoginLimitSerializer,
'ldap': serializers.LDAPSettingSerializer,
+ 'ldap_ha': serializers.LDAPHASettingSerializer,
'email': serializers.EmailSettingSerializer,
'email_content': serializers.EmailContentSettingSerializer,
'wecom': serializers.WeComSettingSerializer,
diff --git a/apps/settings/const.py b/apps/settings/const.py
new file mode 100644
index 000000000..4e4028d4f
--- /dev/null
+++ b/apps/settings/const.py
@@ -0,0 +1,7 @@
+from django.db.models import TextChoices
+
+
+class ImportStatus(TextChoices):
+ ok = 'ok', 'Ok'
+ pending = 'pending', 'Pending'
+ error = 'error', 'Error'
diff --git a/apps/settings/models.py b/apps/settings/models.py
index b39408098..98964ad0e 100644
--- a/apps/settings/models.py
+++ b/apps/settings/models.py
@@ -7,6 +7,7 @@ from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db import models
from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import gettext_lazy as _
+from rest_framework.utils.encoders import JSONEncoder
from common.db.models import JMSBaseModel
from common.utils import signer, get_logger
@@ -63,7 +64,7 @@ class Setting(models.Model):
@cleaned_value.setter
def cleaned_value(self, item):
try:
- v = json.dumps(item)
+ v = json.dumps(item, cls=JSONEncoder)
if self.encrypted:
v = signer.sign(v)
self.value = v
diff --git a/apps/settings/serializers/auth/__init__.py b/apps/settings/serializers/auth/__init__.py
index b5f567618..c1d6ab8f2 100644
--- a/apps/settings/serializers/auth/__init__.py
+++ b/apps/settings/serializers/auth/__init__.py
@@ -4,6 +4,7 @@ from .dingtalk import *
from .feishu import *
from .lark import *
from .ldap import *
+from .ldap_ha import *
from .oauth2 import *
from .oidc import *
from .passkey import *
diff --git a/apps/settings/serializers/auth/base.py b/apps/settings/serializers/auth/base.py
index abfbf79b4..771ea6162 100644
--- a/apps/settings/serializers/auth/base.py
+++ b/apps/settings/serializers/auth/base.py
@@ -11,6 +11,7 @@ class AuthSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Authentication')
AUTH_LDAP = serializers.BooleanField(required=False, label=_('LDAP Auth'))
+ AUTH_LDAP_HA = serializers.BooleanField(required=False, label=_('LDAP Auth HA'))
AUTH_CAS = serializers.BooleanField(required=False, label=_('CAS Auth'))
AUTH_OPENID = serializers.BooleanField(required=False, label=_('OPENID Auth'))
AUTH_SAML2 = serializers.BooleanField(default=False, label=_("SAML2 Auth"))
diff --git a/apps/settings/serializers/auth/ldap.py b/apps/settings/serializers/auth/ldap.py
index e0bd51389..be022a659 100644
--- a/apps/settings/serializers/auth/ldap.py
+++ b/apps/settings/serializers/auth/ldap.py
@@ -33,6 +33,7 @@ class LDAPUserSerializer(serializers.Serializer):
email = serializers.CharField()
groups = serializers.ListField(child=serializers.CharField(), default=[])
existing = serializers.BooleanField(read_only=True)
+ status = serializers.JSONField(read_only=True)
class LDAPSettingSerializer(serializers.Serializer):
@@ -87,7 +88,7 @@ class LDAPSettingSerializer(serializers.Serializer):
default=3600 * 24 * 30,
required=False, label=_('User DN cache timeout (s)'),
help_text=_(
- 'Caching the User DN obtained during user login authentication can effectively'
+ 'Caching the User DN obtained during user login authentication can effectively '
'improve the speed of user authentication., 0 means no cache
'
'If the user OU structure has been adjusted, click Submit to clear the user DN cache'
)
diff --git a/apps/settings/serializers/auth/ldap_ha.py b/apps/settings/serializers/auth/ldap_ha.py
new file mode 100644
index 000000000..c653beeda
--- /dev/null
+++ b/apps/settings/serializers/auth/ldap_ha.py
@@ -0,0 +1,94 @@
+from django.utils.translation import gettext_lazy as _
+from rest_framework import serializers
+
+from common.serializers.fields import EncryptedField
+from .base import OrgListField
+
+__all__ = ['LDAPHATestConfigSerializer', 'LDAPHASettingSerializer']
+
+
+class LDAPHATestConfigSerializer(serializers.Serializer):
+ AUTH_LDAP_HA_SERVER_URI = serializers.CharField(max_length=1024)
+ AUTH_LDAP_HA_BIND_DN = serializers.CharField(max_length=1024, required=False, allow_blank=True)
+ AUTH_LDAP_HA_BIND_PASSWORD = EncryptedField(required=False, allow_blank=True)
+ AUTH_LDAP_HA_SEARCH_OU = serializers.CharField()
+ AUTH_LDAP_HA_SEARCH_FILTER = serializers.CharField()
+ AUTH_LDAP_HA_USER_ATTR_MAP = serializers.JSONField()
+ AUTH_LDAP_HA_START_TLS = serializers.BooleanField(required=False)
+ AUTH_LDAP_HA = serializers.BooleanField(required=False)
+
+
+class LDAPHASettingSerializer(serializers.Serializer):
+ # encrypt_fields 现在使用 write_only 来判断了
+ PREFIX_TITLE = _('LDAP HA')
+
+ AUTH_LDAP_HA_SERVER_URI = serializers.CharField(
+ required=True, max_length=1024, label=_('Server'),
+ help_text=_('LDAP HA server URI')
+ )
+ AUTH_LDAP_HA_BIND_DN = serializers.CharField(
+ required=False, max_length=1024, label=_('Bind DN'),
+ help_text=_('Binding Distinguished Name')
+ )
+ AUTH_LDAP_HA_BIND_PASSWORD = EncryptedField(
+ max_length=1024, required=False, label=_('Password'),
+ help_text=_('Binding password')
+ )
+ AUTH_LDAP_HA_SEARCH_OU = serializers.CharField(
+ max_length=1024, allow_blank=True, required=False, label=_('Search OU'),
+ help_text=_(
+ 'User Search Base, if there are multiple OUs, you can separate them with the `|` symbol'
+ )
+ )
+ AUTH_LDAP_HA_SEARCH_FILTER = serializers.CharField(
+ max_length=1024, required=True, label=_('Search filter'),
+ help_text=_('Selection could include (cn|uid|sAMAccountName=%(user)s)')
+ )
+ AUTH_LDAP_HA_USER_ATTR_MAP = serializers.JSONField(
+ required=True, label=_('User attribute'),
+ help_text=_(
+ 'User attribute mapping, where the `key` is the JumpServer user attribute name and the '
+ '`value` is the LDAP service user attribute name'
+ )
+ )
+ AUTH_LDAP_HA_SYNC_IS_PERIODIC = serializers.BooleanField(
+ required=False, label=_('Periodic run')
+ )
+ AUTH_LDAP_HA_SYNC_CRONTAB = serializers.CharField(
+ required=False, max_length=128, allow_null=True, allow_blank=True,
+ label=_('Crontab')
+ )
+ AUTH_LDAP_HA_SYNC_INTERVAL = serializers.IntegerField(
+ required=False, default=24, allow_null=True, label=_('Interval')
+ )
+ AUTH_LDAP_HA_CONNECT_TIMEOUT = serializers.IntegerField(
+ min_value=1, max_value=300,
+ required=False, label=_('Connect timeout (s)'),
+ )
+ AUTH_LDAP_HA_CACHE_TIMEOUT = serializers.IntegerField(
+ min_value=0, max_value=3600 * 24 * 30 * 12,
+ default=3600 * 24 * 30,
+ required=False, label=_('User DN cache timeout (s)'),
+ help_text=_(
+ 'Caching the User DN obtained during user login authentication can effectively'
+ 'improve the speed of user authentication., 0 means no cache
'
+ 'If the user OU structure has been adjusted, click Submit to clear the user DN cache'
+ )
+ )
+ AUTH_LDAP_HA_SEARCH_PAGED_SIZE = serializers.IntegerField(
+ required=False, label=_('Search paged size (piece)')
+ )
+ AUTH_LDAP_HA_SYNC_RECEIVERS = serializers.ListField(
+ required=False, label=_('Recipient'), max_length=36
+ )
+
+ AUTH_LDAP_HA = serializers.BooleanField(required=False, label=_('LDAP HA'))
+ AUTH_LDAP_HA_SYNC_ORG_IDS = OrgListField()
+
+ def post_save(self):
+ keys = ['AUTH_LDAP_HA_SYNC_IS_PERIODIC', 'AUTH_LDAP_HA_SYNC_INTERVAL', 'AUTH_LDAP_HA_SYNC_CRONTAB']
+ kwargs = {k: self.validated_data[k] for k in keys if k in self.validated_data}
+ if not kwargs:
+ return
+ from settings.tasks import import_ldap_ha_user_periodic
+ import_ldap_ha_user_periodic(**kwargs)
diff --git a/apps/settings/serializers/cleaning.py b/apps/settings/serializers/cleaning.py
index 888559dce..2cbba678f 100644
--- a/apps/settings/serializers/cleaning.py
+++ b/apps/settings/serializers/cleaning.py
@@ -47,3 +47,8 @@ class CleaningSerializer(serializers.Serializer):
help_text=_(
'Session, record, command will be delete if more than duration, only in database, OSS will not be affected.')
)
+
+ ACCOUNT_CHANGE_SECRET_RECORD_KEEP_DAYS = serializers.IntegerField(
+ min_value=MIN_VALUE, max_value=9999,
+ label=_("Change secret and push record retention days (day)"),
+ )
diff --git a/apps/settings/serializers/feature.py b/apps/settings/serializers/feature.py
index 4b219373d..3deb49942 100644
--- a/apps/settings/serializers/feature.py
+++ b/apps/settings/serializers/feature.py
@@ -1,10 +1,11 @@
import uuid
-
+from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from assets.const import Protocol
from common.serializers.fields import EncryptedField
+from common.utils import date_expired_default
__all__ = [
'AnnouncementSettingSerializer', 'OpsSettingSerializer',
@@ -21,6 +22,8 @@ class AnnouncementSerializer(serializers.Serializer):
required=False, allow_null=True, allow_blank=True,
label=_("More Link"), default='',
)
+ DATE_START = serializers.DateTimeField(default=timezone.now, label=_("Date start"))
+ DATE_END = serializers.DateTimeField(default=date_expired_default, label=_("Date end"))
def to_representation(self, instance):
defaults = {'ID': '', 'SUBJECT': '', 'CONTENT': '', 'LINK': '', 'ENABLED': False}
diff --git a/apps/settings/tasks/ldap.py b/apps/settings/tasks/ldap.py
index 1882bf37b..6694cf54c 100644
--- a/apps/settings/tasks/ldap.py
+++ b/apps/settings/tasks/ldap.py
@@ -1,6 +1,6 @@
# coding: utf-8
-#
import time
+
from celery import shared_task
from django.conf import settings
from django.utils.translation import gettext_lazy as _
@@ -9,45 +9,51 @@ from common.utils import get_logger
from common.utils.timezone import local_now_display
from ops.celery.decorator import after_app_ready_start
from ops.celery.utils import (
- create_or_update_celery_periodic_tasks, disable_celery_periodic_task
+ create_or_update_celery_periodic_tasks
)
from orgs.models import Organization
from settings.notifications import LDAPImportMessage
from users.models import User
from ..utils import LDAPSyncUtil, LDAPServerUtil, LDAPImportUtil
-__all__ = ['sync_ldap_user', 'import_ldap_user_periodic', 'import_ldap_user']
+__all__ = [
+ 'sync_ldap_user', 'import_ldap_user_periodic', 'import_ldap_ha_user_periodic',
+ 'import_ldap_user', 'import_ldap_ha_user'
+]
logger = get_logger(__file__)
-def sync_ldap_user():
- LDAPSyncUtil().perform_sync()
+def sync_ldap_user(category='ldap'):
+ LDAPSyncUtil(category=category).perform_sync()
-@shared_task(verbose_name=_('Periodic import ldap user'))
-def import_ldap_user():
+def perform_import(category, util_server):
start_time = time.time()
time_start_display = local_now_display()
- logger.info("Start import ldap user task")
- util_server = LDAPServerUtil()
+ logger.info(f"Start import {category} ldap user task")
+
util_import = LDAPImportUtil()
users = util_server.search()
+
if settings.XPACK_ENABLED:
- org_ids = settings.AUTH_LDAP_SYNC_ORG_IDS
+ org_ids = getattr(settings, f"AUTH_{category.upper()}_SYNC_ORG_IDS")
default_org = None
else:
- # 社区版默认导入Default组织
org_ids = [Organization.DEFAULT_ID]
default_org = Organization.default()
+
orgs = list(set([Organization.get_instance(org_id, default=default_org) for org_id in org_ids]))
new_users, errors = util_import.perform_import(users, orgs)
+
if errors:
- logger.error("Imported LDAP users errors: {}".format(errors))
+ logger.error(f"Imported {category} LDAP users errors: {errors}")
else:
- logger.info('Imported {} users successfully'.format(len(users)))
- if settings.AUTH_LDAP_SYNC_RECEIVERS:
- user_ids = settings.AUTH_LDAP_SYNC_RECEIVERS
+ logger.info(f"Imported {len(users)} {category} users successfully")
+
+ receivers_setting = f"AUTH_{category.upper()}_SYNC_RECEIVERS"
+ if getattr(settings, receivers_setting, None):
+ user_ids = getattr(settings, receivers_setting)
recipient_list = User.objects.filter(id__in=list(user_ids))
end_time = time.time()
extra_kwargs = {
@@ -63,26 +69,77 @@ def import_ldap_user():
LDAPImportMessage(user, extra_kwargs).publish()
-@shared_task(verbose_name=_('Registration periodic import ldap user task'))
-@after_app_ready_start
-def import_ldap_user_periodic(**kwargs):
- task_name = 'import_ldap_user_periodic'
- interval = kwargs.get('AUTH_LDAP_SYNC_INTERVAL', settings.AUTH_LDAP_SYNC_INTERVAL)
- enabled = kwargs.get('AUTH_LDAP_SYNC_IS_PERIODIC', settings.AUTH_LDAP_SYNC_IS_PERIODIC)
- crontab = kwargs.get('AUTH_LDAP_SYNC_CRONTAB', settings.AUTH_LDAP_SYNC_CRONTAB)
+@shared_task(
+ verbose_name=_('Periodic import ldap user'),
+ description=_(
+ "When LDAP auto-sync is configured, this task will be invoked to synchronize users"
+ )
+)
+def import_ldap_user():
+ perform_import('ldap', LDAPServerUtil())
+
+
+@shared_task(
+ verbose_name=_('Periodic import ldap ha user'),
+ description=_(
+ "When LDAP auto-sync is configured, this task will be invoked to synchronize users"
+ )
+)
+def import_ldap_ha_user():
+ perform_import('ldap_ha', LDAPServerUtil(category='ldap_ha'))
+
+
+def register_periodic_task(task_name, task_func, interval_key, enabled_key, crontab_key, **kwargs):
+ interval = kwargs.get(interval_key, settings.AUTH_LDAP_SYNC_INTERVAL)
+ enabled = kwargs.get(enabled_key, settings.AUTH_LDAP_SYNC_IS_PERIODIC)
+ crontab = kwargs.get(crontab_key, settings.AUTH_LDAP_SYNC_CRONTAB)
+
if isinstance(interval, int):
interval = interval * 3600
else:
interval = None
+
if crontab:
- # 优先使用 crontab
- interval = None
+ interval = None # 优先使用 crontab
+
tasks = {
task_name: {
- 'task': import_ldap_user.name,
+ 'task': task_func.name,
'interval': interval,
'crontab': crontab,
'enabled': enabled
}
}
create_or_update_celery_periodic_tasks(tasks)
+
+
+@shared_task(
+ verbose_name=_('Registration periodic import ldap user task'),
+ description=_(
+ """When LDAP auto-sync parameters change, such as Crontab parameters, the LDAP sync task
+ will be re-registered or updated, and this task will be invoked"""
+ )
+)
+@after_app_ready_start
+def import_ldap_user_periodic(**kwargs):
+ register_periodic_task(
+ 'import_ldap_user_periodic', import_ldap_user,
+ 'AUTH_LDAP_SYNC_INTERVAL', 'AUTH_LDAP_SYNC_IS_PERIODIC',
+ 'AUTH_LDAP_SYNC_CRONTAB', **kwargs
+ )
+
+
+@shared_task(
+ verbose_name=_('Registration periodic import ldap ha user task'),
+ description=_(
+ """When LDAP HA auto-sync parameters change, such as Crontab parameters, the LDAP HA sync task
+ will be re-registered or updated, and this task will be invoked"""
+ )
+)
+@after_app_ready_start
+def import_ldap_ha_user_periodic(**kwargs):
+ register_periodic_task(
+ 'import_ldap_ha_user_periodic', import_ldap_ha_user,
+ 'AUTH_LDAP_HA_SYNC_INTERVAL', 'AUTH_LDAP_HA_SYNC_IS_PERIODIC',
+ 'AUTH_LDAP_HA_SYNC_CRONTAB', **kwargs
+ )
diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py
index 5699e4b20..e896c190e 100644
--- a/apps/settings/utils/ldap.py
+++ b/apps/settings/utils/ldap.py
@@ -24,12 +24,14 @@ from ldap3.core.exceptions import (
LDAPAttributeError,
)
-from authentication.backends.ldap import LDAPAuthorizationBackend, LDAPUser
+from authentication.backends.ldap import LDAPAuthorizationBackend, LDAPUser, \
+ LDAPHAAuthorizationBackend
from common.const import LDAP_AD_ACCOUNT_DISABLE
from common.db.utils import close_old_connections
from common.utils import timeit, get_logger
from common.utils.http import is_true
from orgs.utils import tmp_to_org
+from settings.const import ImportStatus
from users.models import User, UserGroup
from users.utils import construct_user_email
@@ -45,7 +47,7 @@ LDAP_USE_CACHE_FLAGS = [1, '1', 'true', 'True', True]
class LDAPConfig(object):
- def __init__(self, config=None):
+ def __init__(self, config=None, category='ldap'):
self.server_uri = None
self.bind_dn = None
self.password = None
@@ -54,6 +56,7 @@ class LDAPConfig(object):
self.search_filter = None
self.attr_map = None
self.auth_ldap = None
+ self.category = category
if isinstance(config, dict):
self.load_from_config(config)
else:
@@ -70,25 +73,26 @@ class LDAPConfig(object):
self.auth_ldap = config.get('auth_ldap')
def load_from_settings(self):
- self.server_uri = settings.AUTH_LDAP_SERVER_URI
- self.bind_dn = settings.AUTH_LDAP_BIND_DN
- self.password = settings.AUTH_LDAP_BIND_PASSWORD
- self.use_ssl = settings.AUTH_LDAP_START_TLS
- self.search_ou = settings.AUTH_LDAP_SEARCH_OU
- self.search_filter = settings.AUTH_LDAP_SEARCH_FILTER
- self.attr_map = settings.AUTH_LDAP_USER_ATTR_MAP
- self.auth_ldap = settings.AUTH_LDAP
+ prefix = 'AUTH_LDAP' if self.category == 'ldap' else 'AUTH_LDAP_HA'
+ self.server_uri = getattr(settings, f"{prefix}_SERVER_URI")
+ self.bind_dn = getattr(settings, f"{prefix}_BIND_DN")
+ self.password = getattr(settings, f"{prefix}_BIND_PASSWORD")
+ self.use_ssl = getattr(settings, f"{prefix}_START_TLS")
+ self.search_ou = getattr(settings, f"{prefix}_SEARCH_OU")
+ self.search_filter = getattr(settings, f"{prefix}_SEARCH_FILTER")
+ self.attr_map = getattr(settings, f"{prefix}_USER_ATTR_MAP")
+ self.auth_ldap = getattr(settings, prefix)
class LDAPServerUtil(object):
- def __init__(self, config=None):
+ def __init__(self, config=None, category='ldap'):
if isinstance(config, dict):
self.config = LDAPConfig(config=config)
elif isinstance(config, LDAPConfig):
self.config = config
else:
- self.config = LDAPConfig()
+ self.config = LDAPConfig(category=category)
self._conn = None
self._paged_size = self.get_paged_size()
self.search_users = None
@@ -199,6 +203,7 @@ class LDAPServerUtil(object):
if not isinstance(value, list):
value = []
user[attr] = value.strip() if isinstance(value, str) else value
+ user['status'] = ImportStatus.pending
return user
def user_entries_to_dict(self, user_entries):
@@ -228,25 +233,29 @@ class LDAPServerUtil(object):
class LDAPCacheUtil(object):
- CACHE_KEY_USERS = 'CACHE_KEY_LDAP_USERS'
- def __init__(self):
+ def __init__(self, category='ldap'):
self.search_users = None
self.search_value = None
+ self.category = category
+ if self.category == 'ldap':
+ self.cache_key_users = 'CACHE_KEY_LDAP_USERS'
+ else:
+ self.cache_key_users = 'CACHE_KEY_LDAP_HA_USERS'
def set_users(self, users):
logger.info('Set ldap users to cache, count: {}'.format(len(users)))
- cache.set(self.CACHE_KEY_USERS, users, None)
+ cache.set(self.cache_key_users, users, None)
def get_users(self):
- users = cache.get(self.CACHE_KEY_USERS)
+ users = cache.get(self.cache_key_users)
count = users if users is None else len(users)
logger.info('Get ldap users from cache, count: {}'.format(count))
return users
def delete_users(self):
logger.info('Delete ldap users from cache')
- cache.delete(self.CACHE_KEY_USERS)
+ cache.delete(self.cache_key_users)
def filter_users(self, users):
if users is None:
@@ -286,10 +295,11 @@ class LDAPSyncUtil(object):
TASK_STATUS_IS_RUNNING = 'RUNNING'
TASK_STATUS_IS_OVER = 'OVER'
- def __init__(self):
- self.server_util = LDAPServerUtil()
- self.cache_util = LDAPCacheUtil()
+ def __init__(self, category='ldap'):
+ self.server_util = LDAPServerUtil(category=category)
+ self.cache_util = LDAPCacheUtil(category=category)
self.task_error_msg = None
+ self.category = category
def clear_cache(self):
logger.info('Clear ldap sync cache')
@@ -345,7 +355,7 @@ class LDAPSyncUtil(object):
def perform_sync(self):
logger.info('Start perform sync ldap users from server to cache')
try:
- ok, msg = LDAPTestUtil().test_config()
+ ok, msg = LDAPTestUtil(category=self.category).test_config()
if not ok:
raise self.LDAPSyncUtilException(msg)
self.sync()
@@ -375,6 +385,7 @@ class LDAPImportUtil(object):
user['email'] = self.get_user_email(user)
if user['username'] not in ['admin']:
user['source'] = User.Source.ldap.value
+ user.pop('status', None)
obj, created = User.objects.update_or_create(
username=user['username'], defaults=user
)
@@ -474,9 +485,13 @@ class LDAPTestUtil(object):
class LDAPBeforeLoginCheckError(LDAPExceptionError):
pass
- def __init__(self, config=None):
- self.config = LDAPConfig(config)
+ def __init__(self, config=None, category='ldap'):
+ self.config = LDAPConfig(config, category)
self.user_entries = []
+ if category == 'ldap':
+ self.backend = LDAPAuthorizationBackend()
+ else:
+ self.backend = LDAPHAAuthorizationBackend()
def _test_connection_bind(self, authentication=None, user=None, password=None):
server = Server(self.config.server_uri)
@@ -654,15 +669,12 @@ class LDAPTestUtil(object):
if not cache.get(CACHE_KEY_LDAP_TEST_CONFIG_TASK_STATUS):
self.test_config()
- backend = LDAPAuthorizationBackend()
- ok, msg = backend.pre_check(username, password)
+ ok, msg = self.backend.pre_check(username, password)
if not ok:
raise self.LDAPBeforeLoginCheckError(msg)
- @staticmethod
- def _test_login_auth(username, password):
- backend = LDAPAuthorizationBackend()
- ldap_user = LDAPUser(backend, username=username.strip())
+ def _test_login_auth(self, username, password):
+ ldap_user = LDAPUser(self.backend, username=username.strip())
ldap_user._authenticate_user_dn(password)
def _test_login(self, username, password):
diff --git a/apps/settings/ws.py b/apps/settings/ws.py
index 22fcb137a..6dca2f0a2 100644
--- a/apps/settings/ws.py
+++ b/apps/settings/ws.py
@@ -3,16 +3,17 @@
import json
import asyncio
-from asgiref.sync import sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.core.cache import cache
from django.conf import settings
from django.utils.translation import gettext_lazy as _, activate
from django.utils import translation
+from urllib.parse import parse_qs
from common.db.utils import close_old_connections
from common.utils import get_logger
from settings.serializers import (
+ LDAPHATestConfigSerializer,
LDAPTestConfigSerializer,
LDAPTestLoginSerializer
)
@@ -23,6 +24,7 @@ from settings.utils import (
LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
LDAP_USE_CACHE_FLAGS, LDAPTestUtil
)
+from .const import ImportStatus
from .tools import (
verbose_ping, verbose_telnet, verbose_nmap,
verbose_tcpdump, verbose_traceroute
@@ -100,8 +102,12 @@ class ToolsWebsocket(AsyncJsonWebsocketConsumer):
class LdapWebsocket(AsyncJsonWebsocketConsumer):
+ category: str
+
async def connect(self):
user = self.scope["user"]
+ query = parse_qs(self.scope['query_string'].decode())
+ self.category = query.get('category', ['ldap'])[0]
if user.is_authenticated:
await self.accept()
else:
@@ -111,12 +117,15 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
data = json.loads(text_data)
msg_type = data.pop('msg_type', 'testing_config')
try:
- tool_func = getattr(self, f'run_{msg_type.lower()}')
- ok, msg = await asyncio.to_thread(tool_func, data)
+ ok, msg = await asyncio.to_thread(self.run_func, f'run_{msg_type.lower()}', data)
await self.send_msg(ok, msg)
except Exception as error:
await self.send_msg(msg='Exception: %s' % error)
+ def run_func(self, func_name, data):
+ with translation.override(getattr(self.scope['user'], 'lang', settings.LANGUAGE_CODE)):
+ return getattr(self, func_name)(data)
+
async def send_msg(self, ok=True, msg=''):
await self.send_json({'ok': ok, 'msg': f'{msg}'})
@@ -124,30 +133,21 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
await self.close()
close_old_connections()
- @staticmethod
- def get_ldap_config(serializer):
- server_uri = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
- bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
- password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
- use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
- search_ou = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
- search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
- attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
- auth_ldap = serializer.validated_data.get('AUTH_LDAP', False)
-
- if not password:
- password = settings.AUTH_LDAP_BIND_PASSWORD
+ def get_ldap_config(self, serializer):
+ prefix = 'AUTH_LDAP_' if self.category == 'ldap' else 'AUTH_LDAP_HA_'
config = {
- 'server_uri': server_uri,
- 'bind_dn': bind_dn,
- 'password': password,
- 'use_ssl': use_ssl,
- 'search_ou': search_ou,
- 'search_filter': search_filter,
- 'attr_map': attr_map,
- 'auth_ldap': auth_ldap
+ 'server_uri': serializer.validated_data.get(f"{prefix}SERVER_URI"),
+ 'bind_dn': serializer.validated_data.get(f"{prefix}BIND_DN"),
+ 'password': (serializer.validated_data.get(f"{prefix}BIND_PASSWORD") or
+ getattr(settings, f"{prefix}BIND_PASSWORD")),
+ 'use_ssl': serializer.validated_data.get(f"{prefix}START_TLS", False),
+ 'search_ou': serializer.validated_data.get(f"{prefix}SEARCH_OU"),
+ 'search_filter': serializer.validated_data.get(f"{prefix}SEARCH_FILTER"),
+ 'attr_map': serializer.validated_data.get(f"{prefix}USER_ATTR_MAP"),
+ 'auth_ldap': serializer.validated_data.get(f"{prefix.rstrip('_')}", False)
}
+
return config
@staticmethod
@@ -159,7 +159,10 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
cache.set(task_key, TASK_STATUS_IS_OVER, ttl)
def run_testing_config(self, data):
- serializer = LDAPTestConfigSerializer(data=data)
+ if self.category == 'ldap':
+ serializer = LDAPTestConfigSerializer(data=data)
+ else:
+ serializer = LDAPHATestConfigSerializer(data=data)
if not serializer.is_valid():
self.send_msg(msg=f'error: {str(serializer.errors)}')
config = self.get_ldap_config(serializer)
@@ -174,24 +177,18 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
self.send_msg(msg=f'error: {str(serializer.errors)}')
username = serializer.validated_data['username']
password = serializer.validated_data['password']
- ok, msg = LDAPTestUtil().test_login(username, password)
+ ok, msg = LDAPTestUtil(category=self.category).test_login(username, password)
return ok, msg
- @staticmethod
- def run_sync_user(data):
- sync_util = LDAPSyncUtil()
+ def run_sync_user(self, data):
+ sync_util = LDAPSyncUtil(category=self.category)
sync_util.clear_cache()
- sync_ldap_user()
+ sync_ldap_user(category=self.category)
msg = sync_util.get_task_error_msg()
ok = False if msg else True
return ok, msg
def run_import_user(self, data):
- lang = getattr(self.scope['user'], 'lang', settings.LANGUAGE_CODE)
- with translation.override(lang):
- return self._run_import_user(data)
-
- def _run_import_user(self, data):
ok = False
org_ids = data.get('org_ids')
username_list = data.get('username_list', [])
@@ -208,10 +205,24 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
msg = _('Total {}, success {}, failure {}').format(
len(users), success_count, len(error_msg)
)
+ self.set_users_status(users, error_msg)
except Exception as e:
msg = str(e)
return ok, msg
+ def set_users_status(self, import_users, errors):
+ util = LDAPCacheUtil(category=self.category)
+ all_users = util.get_users()
+ import_usernames = [u['username'] for u in import_users]
+ errors_mapper = {k: v for err in errors for k, v in err.items()}
+ for user in all_users:
+ username = user['username']
+ if username in errors_mapper:
+ user['status'] = {'error': errors_mapper[username]}
+ elif username in import_usernames:
+ user['status'] = ImportStatus.ok
+ LDAPCacheUtil(category=self.category).set_users(all_users)
+
@staticmethod
def get_orgs(org_ids):
if org_ids:
@@ -220,12 +231,11 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
orgs = [current_org]
return orgs
- @staticmethod
- def get_ldap_users(username_list, cache_police):
+ def get_ldap_users(self, username_list, cache_police):
if '*' in username_list:
- users = LDAPServerUtil().search()
+ users = LDAPServerUtil(category=self.category).search()
elif cache_police in LDAP_USE_CACHE_FLAGS:
- users = LDAPCacheUtil().search(search_users=username_list)
+ users = LDAPCacheUtil(category=self.category).search(search_users=username_list)
else:
- users = LDAPServerUtil().search(search_users=username_list)
+ users = LDAPServerUtil(category=self.category).search(search_users=username_list)
return users
diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html
index 808b32d59..512626d72 100644
--- a/apps/templates/resource_download.html
+++ b/apps/templates/resource_download.html
@@ -52,28 +52,27 @@ p {
JumpServer {% trans 'Offline video player' %} v0.1.9
-
{% endblock %}
diff --git a/apps/terminal/api/component/storage.py b/apps/terminal/api/component/storage.py
index 0121ac18a..9465106a8 100644
--- a/apps/terminal/api/component/storage.py
+++ b/apps/terminal/api/component/storage.py
@@ -9,9 +9,9 @@ from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
+from common.api.mixin import CommonApiMixin
from common.const.http import GET
from common.drf.filters import BaseFilterSet
-from common.api.mixin import CommonApiMixin
from terminal import const
from terminal.filters import CommandStorageFilter, CommandFilter, CommandFilterForStorageTree
from terminal.models import CommandStorage, ReplayStorage
@@ -30,8 +30,10 @@ class BaseStorageViewSetMixin(CommonApiMixin):
if instance.type_null_or_server or instance.is_default:
data = {'msg': _('Deleting the default storage is not allowed')}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
- if instance.is_use():
- data = {'msg': _('Cannot delete storage that is being used')}
+ used_by = instance.used_by()
+ if used_by:
+ names = ', '.join(list(used_by.values_list('name', flat=True)))
+ data = {'msg': _('Cannot delete storage that is being used: {}').format(names)}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
return super().destroy(request, *args, **kwargs)
diff --git a/apps/terminal/api/session/session.py b/apps/terminal/api/session/session.py
index 44b49615c..249b07602 100644
--- a/apps/terminal/api/session/session.py
+++ b/apps/terminal/api/session/session.py
@@ -23,7 +23,7 @@ from common.drf.filters import BaseFilterSet
from common.drf.filters import DatetimeRangeFilterBackend
from common.drf.renders import PassthroughRenderer
from common.permissions import IsServiceAccount
-from common.storage.replay import ReplayStorageHandler
+from common.storage.replay import ReplayStorageHandler, SessionPartReplayStorageHandler
from common.utils import data_to_json, is_uuid, i18n_fmt
from common.utils import get_logger, get_object_or_none
from common.views.mixins import RecordViewLogMixin
@@ -124,33 +124,37 @@ class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
os.chdir(current_dir)
return file
- def get_storage(self):
- return ReplayStorageHandler(self.get_object())
-
@action(methods=[GET], detail=True, renderer_classes=(PassthroughRenderer,), url_path='replay/download',
url_name='replay-download')
def download(self, request, *args, **kwargs):
- storage = self.get_storage()
+ session = self.get_object()
+ storage = ReplayStorageHandler(session)
local_path, url = storage.get_file_path_url()
if local_path is None:
# url => error message
return Response({'error': url}, status=404)
- file = self.prepare_offline_file(storage.obj, local_path)
+ # 如果获取的录像文件类型是 .replay.json 则使用 part 的方式下载
+ if url.endswith('.replay.json'):
+ # part 的方式录像存储, 通过 part_storage 的方式下载
+ part_storage = SessionPartReplayStorageHandler(session)
+ file = part_storage.prepare_offline_tar_file()
+ else:
+ file = self.prepare_offline_file(session, local_path)
response = FileResponse(file)
response['Content-Type'] = 'application/octet-stream'
# 这里要注意哦,网上查到的方法都是response['Content-Disposition']='attachment;filename="filename.py"',
# 但是如果文件名是英文名没问题,如果文件名包含中文,下载下来的文件名会被改为url中的path。
- filename = escape_uri_path('{}.tar'.format(storage.obj.id))
+ filename = escape_uri_path('{}.tar'.format(session.id))
disposition = "attachment; filename*=UTF-8''{}".format(filename)
response["Content-Disposition"] = disposition
detail = i18n_fmt(
- REPLAY_OP, self.request.user, _('Download'), str(storage.obj)
+ REPLAY_OP, self.request.user, _('Download'), str(session)
)
self.record_logs(
- [storage.obj.asset_id], ActionChoices.download, detail,
- model=Session, resource_display=str(storage.obj)
+ [session.asset_id], ActionChoices.download, detail,
+ model=Session, resource_display=str(session)
)
return response
@@ -197,7 +201,7 @@ class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
# so we need to use select_for_update only for have not prefetch_related and annotate
queryset = queryset.select_for_update()
return queryset
-
+
def perform_create(self, serializer):
if hasattr(self.request.user, 'terminal'):
serializer.validated_data["terminal"] = self.request.user.terminal
@@ -245,6 +249,9 @@ class SessionReplayViewSet(AsyncApiMixin, RecordViewLogMixin, viewsets.ViewSet):
tp = 'asciicast'
elif url.endswith('.replay.mp4'):
tp = 'mp4'
+ elif url.endswith('replay.json'):
+ # 新版本将返回元数据信息
+ tp = 'parts'
elif (getattr(session.terminal, 'type', None) in all_guacamole_types) or \
(session.protocol in ('rdp', 'vnc')):
tp = 'guacamole'
@@ -281,9 +288,14 @@ class SessionReplayViewSet(AsyncApiMixin, RecordViewLogMixin, viewsets.ViewSet):
def retrieve(self, request, *args, **kwargs):
session_id = kwargs.get('pk')
session = get_object_or_404(Session, id=session_id)
+ part_filename = request.query_params.get('part_filename')
+ if part_filename:
+ storage = SessionPartReplayStorageHandler(session)
+ local_path, url = storage.get_part_file_path_url(part_filename)
+ else:
+ storage = ReplayStorageHandler(session)
+ local_path, url = storage.get_file_path_url()
- storage = ReplayStorageHandler(session)
- local_path, url = storage.get_file_path_url()
if local_path is None:
# url => error message
return Response({"error": url}, status=404)
diff --git a/apps/terminal/models/component/storage.py b/apps/terminal/models/component/storage.py
index 40c0c0f79..fb714d35b 100644
--- a/apps/terminal/models/component/storage.py
+++ b/apps/terminal/models/component/storage.py
@@ -105,10 +105,10 @@ class CommandStorage(CommonStorageModelMixin, JMSBaseModel):
store = engine_mod.CommandStore(self.config)
return store.ping(timeout=3)
- def is_use(self):
+ def used_by(self):
return Terminal.objects.filter(
command_storage=self.name, is_deleted=False
- ).exists()
+ )
def get_command_queryset(self):
if self.type_null:
@@ -127,9 +127,7 @@ class CommandStorage(CommonStorageModelMixin, JMSBaseModel):
logger.error(f"Command storage `{self.type}` not support")
return Command.objects.none()
- def save(
- self, force_insert=False, force_update=False, using=None, update_fields=None
- ):
+ def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
super().save(
force_insert=force_insert,
force_update=force_update,
@@ -205,10 +203,10 @@ class ReplayStorage(CommonStorageModelMixin, JMSBaseModel):
src = os.path.join(settings.BASE_DIR, "common", target)
return storage.is_valid(src, target)
- def is_use(self):
+ def used_by(self):
return Terminal.objects.filter(
replay_storage=self.name, is_deleted=False
- ).exists()
+ )
class Meta:
verbose_name = _("Replay storage")
diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py
index 8666c5da2..b361102c4 100644
--- a/apps/terminal/models/component/terminal.py
+++ b/apps/terminal/models/component/terminal.py
@@ -158,7 +158,6 @@ class Terminal(StorageMixin, TerminalStatusMixin, JMSBaseModel):
self.user = None
self.is_deleted = True
self.save()
- return
def __str__(self):
status = "Active"
diff --git a/apps/terminal/models/session/session.py b/apps/terminal/models/session/session.py
index ab9acaed7..fa47ca36b 100644
--- a/apps/terminal/models/session/session.py
+++ b/apps/terminal/models/session/session.py
@@ -51,7 +51,7 @@ class Session(OrgModelMixin):
upload_to = 'replay'
ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}'
LOCK_CACHE_KEY_PREFIX = 'TOGGLE_LOCKED_SESSION_{}'
- SUFFIX_MAP = {1: '.gz', 2: '.replay.gz', 3: '.cast.gz', 4: '.replay.mp4'}
+ SUFFIX_MAP = {2: '.replay.gz', 3: '.cast.gz', 4: '.replay.mp4', 5: '.replay.json'}
DEFAULT_SUFFIXES = ['.replay.gz', '.cast.gz', '.gz', '.replay.mp4']
# Todo: 将来干掉 local_path, 使用 default storage 实现
@@ -75,22 +75,22 @@ class Session(OrgModelMixin):
"""
local_path: replay/2021-12-08/session_id.cast.gz
通过后缀名获取本地存储的录像文件路径
- :param suffix: .cast.gz | '.replay.gz' | '.gz'
+ :param suffix: .cast.gz | '.replay.gz'
:return:
"""
rel_path = self.get_relative_path_by_suffix(suffix)
- if suffix == '.gz':
- # 兼容 v1 的版本
- return rel_path
return os.path.join(self.upload_to, rel_path)
def get_relative_path_by_suffix(self, suffix='.cast.gz'):
"""
relative_path: 2021-12-08/session_id.cast.gz
通过后缀名获取外部存储录像文件路径
- :param suffix: .cast.gz | '.replay.gz' | '.gz'
+ :param suffix: .cast.gz | '.replay.gz' | '.replay.json'
:return:
"""
+ if suffix == '.replay.json':
+ meta_filename = str(self.id) + suffix
+ return self.get_replay_part_file_relative_path(meta_filename)
date = self.date_start.strftime('%Y-%m-%d')
return os.path.join(date, str(self.id) + suffix)
@@ -172,17 +172,35 @@ class Session(OrgModelMixin):
display = self.terminal.name if self.terminal else ''
return display
+ def get_replay_dir_relative_path(self):
+ date = self.date_start.strftime('%Y-%m-%d')
+ return os.path.join(date, str(self.id))
+
+ def get_replay_part_file_relative_path(self, filename):
+ return os.path.join(self.get_replay_dir_relative_path(), filename)
+
+ def get_replay_part_file_local_storage_path(self, filename):
+ return os.path.join(self.upload_to, self.get_replay_part_file_relative_path(filename))
+
def save_replay_to_storage_with_version(self, f, version=2):
- suffix = self.SUFFIX_MAP.get(version, '.cast.gz')
- local_path = self.get_local_storage_path_by_suffix(suffix)
+ if version <= 4:
+ # compatible old API and deprecated in future version
+ suffix = self.SUFFIX_MAP.get(version, '.cast.gz')
+ rel_path = self.get_relative_path_by_suffix(suffix)
+ local_path = self.get_local_storage_path_by_suffix(suffix)
+ else:
+ # 文件名依赖 上传的文件名,不再使用默认的文件名
+ filename = f.name
+ rel_path = self.get_replay_part_file_relative_path(filename)
+ local_path = self.get_replay_part_file_local_storage_path(filename)
try:
name = default_storage.save(local_path, f)
except OSError as e:
return None, e
if settings.SERVER_REPLAY_STORAGE:
- from terminal.tasks import upload_session_replay_to_external_storage
- upload_session_replay_to_external_storage.delay(str(self.id))
+ from terminal.tasks import upload_session_replay_file_to_external_storage
+ upload_session_replay_file_to_external_storage.delay(str(self.id), local_path, rel_path)
return name, None
@classmethod
diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py
index 00cabf2e7..1a674613f 100644
--- a/apps/terminal/serializers/applet_host.py
+++ b/apps/terminal/serializers/applet_host.py
@@ -21,8 +21,8 @@ __all__ = [
class DeployOptionsSerializer(serializers.Serializer):
LICENSE_MODE_CHOICES = (
- (4, _('Per Session')),
- (2, _('Per Device')),
+ (2, _('Per Device (Device number limit)')),
+ (4, _('Per User (User number limit)')),
)
# 单用户单会话,
@@ -44,11 +44,23 @@ class DeployOptionsSerializer(serializers.Serializer):
""")
)
IGNORE_VERIFY_CERTS = serializers.BooleanField(default=True, label=_("Ignore Certificate Verification"))
- RDS_Licensing = serializers.BooleanField(default=False, label=_("Existing RDS license"))
+ RDS_Licensing = serializers.BooleanField(
+ default=False, label=_("Existing RDS license"),
+ help_text=_(
+ 'If not exist, the RDS will be in trial mode, and the trial period is 120 days. Detail'
+ )
+ )
RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024)
- RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=2, label=_('RDS Licensing Mode'))
- RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1,
- label=_("RDS Single Session Per User"))
+ RDS_LicensingMode = serializers.ChoiceField(
+ choices=LICENSE_MODE_CHOICES, default=2, label=_('RDS Licensing Mode'),
+ )
+ RDS_fSingleSessionPerUser = serializers.ChoiceField(
+ choices=SESSION_PER_USER, default=1, label=_("RDS Single Session Per User"),
+ help_text=_('Tips: A RDS user can have only one session at a time. If set, when next login connected, '
+ 'previous session will be disconnected.')
+ )
RDS_MaxDisconnectionTime = serializers.IntegerField(
default=60000, label=_("RDS Max Disconnection Time (ms)"),
help_text=_(
diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py
index 7bc08a2ad..d54111c89 100644
--- a/apps/terminal/serializers/session.py
+++ b/apps/terminal/serializers/session.py
@@ -58,6 +58,16 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
'terminal_display': {'label': _('Terminal display')},
}
+ def get_fields(self):
+ fields = super().get_fields()
+ self.pop_fields_if_need(fields)
+ return fields
+
+ def pop_fields_if_need(self, fields):
+ request = self.context.get('request')
+ if request and request.method != 'GET':
+ fields.pop("command_amount", None)
+
def validate_asset(self, value):
max_length = self.Meta.model.asset.field.max_length
value = pretty_string(value, max_length=max_length)
@@ -74,7 +84,7 @@ class SessionDisplaySerializer(SessionSerializer):
class ReplaySerializer(serializers.Serializer):
file = serializers.FileField(allow_empty_file=True)
- version = serializers.IntegerField(write_only=True, required=False, min_value=2, max_value=4)
+ version = serializers.IntegerField(write_only=True, required=False, min_value=2, max_value=5)
class SessionJoinValidateSerializer(serializers.Serializer):
diff --git a/apps/terminal/signal_handlers/session.py b/apps/terminal/signal_handlers/session.py
index c67fcab77..4c79de9ed 100644
--- a/apps/terminal/signal_handlers/session.py
+++ b/apps/terminal/signal_handlers/session.py
@@ -5,7 +5,7 @@ from terminal.models import Session
@receiver(pre_save, sender=Session)
-def on_session_pre_save(sender, instance, **kwargs):
+def on_session_pre_save(sender, instance,**kwargs):
if instance.need_update_cmd_amount:
instance.cmd_amount = instance.compute_command_amount()
diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py
index 0892aeac5..f1d2978a5 100644
--- a/apps/terminal/tasks.py
+++ b/apps/terminal/tasks.py
@@ -28,7 +28,10 @@ RUNNING = False
logger = get_task_logger(__name__)
-@shared_task(verbose_name=_('Periodic delete terminal status'))
+@shared_task(
+ verbose_name=_('Periodic delete terminal status'),
+ description=_("Unused")
+)
@register_as_period_task(interval=3600)
@after_app_ready_start
def delete_terminal_status_period():
@@ -36,7 +39,13 @@ def delete_terminal_status_period():
Status.objects.filter(date_created__lt=yesterday).delete()
-@shared_task(verbose_name=_('Clean orphan session'))
+@shared_task(
+ verbose_name=_('Clean orphan session'),
+ description=_(
+ """Check every 10 minutes for asset connection sessions that have been inactive for 3
+ minutes and mark these sessions as completed"""
+ )
+)
@register_as_period_task(interval=600)
@after_app_ready_start
@tmp_to_root_org()
@@ -55,7 +64,13 @@ def clean_orphan_session():
session.save()
-@shared_task(verbose_name=_('Upload session replay to external storage'))
+@shared_task(
+ verbose_name=_('Upload session replay to external storage'),
+ description=_(
+ """If SERVER_REPLAY_STORAGE is configured in the config.txt, session commands and
+ recordings will be uploaded to external storage"""
+ )
+)
def upload_session_replay_to_external_storage(session_id):
logger.info(f'Start upload session to external storage: {session_id}')
session = Session.objects.filter(id=session_id).first()
@@ -83,9 +98,34 @@ def upload_session_replay_to_external_storage(session_id):
return
+@shared_task(
+ verbose_name=_('Upload session replay part file to external storage'),
+ description=_(
+ """If SERVER_REPLAY_STORAGE is configured in the config.txt, session commands and
+ recordings will be uploaded to external storage"""
+ ))
+def upload_session_replay_file_to_external_storage(session_id, local_path, remote_path):
+ abs_path = default_storage.path(local_path)
+ ok, err = server_replay_storage.upload(abs_path, remote_path)
+ if not ok:
+ logger.error(f'Session replay file {local_path} upload to external error: {err}')
+ return
+
+ try:
+ default_storage.delete(local_path)
+ except:
+ pass
+ return
+
+
+
@shared_task(
verbose_name=_('Run applet host deployment'),
- activity_callback=lambda self, did, *args, **kwargs: ([did],)
+ activity_callback=lambda self, did, *args, **kwargs: ([did],),
+ description=_(
+ """When deploying from the remote application publisher details page, and the 'Deploy'
+ button is clicked, this task will be executed"""
+ )
)
def run_applet_host_deployment(did, install_applets):
with tmp_to_builtin_org(system=1):
@@ -95,7 +135,11 @@ def run_applet_host_deployment(did, install_applets):
@shared_task(
verbose_name=_('Install applet'),
- activity_callback=lambda self, ids, applet_id, *args, **kwargs: (ids,)
+ activity_callback=lambda self, ids, applet_id, *args, **kwargs: (ids,),
+ description=_(
+ """When the 'Deploy' button is clicked in the 'Remote Application' section of the remote
+ application publisher details page, this task will be executed"""
+ )
)
def run_applet_host_deployment_install_applet(ids, applet_id):
with tmp_to_builtin_org(system=1):
@@ -106,7 +150,11 @@ def run_applet_host_deployment_install_applet(ids, applet_id):
@shared_task(
verbose_name=_('Uninstall applet'),
- activity_callback=lambda self, ids, applet_id, *args, **kwargs: (ids,)
+ activity_callback=lambda self, ids, applet_id, *args, **kwargs: (ids,),
+ description=_(
+ """When the 'Uninstall' button is clicked in the 'Remote Application' section of the
+ remote application publisher details page, this task will be executed"""
+ )
)
def run_applet_host_deployment_uninstall_applet(ids, applet_id):
with tmp_to_builtin_org(system=1):
@@ -117,7 +165,11 @@ def run_applet_host_deployment_uninstall_applet(ids, applet_id):
@shared_task(
verbose_name=_('Generate applet host accounts'),
- activity_callback=lambda self, host_id, *args, **kwargs: ([host_id],)
+ activity_callback=lambda self, host_id, *args, **kwargs: ([host_id],),
+ description=_(
+ """When a remote publishing server is created and an account needs to be created
+ automatically, this task will be executed"""
+ )
)
def applet_host_generate_accounts(host_id):
applet_host = AppletHost.objects.filter(id=host_id).first()
@@ -128,7 +180,14 @@ def applet_host_generate_accounts(host_id):
applet_host.generate_accounts()
-@shared_task(verbose_name=_('Check command replay storage connectivity'))
+@shared_task(
+ verbose_name=_('Check command replay storage connectivity'),
+ description=_(
+ """Check every day at midnight whether the external storage for commands and recordings
+ is accessible. If it is not accessible, send a notification to the recipients specified
+ in 'System Settings - Notifications - Subscription - Storage - Connectivity'"""
+ )
+)
@register_as_period_task(crontab='0 0 * * *')
@tmp_to_root_org()
def check_command_replay_storage_connectivity():
diff --git a/apps/terminal/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py
index 9878fa9ef..796632a65 100644
--- a/apps/terminal/utils/db_port_mapper.py
+++ b/apps/terminal/utils/db_port_mapper.py
@@ -13,7 +13,7 @@ logger = get_logger(__file__)
@Singleton
-class DBPortManager(object):
+class DBPortManager:
""" 管理端口-数据库ID的映射, Magnus 要使用 """
CACHE_KEY = 'PORT_DB_MAPPER'
@@ -52,7 +52,9 @@ class DBPortManager(object):
db_ids_to_pop = set(mapper.values()) - set(db_ids)
mapper = self.bulk_pop(db_ids_to_pop, mapper)
- self.set_mapper(mapper)
+
+ if db_ids_to_add or db_ids_to_pop:
+ self.set_mapper(mapper)
if settings.DEBUG:
logger.debug("Oracle listen ports: {}".format(len(mapper.keys())))
@@ -63,6 +65,7 @@ class DBPortManager(object):
db_ids = [str(i) for i in db_ids]
mapper = dict(zip(self.all_avail_ports, list(db_ids)))
self.set_mapper(mapper)
+ return mapper
def bulk_add(self, db_ids, mapper):
for db_id in db_ids:
@@ -125,12 +128,20 @@ class DBPortManager(object):
mapper = self.get_mapper()
return sorted([int(i) for i in mapper.keys()])
+ @staticmethod
+ def oracle_ports_setting_changed():
+ oracle_ports_cache = cache.get('MAGNUS_ORACLE_PORTS') or ''
+ if settings.MAGNUS_ORACLE_PORTS.split('-')[0] != oracle_ports_cache.split('-')[0]:
+ logger.info('Oracle ports setting changed')
+ return True
+ return False
+
def get_mapper(self):
mapper = cache.get(self.CACHE_KEY, {})
- if not mapper:
+ if not mapper or self.oracle_ports_setting_changed():
# redis 可能被清空,重新初始化一下
- self.init()
- return cache.get(self.CACHE_KEY, {})
+ mapper = self.init()
+ return mapper
def set_mapper(self, value):
"""
@@ -139,6 +150,7 @@ class DBPortManager(object):
}
"""
cache.set(self.CACHE_KEY, value, timeout=None)
+ cache.set('MAGNUS_ORACLE_PORTS', settings.MAGNUS_ORACLE_PORTS)
db_port_manager = DBPortManager()
diff --git a/apps/tickets/handlers/base.py b/apps/tickets/handlers/base.py
index c74ca7a0c..702a012cd 100644
--- a/apps/tickets/handlers/base.py
+++ b/apps/tickets/handlers/base.py
@@ -1,8 +1,7 @@
-import html2text
from django.template.loader import render_to_string
from django.utils.translation import gettext as _
-from common.utils import get_logger
+from common.utils import get_logger, convert_html_to_markdown
from tickets.const import TicketState, TicketType
from tickets.utils import (
send_ticket_processed_mail_to_applicant,
@@ -97,9 +96,8 @@ class BaseHandler:
approve_info = _('{} {} the ticket').format(user_display, state_display)
context = self._diff_prev_approve_context(state)
context.update({'approve_info': approve_info})
- body = self.safe_html_script(
- render_to_string('tickets/ticket_approve_diff.html', context)
- )
+ html_str = render_to_string('tickets/ticket_approve_diff.html', context)
+ body = convert_html_to_markdown(html_str)
data = {
'body': body,
'user': user,
@@ -108,9 +106,3 @@ class BaseHandler:
'state': state
}
return self.ticket.comments.create(**data)
-
- @staticmethod
- def safe_html_script(unsafe_html):
- unsafe_html = unsafe_html.replace('\n', '')
- html_str = html2text.html2text(unsafe_html)
- return html_str
diff --git a/apps/users/api/user.py b/apps/users/api/user.py
index 1088da6e8..57a896eae 100644
--- a/apps/users/api/user.py
+++ b/apps/users/api/user.py
@@ -4,6 +4,7 @@ from collections import defaultdict
from django.utils.translation import gettext as _
from rest_framework import generics
from rest_framework.decorators import action
+from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
@@ -57,6 +58,11 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, SuggestionMixin, BulkModelV
raise UnableToDeleteAllUsers()
return True
+ def perform_destroy(self, instance):
+ if instance.username == 'admin':
+ raise PermissionDenied(_("Cannot delete the admin user. Please disable it instead."))
+ super().perform_destroy(instance)
+
@action(methods=['get'], detail=False, url_path='suggestions')
def match(self, request, *args, **kwargs):
with tmp_to_root_org():
diff --git a/apps/users/migrations/0001_initial.py b/apps/users/migrations/0001_initial.py
index 4a9ced4ba..5b47304b2 100644
--- a/apps/users/migrations/0001_initial.py
+++ b/apps/users/migrations/0001_initial.py
@@ -35,7 +35,6 @@ def add_default_admin(apps, schema_editor):
class Migration(migrations.Migration):
-
initial = True
dependencies = [
@@ -50,7 +49,9 @@ class Migration(migrations.Migration):
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
- ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
+ ('is_active', models.BooleanField(default=True,
+ help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
+ verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('username', models.CharField(max_length=128, unique=True, verbose_name='Username')),
@@ -60,33 +61,49 @@ class Migration(migrations.Migration):
('is_service_account', models.BooleanField(default=False, verbose_name='Is service account')),
('avatar', models.ImageField(null=True, upload_to='avatar', verbose_name='Avatar')),
('wechat', common.db.fields.EncryptCharField(blank=True, max_length=128, verbose_name='Wechat')),
- ('phone', common.db.fields.EncryptCharField(blank=True, max_length=128, null=True, verbose_name='Phone')),
- ('mfa_level', models.SmallIntegerField(choices=[(0, "Disabled"), (1, "Enabled"), (2, "Force enabled")], default=0, verbose_name='MFA')),
- ('otp_secret_key', common.db.fields.EncryptCharField(blank=True, max_length=128, null=True, verbose_name='OTP secret key')),
+ ('phone',
+ common.db.fields.EncryptCharField(blank=True, max_length=128, null=True, verbose_name='Phone')),
+ ('mfa_level',
+ models.SmallIntegerField(choices=[(0, "Disabled"), (1, "Enabled"), (2, "Force enabled")], default=0,
+ verbose_name='MFA')),
+ ('otp_secret_key', common.db.fields.EncryptCharField(blank=True, max_length=128, null=True,
+ verbose_name='OTP secret key')),
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Private key')),
('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Public key')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
('is_first_login', models.BooleanField(default=True, verbose_name='Is first login')),
- ('date_expired', models.DateTimeField(blank=True, db_index=True, default=common.utils.django.date_expired_default, null=True, verbose_name='Date expired')),
+ ('date_expired',
+ models.DateTimeField(blank=True, db_index=True, default=common.utils.django.date_expired_default,
+ null=True, verbose_name='Date expired')),
('created_by', models.CharField(blank=True, default='', max_length=30, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, default='', max_length=30, verbose_name='Updated by')),
- ('date_password_last_updated', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date password last updated')),
+ ('date_password_last_updated',
+ models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date password last updated')),
('need_update_password', models.BooleanField(default=False, verbose_name='Need update password')),
- ('source', models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'), ('wecom', 'WeCom'), ('dingtalk', 'DingTalk'), ('feishu', 'FeiShu'), ('lark', 'Lark'), ('slack', 'Slack'), ('custom', 'Custom')], default='local', max_length=30, verbose_name='Source')),
+ ('source', models.CharField(
+ choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('ldap_ha', 'LDAP/AD (HA)'), ('openid', 'OpenID'),
+ ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'),
+ ('wecom', 'WeCom'), ('dingtalk', 'DingTalk'), ('feishu', 'FeiShu'), ('lark', 'Lark'),
+ ('slack', 'Slack'), ('custom', 'Custom')], default='local', max_length=30,
+ verbose_name='Source')),
('wecom_id', models.CharField(default=None, max_length=128, null=True, verbose_name='WeCom')),
('dingtalk_id', models.CharField(default=None, max_length=128, null=True, verbose_name='DingTalk')),
('feishu_id', models.CharField(default=None, max_length=128, null=True, verbose_name='FeiShu')),
('lark_id', models.CharField(default=None, max_length=128, null=True, verbose_name='Lark')),
('slack_id', models.CharField(default=None, max_length=128, null=True, verbose_name='Slack')),
- ('date_api_key_last_used', models.DateTimeField(blank=True, null=True, verbose_name='Date api key used')),
+ ('date_api_key_last_used',
+ models.DateTimeField(blank=True, null=True, verbose_name='Date api key used')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
],
options={
'verbose_name': 'User',
'ordering': ['username'],
- 'permissions': [('invite_user', 'Can invite user'), ('remove_user', 'Can remove user'), ('match_user', 'Can match user')],
+ 'permissions': [('invite_user', 'Can invite user'), ('remove_user', 'Can remove user'),
+ ('match_user', 'Can match user')],
},
- bases=(users.models.user.AuthMixin, users.models.user.SourceMixin, users.models.user.TokenMixin, users.models.user.RoleMixin, users.models.user.MFAMixin, users.models.user.JSONFilterMixin, models.Model),
+ bases=(users.models.user.AuthMixin, users.models.user.SourceMixin, users.models.user.TokenMixin,
+ users.models.user.RoleMixin, users.models.user.MFAMixin, users.models.user.JSONFilterMixin,
+ models.Model),
managers=[
('objects', users.models.user.UserManager()),
],
@@ -97,7 +114,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('password', models.CharField(max_length=128)),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
- ('user', models.ForeignKey(on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='history_passwords', to=settings.AUTH_USER_MODEL, verbose_name='User')),
+ ('user',
+ models.ForeignKey(on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='history_passwords',
+ to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'User password history',
@@ -112,7 +131,8 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
- ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
+ ('org_id',
+ models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('name', models.CharField(max_length=128, verbose_name='Name')),
],
options={
@@ -124,12 +144,15 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='user',
name='groups',
- field=models.ManyToManyField(blank=True, related_name='users', to='users.usergroup', verbose_name='User group'),
+ field=models.ManyToManyField(blank=True, related_name='users', to='users.usergroup',
+ verbose_name='User group'),
),
migrations.AddField(
model_name='user',
name='user_permissions',
- field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'),
+ field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.',
+ related_name='user_set', related_query_name='user', to='auth.permission',
+ verbose_name='user permissions'),
),
migrations.CreateModel(
name='Preference',
@@ -139,7 +162,8 @@ class Migration(migrations.Migration):
('category', models.CharField(max_length=128, verbose_name='Category')),
('value', models.TextField(blank=True, default='', verbose_name='Value')),
('encrypted', models.BooleanField(default=False, verbose_name='Encrypted')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='preferences', to=settings.AUTH_USER_MODEL, verbose_name='Users')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='preferences',
+ to=settings.AUTH_USER_MODEL, verbose_name='Users')),
],
options={
'verbose_name': 'Preference',
diff --git a/apps/users/models/user/_auth.py b/apps/users/models/user/_auth.py
index cd38dda51..a3f8ef234 100644
--- a/apps/users/models/user/_auth.py
+++ b/apps/users/models/user/_auth.py
@@ -228,6 +228,22 @@ class AuthMixin:
return True
return False
+ def check_need_update_password(self):
+ if self.is_local and self.need_update_password:
+ return True
+ return False
+
+ @staticmethod
+ def check_passwd_too_simple(password):
+ simple_passwords = ['admin', 'ChangeMe']
+ if password in simple_passwords:
+ return True
+ return False
+
+ def is_auth_backend_model(self):
+ backend = getattr(self, 'backend', None)
+ return backend == settings.AUTH_BACKEND_MODEL
+
@staticmethod
def get_public_key_md5(key):
try:
diff --git a/apps/users/models/user/_source.py b/apps/users/models/user/_source.py
index 2840caa8a..e7e0dd21f 100644
--- a/apps/users/models/user/_source.py
+++ b/apps/users/models/user/_source.py
@@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _
class Source(models.TextChoices):
local = "local", _("Local")
ldap = "ldap", "LDAP/AD"
+ ldap_ha = "ldap_ha", "LDAP/AD (HA)"
openid = "openid", "OpenID"
radius = "radius", "Radius"
cas = "cas", "CAS"
@@ -34,6 +35,7 @@ class SourceMixin:
settings.AUTH_BACKEND_PUBKEY,
],
Source.ldap: [settings.AUTH_BACKEND_LDAP],
+ Source.ldap_ha: [settings.AUTH_BACKEND_LDAP_HA],
Source.openid: [
settings.AUTH_BACKEND_OIDC_PASSWORD,
settings.AUTH_BACKEND_OIDC_CODE,
@@ -55,6 +57,7 @@ class SourceMixin:
mapper = {
cls.Source.local: True,
cls.Source.ldap: settings.AUTH_LDAP,
+ cls.Source.ldap_ha: settings.AUTH_LDAP_HA,
cls.Source.openid: settings.AUTH_OPENID,
cls.Source.radius: settings.AUTH_RADIUS,
cls.Source.cas: settings.AUTH_CAS,
diff --git a/apps/users/signal_handlers.py b/apps/users/signal_handlers.py
index 5bc116adf..b8fb37085 100644
--- a/apps/users/signal_handlers.py
+++ b/apps/users/signal_handlers.py
@@ -21,11 +21,13 @@ from common.signals import django_ready
from common.utils import get_logger
from jumpserver.utils import get_current_request
from ops.celery.decorator import register_as_period_task
+from orgs.models import Organization
+from orgs.utils import tmp_to_root_org
from rbac.builtin import BuiltinRole
from rbac.const import Scope
from rbac.models import RoleBinding
from settings.signals import setting_changed
-from .models import User, UserPasswordHistory
+from .models import User, UserPasswordHistory, UserGroup
from .signals import post_user_create
logger = get_logger(__file__)
@@ -50,7 +52,11 @@ def user_authenticated_handle(user, created, source, attrs=None, **kwargs):
if created:
user.source = source
user.save()
- bind_user_to_org_role(user)
+
+ if created and isinstance(attrs, dict):
+ org_ids = bind_user_to_org_role(user)
+ group_names = attrs.get('groups')
+ bind_user_to_group(org_ids, group_names, user)
if not attrs:
return
@@ -146,7 +152,7 @@ def radius_create_user(sender, user, **kwargs):
@receiver(openid_create_or_update_user)
-def on_openid_create_or_update_user(sender, request, user, created, name, username, email, **kwargs):
+def on_openid_create_or_update_user(sender, request, user, created, attrs, **kwargs):
if not check_only_allow_exist_user_auth(created):
return
@@ -157,7 +163,13 @@ def on_openid_create_or_update_user(sender, request, user, created, name, userna
)
user.source = User.Source.openid.value
user.save()
- bind_user_to_org_role(user)
+ org_ids = bind_user_to_org_role(user)
+ group_names = attrs.get('groups')
+ bind_user_to_group(org_ids, group_names, user)
+
+ name = attrs.get('name')
+ username = attrs.get('username')
+ email = attrs.get('email')
if not created and settings.AUTH_OPENID_ALWAYS_UPDATE_USER:
logger.debug(
@@ -180,7 +192,13 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
user.save()
-@shared_task(verbose_name=_('Clean up expired user sessions'))
+@shared_task(
+ verbose_name=_('Clean up expired user sessions'),
+ description=_(
+ """After logging in via the web, a user session record is created. At 2 a.m. every day,
+ the system cleans up inactive user devices"""
+ )
+)
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
def clean_expired_user_session_period():
UserSession.clear_expired_sessions()
@@ -225,3 +243,40 @@ def bind_user_to_org_role(user):
]
RoleBinding.objects.bulk_create(bindings, ignore_conflicts=True)
+ return org_ids
+
+
+def bind_user_to_group(org_ids, group_names, user):
+ if isinstance(group_names, str):
+ group_names = [group_names]
+
+ if not isinstance(group_names, list):
+ return
+
+ org_ids = org_ids or [Organization.DEFAULT_ID]
+
+ with tmp_to_root_org():
+ existing_groups = UserGroup.objects.filter(org_id__in=org_ids).values_list('org_id', 'name')
+
+ org_groups_map = {}
+ for org_id, group_name in existing_groups:
+ org_groups_map.setdefault(org_id, []).append(group_name)
+
+ groups_to_create = []
+ for org_id in org_ids:
+ existing_group_names = set(org_groups_map.get(org_id, []))
+ new_group_names = set(group_names) - existing_group_names
+ groups_to_create.extend(
+ UserGroup(org_id=org_id, name=name) for name in new_group_names
+ )
+
+ UserGroup.objects.bulk_create(groups_to_create)
+
+ user_groups = UserGroup.objects.filter(org_id__in=org_ids, name__in=group_names)
+
+ user_group_links = [
+ User.groups.through(user_id=user.id, usergroup_id=group.id)
+ for group in user_groups
+ ]
+ if user_group_links:
+ User.groups.through.objects.bulk_create(user_group_links)
diff --git a/apps/users/tasks.py b/apps/users/tasks.py
index 7fd707ac7..d84de6727 100644
--- a/apps/users/tasks.py
+++ b/apps/users/tasks.py
@@ -22,7 +22,13 @@ from .models import User
logger = get_logger(__file__)
-@shared_task(verbose_name=_('Check password expired'))
+@shared_task(
+ verbose_name=_('Check password expired'),
+ description=_(
+ """Check every day at 10 AM whether the passwords of users in the system are expired,
+ and send a notification 5 days in advance"""
+ )
+)
def check_password_expired():
users = User.get_nature_users().filter(source=User.Source.local)
for user in users:
@@ -36,7 +42,14 @@ def check_password_expired():
PasswordExpirationReminderMsg(user).publish_async()
-@shared_task(verbose_name=_('Periodic check password expired'))
+@shared_task(
+ verbose_name=_('Periodic check password expired'),
+ description=_(
+ """With version iterations, new tasks may be added, or task names and execution times may
+ be modified. Therefore, upon system startup, it is necessary to register or update the
+ parameters of the task that checks if passwords have expired"""
+ )
+)
@after_app_ready_start
def check_password_expired_periodic():
tasks = {
@@ -50,7 +63,13 @@ def check_password_expired_periodic():
create_or_update_celery_periodic_tasks(tasks)
-@shared_task(verbose_name=_('Check user expired'))
+@shared_task(
+ verbose_name=_('Check user expired'),
+ description=_(
+ """Check every day at 2 p.m whether the users in the system are expired, and send a
+ notification 5 days in advance"""
+ )
+)
def check_user_expired():
date_expired_lt = timezone.now() + timezone.timedelta(days=User.DATE_EXPIRED_WARNING_DAYS)
users = User.get_nature_users() \
@@ -67,7 +86,14 @@ def check_user_expired():
UserExpirationReminderMsg(user).publish_async()
-@shared_task(verbose_name=_('Periodic check user expired'))
+@shared_task(
+ verbose_name=_('Periodic check user expired'),
+ description=_(
+ """With version iterations, new tasks may be added, or task names and execution times may
+ be modified. Therefore, upon system startup, it is necessary to register or update the
+ parameters of the task that checks if users have expired"""
+ )
+)
@after_app_ready_start
def check_user_expired_periodic():
tasks = {
@@ -81,7 +107,14 @@ def check_user_expired_periodic():
create_or_update_celery_periodic_tasks(tasks)
-@shared_task(verbose_name=_('Check unused users'))
+@shared_task(
+ verbose_name=_('Check unused users'),
+ description=_(
+ """At 2 p.m. every day, according to the configuration in "System Settings - Security -
+ Auth security - Auto disable threshold" users who have not logged in or whose API keys
+ have not been used for a long time will be disabled"""
+ )
+)
@register_as_period_task(crontab=CRONTAB_AT_PM_TWO)
@tmp_to_root_org()
def check_unused_users():
@@ -96,7 +129,8 @@ def check_unused_users():
seconds_to_subtract = uncommon_users_ttl * 24 * 60 * 60
t = timezone.now() - timedelta(seconds=seconds_to_subtract)
last_login_q = Q(last_login__lte=t) | (Q(last_login__isnull=True) & Q(date_joined__lte=t))
- api_key_q = Q(date_api_key_last_used__lte=t) | (Q(date_api_key_last_used__isnull=True) & Q(date_joined__lte=t))
+ api_key_q = Q(date_api_key_last_used__lte=t) | (
+ Q(date_api_key_last_used__isnull=True) & Q(date_joined__lte=t))
users = User.objects \
.filter(date_joined__lt=t) \
diff --git a/poetry.lock b/poetry.lock
index 04bd3f7e7..051a97173 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -539,22 +539,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "appnope"
-version = "0.1.4"
-description = "Disable App Nap on macOS >= 10.9"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"},
- {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"},
-]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "asgiref"
version = "3.8.1"
@@ -590,29 +574,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "asttokens"
-version = "2.4.1"
-description = "Annotate AST trees with source code positions"
-optional = false
-python-versions = "*"
-files = [
- {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
- {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
-]
-
-[package.dependencies]
-six = ">=1.12.0"
-
-[package.extras]
-astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"]
-test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "async-timeout"
version = "4.0.3"
@@ -879,22 +840,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "backcall"
-version = "0.2.0"
-description = "Specifications for callback functions passed in to an API"
-optional = false
-python-versions = "*"
-files = [
- {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
- {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
-]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "bce-python-sdk"
version = "0.8.87"
@@ -2549,25 +2494,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "executing"
-version = "2.0.1"
-description = "Get the currently executing AST node of a frame, and other information"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
- {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
-]
-
-[package.extras]
-tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "fido2"
version = "1.1.3"
@@ -3349,49 +3275,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "ipython"
-version = "8.14.0"
-description = "IPython: Productive Interactive Computing"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"},
- {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"},
-]
-
-[package.dependencies]
-appnope = {version = "*", markers = "sys_platform == \"darwin\""}
-backcall = "*"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-decorator = "*"
-jedi = ">=0.16"
-matplotlib-inline = "*"
-pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
-pickleshare = "*"
-prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0"
-pygments = ">=2.4.0"
-stack-data = "*"
-traitlets = ">=5"
-
-[package.extras]
-all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"]
-black = ["black"]
-doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"]
-kernel = ["ipykernel"]
-nbconvert = ["nbconvert"]
-nbformat = ["nbformat"]
-notebook = ["ipywidgets", "notebook"]
-parallel = ["ipyparallel"]
-qtconsole = ["qtconsole"]
-test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
-test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "iso8601"
version = "2.1.0"
@@ -3459,30 +3342,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "jedi"
-version = "0.19.1"
-description = "An autocompletion tool for Python that can be used for text editors."
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
- {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
-]
-
-[package.dependencies]
-parso = ">=0.8.3,<0.9.0"
-
-[package.extras]
-docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
-qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
-testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "jinja2"
version = "3.1.2"
@@ -3641,37 +3500,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "kubernetes"
-version = "27.2.0"
-description = "Kubernetes python client"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "kubernetes-27.2.0-py2.py3-none-any.whl", hash = "sha256:0f9376329c85cf07615ed6886bf9bf21eb1cbfc05e14ec7b0f74ed8153cd2815"},
- {file = "kubernetes-27.2.0.tar.gz", hash = "sha256:d479931c6f37561dbfdf28fc5f46384b1cb8b28f9db344ed4a232ce91990825a"},
-]
-
-[package.dependencies]
-certifi = ">=14.05.14"
-google-auth = ">=1.0.1"
-oauthlib = ">=3.2.2"
-python-dateutil = ">=2.5.3"
-pyyaml = ">=5.4.1"
-requests = "*"
-requests-oauthlib = "*"
-six = ">=1.9.0"
-urllib3 = ">=1.24.2"
-websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0"
-
-[package.extras]
-adal = ["adal (>=1.0.2)"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "ldap3"
version = "2.9.1"
@@ -3972,25 +3800,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "matplotlib-inline"
-version = "0.1.7"
-description = "Inline Matplotlib backend for Jupyter"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
- {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
-]
-
-[package.dependencies]
-traitlets = "*"
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "maxminddb"
version = "2.6.2"
@@ -4768,26 +4577,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "parso"
-version = "0.8.4"
-description = "A Python Parser"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"},
- {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"},
-]
-
-[package.extras]
-qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
-testing = ["docopt", "pytest"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "passlib"
version = "1.7.4"
@@ -4861,22 +4650,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "pickleshare"
-version = "0.7.5"
-description = "Tiny 'shelve'-like database with concurrency support"
-optional = false
-python-versions = "*"
-files = [
- {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
- {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
-]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "pillow"
version = "10.0.1"
@@ -5219,25 +4992,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "pure-eval"
-version = "0.2.2"
-description = "Safely evaluate AST nodes without side effects"
-optional = false
-python-versions = "*"
-files = [
- {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
- {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
-]
-
-[package.extras]
-tests = ["pytest"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "pyasn1"
version = "0.5.0"
@@ -6429,27 +6183,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "receptorctl"
-version = "1.4.8"
-description = "\"Receptorctl is a front-end CLI and importable Python library that interacts with Receptor over its control socket interface.\""
-optional = false
-python-versions = "*"
-files = [
- {file = "receptorctl-1.4.8-py3-none-any.whl", hash = "sha256:468d949f85bb50de9fd77cf5af2c173450b346fada787e3907a16f161aff17d6"},
- {file = "receptorctl-1.4.8.tar.gz", hash = "sha256:5cf94bec214641506f8f956247c40cb8cc73e84b1a2490a125e49d35159dfa62"},
-]
-
-[package.dependencies]
-click = "*"
-python-dateutil = "*"
-pyyaml = "*"
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "redis"
version = "5.0.3"
@@ -6968,30 +6701,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "stack-data"
-version = "0.6.3"
-description = "Extract data from python stack frames and tracebacks for informative displays"
-optional = false
-python-versions = "*"
-files = [
- {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
- {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
-]
-
-[package.dependencies]
-asttokens = ">=2.1.0"
-executing = ">=1.2.0"
-pure-eval = "*"
-
-[package.extras]
-tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "stevedore"
version = "5.2.0"
@@ -7115,26 +6824,6 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
-[[package]]
-name = "traitlets"
-version = "5.14.3"
-description = "Traitlets Python configuration system"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
- {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
-]
-
-[package.extras]
-docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
-test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
-
-[package.source]
-type = "legacy"
-url = "https://mirrors.aliyun.com/pypi/simple"
-reference = "aliyun"
-
[[package]]
name = "treelib"
version = "1.6.4"
@@ -7981,4 +7670,4 @@ reference = "aliyun"
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "54e8ab1d59a70cc6857323145725f13f0ae48eb5f286683bb7d0c7bebb31fb45"
+content-hash = "9acfafd75bf7dbb7e0dffb54b7f11f6b09aa4ceff769d193a3906d03ae796ccc"
diff --git a/pyproject.toml b/pyproject.toml
index 518609273..9b2047f6c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -126,7 +126,6 @@ django-auth-ldap = "4.4.0"
boto3 = "1.28.9"
botocore = "1.31.9"
s3transfer = "0.6.1"
-kubernetes = "27.2.0"
mysqlclient = "2.2.4"
pymssql = "2.2.8"
django-redis = "5.3.0"
@@ -135,7 +134,6 @@ pyopenssl = "23.2.0"
redis = { url = "https://github.com/jumpserver-dev/redis-py/archive/refs/tags/v5.0.3.zip" }
pymongo = "4.4.1"
pyfreerdp = "0.0.2"
-ipython = "8.14.0"
forgerypy3 = "0.3.1"
django-debug-toolbar = "4.1.0"
pympler = "1.0.1"
@@ -156,7 +154,6 @@ xlsxwriter = "^3.1.9"
exchangelib = "^5.1.0"
xmlsec = "^1.3.13"
lxml = "5.2.1"
-receptorctl = "^1.4.5"
pydantic = "^2.7.4"
annotated-types = "^0.6.0"
httpx = "^0.27.0"
diff --git a/receptor b/receptor
deleted file mode 100755
index cd1e54d16..000000000
--- a/receptor
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/usr/bin/env python3
-# coding: utf-8
-
-import argparse
-import logging
-import os
-import shutil
-import signal
-import subprocess
-import tempfile
-
-from apps.libs.process.ssh import kill_ansible_ssh_process
-
-ANSIBLE_RUNNER_COMMAND = "ansible-runner"
-
-PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
-APPS_DIR = os.path.join(PROJECT_DIR, 'apps')
-TEMP_DIR = os.path.join(PROJECT_DIR, "tmp")
-
-DEFAULT_SHARE_DIR = os.path.join(PROJECT_DIR, "data", "share")
-DEFAULT_ANSIBLE_MODULES_DIR = os.path.join(APPS_DIR, "libs", "ansible", "modules")
-DEFAULT_CONTROL_SOCK_PATH = os.path.join(DEFAULT_SHARE_DIR, "control.sock")
-
-DEFAULT_TCP_LISTEN_ADDRESS = "0.0.0.0:7521"
-
-logger = logging.getLogger(__name__)
-
-os.chdir(APPS_DIR)
-
-
-class ReceptorService:
- def __init__(self):
- self.pid_file = os.path.join(TEMP_DIR, "receptor.pid")
- self.receptor_command = [
- 'receptor',
- '--local-only',
- '--node', 'id=primary',
- '--log-level', 'level=Error',
- '--control-service',
- 'service=control',
- 'tcplisten={}'.format(DEFAULT_TCP_LISTEN_ADDRESS),
- '--work-command',
- 'worktype={}'.format(ANSIBLE_RUNNER_COMMAND),
- 'command={}'.format(ANSIBLE_RUNNER_COMMAND),
- 'params=worker',
- 'allowruntimeparams=true',
- '--work-command',
- 'worktype={}'.format("kill"),
- 'command={}'.format("python"),
- "params={} kill".format(os.path.join(PROJECT_DIR, "receptor")),
- 'allowruntimeparams=true'
- ]
-
-
- @staticmethod
- def before_start():
- os.makedirs(os.path.join(DEFAULT_SHARE_DIR), exist_ok=True)
- status_dir = os.path.join(tempfile.gettempdir(), "receptor")
- if os.path.exists(status_dir):
- shutil.rmtree(status_dir)
-
- def start(self):
- self.before_start()
- if os.path.exists(self.pid_file):
- with open(self.pid_file, 'r') as f:
- pid_str = f.read()
- try:
- pid = int(pid_str)
- os.kill(pid, 0)
- print("\n- Receptor service is already running.")
- return
- except ProcessLookupError:
- print("\n- PID file exists but process does not, starting Receptor...")
- except ValueError:
- print("\n- PID file is corrupted, starting Receptor...")
- os.remove(self.pid_file)
-
- os.environ.setdefault('ANSIBLE_LIBRARY', DEFAULT_ANSIBLE_MODULES_DIR)
- os.environ.update({'PYTHONPATH': APPS_DIR})
- process = subprocess.Popen(self.receptor_command)
- with open(self.pid_file, 'w') as f:
- f.write(str(process.pid))
- print("\n- Receptor service started successfully.")
-
- def exit_handler(signum, frame):
- process.terminate()
- process.kill()
-
- signal.signal(signal.SIGINT, exit_handler)
- signal.signal(signal.SIGTERM, exit_handler)
- process.wait()
-
- def stop(self):
- if not os.path.exists(self.pid_file):
- print("\n- Receptor service is not running.")
- return
- with open(self.pid_file, 'r') as f:
- pid = int(f.read())
- try:
- os.kill(pid, signal.SIGTERM)
- os.remove(self.pid_file)
- print("\n- Receptor service stopped successfully.")
- except ProcessLookupError:
- print("\n- Failed to stop Receptor service: Process does not exist.")
- os.remove(self.pid_file)
-
- def restart(self):
- self.stop()
- self.start()
-
- def status(self):
- if os.path.exists(self.pid_file):
- with open(self.pid_file, 'r') as f:
- pid_str = f.read()
- try:
- pid = int(pid_str)
- os.kill(pid, 0)
- print("\n- Receptor service is running.")
- return
- except ProcessLookupError:
- print("\n- Receptor service is not running.")
- else:
- print("\n- Receptor service is not running.")
-
-
-def handle_receptor_action(args):
- action = args.action
- srv = ReceptorService()
- if action == "start":
- srv.start()
- elif action == 'stop':
- srv.stop()
- elif action == "restart":
- srv.restart()
- elif action == "status":
- srv.status()
- elif action == "kill":
- kill_progress_tree()
-
-
-def kill_progress_tree(pid=None):
- if not pid:
- try:
- pid_input = input()
- pid = int(pid_input)
- logger.info("progress {} will be kill".format(pid))
- kill_ansible_ssh_process(pid)
- except Exception as e:
- logger.error(e)
- return
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(
- description="""
- Jumpserver receptor service control tools;
- """
- )
- parser.add_argument(
- 'action', type=str,
- choices=("start", "stop", "restart", "status", "kill"),
- help="Action to run"
- )
-
- # parser.add_argument('--pid', type=int, default=42, help='what PID you want to kill')
-
- args = parser.parse_args()
- handle_receptor_action(args)
diff --git a/requirements/collections.yml b/requirements/collections.yml
new file mode 100644
index 000000000..c6ab2ad91
--- /dev/null
+++ b/requirements/collections.yml
@@ -0,0 +1,3 @@
+collections:
+ - name: community.postgresql
+ version: 2.4.0
\ No newline at end of file
diff --git a/utils/activate_user.py b/utils/activate_user.py
new file mode 100644
index 000000000..4f5170c45
--- /dev/null
+++ b/utils/activate_user.py
@@ -0,0 +1,42 @@
+import os
+import sys
+
+import django
+
+if os.path.exists('../apps'):
+ sys.path.insert(0, '../apps')
+elif os.path.exists('./apps'):
+ sys.path.insert(0, './apps')
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
+os.environ.setdefault("DJANGO_DEBUG_SHELL", "1")
+django.setup()
+
+from users.models import User
+from django.utils import timezone
+
+
+def activate_user(username):
+ user = User.objects.filter(username=username).first()
+ if not user:
+ print("Not found user: ", username)
+ return
+
+ print("Activate user: ", username)
+ user.is_active = True
+
+ if user.is_expired:
+ user.date_expired = timezone.now() + timezone.timedelta(days=365)
+
+ if user.password_has_expired:
+ user.date_password_last_updated = timezone.now()
+
+ user.save()
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("Usage: python activate_user.py ")
+ sys.exit(1)
+ username = sys.argv[1]
+ activate_user(username)
diff --git a/utils/clean_site_packages.sh b/utils/clean_site_packages.sh
new file mode 100644
index 000000000..c6e692f6b
--- /dev/null
+++ b/utils/clean_site_packages.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+lib_path="/opt/py3/lib/python3.11/site-packages"
+
+# 清理不需要的模块
+need_clean="jedi"
+for i in $need_clean; do
+ rm -rf "${lib_path}/${i}"
+done
+
+# 清理 ansible connection 中 不需要的模块
+ansible_connection="${lib_path}/ansible_collections"
+need_clean="fortinet dellemc f5networks netapp theforeman google azure cyberark ibm
+ netbox purestorage inspur netapp_eseries sensu check_point vyos arista"
+for i in $need_clean; do
+ echo "rm -rf ${ansible_connection:-tmp}/${i}"
+ rm -rf "${ansible_connection:-tmp}/${i}"
+done
+
+# 清理缓存文件
+cd lib_path
+find . -name "*.pyc" -exec rm -f {} \;
+
+# 清理不需要的国际化文件
+find . -name 'locale' -o -name 'locales' -type d | while read -r dir; do
+ find "$dir" -mindepth 1 -maxdepth 1 -type d \
+ ! -name 'zh_Hans' \
+ ! -name 'zh_Hant' \
+ ! -name 'zh_CN' \
+ ! -name 'en' \
+ ! -name 'en_US' \
+ ! -name 'ja' \
+ ! -name 'fr' \
+ -exec rm -rf {} \;
+done