mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-04-27 19:17:01 +00:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a9d455e867 | ||
|
d06d26ac54 | ||
|
e992c44e11 | ||
|
24fe058fd9 | ||
|
a3fef9cc54 | ||
|
471053e62a | ||
|
dc6308b030 | ||
|
f016ae6161 | ||
|
14a8d877e0 | ||
|
ddf20570a1 | ||
|
1ad9616b7f | ||
|
d7bc6bb201 | ||
|
f855043468 | ||
|
3159a4e794 | ||
|
57fcebfdd3 | ||
|
c500bb4e4c | ||
|
fd062b0da6 | ||
|
bcb112d5c6 | ||
|
533dbf316c | ||
|
9cce94b709 | ||
|
8b815d812b | ||
|
a168fc8a62 | ||
|
faae1a09d1 | ||
|
26e819e120 | ||
|
79579654a1 |
@ -8,4 +8,6 @@ celerybeat.pid
|
||||
.vagrant/
|
||||
apps/xpack/.git
|
||||
.history/
|
||||
.idea
|
||||
.idea
|
||||
.venv/
|
||||
.env
|
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -1,4 +0,0 @@
|
||||
*.mmdb filter=lfs diff=lfs merge=lfs -text
|
||||
*.mo filter=lfs diff=lfs merge=lfs -text
|
||||
*.ipdb filter=lfs diff=lfs merge=lfs -text
|
||||
leak_passwords.db filter=lfs diff=lfs merge=lfs -text
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@ -1,10 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
- package-ecosystem: "uv"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "09:30"
|
||||
timezone: "Asia/Shanghai"
|
||||
target-branch: dev
|
||||
target-branch: dev
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -46,3 +46,6 @@ test.py
|
||||
.test/
|
||||
*.mo
|
||||
apps.iml
|
||||
*.db
|
||||
*.mmdb
|
||||
*.ipdb
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM jumpserver/core-base:20250415_032719 AS stage-build
|
||||
FROM jumpserver/core-base:20250427_062456 AS stage-build
|
||||
|
||||
ARG VERSION
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
FROM python:3.11-slim-bullseye
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.6.14 /uv /uvx /usr/local/bin/
|
||||
# Install APT dependencies
|
||||
ARG DEPENDENCIES=" \
|
||||
ca-certificates \
|
||||
@ -43,18 +43,19 @@ WORKDIR /opt/jumpserver
|
||||
ARG PIP_MIRROR=https://pypi.org/simple
|
||||
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
|
||||
ENV ANSIBLE_COLLECTIONS_PATHS=/opt/py3/lib/python3.11/site-packages/ansible_collections
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
PATH=/opt/py3/bin:$PATH
|
||||
|
||||
ENV UV_LINK_MODE=copy
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache \
|
||||
--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/clean_site_packages.sh,target=clean_site_packages.sh \
|
||||
--mount=type=bind,source=requirements/collections.yml,target=collections.yml \
|
||||
--mount=type=bind,source=requirements/static_files.sh,target=utils/static_files.sh \
|
||||
set -ex \
|
||||
&& python3 -m venv /opt/py3 \
|
||||
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
|
||||
&& . /opt/py3/bin/activate \
|
||||
&& poetry config virtualenvs.create false \
|
||||
&& poetry install --no-cache --only main \
|
||||
&& ansible-galaxy collection install -r collections.yml --force --ignore-certs \
|
||||
&& bash clean_site_packages.sh \
|
||||
&& poetry cache clear pypi --all
|
||||
&& uv venv \
|
||||
&& uv pip install -i${PIP_MIRROR} -r pyproject.toml \
|
||||
&& ln -sf $(pwd)/.venv /opt/py3 \
|
||||
&& bash utils/static_files.sh \
|
||||
&& bash clean_site_packages.sh
|
||||
|
@ -24,11 +24,7 @@ RUN set -ex \
|
||||
WORKDIR /opt/jumpserver
|
||||
|
||||
ARG PIP_MIRROR=https://pypi.org/simple
|
||||
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
RUN set -ex \
|
||||
&& . /opt/py3/bin/activate \
|
||||
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
|
||||
&& poetry install --only xpack \
|
||||
&& poetry cache clear pypi --all
|
||||
|
||||
RUN set -ex \
|
||||
&& uv pip install -i${PIP_MIRROR} --group xpack
|
||||
|
||||
|
@ -62,8 +62,7 @@ class IntegrationApplicationViewSet(OrgBulkModelViewSet):
|
||||
)
|
||||
def get_once_secret(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
secret = instance.get_secret()
|
||||
return Response(data={'id': instance.id, 'secret': secret})
|
||||
return Response(data={'id': instance.id, 'secret': instance.secret})
|
||||
|
||||
@action(['GET'], detail=False, url_path='account-secret',
|
||||
permission_classes=[RBACPermission])
|
||||
|
@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a2805a0264fc07ae597704841ab060edef8bf74654f525bc778cb9195d8cad0e
|
||||
size 2547712
|
@ -85,6 +85,7 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
||||
def on_host_error(self, host, error, result):
|
||||
account = self.host_account_mapper.get(host)
|
||||
try:
|
||||
account.set_connectivity(Connectivity.ERR)
|
||||
error_tp = account.get_err_connectivity(error)
|
||||
account.set_connectivity(error_tp)
|
||||
except Exception as e:
|
||||
print(f'\033[31m Update account {account.name} connectivity failed: {e} \033[0m\n')
|
||||
|
@ -5,6 +5,7 @@ from rest_framework import serializers
|
||||
from accounts.models import IntegrationApplication
|
||||
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
|
||||
from common.serializers.fields import JSONManyToManyField
|
||||
from common.utils import random_string
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
|
||||
@ -37,6 +38,10 @@ class IntegrationApplicationSerializer(BulkOrgResourceModelSerializer):
|
||||
data['logo'] = static('img/logo.png')
|
||||
return data
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs['secret'] = random_string(36)
|
||||
return attrs
|
||||
|
||||
|
||||
class IntegrationAccountSecretSerializer(serializers.Serializer):
|
||||
asset = serializers.CharField(required=False, allow_blank=True)
|
||||
|
@ -1,10 +1,10 @@
|
||||
from .asset import *
|
||||
from .category import *
|
||||
from .domain import *
|
||||
from .favorite_asset import *
|
||||
from .mixin import *
|
||||
from .my_asset import *
|
||||
from .node import *
|
||||
from .platform import *
|
||||
from .protocol import *
|
||||
from .tree import *
|
||||
from .my_asset import *
|
||||
from .zone import *
|
||||
|
@ -37,12 +37,12 @@ class AssetFilterSet(BaseFilterSet):
|
||||
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')
|
||||
zone = drf_filters.CharFilter(method='filter_zone')
|
||||
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"
|
||||
gateway_enabled = drf_filters.BooleanFilter(
|
||||
field_name="platform__gateway_enabled", lookup_expr="exact"
|
||||
)
|
||||
ping_enabled = drf_filters.BooleanFilter(
|
||||
field_name="platform__automation__ping_enabled", lookup_expr="exact"
|
||||
@ -85,11 +85,11 @@ class AssetFilterSet(BaseFilterSet):
|
||||
return queryset
|
||||
|
||||
@staticmethod
|
||||
def filter_domain(queryset, name, value):
|
||||
def filter_zone(queryset, name, value):
|
||||
if is_uuid(value):
|
||||
return queryset.filter(domain_id=value)
|
||||
return queryset.filter(zone_id=value)
|
||||
else:
|
||||
return queryset.filter(domain__name__contains=value)
|
||||
return queryset.filter(zone__name__contains=value)
|
||||
|
||||
@staticmethod
|
||||
def filter_protocols(queryset, name, value):
|
||||
@ -171,10 +171,10 @@ class AssetViewSet(SuggestionMixin, BaseAssetViewSet):
|
||||
@action(methods=["GET"], detail=True, url_path="gateways")
|
||||
def gateways(self, *args, **kwargs):
|
||||
asset = self.get_object()
|
||||
if not asset.domain:
|
||||
if not asset.zone:
|
||||
gateways = Gateway.objects.none()
|
||||
else:
|
||||
gateways = asset.domain.gateways
|
||||
gateways = asset.zone.gateways
|
||||
return self.get_paginated_response_from_queryset(gateways)
|
||||
|
||||
@action(methods=['post'], detail=False, url_path='sync-platform-protocols')
|
||||
|
@ -9,24 +9,24 @@ from common.utils import get_logger
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from .asset import HostViewSet
|
||||
from .. import serializers
|
||||
from ..models import Domain, Gateway
|
||||
from ..models import Zone, Gateway
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||
__all__ = ['ZoneViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||
|
||||
|
||||
class DomainViewSet(OrgBulkModelViewSet):
|
||||
model = Domain
|
||||
class ZoneViewSet(OrgBulkModelViewSet):
|
||||
model = Zone
|
||||
filterset_fields = ("name",)
|
||||
search_fields = filterset_fields
|
||||
serializer_classes = {
|
||||
'default': serializers.DomainSerializer,
|
||||
'list': serializers.DomainListSerializer,
|
||||
'default': serializers.ZoneSerializer,
|
||||
'list': serializers.ZoneListSerializer,
|
||||
}
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
return serializers.DomainWithGatewaySerializer
|
||||
return serializers.ZoneWithGatewaySerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
@ -36,8 +36,8 @@ class DomainViewSet(OrgBulkModelViewSet):
|
||||
|
||||
class GatewayViewSet(HostViewSet):
|
||||
perm_model = Gateway
|
||||
filterset_fields = ("domain__name", "name", "domain")
|
||||
search_fields = ("domain__name",)
|
||||
filterset_fields = ("zone__name", "name", "zone")
|
||||
search_fields = ("zone__name",)
|
||||
|
||||
def get_serializer_classes(self):
|
||||
serializer_classes = super().get_serializer_classes()
|
||||
@ -45,7 +45,7 @@ class GatewayViewSet(HostViewSet):
|
||||
return serializer_classes
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Domain.get_gateway_queryset()
|
||||
queryset = Zone.get_gateway_queryset()
|
||||
return queryset
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Domain.get_gateway_queryset()
|
||||
queryset = Zone.get_gateway_queryset()
|
||||
return queryset
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
@ -1,3 +1,5 @@
|
||||
from collections import Counter
|
||||
|
||||
__all__ = ['FormatAssetInfo']
|
||||
|
||||
|
||||
@ -7,13 +9,37 @@ class FormatAssetInfo:
|
||||
self.tp = tp
|
||||
|
||||
@staticmethod
|
||||
def posix_format(info):
|
||||
for cpu_model in info.get('cpu_model', []):
|
||||
if cpu_model.endswith('GHz') or cpu_model.startswith("Intel"):
|
||||
break
|
||||
else:
|
||||
cpu_model = ''
|
||||
info['cpu_model'] = cpu_model[:48]
|
||||
def get_cpu_model_count(cpus):
|
||||
try:
|
||||
models = [cpus[i + 1] + " " + cpus[i + 2] for i in range(0, len(cpus), 3)]
|
||||
|
||||
model_counts = Counter(models)
|
||||
|
||||
result = ', '.join([f"{model} x{count}" for model, count in model_counts.items()])
|
||||
except Exception as e:
|
||||
print(f"Error processing CPU model list: {e}")
|
||||
result = ''
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_gpu_model_count(gpus):
|
||||
try:
|
||||
model_counts = Counter(gpus)
|
||||
|
||||
result = ', '.join([f"{model} x{count}" for model, count in model_counts.items()])
|
||||
except Exception as e:
|
||||
print(f"Error processing GPU model list: {e}")
|
||||
result = ''
|
||||
|
||||
return result
|
||||
|
||||
def posix_format(self, info):
|
||||
cpus = self.get_cpu_model_count(info.get('cpu_model', []))
|
||||
gpus = self.get_gpu_model_count(info.get('gpu_model', []))
|
||||
|
||||
info['gpu_model'] = gpus
|
||||
info['cpu_model'] = cpus
|
||||
info['cpu_count'] = info.get('cpu_count', 0)
|
||||
return info
|
||||
|
||||
|
@ -23,5 +23,16 @@
|
||||
arch: "{{ ansible_architecture }}"
|
||||
kernel: "{{ ansible_kernel }}"
|
||||
|
||||
|
||||
- name: Get GPU info with nvidia-smi
|
||||
shell: |
|
||||
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader,nounits
|
||||
register: gpu_info
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Merge GPU info into final info
|
||||
set_fact:
|
||||
info: "{{ info | combine({'gpu_model': gpu_info.stdout_lines | default([])}) }}"
|
||||
|
||||
- debug:
|
||||
var: info
|
||||
|
@ -37,10 +37,11 @@ class PingManager(BasePlaybookManager):
|
||||
def on_host_error(self, host, error, result):
|
||||
asset, account = self.host_asset_and_account_mapper.get(host)
|
||||
try:
|
||||
asset.set_connectivity(Connectivity.ERR)
|
||||
error_tp = asset.get_err_connectivity(error)
|
||||
asset.set_connectivity(error_tp)
|
||||
if not account:
|
||||
return
|
||||
account.set_connectivity(Connectivity.ERR)
|
||||
account.set_connectivity(error_tp)
|
||||
except Exception as e:
|
||||
print(f'\033[31m Update account {account.name} or '
|
||||
f'update asset {asset.name} connectivity failed: {e} \033[0m\n')
|
||||
|
@ -7,6 +7,12 @@ class Connectivity(TextChoices):
|
||||
NA = 'na', _('N/A')
|
||||
OK = 'ok', _('OK')
|
||||
ERR = 'err', _('Error')
|
||||
AUTH_ERR = 'auth_err', _('Authentication error')
|
||||
SUDO_ERR = 'sudo_err', _('Sudo permission error')
|
||||
PASSWORD_ERR = 'password_err', _('Invalid password error')
|
||||
OPENSSH_KEY_ERR = 'openssh_key_err', _('OpenSSH key error')
|
||||
NTLM_ERR = 'ntlm_err', _('NTLM credentials rejected error')
|
||||
CREATE_DIR_ERR = 'create_dir_err', _('Create directory error')
|
||||
|
||||
|
||||
class AutomationTypes(TextChoices):
|
||||
|
@ -37,7 +37,7 @@ class FillType(models.TextChoices):
|
||||
class BaseType(TextChoices):
|
||||
"""
|
||||
约束应该考虑代是对平台对限制,避免多余对选项,如: mysql 开启 ssh,
|
||||
或者开启了也没有作用, 比如 k8s 开启了 domain,目前还不支持
|
||||
或者开启了也没有作用, 比如 k8s 开启了 gateway 目前还不支持
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
|
@ -13,11 +13,11 @@ class CloudTypes(BaseType):
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': False,
|
||||
'gateway_enabled': False,
|
||||
'su_enabled': False,
|
||||
},
|
||||
cls.K8S: {
|
||||
'domain_enabled': True,
|
||||
'gateway_enabled': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class CustomTypes(BaseType):
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': False,
|
||||
'gateway_enabled': False,
|
||||
'su_enabled': False,
|
||||
},
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class DatabaseTypes(BaseType):
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': True,
|
||||
'gateway_enabled': True,
|
||||
'su_enabled': False,
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ class DeviceTypes(BaseType):
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': True,
|
||||
'ds_enabled': False,
|
||||
'gateway_enabled': True,
|
||||
'ds_enabled': True,
|
||||
'su_enabled': True,
|
||||
'su_methods': ['enable', 'super', 'super_level']
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class DirectoryTypes(BaseType):
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': True,
|
||||
'domain_enabled': True,
|
||||
'gateway_enabled': True,
|
||||
'ds_enabled': False,
|
||||
'su_enabled': True,
|
||||
},
|
||||
|
@ -11,7 +11,7 @@ class GPTTypes(BaseType):
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': False,
|
||||
'gateway_enabled': False,
|
||||
'su_enabled': False,
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ class HostTypes(BaseType):
|
||||
'*': {
|
||||
'charset_enabled': True,
|
||||
'charset': 'utf-8', # default
|
||||
'domain_enabled': True,
|
||||
'gateway_enabled': True,
|
||||
'su_enabled': True,
|
||||
'ds_enabled': True,
|
||||
'su_methods': ['sudo', 'su', 'only_sudo', 'only_su'],
|
||||
@ -81,7 +81,7 @@ class HostTypes(BaseType):
|
||||
{'name': 'Linux'},
|
||||
{
|
||||
'name': GATEWAY_NAME,
|
||||
'domain_enabled': True,
|
||||
'gateway_enabled': True,
|
||||
}
|
||||
],
|
||||
cls.UNIX: [
|
||||
|
@ -312,7 +312,7 @@ class AllTypes(ChoicesMixin):
|
||||
'category': category,
|
||||
'type': tp, 'internal': True,
|
||||
'charset': constraints.get('charset', 'utf-8'),
|
||||
'domain_enabled': constraints.get('domain_enabled', False),
|
||||
'gateway_enabled': constraints.get('gateway_enabled', False),
|
||||
'su_enabled': constraints.get('su_enabled', False),
|
||||
}
|
||||
if data['su_enabled'] and data.get('su_methods'):
|
||||
|
@ -11,7 +11,7 @@ class WebTypes(BaseType):
|
||||
return {
|
||||
'*': {
|
||||
'charset_enabled': False,
|
||||
'domain_enabled': False,
|
||||
'gateway_enabled': False,
|
||||
'su_enabled': False,
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
# Generated by Django 4.1.13 on 2024-05-09 03:16
|
||||
|
||||
import json
|
||||
import assets.models.asset.common
|
||||
from django.db.models import F, Q
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db.models import F
|
||||
|
||||
import assets.models.asset.common
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@ -39,22 +39,26 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='automationexecution',
|
||||
name='automation',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation task'),
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions',
|
||||
to='assets.baseautomation', verbose_name='Automation task'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.domain', verbose_name='Zone'),
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='assets', to='assets.domain', verbose_name='Zone'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(default=assets.models.asset.common.default_node, related_name='assets', to='assets.node', verbose_name='Nodes'),
|
||||
field=models.ManyToManyField(default=assets.models.asset.common.default_node, related_name='assets',
|
||||
to='assets.node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.platform', verbose_name='Platform'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='assets',
|
||||
to='assets.platform', verbose_name='Platform'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AssetBaseAutomation',
|
||||
@ -71,7 +75,9 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='GatherFactsAutomation',
|
||||
fields=[
|
||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
('baseautomation_ptr',
|
||||
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
|
||||
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Gather asset facts',
|
||||
@ -81,7 +87,9 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='PingAutomation',
|
||||
fields=[
|
||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
('baseautomation_ptr',
|
||||
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
|
||||
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Ping asset',
|
||||
|
@ -18,7 +18,7 @@ platforms_data_json = '''[
|
||||
"type": "linux",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -119,7 +119,7 @@ platforms_data_json = '''[
|
||||
"type": "unix",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -209,7 +209,7 @@ platforms_data_json = '''[
|
||||
"type": "unix",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -299,7 +299,7 @@ platforms_data_json = '''[
|
||||
"type": "unix",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -389,7 +389,7 @@ platforms_data_json = '''[
|
||||
"type": "windows",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -481,7 +481,7 @@ platforms_data_json = '''[
|
||||
"security": "any"
|
||||
},
|
||||
"internal": false,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -582,7 +582,7 @@ platforms_data_json = '''[
|
||||
"type": "other",
|
||||
"meta": {},
|
||||
"internal": false,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -684,7 +684,7 @@ platforms_data_json = '''[
|
||||
"security": "rdp"
|
||||
},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -776,7 +776,7 @@ platforms_data_json = '''[
|
||||
"security": "tls"
|
||||
},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -866,7 +866,7 @@ platforms_data_json = '''[
|
||||
"type": "unix",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -956,7 +956,7 @@ platforms_data_json = '''[
|
||||
"type": "linux",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1057,7 +1057,7 @@ platforms_data_json = '''[
|
||||
"type": "windows",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1136,7 +1136,7 @@ platforms_data_json = '''[
|
||||
"type": "general",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1201,7 +1201,7 @@ platforms_data_json = '''[
|
||||
"type": "general",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": "enable",
|
||||
"custom_fields": [],
|
||||
@ -1266,7 +1266,7 @@ platforms_data_json = '''[
|
||||
"type": "general",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": "super",
|
||||
"custom_fields": [],
|
||||
@ -1332,7 +1332,7 @@ platforms_data_json = '''[
|
||||
"type": "general",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": true,
|
||||
"su_method": "super_level",
|
||||
"custom_fields": [],
|
||||
@ -1397,7 +1397,7 @@ platforms_data_json = '''[
|
||||
"type": "mysql",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1449,7 +1449,7 @@ platforms_data_json = '''[
|
||||
"type": "mariadb",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1501,7 +1501,7 @@ platforms_data_json = '''[
|
||||
"type": "postgresql",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1553,7 +1553,7 @@ platforms_data_json = '''[
|
||||
"type": "oracle",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1605,7 +1605,7 @@ platforms_data_json = '''[
|
||||
"type": "sqlserver",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1657,7 +1657,7 @@ platforms_data_json = '''[
|
||||
"type": "clickhouse",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1709,7 +1709,7 @@ platforms_data_json = '''[
|
||||
"type": "mongodb",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1761,7 +1761,7 @@ platforms_data_json = '''[
|
||||
"type": "redis",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1815,7 +1815,7 @@ platforms_data_json = '''[
|
||||
"type": "redis",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1869,7 +1869,7 @@ platforms_data_json = '''[
|
||||
"type": "website",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": false,
|
||||
"gateway_enabled": false,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1924,7 +1924,7 @@ platforms_data_json = '''[
|
||||
"type": "private",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": false,
|
||||
"gateway_enabled": false,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -1979,7 +1979,7 @@ platforms_data_json = '''[
|
||||
"type": "k8s",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": false,
|
||||
"gateway_enabled": false,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -2029,7 +2029,7 @@ platforms_data_json = '''[
|
||||
"type": "chatgpt",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": false,
|
||||
"gateway_enabled": false,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -2081,7 +2081,7 @@ platforms_data_json = '''[
|
||||
"type": "db2",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -2131,7 +2131,7 @@ platforms_data_json = '''[
|
||||
"type": "dameng",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
|
@ -52,6 +52,6 @@ class Migration(migrations.Migration):
|
||||
related_name="assets",
|
||||
to="assets.directoryservice",
|
||||
verbose_name="Directory services",
|
||||
),
|
||||
)
|
||||
),
|
||||
]
|
||||
|
@ -19,7 +19,7 @@ def add_ds_platforms(apps, schema_editor):
|
||||
"type": "windows_ad",
|
||||
"meta": {},
|
||||
"internal": true,
|
||||
"domain_enabled": true,
|
||||
"gateway_enabled": true,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [],
|
||||
@ -117,7 +117,7 @@ def add_ds_platforms(apps, schema_editor):
|
||||
"meta": {
|
||||
},
|
||||
"internal": true,
|
||||
"domain_enabled": false,
|
||||
"gateway_enabled": false,
|
||||
"su_enabled": false,
|
||||
"su_method": null,
|
||||
"custom_fields": [
|
||||
|
26
apps/assets/migrations/0018_rename_domain_zone.py
Normal file
26
apps/assets/migrations/0018_rename_domain_zone.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Generated by Django 4.1.13 on 2025-04-18 08:05
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("assets", "0017_auto_20250407_1124"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="platform",
|
||||
old_name="domain_enabled",
|
||||
new_name="gateway_enabled",
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name="Domain",
|
||||
new_name="Zone",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="asset",
|
||||
old_name="domain",
|
||||
new_name="zone",
|
||||
),
|
||||
]
|
@ -1,9 +1,10 @@
|
||||
# noqa
|
||||
from .base import *
|
||||
from .platform import *
|
||||
from .asset import *
|
||||
from .label import Label
|
||||
from .gateway import *
|
||||
from .domain import *
|
||||
from .zone import * # noqa
|
||||
from .node import *
|
||||
from .favorite_asset import *
|
||||
from .automations import *
|
||||
|
@ -168,8 +168,8 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
|
||||
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',
|
||||
zone = models.ForeignKey(
|
||||
"assets.Zone", null=True, blank=True, related_name='assets',
|
||||
verbose_name=_("Zone"), on_delete=models.SET_NULL
|
||||
)
|
||||
nodes = models.ManyToManyField(
|
||||
@ -177,7 +177,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
|
||||
)
|
||||
directory_services = models.ManyToManyField(
|
||||
'assets.DirectoryService', related_name='assets',
|
||||
verbose_name=_("Directory service")
|
||||
verbose_name=_("Directory services")
|
||||
)
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
||||
gathered_info = models.JSONField(verbose_name=_('Gathered info'), default=dict, blank=True) # 资产的一些信息,如 硬件信息
|
||||
@ -244,7 +244,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
|
||||
platform = self.platform
|
||||
auto_config = {
|
||||
'su_enabled': platform.su_enabled,
|
||||
'domain_enabled': platform.domain_enabled,
|
||||
'gateway_enabled': platform.gateway_enabled,
|
||||
'ansible_enabled': False
|
||||
}
|
||||
automation = getattr(self.platform, 'automation', None)
|
||||
@ -362,11 +362,11 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
|
||||
|
||||
@lazyproperty
|
||||
def gateway(self):
|
||||
if not self.domain_id:
|
||||
if not self.zone_id:
|
||||
return
|
||||
if not self.platform.domain_enabled:
|
||||
if not self.platform.gateway_enabled:
|
||||
return
|
||||
return self.domain.select_gateway()
|
||||
return self.zone.select_gateway()
|
||||
|
||||
def as_node(self):
|
||||
from assets.models import Node
|
||||
|
@ -23,6 +23,28 @@ class AbsConnectivity(models.Model):
|
||||
self.date_verified = timezone.now()
|
||||
self.save(update_fields=['connectivity', 'date_verified'])
|
||||
|
||||
@staticmethod
|
||||
def get_err_connectivity(msg=None):
|
||||
msg = (msg or '').strip().lower()
|
||||
|
||||
error_map = {
|
||||
'permission denied': Connectivity.AUTH_ERR,
|
||||
'authentication failed': Connectivity.AUTH_ERR,
|
||||
'authentication failure': Connectivity.AUTH_ERR,
|
||||
'is not in the sudoers file': Connectivity.SUDO_ERR,
|
||||
'expected openssh key': Connectivity.OPENSSH_KEY_ERR,
|
||||
'invalid/incorrect password': Connectivity.PASSWORD_ERR,
|
||||
'failed to create directory': Connectivity.CREATE_DIR_ERR,
|
||||
'ntlm: the specified credentials were rejected by the server': Connectivity.NTLM_ERR,
|
||||
|
||||
}
|
||||
|
||||
for key, value in error_map.items():
|
||||
if key in msg:
|
||||
return value
|
||||
|
||||
return Connectivity.ERR
|
||||
|
||||
@property
|
||||
def is_connective(self):
|
||||
if self.connectivity == Connectivity.OK:
|
||||
|
@ -101,7 +101,7 @@ class Platform(LabeledMixin, JMSBaseModel):
|
||||
default=CharsetChoices.utf8, choices=CharsetChoices.choices,
|
||||
max_length=8, verbose_name=_("Charset")
|
||||
)
|
||||
domain_enabled = models.BooleanField(default=True, verbose_name=_("Gateway enabled"))
|
||||
gateway_enabled = models.BooleanField(default=True, verbose_name=_("Gateway enabled"))
|
||||
ds_enabled = models.BooleanField(default=False, verbose_name=_("DS enabled"))
|
||||
# 账号有关的
|
||||
su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled"))
|
||||
|
@ -12,10 +12,10 @@ from .gateway import Gateway
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
__all__ = ['Domain']
|
||||
__all__ = ['Zone']
|
||||
|
||||
|
||||
class Domain(LabeledMixin, JMSOrgBaseModel):
|
||||
class Zone(LabeledMixin, JMSOrgBaseModel):
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
|
||||
class Meta:
|
||||
@ -49,7 +49,7 @@ class Domain(LabeledMixin, JMSOrgBaseModel):
|
||||
|
||||
@property
|
||||
def gateways(self):
|
||||
queryset = self.get_gateway_queryset().filter(domain=self)
|
||||
queryset = self.get_gateway_queryset().filter(zone=self)
|
||||
return queryset
|
||||
|
||||
@classmethod
|
@ -154,7 +154,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields_fk = ['domain', 'platform']
|
||||
fields_fk = ['zone', 'platform']
|
||||
fields_mini = ['id', 'name', 'address'] + fields_fk
|
||||
fields_small = fields_mini + ['is_active', 'comment']
|
||||
fields_m2m = [
|
||||
@ -233,7 +233,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related('domain', 'nodes', 'protocols', 'directory_services') \
|
||||
queryset = queryset.prefetch_related('zone', 'nodes', 'protocols', 'directory_services') \
|
||||
.prefetch_related('platform', 'platform__automation') \
|
||||
.annotate(category=F("platform__category")) \
|
||||
.annotate(type=F("platform__type")) \
|
||||
@ -271,9 +271,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
|
||||
raise serializers.ValidationError({'platform': _("Platform not exist")})
|
||||
return platform
|
||||
|
||||
def validate_domain(self, value):
|
||||
def validate_zone(self, value):
|
||||
platform = self._asset_platform
|
||||
if platform.domain_enabled:
|
||||
if platform.gateway_enabled:
|
||||
return value
|
||||
else:
|
||||
return None
|
||||
|
@ -6,7 +6,7 @@ class HostGatheredInfoSerializer(serializers.Serializer):
|
||||
vendor = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Vendor'))
|
||||
model = serializers.CharField(max_length=54, required=False, allow_blank=True, label=_('Model'))
|
||||
sn = serializers.CharField(max_length=128, required=False, allow_blank=True, label=_('Serial number'))
|
||||
cpu_model = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('CPU model'))
|
||||
cpu_model = serializers.CharField(allow_blank=True, required=False, label=_('CPU model'))
|
||||
cpu_count = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU count'))
|
||||
cpu_cores = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU cores'))
|
||||
cpu_vcpus = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU vcpus'))
|
||||
@ -17,6 +17,8 @@ class HostGatheredInfoSerializer(serializers.Serializer):
|
||||
distribution_version = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS version'))
|
||||
arch = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS arch'))
|
||||
|
||||
gpu_model = serializers.CharField(allow_blank=True, required=False, label=_('GPU model'))
|
||||
|
||||
|
||||
category_gathered_serializer_map = {
|
||||
'host': HostGatheredInfoSerializer,
|
||||
|
@ -8,12 +8,12 @@ from common.serializers import ResourceLabelsMixin
|
||||
from common.serializers.fields import ObjectRelatedField
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .gateway import GatewayWithAccountSecretSerializer
|
||||
from ..models import Domain, Gateway
|
||||
from ..models import Zone, Gateway
|
||||
|
||||
__all__ = ['DomainSerializer', 'DomainWithGatewaySerializer', 'DomainListSerializer']
|
||||
__all__ = ['ZoneSerializer', 'ZoneWithGatewaySerializer', 'ZoneListSerializer']
|
||||
|
||||
|
||||
class DomainSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
|
||||
class ZoneSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
|
||||
gateways = ObjectRelatedField(
|
||||
many=True, required=False, label=_('Gateway'), queryset=Gateway.objects,
|
||||
help_text=_(
|
||||
@ -23,7 +23,7 @@ class DomainSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
|
||||
assets_amount = serializers.IntegerField(label=_('Assets amount'), read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
model = Zone
|
||||
fields_mini = ['id', 'name']
|
||||
fields_small = fields_mini + ['comment']
|
||||
fields_m2m = ['assets', 'gateways', 'labels', 'assets_amount']
|
||||
@ -55,9 +55,9 @@ class DomainSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class DomainListSerializer(DomainSerializer):
|
||||
class Meta(DomainSerializer.Meta):
|
||||
fields = list(set(DomainSerializer.Meta.fields + ['assets_amount']) - {'assets'})
|
||||
class ZoneListSerializer(ZoneSerializer):
|
||||
class Meta(ZoneSerializer.Meta):
|
||||
fields = list(set(ZoneSerializer.Meta.fields + ['assets_amount']) - {'assets'})
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
@ -67,9 +67,9 @@ class DomainListSerializer(DomainSerializer):
|
||||
return queryset
|
||||
|
||||
|
||||
class DomainWithGatewaySerializer(serializers.ModelSerializer):
|
||||
class ZoneWithGatewaySerializer(serializers.ModelSerializer):
|
||||
gateways = GatewayWithAccountSecretSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
model = Zone
|
||||
fields = '__all__'
|
||||
|
@ -194,7 +194,7 @@ class PlatformSerializer(ResourceLabelsMixin, CommonSerializerMixin, WritableNes
|
||||
]
|
||||
fields_m2m = ['assets', 'assets_amount']
|
||||
fields = fields_small + fields_m2m + [
|
||||
"protocols", "domain_enabled", "su_enabled", "su_method",
|
||||
"protocols", "gateway_enabled", "su_enabled", "su_method",
|
||||
"ds_enabled", "automation", "comment", "custom_fields", "labels"
|
||||
] + read_only_fields
|
||||
extra_kwargs = {
|
||||
@ -205,11 +205,11 @@ class PlatformSerializer(ResourceLabelsMixin, CommonSerializerMixin, WritableNes
|
||||
"similar to logging in with a regular account and then switching to root"
|
||||
)
|
||||
},
|
||||
"domain_enabled": {
|
||||
"gateway_enabled": {
|
||||
"label": _('Gateway enabled'),
|
||||
"help_text": _("Assets can be connected using a zone gateway")
|
||||
},
|
||||
"domain_default": {"label": _('Default Domain')},
|
||||
"zone_default": {"label": _('Default zone')},
|
||||
'assets': {'required': False, 'label': _('Assets')},
|
||||
}
|
||||
|
||||
@ -262,8 +262,8 @@ class PlatformSerializer(ResourceLabelsMixin, CommonSerializerMixin, WritableNes
|
||||
def validate_su_enabled(self, su_enabled):
|
||||
return su_enabled and self.constraints.get('su_enabled', False)
|
||||
|
||||
def validate_domain_enabled(self, domain_enabled):
|
||||
return domain_enabled and self.constraints.get('domain_enabled', False)
|
||||
def validate_gateway_enabled(self, gateway_enabled):
|
||||
return gateway_enabled and self.constraints.get('gateway_enabled', False)
|
||||
|
||||
def validate_automation(self, automation):
|
||||
automation = automation or {}
|
||||
|
@ -20,7 +20,7 @@ router.register(r'directories', api.DSViewSet, 'ds')
|
||||
router.register(r'customs', api.CustomViewSet, 'custom')
|
||||
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
|
||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'domains', api.DomainViewSet, 'domain')
|
||||
router.register(r'zones', api.ZoneViewSet, 'zone')
|
||||
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
||||
router.register(r'protocol-settings', api.PlatformProtocolViewSet, 'protocol-setting')
|
||||
|
@ -0,0 +1,32 @@
|
||||
# Generated by Django 4.1.13 on 2025-04-21 06:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('audits', '0005_rename_serviceaccesslog'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ftplog',
|
||||
name='account',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='Account'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ftplog',
|
||||
name='asset',
|
||||
field=models.CharField(db_index=True, max_length=1024, verbose_name='Asset'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ftplog',
|
||||
name='date_start',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date start'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='ftplog',
|
||||
index=models.Index(fields=['date_start', 'org_id'], name='idx_date_start_org'),
|
||||
),
|
||||
]
|
@ -56,19 +56,22 @@ class FTPLog(OrgModelMixin):
|
||||
remote_addr = models.CharField(
|
||||
max_length=128, verbose_name=_("Remote addr"), blank=True, null=True
|
||||
)
|
||||
asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
|
||||
account = models.CharField(max_length=128, verbose_name=_("Account"))
|
||||
asset = models.CharField(max_length=1024, verbose_name=_("Asset"), db_index=True)
|
||||
account = models.CharField(max_length=128, verbose_name=_("Account"), db_index=True)
|
||||
operate = models.CharField(
|
||||
max_length=16, verbose_name=_("Operate"), choices=OperateChoices.choices
|
||||
)
|
||||
filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
|
||||
is_success = models.BooleanField(default=True, verbose_name=_("Success"))
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_("Date start"), db_index=True)
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_("Date start"))
|
||||
has_file = models.BooleanField(default=False, verbose_name=_("Can Download"))
|
||||
session = models.CharField(max_length=36, verbose_name=_("Session"), default=uuid.uuid4)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("File transfer log")
|
||||
indexes = [
|
||||
models.Index(fields=['date_start', 'org_id'], name='idx_date_start_org'),
|
||||
]
|
||||
|
||||
@property
|
||||
def filepath(self):
|
||||
|
@ -251,7 +251,7 @@ class ConnectionToken(JMSOrgBaseModel):
|
||||
raise JMSException({'error': 'No host account available, please check the applet, host and account'})
|
||||
|
||||
host, account, lock_key = bulk_get(host_account, ('host', 'account', 'lock_key'))
|
||||
gateway = host.domain.select_gateway() if host.domain else None
|
||||
gateway = host.zone.select_gateway() if host.zone else None
|
||||
platform = host.platform
|
||||
|
||||
data = {
|
||||
@ -305,17 +305,17 @@ class ConnectionToken(JMSOrgBaseModel):
|
||||
return account
|
||||
|
||||
@lazyproperty
|
||||
def domain(self):
|
||||
if not self.asset.platform.domain_enabled:
|
||||
def zone(self):
|
||||
if not self.asset.platform.gateway_enabled:
|
||||
return
|
||||
if self.asset.platform.name == GATEWAY_NAME:
|
||||
return
|
||||
domain = self.asset.domain if self.asset.domain else None
|
||||
return domain
|
||||
zone = self.asset.zone if self.asset.zone else None
|
||||
return zone
|
||||
|
||||
@lazyproperty
|
||||
def gateway(self):
|
||||
if not self.asset or not self.domain:
|
||||
if not self.asset or not self.zone:
|
||||
return
|
||||
return self.asset.gateway
|
||||
|
||||
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
||||
from accounts.const import SecretType
|
||||
from accounts.models import Account
|
||||
from acls.models import CommandGroup, CommandFilterACL
|
||||
from assets.models import Asset, Platform, Gateway, Domain
|
||||
from assets.models import Asset, Platform, Gateway, Zone
|
||||
from assets.serializers.asset import AssetProtocolsSerializer
|
||||
from assets.serializers.platform import PlatformSerializer
|
||||
from common.serializers.fields import LabeledChoiceField
|
||||
@ -135,7 +135,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
|
||||
account = _ConnectionTokenAccountSerializer(read_only=True, source='account_object')
|
||||
gateway = _ConnectionTokenGatewaySerializer(read_only=True)
|
||||
platform = _ConnectionTokenPlatformSerializer(read_only=True)
|
||||
domain = ObjectRelatedField(queryset=Domain.objects, required=False, label=_('Domain'))
|
||||
zone = ObjectRelatedField(queryset=Zone.objects, required=False, label=_('Domain'))
|
||||
command_filter_acls = _ConnectionTokenCommandFilterACLSerializer(read_only=True, many=True)
|
||||
expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True)
|
||||
connect_method = _ConnectTokenConnectMethodSerializer(read_only=True, source='connect_method_object')
|
||||
@ -148,7 +148,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
|
||||
fields = [
|
||||
'id', 'value', 'user', 'asset', 'account',
|
||||
'platform', 'command_filter_acls', 'protocol',
|
||||
'domain', 'gateway', 'actions', 'expire_at',
|
||||
'zone', 'gateway', 'actions', 'expire_at',
|
||||
'from_ticket', 'expire_now', 'connect_method',
|
||||
'connect_options', 'face_monitor_token'
|
||||
]
|
||||
|
@ -3,7 +3,6 @@ import pycountry
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from phonenumbers import PhoneMetadata
|
||||
from common.utils import lazyproperty
|
||||
|
||||
ADMIN = 'Admin'
|
||||
USER = 'User'
|
||||
@ -77,6 +76,7 @@ class Language(models.TextChoices):
|
||||
pt_br = 'pt-br', 'Português (Brasil)'
|
||||
es = 'es', 'Español'
|
||||
ru = 'ru', 'Русский'
|
||||
ko = 'ko', '한국어'
|
||||
|
||||
@classmethod
|
||||
def get_code_mapper(cls):
|
||||
|
@ -34,10 +34,14 @@ class SimpleMetadataWithFilters(SimpleMetadata):
|
||||
"""
|
||||
actions = {}
|
||||
view.raw_action = getattr(view, "action", None)
|
||||
query_action = request.query_params.get("action", None)
|
||||
for method in self.methods & set(view.allowed_methods):
|
||||
if hasattr(view, "action_map"):
|
||||
view.action = view.action_map.get(method.lower(), view.action)
|
||||
|
||||
if query_action and query_action.lower() != method.lower():
|
||||
continue
|
||||
|
||||
view.request = clone_request(request, method)
|
||||
try:
|
||||
# Test global permissions
|
||||
|
@ -14,6 +14,7 @@ from uuid import UUID
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db.models import QuerySet as DJQuerySet
|
||||
from django.db.models import Q
|
||||
from elasticsearch7 import Elasticsearch
|
||||
from elasticsearch7.helpers import bulk
|
||||
from elasticsearch7.exceptions import RequestError, SSLError
|
||||
@ -78,6 +79,10 @@ class ESClientV7(ESClientBase):
|
||||
def get_sort(cls, field, direction):
|
||||
return f'{field}:{direction}'
|
||||
|
||||
@classmethod
|
||||
def get_sorts(cls, sorts: list):
|
||||
return ','.join(sorts)
|
||||
|
||||
|
||||
class ESClientV6(ESClientV7):
|
||||
|
||||
@ -99,6 +104,10 @@ class ESClientV8(ESClientBase):
|
||||
def get_sort(cls, field, direction):
|
||||
return {field: {'order': direction}}
|
||||
|
||||
@classmethod
|
||||
def get_sorts(cls, sorts: list):
|
||||
return sorts
|
||||
|
||||
|
||||
def get_es_client_version(**kwargs):
|
||||
try:
|
||||
@ -190,8 +199,7 @@ class ES(object):
|
||||
mappings['aliases'] = {
|
||||
self.query_index: {}
|
||||
}
|
||||
if self.es.indices.exists(index=self.index):
|
||||
return
|
||||
|
||||
try:
|
||||
self.es.indices.create(index=self.index, body=mappings)
|
||||
except (RequestError, BadRequestError) as e:
|
||||
@ -245,6 +253,7 @@ class ES(object):
|
||||
}
|
||||
if sort is not None:
|
||||
search_params['sort'] = sort
|
||||
logger.info('search_params: {}'.format(search_params))
|
||||
data = self.es.search(**search_params)
|
||||
|
||||
source_data = []
|
||||
@ -319,10 +328,12 @@ class ES(object):
|
||||
kwargs = new_kwargs
|
||||
|
||||
index_in_field = 'id__in'
|
||||
keyword_fields = self.keyword_fields
|
||||
exact_fields = self.exact_fields
|
||||
match_fields = self.match_fields
|
||||
|
||||
match = {}
|
||||
search = []
|
||||
exact = {}
|
||||
index = {}
|
||||
|
||||
@ -330,11 +341,17 @@ class ES(object):
|
||||
index['values'] = kwargs[index_in_field]
|
||||
|
||||
for k, v in kwargs.items():
|
||||
if k in exact_fields:
|
||||
exact[k] = v
|
||||
if k in exact_fields.union(keyword_fields):
|
||||
exact['{}.keyword'.format(k)] = v
|
||||
elif k in match_fields:
|
||||
match[k] = v
|
||||
|
||||
args = kwargs.get('search')
|
||||
for item in args:
|
||||
for k, v in item.items():
|
||||
if k in match_fields:
|
||||
search.append(item)
|
||||
|
||||
# 处理时间
|
||||
time_field_name, time_range = self.handler_time_field(kwargs)
|
||||
|
||||
@ -363,10 +380,12 @@ class ES(object):
|
||||
body = {
|
||||
'query': {
|
||||
'bool': {
|
||||
'must': [
|
||||
'must': [],
|
||||
'should': should + [
|
||||
{'match': {k: v}} for k, v in match.items()
|
||||
] + [
|
||||
{'match': item} for item in search
|
||||
],
|
||||
'should': should,
|
||||
'filter': self.handle_exact_fields(exact) +
|
||||
[
|
||||
{
|
||||
@ -403,6 +422,17 @@ class QuerySet(DJQuerySet):
|
||||
_method_calls = {k: list(v) for k, v in groupby(self._method_calls, lambda x: x[0])}
|
||||
return _method_calls
|
||||
|
||||
def _grouped_search_args(self, query):
|
||||
conditions = {}
|
||||
for q in query:
|
||||
for c in q.children:
|
||||
if isinstance(c, Q):
|
||||
child = self._grouped_search_args(c)
|
||||
[conditions.setdefault(k, []).extend(v) for k, v in child.items()]
|
||||
else:
|
||||
conditions.setdefault(c[0], []).append(c[1])
|
||||
return conditions
|
||||
|
||||
@lazyproperty
|
||||
def _filter_kwargs(self):
|
||||
_method_calls = self._grouped_method_calls
|
||||
@ -410,14 +440,14 @@ 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
|
||||
}
|
||||
|
||||
# input 输入
|
||||
multi_args = tuple(reduce(lambda x, y: x + y, (sub for sub in multi_args if sub),()))
|
||||
args = self._grouped_search_args(multi_args)
|
||||
striped_args = [{k.replace('__icontains', ''): v} for k, values in args.items() for v in values]
|
||||
|
||||
kwargs = reduce(lambda x, y: {**x, **y}, multi_kwargs, {})
|
||||
kwargs.update(args)
|
||||
striped_kwargs = {}
|
||||
striped_kwargs = {'search': striped_args}
|
||||
for k, v in kwargs.items():
|
||||
k = k.replace('__exact', '')
|
||||
k = k.replace('__startswith', '')
|
||||
@ -428,6 +458,7 @@ class QuerySet(DJQuerySet):
|
||||
@lazyproperty
|
||||
def _sort(self):
|
||||
order_by = self._grouped_method_calls.get('order_by')
|
||||
_sorts = [self._storage.client.get_sort('_score', 'desc')]
|
||||
if order_by:
|
||||
for call in reversed(order_by):
|
||||
fields = call[1]
|
||||
@ -440,7 +471,10 @@ class QuerySet(DJQuerySet):
|
||||
direction = 'asc'
|
||||
field = field.lstrip('-+')
|
||||
sort = self._storage.client.get_sort(field, direction)
|
||||
return sort
|
||||
_sorts.append(sort)
|
||||
break
|
||||
sorts = self._storage.client.get_sorts(_sorts)
|
||||
return sorts
|
||||
|
||||
def __execute(self):
|
||||
_filter_kwargs = self._filter_kwargs
|
||||
@ -514,4 +548,4 @@ class QuerySet(DJQuerySet):
|
||||
return iter(self.__execute())
|
||||
|
||||
def __len__(self):
|
||||
return self.count()
|
||||
return self.count()
|
@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:860b4d38beff81667c64da41c026a7dd28c3c93a28ae61fefaa7c26875f35638
|
||||
size 73906864
|
@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c5119fd8911a107a7112422ade326766fe3d9538ac15bca06e3c622191c84e18
|
||||
size 61086554
|
@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b82b874152c798dda407ffe7544e1f5ec67efa1f5c334efc0d3893b8053b4be1
|
||||
size 3649897
|
@ -8,13 +8,14 @@ from .const import RED, GREEN, RESET
|
||||
|
||||
class BaseTranslateManager:
|
||||
bulk_size = 15
|
||||
SEPARATOR = "<SEP>"
|
||||
SEPARATOR = "<-SEP->"
|
||||
LANG_MAPPER = {
|
||||
'ja': 'Japanese',
|
||||
'zh_Hant': 'Traditional Chinese',
|
||||
'pt_BR': 'Portuguese (Brazil)',
|
||||
'es': 'Spanish',
|
||||
'ru': 'Russian',
|
||||
'ko': 'Korean',
|
||||
}
|
||||
|
||||
def __init__(self, dir_path, oai_trans_instance):
|
||||
|
85
apps/i18n/chen/ko.json
Normal file
85
apps/i18n/chen/ko.json
Normal file
@ -0,0 +1,85 @@
|
||||
{
|
||||
"ACLRejectError": "이 명령은 실행할 수 없습니다",
|
||||
"AffectedRows": "영향을 받은 행 수",
|
||||
"AlreadyFirstPageError": "이미 첫 페이지입니다.",
|
||||
"AlreadyLastPageError": "이미 마지막 페이지입니다.",
|
||||
"Cancel": "취소",
|
||||
"ChangeContextError": "콘텍스트 전환 실패",
|
||||
"CommandReview": "명령어 검토",
|
||||
"CommandReviewMessage": "귀하가 입력한 명령은 검토 후 실행해야 합니다. 검토 요청을 하시겠습니까?",
|
||||
"CommandReviewRejectBy": "명령 검토가 %s에 의해 거부되었습니다.",
|
||||
"CommandReviewTimeoutError": "명령 재검토 시간 초과",
|
||||
"CommandWarningDialogMessage": "귀하가 실행한 명령은 위험이 있습니다. 경고 알림이 관리자에게 전송됩니다. 계속하시겠습니까?",
|
||||
"Confirm": "확인",
|
||||
"ConnectError": "연결 실패",
|
||||
"ConnectSuccess": "연결 성공",
|
||||
"Connected": "연결됨",
|
||||
"Copy": "복사",
|
||||
"CopyFailed": "복사 실패",
|
||||
"CopyNotAllowed": "복사가 허용되지 않습니다. 관리자에게 권한을 요청해 주십시오!",
|
||||
"CopySucceeded": "복사 성공",
|
||||
"Current": "현재",
|
||||
"DatabaseExplorer": "데이터베이스 탐색기",
|
||||
"DatabaseProperties": "데이터 원본 속성",
|
||||
"DownloadNotAllowed": "다운로드가 허용되지 않습니다. 관리자에게 권한을 활성화해 달라고 연락하십시오!",
|
||||
"DriverClass": "드라이버 클래스",
|
||||
"DriverVersion": "드라이버 버전",
|
||||
"ErrorMessage": "오류 메시지",
|
||||
"ExecuteError": "실행 실패",
|
||||
"ExecuteSuccess": "실행 성공",
|
||||
"ExecutionCanceled": "실행이 취소되었습니다.",
|
||||
"ExportALL": "모든 데이터 내보내기",
|
||||
"ExportAll": "모두 내보내기",
|
||||
"ExportCurrent": "현재 페이지 내보내기",
|
||||
"ExportData": "데이터 내보내기",
|
||||
"FetchError": "데이터를 가져오는 데 실패했습니다",
|
||||
"Format": "형식",
|
||||
"FormatHotKey": "형식화 (Ctrl + L)",
|
||||
"InitializeDatasource": "데이터 소스 초기화",
|
||||
"InitializeDatasourceFailed": "데이터 소스 초기화 실패",
|
||||
"InitializingDatasourceMessage": "데이터 소스를 초기화하는 중입니다. 잠시만 기다려 주십시오...",
|
||||
"InsertStatement": "삽입 문",
|
||||
"JDBCURL": "JDBC URL",
|
||||
"LogOutput": "로그 출력",
|
||||
"Name": "이름",
|
||||
"NewQuery": "새 쿼리 만들기",
|
||||
"NoPermissionError": "이 작업을 수행할 권한이 없습니다.",
|
||||
"NumRow": "{num} 행",
|
||||
"Open": "열기",
|
||||
"OverMaxIdleTimeError": "이 세션의 유휴 시간이 %d 분을 초과하여 종료되었습니다",
|
||||
"OverMaxSessionTimeError": "이 세션의 시간이 %d 시간을 초과하여 종료되었습니다.",
|
||||
"ParseError": "분석 실패",
|
||||
"PasteNotAllowed": "붙여넣기가 허용되지 않습니다. 관리자에게 문의하여 권한을 요청하십시오!",
|
||||
"PermissionAlreadyExpired": "권한이 만료되었습니다.",
|
||||
"PermissionExpiredDialogMessage": "권한이 만료되었습니다. 세션은 10분 후 만료될 예정입니다. 관리자가 연장할 수 있도록 신속하게 연락해 주시기 바랍니다.",
|
||||
"PermissionExpiredDialogTitle": "권한이 만료되었습니다.",
|
||||
"PermissionsExpiredOn": "이 세션에 연결된 권한이 %s에 만료되었습니다",
|
||||
"Properties": "속성",
|
||||
"Refresh": "갱신",
|
||||
"Run": "실행",
|
||||
"RunHotKey": "실행하기 (Ctrl + Enter)",
|
||||
"RunSelected": "선택 실행",
|
||||
"Save": "저장",
|
||||
"SaveSQL": "SQL 저장",
|
||||
"SaveSucceed": "저장 성공",
|
||||
"Scope": "범위",
|
||||
"SelectSQL": "SQL 선택",
|
||||
"SessionClosedBy": "세션이 %s에 의해 종료되었습니다",
|
||||
"SessionFinished": "세션이 종료되었습니다",
|
||||
"SessionLockedError": "현재 세션이 잠겨 있어 명령을 계속 실행할 수 없습니다",
|
||||
"SessionLockedMessage": "현재 세션이 %s에 의해 잠겨 있어 명령어를 계속 실행할 수 없습니다",
|
||||
"SessionUnlockedMessage": "이 세션은 %s에 의해 해제되었으며, 명령을 계속 실행할 수 있습니다",
|
||||
"ShowProperties": "속성",
|
||||
"StopHotKey": "중지 (Ctrl + C)",
|
||||
"Submit": "제출",
|
||||
"Total": "총계",
|
||||
"Type": "형식",
|
||||
"UpdateStatement": "업데이트 명령어",
|
||||
"User": "사용자",
|
||||
"UserCancelCommandReviewError": "사용자가 명령 승인을 취소했습니다",
|
||||
"Version": "버전",
|
||||
"ViewData": "데이터 보기",
|
||||
"WaitCommandReviewMessage": "승인 요청이 발송되었습니다, 승인 결과를 기다려 주세요",
|
||||
"Warning": "경고",
|
||||
"initializingDatasourceFailedMessage": "연결 실패, 데이터베이스 연결 구성이 올바른지 확인해 주세요"
|
||||
}
|
@ -1,85 +1,85 @@
|
||||
{
|
||||
"ACLRejectError": "Эта команда не может быть выполнена.",
|
||||
"AffectedRows": "Количество затронутых строк",
|
||||
"AlreadyFirstPageError": "Это уже первая страница",
|
||||
"ACLRejectError": "Выполнение команды запрещено правилом",
|
||||
"AffectedRows": "Затронутые строки",
|
||||
"AlreadyFirstPageError": "Это первая страница",
|
||||
"AlreadyLastPageError": "Это последняя страница",
|
||||
"Cancel": "Отменить",
|
||||
"ChangeContextError": "Сменить контекст не удалось",
|
||||
"CommandReview": "Команда проверки",
|
||||
"CommandReviewMessage": "Команда, которую вы ввели, требует подтверждения перед выполнением, хотите ли вы начать процесс проверки?",
|
||||
"ChangeContextError": "Ошибка смены контекста",
|
||||
"CommandReview": "Проверка команды",
|
||||
"CommandReviewMessage": "Введенная вами команда требует проверки перед выполнением, хотите отправить запрос на проверку?",
|
||||
"CommandReviewRejectBy": "Команда проверки отклонена %s",
|
||||
"CommandReviewTimeoutError": "Тайм-аут проверки команды",
|
||||
"CommandWarningDialogMessage": "Выполняемая вами команда содержит риски, уведомление будет отправлено администратору. Продолжить?",
|
||||
"CommandWarningDialogMessage": "Команда, которую вы хотите выполнить, содержит риски, уведомление будет отправлено администратору. Продолжить?",
|
||||
"Confirm": "Подтвердить",
|
||||
"ConnectError": "Ошибка соединения",
|
||||
"ConnectSuccess": "Соединение успешно",
|
||||
"ConnectError": "Ошибка подключения",
|
||||
"ConnectSuccess": "Успешно подключено",
|
||||
"Connected": "Подключено",
|
||||
"Copy": "Копировать.",
|
||||
"Copy": "Копировать",
|
||||
"CopyFailed": "Копирование не удалось",
|
||||
"CopyNotAllowed": "Копирование запрещено, пожалуйста, свяжитесь с администратором для получения прав!",
|
||||
"CopySucceeded": "Копирование выполнено успешно",
|
||||
"Current": "Текущая",
|
||||
"DatabaseExplorer": "Обозреватель базы данных.",
|
||||
"CopySucceeded": "Успешное копирование",
|
||||
"Current": "Текущий",
|
||||
"DatabaseExplorer": "Проводник баз данных",
|
||||
"DatabaseProperties": "Свойства источника данных",
|
||||
"DownloadNotAllowed": "Скачать нельзя, пожалуйста, свяжитесь с администратором для получения доступа!",
|
||||
"DownloadNotAllowed": "Загрузка не разрешена, пожалуйста, свяжитесь с администратором для получения доступа!",
|
||||
"DriverClass": "Класс драйвера",
|
||||
"DriverVersion": "Версия драйвера",
|
||||
"ErrorMessage": "Сообщение об ошибке",
|
||||
"ExecuteError": "Выполнение не удалось",
|
||||
"ExecuteSuccess": "Выполнено успешно",
|
||||
"ExecutionCanceled": "Выполнение отменено.",
|
||||
"ExecutionCanceled": "Выполнение отменено",
|
||||
"ExportALL": "Экспортировать все данные",
|
||||
"ExportAll": "Экспортировать все",
|
||||
"ExportCurrent": "Экспорт текущей страницы",
|
||||
"ExportData": "Экспорт данных.",
|
||||
"ExportData": "Экспорт данных",
|
||||
"FetchError": "Не удалось получить данные",
|
||||
"Format": "Формат.",
|
||||
"Format": "Формат",
|
||||
"FormatHotKey": "Форматировать (Ctrl + L)",
|
||||
"InitializeDatasource": "Инициализация источника данных",
|
||||
"InitializeDatasourceFailed": "Не удалось инициализировать источник данных",
|
||||
"InitializingDatasourceMessage": "Инициализация источника данных, пожалуйста, подождите...",
|
||||
"InsertStatement": "Вставить оператор",
|
||||
"InsertStatement": "Инструкция INSERT",
|
||||
"JDBCURL": "JDBC URL",
|
||||
"LogOutput": "Журнал вывода",
|
||||
"Name": "Название",
|
||||
"NewQuery": "Создать новый запрос",
|
||||
"NoPermissionError": "Нет прав на выполнение данного действия",
|
||||
"NewQuery": "Новый запрос",
|
||||
"NoPermissionError": "Нет прав на выполнение этого действия",
|
||||
"NumRow": "{num} строк",
|
||||
"Open": "Открыть",
|
||||
"OverMaxIdleTimeError": "Так как эта сессия простаивает более %d минут, она была закрыта.",
|
||||
"OverMaxSessionTimeError": "Так как эта сессия превышает %d часов, она была закрыта.",
|
||||
"OverMaxIdleTimeError": "Сессия была закрыта, так как время простоя превысило %d минут",
|
||||
"OverMaxSessionTimeError": "Сессия была закрыта, так как продолжительность сессии превысила %d часов",
|
||||
"ParseError": "Ошибка анализа",
|
||||
"PasteNotAllowed": "Вставка не разрешена, пожалуйста, свяжитесь с администратором для получения прав!",
|
||||
"PermissionAlreadyExpired": "Срок действия авторизации истек",
|
||||
"PermissionExpiredDialogMessage": "Авторизация истекла, сессия истечёт через десять минут, пожалуйста, свяжитесь с администратором для продления",
|
||||
"PermissionExpiredDialogTitle": "Авторизация истекла",
|
||||
"PermissionsExpiredOn": "Права, связанные с этой сессией, истекли %s",
|
||||
"PermissionAlreadyExpired": "Срок действия разрешения истек",
|
||||
"PermissionExpiredDialogMessage": "Разрешение истекло, сессия истечет через 10 минут, пожалуйста, свяжитесь с администратором для продления",
|
||||
"PermissionExpiredDialogTitle": "Разрешение истекло",
|
||||
"PermissionsExpiredOn": "Разрешение для этой сессии истекло %s",
|
||||
"Properties": "Свойства",
|
||||
"Refresh": "Обновить",
|
||||
"Run": "Запустить",
|
||||
"RunHotKey": "Запуск (Ctrl + Enter).",
|
||||
"RunSelected": "Запустить выделенное.",
|
||||
"Run": "Запуск",
|
||||
"RunHotKey": "Запуск (Ctrl + Enter)",
|
||||
"RunSelected": "Запустить выбранные",
|
||||
"Save": "Сохранить",
|
||||
"SaveSQL": "Сохранить SQL.",
|
||||
"SaveSucceed": "Сохранение успешно",
|
||||
"Scope": "Диапазон",
|
||||
"SaveSQL": "Сохранить SQL",
|
||||
"SaveSucceed": "Успешно сохранено",
|
||||
"Scope": "Область",
|
||||
"SelectSQL": "Выбрать SQL",
|
||||
"SessionClosedBy": "Сессия была закрыта %s",
|
||||
"SessionFinished": "Сессия завершена",
|
||||
"SessionLockedError": "Текущая сессия заблокирована, команда не может быть продолжена",
|
||||
"SessionLockedMessage": "Эта сессия была заблокирована %s и команда не может быть продолжена",
|
||||
"SessionLockedError": "Текущая сессия заблокирована, невозможно продолжить выполнение команды",
|
||||
"SessionLockedMessage": " Эта сессия была заблокирована %s, невозможно продолжить выполнение команды",
|
||||
"SessionUnlockedMessage": "Эта сессия была разблокирована %s, можно продолжить выполнение команды",
|
||||
"ShowProperties": "Свойства",
|
||||
"StopHotKey": "Остановить (Ctrl + C)",
|
||||
"Submit": "Отправить",
|
||||
"Total": "Всего:",
|
||||
"Total": "Всего",
|
||||
"Type": "Тип",
|
||||
"UpdateStatement": "Обновить запрос",
|
||||
"User": "Пользователь.",
|
||||
"User": "Пользователь",
|
||||
"UserCancelCommandReviewError": "Пользователь отменил проверку команды",
|
||||
"Version": "Версия",
|
||||
"ViewData": "Просмотреть данные",
|
||||
"WaitCommandReviewMessage": "Запрос на пересмотр инициирован, пожалуйста, ожидайте результата пересмотра",
|
||||
"ViewData": "Просмотр данных",
|
||||
"WaitCommandReviewMessage": "Запрос на проверку отправлен, пожалуйста, ожидайте результатов проверки",
|
||||
"Warning": "Предупреждение",
|
||||
"initializingDatasourceFailedMessage": "Ошибка подключения, пожалуйста, проверьте правильность конфигурации подключения к базе данных."
|
||||
"initializingDatasourceFailedMessage": "Ошибка подключения, пожалуйста, проверьте настройки подключения к базе данных"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -19,85 +19,87 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
#: static/js/jumpserver.js:267
|
||||
msgid "Update is successful!"
|
||||
msgstr ""
|
||||
msgstr "Actualización exitosa"
|
||||
|
||||
#: static/js/jumpserver.js:269
|
||||
msgid "An unknown error occurred while updating.."
|
||||
msgstr ""
|
||||
msgstr "Se produjo un error desconocido durante la actualización"
|
||||
|
||||
#: static/js/jumpserver.js:342
|
||||
msgid "Not found"
|
||||
msgstr ""
|
||||
msgstr "No encontrado"
|
||||
|
||||
#: static/js/jumpserver.js:344
|
||||
msgid "Server error"
|
||||
msgstr ""
|
||||
msgstr "Error del servidor"
|
||||
|
||||
#: static/js/jumpserver.js:346 static/js/jumpserver.js:384
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
msgstr "Error"
|
||||
|
||||
#: static/js/jumpserver.js:352 static/js/jumpserver.js:393
|
||||
msgid "Delete the success"
|
||||
msgstr ""
|
||||
msgstr "Eliminado exitosamente"
|
||||
|
||||
#: static/js/jumpserver.js:359
|
||||
msgid "Are you sure about deleting it?"
|
||||
msgstr ""
|
||||
msgstr "¿Estás seguro que deseas eliminar?"
|
||||
|
||||
#: static/js/jumpserver.js:363 static/js/jumpserver.js:404
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: static/js/jumpserver.js:365 static/js/jumpserver.js:406
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
msgstr "Confirmar"
|
||||
|
||||
#: static/js/jumpserver.js:384
|
||||
msgid ""
|
||||
"The organization contains undeleted information. Please try again after "
|
||||
"deleting"
|
||||
msgstr ""
|
||||
msgstr "La organización contiene información no eliminada, por favor elimínela y vuelva a intentarlo"
|
||||
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid ""
|
||||
"Do not perform this operation under this organization. Try again after "
|
||||
"switching to another organization"
|
||||
msgstr ""
|
||||
msgstr "No realice esta operación en esta organización, cambie a otra organización e inténtelo de nuevo"
|
||||
|
||||
#: static/js/jumpserver.js:400
|
||||
msgid ""
|
||||
"Please ensure that the following information in the organization has been "
|
||||
"deleted"
|
||||
msgstr ""
|
||||
msgstr "Asegúrese de que se haya eliminado la siguiente información de su organización"
|
||||
|
||||
#: static/js/jumpserver.js:401
|
||||
msgid ""
|
||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||
"Labels、Asset permission"
|
||||
msgstr ""
|
||||
"Lista de usuarios, grupo de usuarios, lista de activos, lista de dominios, usuario privilegiado, usuario del sistema, gestión de etiquetas, autorización de activos"
|
||||
"regla"
|
||||
|
||||
#: static/js/jumpserver.js:650
|
||||
msgid "Unknown error occur"
|
||||
msgstr ""
|
||||
msgstr "Se produjo un error desconocido"
|
||||
|
||||
#: static/js/jumpserver.js:902
|
||||
msgid "Password minimum length {N} bits"
|
||||
msgstr ""
|
||||
msgstr "La longitud mínima de la contraseña es {N} caracteres"
|
||||
|
||||
#: static/js/jumpserver.js:903
|
||||
msgid "Must contain capital letters"
|
||||
msgstr ""
|
||||
msgstr "Debe contener letras mayúsculas"
|
||||
|
||||
#: static/js/jumpserver.js:904
|
||||
msgid "Must contain lowercase letters"
|
||||
msgstr ""
|
||||
msgstr "Debe contener letras minúsculas"
|
||||
|
||||
#: static/js/jumpserver.js:905
|
||||
msgid "Must contain numeric characters"
|
||||
msgstr ""
|
||||
msgstr "Debe contener caracteres numéricos"
|
||||
|
||||
#: static/js/jumpserver.js:906
|
||||
msgid "Must contain special characters"
|
||||
msgstr ""
|
||||
msgstr "Debe contener caracteres especiales"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -107,45 +107,3 @@ msgstr "数字を含める必要があります。"
|
||||
#: static/js/jumpserver.js:906
|
||||
msgid "Must contain special characters"
|
||||
msgstr "特殊文字を含める必要があります"
|
||||
|
||||
#~ msgid "Loading"
|
||||
#~ msgstr "読み込み中"
|
||||
|
||||
#~ msgid "Search"
|
||||
#~ msgstr "検索"
|
||||
|
||||
#, javascript-format
|
||||
#~ msgid "Selected item %d"
|
||||
#~ msgstr "選択したアイテム % d"
|
||||
|
||||
#~ msgid "Per page _MENU_"
|
||||
#~ msgstr "各ページ _MENU_"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
|
||||
#~ msgstr ""
|
||||
#~ "アイテムの結果を表示します _START_ に着く _END_; 合計 _TOTAL_ エントリ"
|
||||
|
||||
#~ msgid "No match"
|
||||
#~ msgstr "一致しません"
|
||||
|
||||
#~ msgid "No record"
|
||||
#~ msgstr "記録なし"
|
||||
|
||||
#~ msgid "Export failed"
|
||||
#~ msgstr "エクスポートに失敗しました"
|
||||
|
||||
#~ msgid "Import Success"
|
||||
#~ msgstr "インポートの成功"
|
||||
|
||||
#~ msgid "Update Success"
|
||||
#~ msgstr "更新の成功"
|
||||
|
||||
#~ msgid "Count"
|
||||
#~ msgstr "カウント"
|
||||
|
||||
#~ msgid "Import failed"
|
||||
#~ msgstr "インポートに失敗しました"
|
||||
|
||||
#~ msgid "Update failed"
|
||||
#~ msgstr "更新に失敗しました"
|
||||
|
11038
apps/i18n/core/ko/LC_MESSAGES/django.po
Normal file
11038
apps/i18n/core/ko/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
105
apps/i18n/core/ko/LC_MESSAGES/djangojs.po
Normal file
105
apps/i18n/core/ko/LC_MESSAGES/djangojs.po
Normal file
@ -0,0 +1,105 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-24 14:25+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: static/js/jumpserver.js:267
|
||||
msgid "Update is successful!"
|
||||
msgstr "업데이트 성공"
|
||||
|
||||
#: static/js/jumpserver.js:269
|
||||
msgid "An unknown error occurred while updating.."
|
||||
msgstr "업데이트 중 알 수 없는 오류가 발생했습니다."
|
||||
|
||||
#: static/js/jumpserver.js:342
|
||||
msgid "Not found"
|
||||
msgstr "찾을 수 없음"
|
||||
|
||||
#: static/js/jumpserver.js:344
|
||||
msgid "Server error"
|
||||
msgstr "서버 오류"
|
||||
|
||||
#: static/js/jumpserver.js:346 static/js/jumpserver.js:384
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid "Error"
|
||||
msgstr "오류"
|
||||
|
||||
#: static/js/jumpserver.js:352 static/js/jumpserver.js:393
|
||||
msgid "Delete the success"
|
||||
msgstr "성공적으로 삭제되었습니다"
|
||||
|
||||
#: static/js/jumpserver.js:359
|
||||
msgid "Are you sure about deleting it?"
|
||||
msgstr "정말 삭제하시겠습니까?"
|
||||
|
||||
#: static/js/jumpserver.js:363 static/js/jumpserver.js:404
|
||||
msgid "Cancel"
|
||||
msgstr "취소"
|
||||
|
||||
#: static/js/jumpserver.js:365 static/js/jumpserver.js:406
|
||||
msgid "Confirm"
|
||||
msgstr "확인"
|
||||
|
||||
#: static/js/jumpserver.js:384
|
||||
msgid ""
|
||||
"The organization contains undeleted information. Please try again after "
|
||||
"deleting"
|
||||
msgstr "조직에 삭제되지 않은 정보가 포함되어 있습니다. 삭제하고 다시 시도하세요."
|
||||
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid ""
|
||||
"Do not perform this operation under this organization. Try again after "
|
||||
"switching to another organization"
|
||||
msgstr "이 조직에서는 이 작업을 수행하지 마십시오. 다른 조직으로 전환한 후 다시 시도하십시오."
|
||||
|
||||
#: static/js/jumpserver.js:400
|
||||
msgid ""
|
||||
"Please ensure that the following information in the organization has been "
|
||||
"deleted"
|
||||
msgstr "조직에서 다음 정보가 삭제되었는지 확인하세요"
|
||||
|
||||
#: static/js/jumpserver.js:401
|
||||
msgid ""
|
||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||
"Labels、Asset permission"
|
||||
msgstr "사용자 목록, 사용자 그룹, 자산 목록, 도메인 목록, 권한이 있는 사용자, 시스템 사용자, 태그 관리, 자산 권한 부여"
|
||||
"규칙"
|
||||
|
||||
#: static/js/jumpserver.js:650
|
||||
msgid "Unknown error occur"
|
||||
msgstr "알 수 없는 오류가 발생했습니다"
|
||||
|
||||
#: static/js/jumpserver.js:902
|
||||
msgid "Password minimum length {N} bits"
|
||||
msgstr "최소 비밀번호 길이는 {N}자입니다"
|
||||
|
||||
#: static/js/jumpserver.js:903
|
||||
msgid "Must contain capital letters"
|
||||
msgstr "대문자를 포함해야 합니다"
|
||||
|
||||
#: static/js/jumpserver.js:904
|
||||
msgid "Must contain lowercase letters"
|
||||
msgstr "소문자를 포함해야 합니다"
|
||||
|
||||
#: static/js/jumpserver.js:905
|
||||
msgid "Must contain numeric characters"
|
||||
msgstr "숫자를 포함해야 합니다"
|
||||
|
||||
#: static/js/jumpserver.js:906
|
||||
msgid "Must contain special characters"
|
||||
msgstr "특수문자를 포함해야 합니다"
|
File diff suppressed because it is too large
Load Diff
@ -20,85 +20,87 @@ msgstr ""
|
||||
|
||||
#: static/js/jumpserver.js:267
|
||||
msgid "Update is successful!"
|
||||
msgstr ""
|
||||
msgstr "Atualização bem-sucedida"
|
||||
|
||||
#: static/js/jumpserver.js:269
|
||||
msgid "An unknown error occurred while updating.."
|
||||
msgstr ""
|
||||
msgstr "Ocorreu um erro desconhecido durante a atualização"
|
||||
|
||||
#: static/js/jumpserver.js:342
|
||||
msgid "Not found"
|
||||
msgstr ""
|
||||
msgstr "Não encontrado"
|
||||
|
||||
#: static/js/jumpserver.js:344
|
||||
msgid "Server error"
|
||||
msgstr ""
|
||||
msgstr "Erro do servidor"
|
||||
|
||||
#: static/js/jumpserver.js:346 static/js/jumpserver.js:384
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
msgstr "Erro"
|
||||
|
||||
#: static/js/jumpserver.js:352 static/js/jumpserver.js:393
|
||||
msgid "Delete the success"
|
||||
msgstr ""
|
||||
msgstr "Excluído com sucesso"
|
||||
|
||||
#: static/js/jumpserver.js:359
|
||||
msgid "Are you sure about deleting it?"
|
||||
msgstr ""
|
||||
msgstr "Tem certeza de que deseja excluir?"
|
||||
|
||||
#: static/js/jumpserver.js:363 static/js/jumpserver.js:404
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: static/js/jumpserver.js:365 static/js/jumpserver.js:406
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
msgstr "Confirmar"
|
||||
|
||||
#: static/js/jumpserver.js:384
|
||||
msgid ""
|
||||
"The organization contains undeleted information. Please try again after "
|
||||
"deleting"
|
||||
msgstr ""
|
||||
msgstr "A organização contém informações não excluídas, exclua-as e tente novamente"
|
||||
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid ""
|
||||
"Do not perform this operation under this organization. Try again after "
|
||||
"switching to another organization"
|
||||
msgstr ""
|
||||
msgstr "Não execute esta operação nesta organização, mude para outra organização e tente novamente"
|
||||
|
||||
#: static/js/jumpserver.js:400
|
||||
msgid ""
|
||||
"Please ensure that the following information in the organization has been "
|
||||
"deleted"
|
||||
msgstr ""
|
||||
msgstr "Certifique-se de que as seguintes informações da sua organização foram excluídas"
|
||||
|
||||
#: static/js/jumpserver.js:401
|
||||
msgid ""
|
||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||
"Labels、Asset permission"
|
||||
msgstr ""
|
||||
"Lista de usuários, grupo de usuários, lista de ativos, lista de domínios, usuário privilegiado, usuário do sistema, gerenciamento de tags, autorização de ativos"
|
||||
"regra"
|
||||
|
||||
#: static/js/jumpserver.js:650
|
||||
msgid "Unknown error occur"
|
||||
msgstr ""
|
||||
msgstr "Ocorreu um erro desconhecido"
|
||||
|
||||
#: static/js/jumpserver.js:902
|
||||
msgid "Password minimum length {N} bits"
|
||||
msgstr ""
|
||||
msgstr "O comprimento mínimo da senha é {N} caracteres"
|
||||
|
||||
#: static/js/jumpserver.js:903
|
||||
msgid "Must contain capital letters"
|
||||
msgstr ""
|
||||
msgstr "Deve conter letras maiúsculas"
|
||||
|
||||
#: static/js/jumpserver.js:904
|
||||
msgid "Must contain lowercase letters"
|
||||
msgstr ""
|
||||
msgstr "Deve conter letras minúsculas"
|
||||
|
||||
#: static/js/jumpserver.js:905
|
||||
msgid "Must contain numeric characters"
|
||||
msgstr ""
|
||||
msgstr "Deve conter caracteres numéricos"
|
||||
|
||||
#: static/js/jumpserver.js:906
|
||||
msgid "Must contain special characters"
|
||||
msgstr ""
|
||||
msgstr "Deve conter caracteres especiais"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,85 +21,85 @@ msgstr ""
|
||||
"(n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
#: static/js/jumpserver.js:267
|
||||
msgid "Update is successful!"
|
||||
msgstr ""
|
||||
msgstr "Обновление успешно выполнено"
|
||||
|
||||
#: static/js/jumpserver.js:269
|
||||
msgid "An unknown error occurred while updating.."
|
||||
msgstr ""
|
||||
msgstr "Произошла неизвестная ошибка при обновлении"
|
||||
|
||||
#: static/js/jumpserver.js:342
|
||||
msgid "Not found"
|
||||
msgstr ""
|
||||
msgstr "Не найдено"
|
||||
|
||||
#: static/js/jumpserver.js:344
|
||||
msgid "Server error"
|
||||
msgstr ""
|
||||
msgstr "Ошибка сервера"
|
||||
|
||||
#: static/js/jumpserver.js:346 static/js/jumpserver.js:384
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
msgstr "Ошибка"
|
||||
|
||||
#: static/js/jumpserver.js:352 static/js/jumpserver.js:393
|
||||
msgid "Delete the success"
|
||||
msgstr ""
|
||||
msgstr "Удален успешно"
|
||||
|
||||
#: static/js/jumpserver.js:359
|
||||
msgid "Are you sure about deleting it?"
|
||||
msgstr ""
|
||||
msgstr "Вы уверены, что хотите удалить?"
|
||||
|
||||
#: static/js/jumpserver.js:363 static/js/jumpserver.js:404
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "Отмена"
|
||||
|
||||
#: static/js/jumpserver.js:365 static/js/jumpserver.js:406
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
msgstr "Подтвердить"
|
||||
|
||||
#: static/js/jumpserver.js:384
|
||||
msgid ""
|
||||
"The organization contains undeleted information. Please try again after "
|
||||
"deleting"
|
||||
msgstr ""
|
||||
msgstr "Организация содержит неудалённую информацию, пожалуйста, удалите её и повторите попытку"
|
||||
|
||||
#: static/js/jumpserver.js:386
|
||||
msgid ""
|
||||
"Do not perform this operation under this organization. Try again after "
|
||||
"switching to another organization"
|
||||
msgstr ""
|
||||
msgstr "Не выполняйте эту операцию в этой организации, переключитесь на другую организацию и попробуйте снова"
|
||||
|
||||
#: static/js/jumpserver.js:400
|
||||
msgid ""
|
||||
"Please ensure that the following information in the organization has been "
|
||||
"deleted"
|
||||
msgstr ""
|
||||
msgstr "Пожалуйста, убедитесь, что следующая информация в вашей организации удалена"
|
||||
|
||||
#: static/js/jumpserver.js:401
|
||||
msgid ""
|
||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||
"Labels、Asset permission"
|
||||
msgstr ""
|
||||
msgstr "Список пользователей, группа пользователей, список активов, список доменов, привилегированный пользователь, системный пользователь, управление тегами, авторизация активов правило"
|
||||
|
||||
#: static/js/jumpserver.js:650
|
||||
msgid "Unknown error occur"
|
||||
msgstr ""
|
||||
msgstr "Произошла неизвестная ошибка"
|
||||
|
||||
#: static/js/jumpserver.js:902
|
||||
msgid "Password minimum length {N} bits"
|
||||
msgstr ""
|
||||
msgstr "Минимальная длина пароля — {N} символов"
|
||||
|
||||
#: static/js/jumpserver.js:903
|
||||
msgid "Must contain capital letters"
|
||||
msgstr ""
|
||||
msgstr "Должен содержать заглавные буквы"
|
||||
|
||||
#: static/js/jumpserver.js:904
|
||||
msgid "Must contain lowercase letters"
|
||||
msgstr ""
|
||||
msgstr "Должен содержать строчные буквы"
|
||||
|
||||
#: static/js/jumpserver.js:905
|
||||
msgid "Must contain numeric characters"
|
||||
msgstr ""
|
||||
msgstr "Должен содержать числовые символы"
|
||||
|
||||
#: static/js/jumpserver.js:906
|
||||
msgid "Must contain special characters"
|
||||
msgstr ""
|
||||
msgstr "Должен содержать специальные символы"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -26,11 +26,11 @@ msgstr "更新时发生未知错误"
|
||||
|
||||
#: static/js/jumpserver.js:342
|
||||
msgid "Not found"
|
||||
msgstr ""
|
||||
msgstr "未找到"
|
||||
|
||||
#: static/js/jumpserver.js:344
|
||||
msgid "Server error"
|
||||
msgstr ""
|
||||
msgstr "服务器错误"
|
||||
|
||||
#: static/js/jumpserver.js:346 static/js/jumpserver.js:384
|
||||
#: static/js/jumpserver.js:386
|
||||
|
File diff suppressed because it is too large
Load Diff
96
apps/i18n/koko/ko.json
Normal file
96
apps/i18n/koko/ko.json
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"ActionPerm": "조작 권한",
|
||||
"Cancel": "취소",
|
||||
"CancelFileUpload": "파일 업로드 취소",
|
||||
"Clone Connect": "복사 창",
|
||||
"Close All Tabs": "모두 닫기",
|
||||
"Close Current Tab": "현재 닫기",
|
||||
"Confirm": "확인",
|
||||
"ConfirmBtn": "확인",
|
||||
"ConfirmDelete": "이 파일을 삭제하시겠습니까?",
|
||||
"Connect": "연결",
|
||||
"CopyLink": "링크 및 인증 코드 복사",
|
||||
"CopyShareURLSuccess": "공유 주소 복사 성공",
|
||||
"CreateLink": "공유 링크 생성",
|
||||
"CreateSuccess": "생성이 성공적으로 완료되었습니다",
|
||||
"Custom Setting": "사용자 정의 설정",
|
||||
"DangerWarning": "이것은 위험한 작업입니다",
|
||||
"Delete": "삭제",
|
||||
"DownArrow": "아래 화살표",
|
||||
"Download": "다운로드",
|
||||
"DownloadProgress": "다운로드 진행 상황",
|
||||
"DownloadSuccess": "다운로드 성공",
|
||||
"Downloading": "다운로드 중",
|
||||
"EndFileTransfer": "파일 전송 종료",
|
||||
"ExceedTransferSize": "최대 전송 크기를 초과했습니다",
|
||||
"Expand": "펼치기",
|
||||
"ExpiredTime": "유효 기간",
|
||||
"FileListError": "파일 목록 정보 가져오기 실패",
|
||||
"FileManagement": "파일 관리",
|
||||
"FileManagementExpired": "현재 파일 관리 세션이 만료되었습니다.",
|
||||
"FileUploadInterrupted": "파일 업로드 중단",
|
||||
"GetShareUser": "사용자 이름 입력",
|
||||
"Hotkeys": "단축키",
|
||||
"InputVerifyCode": "인증 코드를 입력하세요",
|
||||
"JoinShare": "공유에 가입",
|
||||
"JoinedWithSuccess": "성공적으로 가입되었습니다",
|
||||
"KubernetesManagement": "Kubernetes 관리",
|
||||
"LastModified": "최종 수정 시간",
|
||||
"LeaveShare": "공유 종료",
|
||||
"LeftArrow": "뒤로 화살표",
|
||||
"LinkAddr": "링크 주소",
|
||||
"List": "목록",
|
||||
"Minute": "분",
|
||||
"Minutes": "분",
|
||||
"MustOneFile": "하나의 파일만 선택할 수 있습니다",
|
||||
"MustSelectOneFile": "파일 하나를 선택해야 합니다",
|
||||
"Name": "이름",
|
||||
"NewFolder": "새 폴더 만들기",
|
||||
"NoLink": "주소 없음",
|
||||
"OnlineUsers": "온라인 인원",
|
||||
"OperationSuccessful": "작업 성공",
|
||||
"Paste": "붙여넣기",
|
||||
"PauseSession": "이번 대화 일시 중지",
|
||||
"PermissionDenied": "권한 없음",
|
||||
"PermissionExpired": "권한이 만료되었습니다",
|
||||
"PermissionValid": "권한 유효",
|
||||
"ReadOnly": "읽기 전용",
|
||||
"Reconnect": "재연결",
|
||||
"Refresh": "새로 고침",
|
||||
"Remove": "제거",
|
||||
"RemoveShareUser": "귀하가 공유 세션에서 제거되었습니다",
|
||||
"RemoveShareUserConfirm": "정말로 이 사용자를 제거하시겠습니까?",
|
||||
"Rename": "이름 변경",
|
||||
"ResumeSession": "이 세션을 복원하시겠습니까?",
|
||||
"RightArrow": "앞으로 화살표",
|
||||
"Search": "검색",
|
||||
"SelectAction": "선택하십시오",
|
||||
"SelectTheme": "주제를 선택하세요",
|
||||
"Self": "나",
|
||||
"Settings": "설정",
|
||||
"Share": "공유",
|
||||
"ShareUser": "공유 사용자",
|
||||
"ShareUserHelpText": "사용자가 선택되지 않았습니다, 모든 사람이 참여할 수 있습니다",
|
||||
"Size": "크기",
|
||||
"Sync": "동기화",
|
||||
"SyncUserPreferenceFailed": "동기화 설정 실패",
|
||||
"SyncUserPreferenceSuccess": "동기화 설정 성공",
|
||||
"Theme": "주제",
|
||||
"ThemeColors": "테마 색상",
|
||||
"ThemeConfig": "주제",
|
||||
"TransferHistory": "전송 히스토리",
|
||||
"Type": "유형",
|
||||
"UpArrow": "위쪽 화살표",
|
||||
"Upload": "업로드",
|
||||
"UploadEnd": "업로드가 완료되었습니다. 후속 처리를 기다려 주십시오.",
|
||||
"UploadProgress": "업로드 진행 상황",
|
||||
"UploadStart": "업로드 시작",
|
||||
"UploadSuccess": "업로드 성공",
|
||||
"UploadTips": "파일을 여기로 드래그하거나 업로드를 클릭하세요",
|
||||
"UploadTitle": "파일 업로드",
|
||||
"User": "사용자",
|
||||
"VerifyCode": "인증 코드",
|
||||
"WaitFileTransfer": "파일 전송 완료 대기",
|
||||
"WebSocketClosed": "WebSocket이 닫혔습니다",
|
||||
"Writable": "쓰기 가능"
|
||||
}
|
@ -1,25 +1,25 @@
|
||||
{
|
||||
"ActionPerm": "Операционные права",
|
||||
"ActionPerm": "Разрешения на действия",
|
||||
"Cancel": "Отмена",
|
||||
"CancelFileUpload": "Отменить передачу файла",
|
||||
"Clone Connect": "Скопировать окно",
|
||||
"Close All Tabs": "закрыть все",
|
||||
"Close Current Tab": "Закрыть текущий",
|
||||
"Clone Connect": "Клонировать окно",
|
||||
"Close All Tabs": "Закрыть все вкладки",
|
||||
"Close Current Tab": "Закрыть эту вкладку",
|
||||
"Confirm": "Подтвердить",
|
||||
"ConfirmBtn": "подтвердить",
|
||||
"ConfirmBtn": "Подтвердить",
|
||||
"ConfirmDelete": "Вы уверены, что хотите удалить этот файл?",
|
||||
"Connect": "Соединение",
|
||||
"Connect": "Подключение",
|
||||
"CopyLink": "Скопировать ссылку и код подтверждения",
|
||||
"CopyShareURLSuccess": "Успешно скопирован адрес для",
|
||||
"CreateLink": "создать ссылку для общего доступа",
|
||||
"CopyShareURLSuccess": "URL общего доступа успешно скопирован",
|
||||
"CreateLink": "Создать ссылку для общего доступа",
|
||||
"CreateSuccess": "Успешно создано",
|
||||
"Custom Setting": "Настройка по индивидуальным параметрам",
|
||||
"Custom Setting": "Пользовательские настройки",
|
||||
"DangerWarning": "Это опасная операция",
|
||||
"Delete": "Удалить",
|
||||
"DownArrow": "Стрелка вниз",
|
||||
"Download": "Скачать",
|
||||
"DownloadProgress": "Прогресс загрузки",
|
||||
"DownloadSuccess": "Скачивание успешно",
|
||||
"DownloadSuccess": "Успешное скачивание",
|
||||
"Downloading": "Ведётся загрузка",
|
||||
"EndFileTransfer": "Передача файла завершена",
|
||||
"ExceedTransferSize": "Превышен максимальный размер передачи",
|
||||
@ -29,65 +29,65 @@
|
||||
"FileManagement": "Управление файлами",
|
||||
"FileManagementExpired": "Текущая сессия управления файлами истекла.",
|
||||
"FileUploadInterrupted": "Передача файла прервана",
|
||||
"GetShareUser": "введите имя пользователя",
|
||||
"GetShareUser": "Введите имя пользователя",
|
||||
"Hotkeys": "Горячие клавиши",
|
||||
"InputVerifyCode": "Пожалуйста, введите код подтверждения",
|
||||
"JoinShare": "присоединиться к общему доступу",
|
||||
"InputVerifyCode": "Введите код подтверждения",
|
||||
"JoinShare": "Присоединиться к общей сессии",
|
||||
"JoinedWithSuccess": "Успешно присоединился",
|
||||
"KubernetesManagement": "Kubernetes 管理",
|
||||
"KubernetesManagement": "Управление Kubernetes",
|
||||
"LastModified": "Последнее время изменения",
|
||||
"LeaveShare": "Выйти из общего доступа",
|
||||
"LeftArrow": "Стрелка назад",
|
||||
"LeaveShare": "Покинуть общую сессию",
|
||||
"LeftArrow": "Стрелка влево",
|
||||
"LinkAddr": "Адрес ссылки",
|
||||
"List": "Список",
|
||||
"Minute": "минуты",
|
||||
"Minutes": "минут",
|
||||
"Minute": "Минуты",
|
||||
"Minutes": "Минут",
|
||||
"MustOneFile": "Можно выбрать только один файл",
|
||||
"MustSelectOneFile": "Необходимо выбрать файл",
|
||||
"Name": "Название",
|
||||
"NewFolder": "Создать папку",
|
||||
"NoLink": "без адреса",
|
||||
"OnlineUsers": "Онлайн участники",
|
||||
"NoLink": "Без адреса",
|
||||
"OnlineUsers": "Пользователи онлайн",
|
||||
"OperationSuccessful": "Операция выполнена успешно",
|
||||
"Paste": "вставить",
|
||||
"PauseSession": "Приостановить эту сессию",
|
||||
"Paste": "Вставить",
|
||||
"PauseSession": "Приостановить сессию",
|
||||
"PermissionDenied": "Нет разрешения",
|
||||
"PermissionExpired": "Срок действия прав истек",
|
||||
"PermissionExpired": "Разрешение истекло",
|
||||
"PermissionValid": "Доступ разрешен",
|
||||
"ReadOnly": "Только для чтения",
|
||||
"Reconnect": "Переподключение",
|
||||
"Refresh": "Обновить",
|
||||
"Remove": "Удалить",
|
||||
"RemoveShareUser": "вас удалили из общего сеанса",
|
||||
"RemoveShareUserConfirm": "Вы уверены, что хотите удалить этого пользователя?",
|
||||
"RemoveShareUser": "Вас удалили из общей сессии",
|
||||
"RemoveShareUserConfirm": "Вы действительно хотите удалить этого пользователя из сессии?",
|
||||
"Rename": "Переименовать",
|
||||
"ResumeSession": "Восстановить эту сессию",
|
||||
"RightArrow": "Стрелка вперед",
|
||||
"Search": "поиск",
|
||||
"SelectAction": "пожалуйста, выберите",
|
||||
"SelectTheme": "Пожалуйста, выберите тему",
|
||||
"ResumeSession": "Возобновить сессию",
|
||||
"RightArrow": "Стрелка вправо",
|
||||
"Search": "Поиск",
|
||||
"SelectAction": "Выберите действие",
|
||||
"SelectTheme": "Выберите тему",
|
||||
"Self": "Я",
|
||||
"Settings": "Настройки",
|
||||
"Share": "Поделиться",
|
||||
"ShareUser": "Поделиться с пользователем",
|
||||
"ShareUserHelpText": "Пользователь не выбран, разрешить вход всем",
|
||||
"ShareUser": "Поделиться с",
|
||||
"ShareUserHelpText": "Пустое поле означает, что присоединиться может каждый желающий.",
|
||||
"Size": "Размер",
|
||||
"Sync": "Синхронизировать",
|
||||
"Sync": "Синхронизация",
|
||||
"SyncUserPreferenceFailed": "Ошибка синхронизации настроек",
|
||||
"SyncUserPreferenceSuccess": "Синхронизация настроек выполнена успешно",
|
||||
"Theme": "тему",
|
||||
"ThemeColors": "цвет темы",
|
||||
"ThemeConfig": "Тема",
|
||||
"SyncUserPreferenceSuccess": "Настройки успешно синхронизированы",
|
||||
"Theme": "Тема",
|
||||
"ThemeColors": "Цвета темы",
|
||||
"ThemeConfig": "Настройки темы",
|
||||
"TransferHistory": "История передачи",
|
||||
"Type": "Тип",
|
||||
"UpArrow": "Кнопка вверх",
|
||||
"Upload": "Загрузить",
|
||||
"UploadEnd": "Загрузка завершена, пожалуйста, подождите дальнейшей обработки",
|
||||
"UploadProgress": "Прогресс передачи",
|
||||
"UploadStart": "Начало загрузки",
|
||||
"UploadSuccess": "Загрузка прошла успешно",
|
||||
"UploadStart": "Загрузка началась",
|
||||
"UploadSuccess": "Загрузка успешно завершена",
|
||||
"UploadTips": "Перетащите файл сюда или нажмите для загрузки",
|
||||
"UploadTitle": "загрузить файл",
|
||||
"UploadTitle": "Загрузить файл",
|
||||
"User": "Пользователь",
|
||||
"VerifyCode": "Код подтверждения",
|
||||
"WaitFileTransfer": "Ожидание завершения передачи файла",
|
||||
|
@ -156,7 +156,7 @@
|
||||
"AssetLoginACLHelpMsg": "When logging into assets, it can be audited based on the user's login ip and time segment to determine whether the assets can be logged into",
|
||||
"AssetLoginACLHelpText": "When logging into assets, it can be audited based on the user's login ip and time segment to determine whether the assets can be logged into",
|
||||
"AssetName": "Asset name",
|
||||
"AssetPermission": "Authorization",
|
||||
"AssetPermission": "Authorizations",
|
||||
"AssetPermissionCreate": "Create asset authorization rule",
|
||||
"AssetPermissionDetail": "Asset authorization details",
|
||||
"AssetPermissionHelpMsg": "Asset authorization allows you to select users and assets, grant the assets to users for access. once completed, users can conveniently view these assets. additionally, you can set specific permissions to further define the users' rights to the assets.",
|
||||
|
1541
apps/i18n/lina/ko.json
Normal file
1541
apps/i18n/lina/ko.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
84
apps/i18n/lion/ko.json
Normal file
84
apps/i18n/lion/ko.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"ActionPerm": "작업 권한",
|
||||
"AutoFit": "자동 조정",
|
||||
"Cancel": "취소",
|
||||
"ClearDone": "정리 완료",
|
||||
"Clipboard": "클립보드",
|
||||
"Close": "닫기",
|
||||
"ConfirmBtn": "확인",
|
||||
"Connecting": "연결 중",
|
||||
"CopyLink": "링크 및 인증 코드 복사",
|
||||
"CopyShareURLSuccess": "공유 주소 복사 성공",
|
||||
"CreateLink": "공유 링크 생성",
|
||||
"CreateSuccess": "생성 성공",
|
||||
"Display": "공유",
|
||||
"ErrTitle": "연결 이상",
|
||||
"ExpiredTime": "유효 기간",
|
||||
"Files": "파일 관리",
|
||||
"GetShareUser": "사용자 이름을 입력하세요",
|
||||
"GuaErrClientUnauthorized": "사용자 이름과 비밀번호 인증 오류, 로그인 실패",
|
||||
"GuaErrSessionConflict": "다른 연결과 충돌로 인해 원격 데스크톱 서버가 본 연결을 종료했습니다. 나중에 다시 시도해 주세요.",
|
||||
"GuaErrUnSupport": "해당 작업 요청이 금지되었습니다",
|
||||
"GuaErrUpStreamTimeout": "원격 데스크톱 서버가 응답하지 않습니다",
|
||||
"GuaErrUpstreamNotFound": "원격 데스크톱 서버에 연결할 수 없습니다 (네트워크 도달 불가 | 보안 정책 오류)",
|
||||
"GuacamoleErrAccessDenied": "원격 연결 접근이 거부되었습니다",
|
||||
"GuacamoleErrActiveSessionTimeLimitExceeded": "원격 연결의 활동 시간이 제한을 초과함",
|
||||
"GuacamoleErrAuthenticationFailure": "원격 연결 인증 실패",
|
||||
"GuacamoleErrConnectionFailed": "원격 연결 실패",
|
||||
"GuacamoleErrCredentialsExpired": "원격 연결의 자격 증명이 만료되었습니다",
|
||||
"GuacamoleErrDNSLookupFailed": "원격 연결의 DNS 조회 실패",
|
||||
"GuacamoleErrDisconnected": "원격 연결이 끊어짐",
|
||||
"GuacamoleErrDisconnectedByOtherConnection": "원격 연결이 다른 연결에 의해 끊겼습니다",
|
||||
"GuacamoleErrForciblyDisconnected": "원격 연결이 강제로 종료되었습니다",
|
||||
"GuacamoleErrIdleSessionTimeLimitExceeded": "원격 연결의 유휴 시간이 제한을 초과했습니다",
|
||||
"GuacamoleErrInsufficientPrivileges": "원격 연결 사용자 권한 부족",
|
||||
"GuacamoleErrLoggedOff": "원격 연결 사용자가 로그아웃되었습니다",
|
||||
"GuacamoleErrManuallyDisconnected": "원격 연결이 수동으로 끊어졌습니다",
|
||||
"GuacamoleErrManuallyLoggedOff": "원격 연결의 사용자가 수동으로 로그아웃되었습니다.",
|
||||
"GuacamoleErrSSLTLSConnectionFailed": "원격 연결 SSL/TLS 연결 실패",
|
||||
"GuacamoleErrSecurityNegotiationFailed": "원격 연결의 보안 협상이 실패했습니다",
|
||||
"GuacamoleErrServerRefusedConnection": "원격 연결의 서버가 연결을 거부함",
|
||||
"GuacamoleErrServerRefusedConnectionBySecurityType": "원격 연결의 서버에서 연결을 거부했습니다. 보안 유형이 일치하지 않을 수 있습니다",
|
||||
"GuacamoleErrUnableToConnectToVNCServer": "VNC 서버에 연결할 수 없습니다",
|
||||
"GuacamoleErrUnsupportedCredentialTypeRequested": "원격 연결의 자격 증명 유형이 지원되지 않습니다",
|
||||
"GuacamoleErrUpstreamError": "원격 연결의 서버에서 오류가 발생했습니다",
|
||||
"JMSErrAPIFailed": "Core API 오류가 발생했습니다",
|
||||
"JMSErrAuthUser": "사용자 인증되지 않음",
|
||||
"JMSErrBadParams": "요청 매개변수 오류",
|
||||
"JMSErrDisconnected": "대화 연결이 끊어졌습니다",
|
||||
"JMSErrGatewayFailed": "게이트웨이 연결 실패",
|
||||
"JMSErrGuacamoleServer": "Guacamole 서버에 연결할 수 없음",
|
||||
"JMSErrIdleTimeOut": "최대 유휴 시간 {PLACEHOLDER}분 초과, 연결 종료",
|
||||
"JMSErrMaxSession": "최대 세션 시간 {PLACEHOLDER}시간을 초과하여 연결이 끊겼습니다",
|
||||
"JMSErrNoSession": "세션을 찾을 수 없음",
|
||||
"JMSErrPermission": "연결 권한이 없습니다",
|
||||
"JMSErrPermissionExpired": "권한이 만료되어 연결이 끊어졌습니다",
|
||||
"JMSErrRemoveShareUser": "당신은 공유 세션에서 제거되었습니다",
|
||||
"JMSErrTerminatedByAdmin": "관리자 세션 종료",
|
||||
"JoinShare": "공유 참여",
|
||||
"LeaveShare": "공유에서 나가기",
|
||||
"LinkAddr": "링크 주소",
|
||||
"Minute": "분",
|
||||
"Minutes": "분",
|
||||
"OK": "확인",
|
||||
"Password": "비밀번호",
|
||||
"PauseSession": "세션 일시 중지",
|
||||
"ReadOnly": "읽기 전용",
|
||||
"RemoveShareUserConfirm": "이 사용자를 제거하시겠습니까?",
|
||||
"RequireParams": "필수 매개변수",
|
||||
"ResumeSession": "세션 복원",
|
||||
"SelectAction": "선택해 주세요",
|
||||
"Settings": "설정",
|
||||
"Share": "공유",
|
||||
"ShareUser": "공유 사용자",
|
||||
"ShareUserHelpText": "사용자가 선택되지 않았습니다, 즉 모든 사용자가 참여할 수 있습니다",
|
||||
"Shortcuts": "단축키",
|
||||
"Skip": "건너뛰기",
|
||||
"Submit": "제출",
|
||||
"UploadFile": "파일 업로드",
|
||||
"UploadSuccess": "업로드 성공",
|
||||
"Username": "사용자 이름",
|
||||
"VerifyCode": "인증 코드",
|
||||
"WebSocketError": "WebSocket 연결에 실패했습니다. 네트워크를 확인해 주세요",
|
||||
"Writable": "읽기 및 쓰기"
|
||||
}
|
@ -1,77 +1,77 @@
|
||||
{
|
||||
"ActionPerm": "Права на действие",
|
||||
"AutoFit": "Автоматическая адаптация",
|
||||
"ActionPerm": "Разрешения на действия",
|
||||
"AutoFit": "Автоподбор",
|
||||
"Cancel": "Отмена",
|
||||
"ClearDone": "Очистка завершена",
|
||||
"ClearDone": "Очистка завершенных",
|
||||
"Clipboard": "Буфер обмена",
|
||||
"Close": "Закрыть",
|
||||
"ConfirmBtn": "ОК",
|
||||
"Connecting": "Соединение в процессе",
|
||||
"ConfirmBtn": "Подтвердить",
|
||||
"Connecting": "Подключение",
|
||||
"CopyLink": "Скопировать ссылку и код подтверждения",
|
||||
"CopyShareURLSuccess": "Копирование адреса для分享 успешно",
|
||||
"CreateLink": "Создать ссылку для совместного доступа",
|
||||
"CreateSuccess": "Создание прошло успешно",
|
||||
"CopyShareURLSuccess": "URL общего доступа успешно скопирован",
|
||||
"CreateLink": "Создать ссылку для общего доступа",
|
||||
"CreateSuccess": "Успешно создано",
|
||||
"Display": "Показать",
|
||||
"ErrTitle": "Ошибка соединения",
|
||||
"ErrTitle": "Ошибка подключения",
|
||||
"ExpiredTime": "Срок действия",
|
||||
"Files": "Управление файлами",
|
||||
"GetShareUser": "Введите имя пользователя",
|
||||
"GuaErrClientUnauthorized": "Ошибка аутентификации имени пользователя и пароля, вход не удался",
|
||||
"GuaErrSessionConflict": "Соединение с удаленным рабочим столом было закрыто из-за конфликта с другим соединением. Пожалуйста, попробуйте позже.",
|
||||
"GuaErrUnSupport": "Данный запрос действия запрещен",
|
||||
"GuaErrUpStreamTimeout": "Сервер удаленного рабочего стола не отвечает",
|
||||
"GuaErrUpstreamNotFound": "Не удается подключиться к серверу удаленного рабочего стола (сеть недоступна | ошибка политик безопасности)",
|
||||
"GuacamoleErrAccessDenied": "Доступ к удаленному соединению запрещен.",
|
||||
"GuacamoleErrActiveSessionTimeLimitExceeded": "Время активности удаленного подключения превысило лимит",
|
||||
"GuaErrClientUnauthorized": "Ошибка авторизации: неверное имя пользователя или пароль, вход не выполнен",
|
||||
"GuaErrSessionConflict": "Конфликт сессий: подключение закрыто удаленным сервером из-за конфликта с другим подключением. Попробуйте позже.",
|
||||
"GuaErrUnSupport": "Запрашиваемая операция не поддерживается",
|
||||
"GuaErrUpStreamTimeout": "Время ожидания истекло: удаленный сервер не отвечает",
|
||||
"GuaErrUpstreamNotFound": "Не удается подключиться к удаленному серверу (сеть недоступна | ошибка политики безопасности)",
|
||||
"GuacamoleErrAccessDenied": "Доступ запрещен сервером (учетная запись заблокирована/отключена?)",
|
||||
"GuacamoleErrActiveSessionTimeLimitExceeded": "Превышено время активности сессии удаленного подключения",
|
||||
"GuacamoleErrAuthenticationFailure": "Ошибка аутентификации удалённого подключения",
|
||||
"GuacamoleErrConnectionFailed": "Удалённое подключение не удалось",
|
||||
"GuacamoleErrCredentialsExpired": "Срок действия учетных данных удаленного подключения истек",
|
||||
"GuacamoleErrDNSLookupFailed": "Ошибка DNS удалённого подключения",
|
||||
"GuacamoleErrDNSLookupFailed": "Ошибка запроса DNS для удаленного подключения",
|
||||
"GuacamoleErrDisconnected": "Удалённое подключение разорвано",
|
||||
"GuacamoleErrDisconnectedByOtherConnection": "Удаленное соединение было разорвано другим соединением",
|
||||
"GuacamoleErrDisconnectedByOtherConnection": "Удаленное подключение было разорвано другим соединением",
|
||||
"GuacamoleErrForciblyDisconnected": "Удаленное соединение было принудительно разорвано",
|
||||
"GuacamoleErrIdleSessionTimeLimitExceeded": "Время бездействия удаленного соединения превышает лимит",
|
||||
"GuacamoleErrInsufficientPrivileges": "Пользователь с удалённым подключением не имеет достаточных прав",
|
||||
"GuacamoleErrLoggedOff": "Пользователь с удалённым подключением вышел",
|
||||
"GuacamoleErrIdleSessionTimeLimitExceeded": "Время простоя удаленного подключения превышено",
|
||||
"GuacamoleErrInsufficientPrivileges": "У пользователя недостаточно прав для удаленного подключения",
|
||||
"GuacamoleErrLoggedOff": "Пользователь удаленного соединения вышел",
|
||||
"GuacamoleErrManuallyDisconnected": "Удаленное соединение было разорвано вручную",
|
||||
"GuacamoleErrManuallyLoggedOff": "Пользователь удаленного подключения был вручную отключен",
|
||||
"GuacamoleErrSSLTLSConnectionFailed": "SSL/TLS соединение удаленного соединения не удалось",
|
||||
"GuacamoleErrSecurityNegotiationFailed": "Безопасные переговоры удаленного соединения не удались",
|
||||
"GuacamoleErrServerRefusedConnection": "Сервер удаленного соединения отклонил запрос",
|
||||
"GuacamoleErrServerRefusedConnectionBySecurityType": "Удаленный сервер отклонил соединение, возможно, тип безопасности не совпадает",
|
||||
"GuacamoleErrUnableToConnectToVNCServer": "Невозможно подключиться к VNC серверу",
|
||||
"GuacamoleErrUnsupportedCredentialTypeRequested": "Тип учетных данных для удаленного подключения не поддерживается.",
|
||||
"GuacamoleErrUpstreamError": "На сервере удалённого подключения произошла ошибка",
|
||||
"JMSErrAPIFailed": "Произошла ошибка Core API",
|
||||
"JMSErrAuthUser": "Пользователь не аутентифицирован.",
|
||||
"GuacamoleErrSSLTLSConnectionFailed": "Ошибка соединения SSL/TLS (недоверенный/самозаверенный сертификат?)",
|
||||
"GuacamoleErrSecurityNegotiationFailed": "Не удалось согласовать безопасность (неверный тип безопасности?)",
|
||||
"GuacamoleErrServerRefusedConnection": "Сервер удаленного соединения отклонил подключение",
|
||||
"GuacamoleErrServerRefusedConnectionBySecurityType": "Сервер отклонил подключение (неверный тип безопасности?)",
|
||||
"GuacamoleErrUnableToConnectToVNCServer": "Не удается подключиться к VNC серверу",
|
||||
"GuacamoleErrUnsupportedCredentialTypeRequested": "Запрашиваемый тип учетных данных не поддерживается",
|
||||
"GuacamoleErrUpstreamError": "Ошибка на стороне удаленного сервера",
|
||||
"JMSErrAPIFailed": "Ошибка API Core",
|
||||
"JMSErrAuthUser": "Пользователь не аутентифицирован",
|
||||
"JMSErrBadParams": "Ошибка параметров запроса",
|
||||
"JMSErrDisconnected": "Соединение сеанса было разорвано.",
|
||||
"JMSErrDisconnected": "Соединение сессии разорвано",
|
||||
"JMSErrGatewayFailed": "Ошибка подключения к шлюзу",
|
||||
"JMSErrGuacamoleServer": "Не удаётся подключиться к серверу Guacamole",
|
||||
"JMSErrIdleTimeOut": "Превышено максимальное время бездействия {PLACEHOLDER} минут, соединение разорвано.",
|
||||
"JMSErrGuacamoleServer": "Не удается подключиться к серверу Guacamole",
|
||||
"JMSErrIdleTimeOut": "Превышено максимальное время простоя {PLACEHOLDER} минут, соединение разорвано",
|
||||
"JMSErrMaxSession": "Превышено максимальное время сессии {PLACEHOLDER} часов, соединение разорвано",
|
||||
"JMSErrNoSession": "Не удалось найти сессию",
|
||||
"JMSErrPermission": "Нет прав на подключение",
|
||||
"JMSErrPermissionExpired": "Авторизация истекла, соединение разорвано.",
|
||||
"JMSErrRemoveShareUser": "Вы были исключены из общей сессии",
|
||||
"JMSErrTerminatedByAdmin": "Администратор завершил сессию",
|
||||
"JoinShare": "Добавить общий доступ",
|
||||
"LeaveShare": "Выйти из общего доступа",
|
||||
"LinkAddr": "Ссылка",
|
||||
"JMSErrNoSession": "Сессия не найдена",
|
||||
"JMSErrPermission": "Нет прав для подключения",
|
||||
"JMSErrPermissionExpired": "Авторизация истекла, соединение разорвано",
|
||||
"JMSErrRemoveShareUser": "Вы были удалены из общей сессии",
|
||||
"JMSErrTerminatedByAdmin": "Сессия прервана администратором",
|
||||
"JoinShare": "Присоединиться к общей сессии",
|
||||
"LeaveShare": "Покинуть общую сессию",
|
||||
"LinkAddr": "Адрес ссылки",
|
||||
"Minute": "Минуты",
|
||||
"Minutes": "Минуты",
|
||||
"OK": "Подтвердить",
|
||||
"Minutes": "Минут",
|
||||
"OK": "ОК",
|
||||
"Password": "Пароль",
|
||||
"PauseSession": "Приостановить сессию",
|
||||
"ReadOnly": "Только для чтения",
|
||||
"RemoveShareUserConfirm": "Вы уверены, что хотите удалить этого пользователя?",
|
||||
"RequireParams": "Обязательный параметр",
|
||||
"ResumeSession": "Восстановление сессии",
|
||||
"SelectAction": "Пожалуйста, выберите",
|
||||
"RemoveShareUserConfirm": "Вы действительно хотите удалить этого пользователя из сессии?",
|
||||
"RequireParams": "Обязательные параметры",
|
||||
"ResumeSession": "Возобновить сессию",
|
||||
"SelectAction": "Выберите действие",
|
||||
"Settings": "Настройки",
|
||||
"Share": "Поделиться",
|
||||
"ShareUser": "Поделиться с пользователем",
|
||||
"ShareUserHelpText": "Пользователь не выбран, разрешено присоединение всем",
|
||||
"ShareUser": "Поделиться с",
|
||||
"ShareUserHelpText": "Пустое поле означает, что присоединиться может каждый желающий",
|
||||
"Shortcuts": "Горячие клавиши",
|
||||
"Skip": "Пропустить",
|
||||
"Submit": "Отправить",
|
||||
@ -80,5 +80,5 @@
|
||||
"Username": "Имя пользователя",
|
||||
"VerifyCode": "Код подтверждения",
|
||||
"WebSocketError": "Ошибка соединения WebSocket, проверьте сеть",
|
||||
"Writable": "Чтение и запись"
|
||||
"Writable": "Редактируемый"
|
||||
}
|
@ -39,7 +39,6 @@
|
||||
"Collapse": "Collapse",
|
||||
"Command": "Command",
|
||||
"Command Line": "Command Line",
|
||||
"Command line": "Command line",
|
||||
"CommandBar": "Command bar",
|
||||
"Confirm": "Confirm",
|
||||
"Connect": "Connect",
|
||||
|
@ -38,7 +38,6 @@
|
||||
"Close split connect": "Cerrar pantalla dividida",
|
||||
"Command": "Comando",
|
||||
"Command Line": "Línea de comando",
|
||||
"Command line": "Conectar línea de comandos",
|
||||
"CommandBar": "Barra de comandos",
|
||||
"Confirm": "Confirmar",
|
||||
"Connect": "Conectar",
|
||||
|
@ -38,7 +38,6 @@
|
||||
"Close split connect": "分割表示を閉じる",
|
||||
"Command": "命令",
|
||||
"Command Line": "コマンドライン",
|
||||
"Command line": "命令行",
|
||||
"CommandBar": "コマンドバー",
|
||||
"Confirm": "確認",
|
||||
"Connect": "接続",
|
||||
|
236
apps/i18n/luna/ko.json
Normal file
236
apps/i18n/luna/ko.json
Normal file
@ -0,0 +1,236 @@
|
||||
{
|
||||
"ACL reject login asset": "이번 로그인은 접근 제어 정책의 제한으로 인해 거부되었습니다.",
|
||||
"Account info": "계정 정보",
|
||||
"Account not found": "계정 없음",
|
||||
"Account: ": "계정: {{value}}",
|
||||
"Action: ": "작업:",
|
||||
"Advanced option": "고급 옵션",
|
||||
"All sessions": "모든 세션",
|
||||
"Applet": "원격 애플리케이션",
|
||||
"Applet connect method": "원격 애플리케이션 연결 방식",
|
||||
"Are you sure to reconnect it?(RDP not support)": "정말로 재연결하시겠습니까? (RDP는 현재 지원하지 않습니다)",
|
||||
"Asset disabled": "해당 자산이 비활성화되었습니다. 관리자에게 문의해 주십시오.",
|
||||
"Asset not found or You have no permission to access it, please refresh asset tree": "자산을 찾을 수 없거나 접근 권한이 없습니다. 자산 트리를 새로 고치세요.",
|
||||
"Asset tree loading method": "자산 트리 로딩 방식 설정",
|
||||
"Asset: ": "자산: {{value}}",
|
||||
"Assignees": "접수인",
|
||||
"Auto": "자동",
|
||||
"Automatic login next": "다음 번에 자동으로 로그인 (자산 연결을 우클릭하여 다시 선택할 수 있습니다)",
|
||||
"Backspace as Ctrl+H": "문자 터미널 백스페이스는 Ctrl+H로",
|
||||
"Batch actions": "대량 작업",
|
||||
"Batch connect": "배치 연결",
|
||||
"Belgian French keyboard layout": "벨기에 프랑스어 (Azerty)",
|
||||
"CLI": "명령행",
|
||||
"CLI font size": "문자 터미널 글꼴 크기",
|
||||
"Cancel": "취소",
|
||||
"Charset": "문자 집합",
|
||||
"Checkbox": "다중 선택",
|
||||
"Choose a User": "사용자를 선택하세요",
|
||||
"Click to copy": "클릭하여 복사",
|
||||
"Client": "클라이언트",
|
||||
"Clone Connect": "창 복사",
|
||||
"Close": "닫기",
|
||||
"Close All Tabs": "모두 닫기",
|
||||
"Close Current Tab": "현재 종료",
|
||||
"Close Left Tabs": "왼쪽 닫기",
|
||||
"Close Other Tabs": "다른 종료",
|
||||
"Close Right Tabs": "오른쪽 닫기",
|
||||
"Close split connect": "분할 화면 종료",
|
||||
"Command": "명령",
|
||||
"Command Line": "명령어 줄",
|
||||
"CommandBar": "명령어 바",
|
||||
"Confirm": "확인",
|
||||
"Connect": "연결",
|
||||
"Connect checked": "선택한 연결",
|
||||
"Connect command line": "명령 줄 연결",
|
||||
"Connect method": "연결 방식",
|
||||
"ConnectionTime": "연결 시간",
|
||||
"Copied": "복사 완료",
|
||||
"Copy link": "링크 복사",
|
||||
"Current online": "현재 온라인",
|
||||
"Current session": "현재 세션",
|
||||
"DarkBlue": "딥 블루",
|
||||
"Database": "데이터베이스",
|
||||
"Database connect info": "데이터베이스 연결 정보",
|
||||
"Database disabled": "이 링크 방식은 지원되지 않습니다. 관리자에게 문의하십시오.",
|
||||
"Database info": "데이터베이스 정보",
|
||||
"Database token help text": "데이터베이스 유형 토큰은 5분 동안 캐시됩니다. 즉, 토큰 사용 후 즉시 만료되지 않고, 클라이언트가 5분 후에 연결이 끊어져야 이 토큰이 완전히 만료됩니다.",
|
||||
"Databases": "데이터베이스",
|
||||
"Default": "기본값",
|
||||
"Directly": "사용자는 연결할 자산과 계정을 지정합니다.",
|
||||
"Disable auto completion": "자동 완성 비활성화",
|
||||
"Disconnect": "연결 끊기",
|
||||
"Disfavor": "즐겨찾기 취소",
|
||||
"Do not close this page": "이 페이지를 닫지 마십시오.",
|
||||
"Document": "문서",
|
||||
"Don't prompt again": "다음 번에 다시 알리지 않음",
|
||||
"Download": "다운로드",
|
||||
"Download the latest client": "최신 클라이언트를 다운로드하세요.",
|
||||
"Driver redirect": "디스크 장착",
|
||||
"Expand": "확장",
|
||||
"Expand all": "모두 확장",
|
||||
"Expand all asset": "노드 아래의 모든 자산 확장",
|
||||
"Expire time": "만료 시간",
|
||||
"Face online required": "이번 로그인에서는 얼굴 인증 및 모니터링이 필요합니다. 계속하시겠습니까?",
|
||||
"Face verify required": "이번 로그인에는 얼굴 인식이 필요합니다. 계속하시겠습니까?",
|
||||
"Face verify success": "얼굴 인식 성공",
|
||||
"Failed to open address": "주소 열기 실패",
|
||||
"Favorite": "즐겨찾기",
|
||||
"File Manager": "파일 관리",
|
||||
"Fold": "접기",
|
||||
"Fold all": "모두 접기",
|
||||
"Force refresh": "강제 새로 고침",
|
||||
"Found": "발견",
|
||||
"French keyboard layout": "프랑스어 (Azerty)",
|
||||
"Full Screen": "전체 화면 표시",
|
||||
"Full screen": "전체 화면",
|
||||
"GUI": "그래픽화",
|
||||
"General": "기본 설정",
|
||||
"Go to Settings": "설정으로 가기",
|
||||
"Help": "도움",
|
||||
"Help or download": "메뉴 도움 → 다운로드",
|
||||
"Help text": "설명",
|
||||
"Hide left manager": "좌측 메뉴 숨기기",
|
||||
"High-speed broadband (2 Mbps – 10 Mbps )": "고속 광대역 (2 Mbps - 10 Mbps)",
|
||||
"Host": "호스트",
|
||||
"Info": "알림",
|
||||
"InstallClientMsg": "JumpServer 클라이언트가 설치되어 있지 않습니다. 지금 설치하러 가시겠습니까?",
|
||||
"Japanese keyboard layout": "일본어 (Qwerty)",
|
||||
"Keyboard keys": "옵션 + Shift + 왼쪽 / 오른쪽",
|
||||
"Keyboard layout": "키보드 레이아웃",
|
||||
"Keyboard switch session": "세션 전환 → 단축키",
|
||||
"Kubernetes": "Kubernetes",
|
||||
"Language": "언어",
|
||||
"Last login": "지난 로그인",
|
||||
"Launch Program": "프로그램 시작",
|
||||
"LeftInfo": "명령 기록을 클릭하면 녹화를 빠르게 위치할 수 있습니다",
|
||||
"Load tree async": "비동기 자산 트리 로딩",
|
||||
"Loading": "로딩 중",
|
||||
"Log out": "로그아웃",
|
||||
"Login reminder": "로그인 알림",
|
||||
"Login review approved": "로그인 심사가 완료되었으며, 자산에 연결 중입니다...",
|
||||
"LoginExpireMsg": "현재 로그인 세션이 만료되었습니다. 창을 닫지 마세요. 새로운 창에서 로그인을 하시면 현재 페이지를 복원할 수 있습니다. 복원이 되지 않을 경우 페이지를 새로 고치세요.",
|
||||
"Low Speed Broadband (256 Kbps - 2 Mbps)": "저속 광대역 (256 Kbps - 2 Mbps)",
|
||||
"Manual accounts": "수동 계정",
|
||||
"Module": "모듈",
|
||||
"Multi Screen": "다중 화면 표시",
|
||||
"My applications": "내 애플리케이션",
|
||||
"My assets": "내 자산",
|
||||
"Name": "이름",
|
||||
"Native": "클라이언트",
|
||||
"Need review for login asset": "이번 로그인은 수동 검토가 필요합니다. 계속하시겠습니까?",
|
||||
"Need to use": "사용이 필요합니다.",
|
||||
"No": "아니오",
|
||||
"No account available": "이용 가능한 계정이 없습니다",
|
||||
"No available connect method": "사용 가능한 연결 방법이 없습니다.",
|
||||
"No facial features": "얼굴 특징이 없습니다. 개인 정보 페이지로 이동하여 연결해 주세요.",
|
||||
"No matching found": "일치하는 항목이 없습니다.",
|
||||
"No permission": "권한이 없습니다.",
|
||||
"No protocol available": "사용 가능한 프로토콜이 없습니다.",
|
||||
"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": "비밀번호는 시스템에 로그인할 때 사용하는 비밀번호입니다.",
|
||||
"Pause": "일시 중지",
|
||||
"Pause task has been send": "작업 일시중지 명령이 전송되었습니다.",
|
||||
"Play List": "재생 목록",
|
||||
"Please choose an account": "사용자를 선택하십시오.",
|
||||
"Please input password": "비밀번호를 입력하세요.",
|
||||
"Port": "포트",
|
||||
"Protocol": "프로토콜",
|
||||
"Protocol: ": "협의: {{value}}",
|
||||
"RDP Client": "RDP 클라이언트",
|
||||
"RDP File": "RDP 파일",
|
||||
"RDP client options": "RDP 클라이언트 옵션",
|
||||
"RDP color quality": "RDP 색상 품질",
|
||||
"RDP connection speed": "RDP 연결 속도",
|
||||
"RDP resolution": "RDP 해상도",
|
||||
"RDP smart size": "RDP 스마트 크기",
|
||||
"Re-use for a long time after opening": "연결 정보가 활성화되면 장시간 동안 여러 번 사용할 수 있습니다.",
|
||||
"Reconnect": "재연결",
|
||||
"Refresh": "새로 고침",
|
||||
"Remember password": "비밀번호 기억하기",
|
||||
"Remember select": "선택 기억하기",
|
||||
"RemoteApp": "원격 애플리케이션",
|
||||
"Reselect connection method": "연결 방식을 다시 선택할 수 있습니다",
|
||||
"Resume": "복구",
|
||||
"Resume task has been send": "작업 복구가 전송되었습니다",
|
||||
"Right click asset": "자산을 마우스 오른쪽 버튼으로 클릭 → 연결",
|
||||
"Right click node": "노드 오른쪽 클릭 → 모두 확장",
|
||||
"Right mouse quick paste": "우클릭하여 빠르게 붙여넣기",
|
||||
"Run it by client": "클라이언트를 사용하여 실행",
|
||||
"SQL Client": "SQL 클라이언트",
|
||||
"Save command": "저장 명령",
|
||||
"Save success": "저장 성공",
|
||||
"Search": "검색",
|
||||
"Select account": "계정 선택",
|
||||
"Send command": "명령 전송",
|
||||
"Send text to all ssh terminals": "모든 SSH 터미널로 텍스트 전송",
|
||||
"Session": "세션",
|
||||
"SessionIsBeingMonitored": "세션이 모니터링되고 있습니다.",
|
||||
"Set reusable": "재사용 활성화",
|
||||
"Setting": "설정",
|
||||
"Settings or basic settings": "메뉴 설정 → 기본 설정",
|
||||
"ShareSession": "세션 공유",
|
||||
"Show left manager": "왼쪽 사이드바 표시",
|
||||
"Skip": "건너뛰기",
|
||||
"Skip manual password": "수동 비밀번호 창 건너뛰기",
|
||||
"Special accounts": "특별 계정",
|
||||
"Speed": "속도",
|
||||
"Split connect": "분할 화면 연결",
|
||||
"Split connect number": "하나의 세션은 최대 3개의 분할 화면 연결을 지원합니다.",
|
||||
"Split vertically": "수직 분할 화면",
|
||||
"SplitVertical": "수직 분할",
|
||||
"Start Time: ": "시작 시간: {{value}}",
|
||||
"Stop": "중지",
|
||||
"Support": "지원",
|
||||
"Swiss French keyboard layout": "스위스 프랑스어 (Qwertz)",
|
||||
"Switch to input command": "명령어 입력으로 전환",
|
||||
"Switch to quick command": "빠른 명령으로 전환",
|
||||
"Tab List": "창 목록",
|
||||
"Tabs": "창",
|
||||
"The connection method is invalid, please refresh the page": "해당 연결 방식은 만료되었습니다. 페이지를 새로고침하십시오.",
|
||||
"Theme": "주제",
|
||||
"Ticket review approved for login asset": "이번 로그인 검토가 통과되었습니다. 자산에 연결하시겠습니까?",
|
||||
"Ticket review closed for login asset": "이번 로그인 검토가 종료되어 자산에 연결할 수 없습니다.",
|
||||
"Ticket review pending for login asset": "로그인 신청이 제출되었습니다. 수리 담당자가 검토하기를 기다리고 있습니다. 해당 링크를 복사하여 그에게 보낼 수도 있습니다.",
|
||||
"Ticket review rejected for login asset": "이번 로그인 심사가 거부되었습니다. 자산에 연결할 수 없습니다.",
|
||||
"Tips": "提示",
|
||||
"Token expired": "토큰이 만료되었습니다. 다시 연결해 주세요.",
|
||||
"Tool download": "도구 다운로드",
|
||||
"Turkey keyboard layout": "터키어-Q (Qwerty)",
|
||||
"TurnOffReminders": "현재 연결을 종료하시겠습니까?",
|
||||
"Type tree": "유형 트리",
|
||||
"UK English keyboard layout": "영국 영어 (Qwerty)",
|
||||
"US English keyboard layout": "US English (Qwerty)",
|
||||
"User": "사용자",
|
||||
"User: ": "사용자: {{value}}",
|
||||
"Username": "사용자 이름",
|
||||
"Username@Domain": "사용자 이름@AD 도메인",
|
||||
"Users": "사용자",
|
||||
"Using token": "토큰 사용",
|
||||
"View": "보기",
|
||||
"Viewer": "조회자",
|
||||
"VirtualApp": "가상 애플리케이션",
|
||||
"Web Terminal": "웹 터미널",
|
||||
"Website": "공식 웹사이트",
|
||||
"With secret accounts": "호스팅 계정",
|
||||
"Yes": "예",
|
||||
"asset": "자산",
|
||||
"cols": "열 수",
|
||||
"confirm": "확인",
|
||||
"connect info": "연결 정보",
|
||||
"connectDisabledTipsMethodDisabled": "알림: 유효한 원격 애플리케이션 배포기를 찾을 수 없습니다. 현재 리소스에 연결할 수 없으니 관리자에게 문의해 주시기 바랍니다.",
|
||||
"connectDisabledTipsNoAccount": "알림: 유효한 권한 계정을 찾을 수 없습니다. 현재 리소스에 연결할 수 없습니다. 관리자를 통해 처리해 주시기 바랍니다",
|
||||
"connectDisabledTipsNoConnectMethod": "알림: 유효한 연결 방식을 찾을 수 없습니다. 현재 리소스에 연결할 수 없습니다. 관리자에게 문의하여 처리하십시오.",
|
||||
"download": "다운로드",
|
||||
"recordingIsBeingDownloaded": "비디오가 다운로드 중입니다. 잠시만 기다려 주십시오.",
|
||||
"rows": "행 수",
|
||||
"start time": "시작 시간",
|
||||
"success": "성공",
|
||||
"system user": "시스템 사용자",
|
||||
"user": "사용자"
|
||||
}
|
@ -38,7 +38,6 @@
|
||||
"Close split connect": "Fechar divisão de tela",
|
||||
"Command": "Comando",
|
||||
"Command Line": "Linha de Comando",
|
||||
"Command line": " Conectar To Line Command ",
|
||||
"CommandBar": "Barra de comandos",
|
||||
"Confirm": "Confirmar",
|
||||
"Connect": "Conectar",
|
||||
|
@ -1,144 +1,144 @@
|
||||
{
|
||||
"ACL reject login asset": "Этот вход был отклонён из-за ограничений политики контроля доступа",
|
||||
"Account info": "Информация об аккаунте",
|
||||
"ACL reject login asset": "Вход в актив отклонен из-за ограничений политики контроля доступа",
|
||||
"Account info": "Информация об учетной записи",
|
||||
"Account not found": "Учетная запись не найдена",
|
||||
"Account: ": "Учетная запись: {{value}}",
|
||||
"Action: ": "Операция:",
|
||||
"Action: ": "Действие:",
|
||||
"Advanced option": "Расширенные настройки",
|
||||
"All sessions": "Все сеансы",
|
||||
"All sessions": "Все сессии",
|
||||
"Applet": "Удаленное приложение",
|
||||
"Applet connect method": "Способ подключения к удалённому приложению",
|
||||
"Are you sure to reconnect it?(RDP not support)": "Вы уверены, что хотите переподключиться? (RDP временно не поддерживается)",
|
||||
"Asset disabled": "Этот актив был отключен, пожалуйста, свяжитесь с администратором",
|
||||
"Asset not found or You have no permission to access it, please refresh asset tree": "Актив не найден или у вас нет на него прав доступа, пожалуйста, обновите дерево активов",
|
||||
"Asset tree loading method": "Настройка способа загрузки дерева активов",
|
||||
"Are you sure to reconnect it?(RDP not support)": "Вы уверены, что хотите переподключиться? (RDP не поддерживается)",
|
||||
"Asset disabled": "Актив отключен, пожалуйста, свяжитесь с администратором",
|
||||
"Asset not found or You have no permission to access it, please refresh asset tree": "Актив не найден или у вас нет прав на доступ к нему, пожалуйста, обновите дерево активов",
|
||||
"Asset tree loading method": "Способ загрузки дерева активов",
|
||||
"Asset: ": "Актив: {{value}}",
|
||||
"Assignees": "Ответственное лицо",
|
||||
"Auto": "Автоматически",
|
||||
"Automatic login next": "Автоматический вход в следующий раз (правый клик по подключению к активу для выбора заново)",
|
||||
"Backspace as Ctrl+H": "Символьный терминал Backspace как Ctrl+H",
|
||||
"Batch actions": "Массовые операции",
|
||||
"Automatic login next": "Автологин при следующем входе (ПКМ на активе для выбора способа заново)",
|
||||
"Backspace as Ctrl+H": "Backspace как Ctrl+H",
|
||||
"Batch actions": "Пакетные операции",
|
||||
"Batch connect": "Пакетное подключение",
|
||||
"Belgian French keyboard layout": "Бельгийский французский (Азерти)",
|
||||
"Belgian French keyboard layout": "Бельгийская французская раскладка (Azerty)",
|
||||
"CLI": "Командная строка",
|
||||
"CLI font size": "Размер шрифта терминала",
|
||||
"Cancel": "Отмена",
|
||||
"Charset": "Набор символов",
|
||||
"Checkbox": "Множественный выбор",
|
||||
"Choose a User": "выберите пользователя",
|
||||
"Click to copy": "Нажмите для копирования",
|
||||
"Charset": "Кодировка",
|
||||
"Checkbox": "Чекбокс",
|
||||
"Choose a User": "Выберите пользователя",
|
||||
"Click to copy": "Нажмите, чтобы скопировать",
|
||||
"Client": "Клиент",
|
||||
"Clone Connect": "Копировать окно",
|
||||
"Clone Connect": "Клонировать окно",
|
||||
"Close": "Закрыть",
|
||||
"Close All Tabs": "Закрыть все",
|
||||
"Close Current Tab": "Закрыть текущий",
|
||||
"Close Left Tabs": "Закрыть слева",
|
||||
"Close Other Tabs": "закрыть другие",
|
||||
"Close Right Tabs": "Закрыть справа",
|
||||
"Close All Tabs": "Закрыть все вкладки",
|
||||
"Close Current Tab": "Закрыть эту вкладку",
|
||||
"Close Left Tabs": "Закрыть вкладку слева",
|
||||
"Close Other Tabs": "Закрыть остальные вкладки",
|
||||
"Close Right Tabs": "Закрыть вкладку справа",
|
||||
"Close split connect": "Закрыть разделенный экран",
|
||||
"Command": "Команда",
|
||||
"Command Line": "командная строка",
|
||||
"Command Line": "Командная строка",
|
||||
"Command line": "Подключить командную строку",
|
||||
"CommandBar": "Командная строка",
|
||||
"Confirm": "Подтверждение",
|
||||
"CommandBar": "Панель команд",
|
||||
"Confirm": "Подтвердить",
|
||||
"Connect": "Подключение",
|
||||
"Connect checked": "Подключение выбрано",
|
||||
"Connect command line": "Подключение к командной строке",
|
||||
"Connect checked": "Подключить выбранное",
|
||||
"Connect command line": "Подключение из командной строки",
|
||||
"Connect method": "Способ подключения",
|
||||
"ConnectionTime": "Время соединения",
|
||||
"ConnectionTime": "Время подключения",
|
||||
"Copied": "Скопировано",
|
||||
"Copy link": "Копировать ссылку",
|
||||
"Current online": "Текущий онлайн",
|
||||
"Current session": "Текущий сеанс",
|
||||
"DarkBlue": "Глубокий синий",
|
||||
"Current online": "Сейчас онлайн",
|
||||
"Current session": "Текущая сессия",
|
||||
"DarkBlue": "Темно-синий",
|
||||
"Database": "База данных",
|
||||
"Database connect info": "Информация о подключении к базе данных",
|
||||
"Database disabled": "Данный способ подключения не поддерживается, пожалуйста, свяжитесь с администратором",
|
||||
"Database disabled": "Этот способ подключения не поддерживается, пожалуйста, свяжитесь с администратором",
|
||||
"Database info": "Информация о базе данных",
|
||||
"Database token help text": "Тип базы данных токена будет кэшироваться в течение 5 минут, что означает, что токен не будет немедленно недействительным после использования. Он станет полностью недействительным только через 5 минут после отключения клиента. \nКомандная строка \nАвтологин в следующий раз (щелкните правой кнопкой мыши на подключение к активу для повторного выбора) \nСкачать последнюю версию клиента \nМожно заново выбрать способ подключения \nМонтирование диска \nЗакрыть правую сторону \nОкно \nОтменить избранное \nФайловый менеджер \nСимвольный терминал Backspace как Ctrl+H \nБританский английский (Qwerty) \nКачество цвета RDP \nЗапомнить пароль \nАктивы",
|
||||
"Database token help text": "Токен подключения к базе данных будет кэшироваться в течение 5 минут, то есть токен будет аннулирован не сразу после использования, а через пять минут после отключения клиента",
|
||||
"Databases": "База данных",
|
||||
"Default": "По умолчанию",
|
||||
"Directly": "Имя пользователя указывает на связанные активы и аккаунт",
|
||||
"Disable auto completion": "Отключить автозаполнение",
|
||||
"Disconnect": "Отключить связь",
|
||||
"Disfavor": "Убрать из избранного",
|
||||
"Directly": "Имя пользователя указывает на связанные активы и УЗ",
|
||||
"Disable auto completion": "Отключить автодополнение",
|
||||
"Disconnect": "Отключить",
|
||||
"Disfavor": "Удалить из избранного",
|
||||
"Do not close this page": "Не закрывайте эту страницу",
|
||||
"Document": "Документ",
|
||||
"Don't prompt again": "В следующий раз не напоминать",
|
||||
"Don't prompt again": "Больше не показывать",
|
||||
"Download": "Скачать",
|
||||
"Download the latest client": "Скачать последнюю версию клиента",
|
||||
"Driver redirect": "Монтирование диска",
|
||||
"Expand": "Раскрыть",
|
||||
"Driver redirect": "Перенаправление диска",
|
||||
"Expand": "Развернуть",
|
||||
"Expand all": "Развернуть все",
|
||||
"Expand all asset": "Развернуть все активы под узлом",
|
||||
"Expand all asset": "Развернуть все активы в папке",
|
||||
"Expire time": "Срок действия",
|
||||
"Face online required": "Для данного входа требуется верификация лицом и мониторинг, продолжить?",
|
||||
"Face verify required": "Для входа требуется подтверждение лица, продолжить?",
|
||||
"Face verify success": "Успешная верификация лица",
|
||||
"Face online required": "Для входа требуется верификация по лицу и мониторинг. Продолжить?",
|
||||
"Face verify required": "Для входа требуется верификация по лицу. Продолжить?",
|
||||
"Face verify success": "Успешная верификация по лицу",
|
||||
"Failed to open address": "Не удалось открыть адрес",
|
||||
"Favorite": "Избранное",
|
||||
"File Manager": "Файловое управление",
|
||||
"File Manager": "Файловый менеджер",
|
||||
"Fold": "Свернуть",
|
||||
"Fold all": "Свернуть все",
|
||||
"Force refresh": "Принудительное обновление",
|
||||
"Found": "Обнаружение",
|
||||
"French keyboard layout": "Французский (Азерти)",
|
||||
"Full Screen": "Полноэкранный режим",
|
||||
"Found": "Найдено",
|
||||
"French keyboard layout": "Французская раскладка (Azerty)",
|
||||
"Full Screen": "Полный экран",
|
||||
"Full screen": "Во весь экран",
|
||||
"GUI": "Графический интерфейс",
|
||||
"General": "Основная конфигурация",
|
||||
"General": "Основные настройки",
|
||||
"Go to Settings": "Перейти в настройки",
|
||||
"Help": "Помощь",
|
||||
"Help or download": "Меню справки → Скачать",
|
||||
"Help or download": "Помощь → Скачать",
|
||||
"Help text": "Описание",
|
||||
"Hide left manager": "Скрыть левую панель",
|
||||
"High-speed broadband (2 Mbps – 10 Mbps )": "высокоскоростной интернет (2 Mbps - 10 Mbps)",
|
||||
"High-speed broadband (2 Mbps – 10 Mbps )": "Высокоскоростной интернет (2 Мбит/с – 10 Мбит/с)",
|
||||
"Host": "Хост",
|
||||
"Info": "Уведомление",
|
||||
"InstallClientMsg": "Клиент JumpServer не установлен, хотите перейти к загрузке и установке?",
|
||||
"Info": "Подсказка",
|
||||
"InstallClientMsg": "Клиент JumpServer не установлен, хотите скачать и установить?",
|
||||
"Japanese keyboard layout": "Японский (Qwerty)",
|
||||
"Keyboard keys": "Option + Shift + Left / Right",
|
||||
"Keyboard layout": "Раскладка клавиатуры",
|
||||
"Keyboard switch session": "Переключить сеанс → горячие клавиши",
|
||||
"Keyboard switch session": "Переключить сессию → Горячие клавиши",
|
||||
"Kubernetes": "Kubernetes",
|
||||
"Language": "язык",
|
||||
"Language": "Язык",
|
||||
"Last login": "Последний вход",
|
||||
"Launch Program": "Запуск программы",
|
||||
"LeftInfo": "Нажмите на запись команды, чтобы быстро найти видео",
|
||||
"Load tree async": "Асинхронная загрузка дерева активов",
|
||||
"Loading": "загрузка",
|
||||
"Loading": "Загрузка",
|
||||
"Log out": "Выйти",
|
||||
"Login reminder": "Вход в систему",
|
||||
"Login review approved": "Аудит входа прошел, подключение к активам...",
|
||||
"LoginExpireMsg": "Текущий вход истек, не закрывайте окно. После входа в новом окне вы сможете восстановить текущую страницу, если восстановление не произошло, пожалуйста, обновите страницу.",
|
||||
"Low Speed Broadband (256 Kbps - 2 Mbps)": "Низкая скорость широкополосного доступа (256 Кбит/с - 2 Мбит/с)",
|
||||
"Manual accounts": "Ручной аккаунт",
|
||||
"Login reminder": "Уведомление о входе",
|
||||
"Login review approved": "Вход одобрен, подключение к активам...",
|
||||
"LoginExpireMsg": "Время сессии истекло, не закрывайте окно. После входа в новом окне страница будет восстановлена, если не восстановилась, обновите страницу.",
|
||||
"Low Speed Broadband (256 Kbps - 2 Mbps)": "Медленный интернет (256 Кбит/с - 2 Мбит/с)",
|
||||
"Manual accounts": "Ввод УЗ вручную",
|
||||
"Module": "Модуль",
|
||||
"Multi Screen": "Многоэкранный режим",
|
||||
"My applications": "Мое приложение",
|
||||
"My applications": "Мои приложения",
|
||||
"My assets": "Мои активы",
|
||||
"Name": "Имя",
|
||||
"Native": "Клиент",
|
||||
"Need review for login asset": "Для входа требуется ручная проверка, продолжить?",
|
||||
"Need to use": "Необходимо использовать",
|
||||
"No": "Нет",
|
||||
"No account available": "Нет доступных аккаунтов",
|
||||
"No available connect method": "Нет доступных методов подключения",
|
||||
"No facial features": "Нет данных о лицевых признаках, пожалуйста, перейдите на страницу личной информации для привязки.",
|
||||
"No matching found": "Нет совпадений",
|
||||
"No permission": "Нет доступа",
|
||||
"No account available": "Нет доступных учетных записей",
|
||||
"No available connect method": "Нет доступного метода подключения",
|
||||
"No facial features": "Нет характеристик лица, пожалуйста, перейдите в личную инфрмацию для привязки",
|
||||
"No matching found": "Совпадений не найдено",
|
||||
"No permission": "Нет разрешений",
|
||||
"No protocol available": "Нет доступных протоколов",
|
||||
"NoTabs": "Нет окон",
|
||||
"Not quick command": "Нет доступных горячих команд",
|
||||
"NoTabs": "Нет вкладок",
|
||||
"Not quick command": "Нет быстрых команд",
|
||||
"Open in new window": "Открыть в новом окне",
|
||||
"Operator": "Оперативник",
|
||||
"Operator": "Оператор",
|
||||
"Password": "Пароль",
|
||||
"Password is token password on the table": "пароль — это токен из таблицы",
|
||||
"Password is your password login to system": "Пароль - это пароль для входа в систему",
|
||||
"Password is token password on the table": "Пароль — это токен из таблицы",
|
||||
"Password is your password login to system": "Пароль — это ваш пароль для входа в систему",
|
||||
"Pause": "Пауза",
|
||||
"Pause task has been send": "Задание на приостановление отправлено",
|
||||
"Pause task has been send": "Задача приостановки отправлена",
|
||||
"Play List": "Плейлист",
|
||||
"Please choose an account": "Пожалуйста, выберите пользователя",
|
||||
"Please choose an account": "Пожалуйста, выберите учетную запись",
|
||||
"Please input password": "Пожалуйста, введите пароль",
|
||||
"Port": "Порт",
|
||||
"Protocol": "Протокол",
|
||||
@ -147,91 +147,92 @@
|
||||
"RDP File": "RDP файл",
|
||||
"RDP client options": "Опции клиента RDP",
|
||||
"RDP color quality": "Качество цвета RDP",
|
||||
"RDP connection speed": "скорость RDP подключения",
|
||||
"RDP connection speed": "Скорость подключения RDP",
|
||||
"RDP resolution": "Разрешение RDP",
|
||||
"RDP smart size": "Умный размер RDP",
|
||||
"Re-use for a long time after opening": "После активации эта информация о подключении может использоваться длительное время и многократно",
|
||||
"Reconnect": "переподключиться",
|
||||
"Re-use for a long time after opening": "После включения эта информация о подключении может использоваться длительное время и многократно",
|
||||
"Reconnect": "Переподключение",
|
||||
"Refresh": "Обновить",
|
||||
"Remember password": "Запомнить пароль",
|
||||
"Remember select": "Запомнить выбор",
|
||||
"RemoteApp": "Удаленное приложение",
|
||||
"Reselect connection method": "Можно выбрать способ подключения заново",
|
||||
"Resume": "Восстановление",
|
||||
"Resume task has been send": "Задача на восстановление была отправлена",
|
||||
"Right click asset": "Щелкните правой кнопкой мыши на активе → Подключить",
|
||||
"Right click node": "Щелкните правой кнопкой мыши на узле → Развернуть все",
|
||||
"Right mouse quick paste": "Правый клик для быстрого вставления",
|
||||
"Reselect connection method": "Можно повторно выбрать способ подключения",
|
||||
"Resume": "Восстановить",
|
||||
"Resume task has been send": "Задача на восстановление отправлена",
|
||||
"Right click asset": "Щелкните правой кнопкой мыши на активе → Подключение",
|
||||
"Right click node": "Щелкните правой кнопкой мыши на папке → Развернуть все",
|
||||
"Right mouse quick paste": "Быстрая вставка правой кнопкой мыши",
|
||||
"Run it by client": "Выполнить с помощью клиента",
|
||||
"SQL Client": "SQL клиент",
|
||||
"Save command": "Сохранить команду",
|
||||
"Save success": "Сохранено успешно",
|
||||
"Search": "Поиск",
|
||||
"Select account": "Выбрать аккаунт",
|
||||
"Select account": "Выбрать УЗ",
|
||||
"Send command": "Отправить команду",
|
||||
"Send text to all ssh terminals": "Отправить текст на все ssh терминалы",
|
||||
"Session": "Сессия",
|
||||
"SessionIsBeingMonitored": "Сессия находится под наблюдением",
|
||||
"Set reusable": "Включить повторное использование",
|
||||
"Setting": "Настройки",
|
||||
"Settings or basic settings": "Настройки меню → Основные настройки",
|
||||
"ShareSession": "Делиться сессией",
|
||||
"Show left manager": "Отобразить боковую панель слева",
|
||||
"Settings or basic settings": "Настройки → Основные настройки",
|
||||
"ShareSession": "Поделиться сессией",
|
||||
"Show left manager": "Показать левую панель",
|
||||
"Skip": "Пропустить",
|
||||
"Skip manual password": "Пропустить окно ввода пароля вручную",
|
||||
"Skip manual password": "Пропустить окно ручного ввода пароля",
|
||||
"Special accounts": "Специальная учетная запись",
|
||||
"Speed": "Скорость",
|
||||
"Split connect": "Подключение на раздвоенном экране",
|
||||
"Split connect number": "Одна сессия поддерживает максимум 3 соединения с разделённым экранами",
|
||||
"Split vertically": "Вертикальное распределение экрана",
|
||||
"Split connect": "Подключение с разделением экрана",
|
||||
"Split connect number": "Одна сессия поддерживает максимум 3 подключения с разделением экрана",
|
||||
"Split vertically": "Вертикальное разделение экрана",
|
||||
"SplitVertical": "Вертикальное разделение",
|
||||
"Start Time: ": "Время начала: {{value}}",
|
||||
"Stop": "остановить",
|
||||
"Stop": "Остановить",
|
||||
"Support": "Поддержка",
|
||||
"Swiss French keyboard layout": "Швейцарский французский (Qwertz)",
|
||||
"Switch to input command": "Переключиться на ввод команд",
|
||||
"Switch to quick command": "Переключиться на быстрые команды",
|
||||
"Tab List": "Список окон",
|
||||
"Tabs": "Окно",
|
||||
"Tab List": "Список вкладок",
|
||||
"Tabs": "Вкладки",
|
||||
"The connection method is invalid, please refresh the page": "Этот способ подключения больше недействителен, пожалуйста, обновите страницу",
|
||||
"Theme": "Тема",
|
||||
"Ticket review approved for login asset": "Проверка входа одобрена, подключить активы?",
|
||||
"Ticket review closed for login asset": "Текущая проверка входа завершена, подключение к активам невозможно",
|
||||
"Ticket review pending for login asset": "Заявка на вход в систему подана, ожидайте проверки ответственным лицом, вы также можете скопировать ссылку и отправить ему",
|
||||
"Ticket review rejected for login asset": "Авторизация для текущего входа отклонена, подключение к активам невозможно",
|
||||
"Tips": "Уведомление",
|
||||
"Token expired": "Токен истёк, пожалуйста, переподключитесь",
|
||||
"Ticket review approved for login asset": "Проверка входа пройдена, хотите подключиться к активу?",
|
||||
"Ticket review closed for login asset": "Проверка входа закрыта, подключение к активу невозможно",
|
||||
"Ticket review pending for login asset": "Заявка на вход подана, ожидайте проверки исполнителем, вы также можете скопировать ссылку и отправить ему",
|
||||
"Ticket review rejected for login asset": "Проверка входа отклонена, подключение к активу невозможно",
|
||||
"Tips": "Подсказки",
|
||||
"Token expired": "Токен истек, пожалуйста, подключитесь снова",
|
||||
"Tool download": "Скачать инструменты",
|
||||
"Turkey keyboard layout": "Турецкий-Q (Qwerty)",
|
||||
"TurnOffReminders": "Вы уверены, что хотите закрыть текущее соединение?",
|
||||
"Type tree": "Типовая структура",
|
||||
"UK English keyboard layout": "Английский (Великобритания) (Qwerty)",
|
||||
"US English keyboard layout": "US English (Qwerty)",
|
||||
"TurnOffReminders": "Вы уверены, что хотите закрыть текущее подключение?",
|
||||
"Type tree": "Дерево типов",
|
||||
"UK English keyboard layout": "Английский (UK) (Qwerty)",
|
||||
"US English keyboard layout": "Английский (США) (Qwerty)",
|
||||
"User": "Пользователь",
|
||||
"User: ": "Пользователь: {{value}}",
|
||||
"Username": "Имя пользователя",
|
||||
"Username@Domain": "Имя пользователя@AD-домен",
|
||||
"Users": "Пользователь",
|
||||
"Using token": "Использовать Token",
|
||||
"Users": "Пользователи",
|
||||
"Using token": "С использованием токена",
|
||||
"View": "Вид",
|
||||
"Viewer": "Просмотр пользователя",
|
||||
"Viewer": "Просмотр",
|
||||
"VirtualApp": "Виртуальное приложение",
|
||||
"Web Terminal": "Web терминал",
|
||||
"Web Terminal": "Веб-терминал",
|
||||
"Website": "Официальный сайт",
|
||||
"With secret accounts": "Эскроу-аккаунт",
|
||||
"With secret accounts": "УЗ с сохраненным секретом",
|
||||
"Yes": "Да",
|
||||
"asset": "Активы",
|
||||
"cols": "Количество столбцов",
|
||||
"confirm": "Подтвердить",
|
||||
"connect info": "Информация о подключении",
|
||||
"connectDisabledTipsMethodDisabled": "Подсказка: не удалось найти действующий сервер публикации удаленных приложений, текущий ресурс не может быть подключен, пожалуйста, свяжитесь с администратором для решения проблемы.",
|
||||
"asset": "актив",
|
||||
"cols": "столбцы",
|
||||
"confirm": "подтвердить",
|
||||
"connect info": "информация о подключении",
|
||||
"connectDisabledTipsMethodDisabled": "Подсказка: не удалось найти действующий сервер публикации удаленных приложений, текущий ресурс не может быть подключен, пожалуйста, свяжитесь с администратором для решения проблемы",
|
||||
"connectDisabledTipsNoAccount": "Подсказка: Не найдено действующей учетной записи, текущий ресурс не может быть подключен, пожалуйста, свяжитесь с администратором для решения проблемы",
|
||||
"connectDisabledTipsNoConnectMethod": "Подсказка: не найдено действующего способа подключения, текущий ресурс нельзя подключить, пожалуйста, свяжитесь с администратором для обработки",
|
||||
"connectDisabledTipsNoConnectMethod": "Подсказка: не найдено действующего способа подключения, текущий ресурс нельзя подключить, пожалуйста, свяжитесь с администратором для решения проблемы",
|
||||
"download": "скачать",
|
||||
"recordingIsBeingDownloaded": "Видеозапись загружается, пожалуйста, подождите",
|
||||
"rows": "Количество строк",
|
||||
"start time": "Время начала",
|
||||
"success": "Успешно",
|
||||
"system user": "Системный пользователь",
|
||||
"user": "Пользователь"
|
||||
"recordingIsBeingDownloaded": "Запись загружается, пожалуйста, подождите",
|
||||
"rows": "строки",
|
||||
"start time": "время начала",
|
||||
"success": "успешно",
|
||||
"system user": "системный пользователь",
|
||||
"user": "пользователь",
|
||||
"WordSep": " "
|
||||
}
|
@ -38,7 +38,6 @@
|
||||
"Close split connect": "关闭分屏",
|
||||
"Command": "命令",
|
||||
"Command Line": "命令行",
|
||||
"Command line": "连接命令行",
|
||||
"CommandBar": "命令栏",
|
||||
"Confirm": "确认",
|
||||
"Connect": "连接",
|
||||
|
3
apps/jumpserver/api/__init__.py
Normal file
3
apps/jumpserver/api/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .aggregate import *
|
||||
from .dashboard import IndexApi
|
||||
from .health import PrometheusMetricsApi, HealthCheckView
|
9
apps/jumpserver/api/aggregate/__init__.py
Normal file
9
apps/jumpserver/api/aggregate/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from .detail import ResourceDetailApi
|
||||
from .list import ResourceListApi
|
||||
from .supported import ResourceTypeListApi
|
||||
|
||||
__all__ = [
|
||||
'ResourceListApi',
|
||||
'ResourceDetailApi',
|
||||
'ResourceTypeListApi',
|
||||
]
|
57
apps/jumpserver/api/aggregate/const.py
Normal file
57
apps/jumpserver/api/aggregate/const.py
Normal file
@ -0,0 +1,57 @@
|
||||
list_params = [
|
||||
{
|
||||
"name": "search",
|
||||
"in": "query",
|
||||
"description": "A search term.",
|
||||
"required": False,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "order",
|
||||
"in": "query",
|
||||
"description": "Which field to use when ordering the results.",
|
||||
"required": False,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Number of results to return per page. Default is 10.",
|
||||
"required": False,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"in": "query",
|
||||
"description": "The initial index from which to return the results.",
|
||||
"required": False,
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
common_params = [
|
||||
{
|
||||
"name": "resource",
|
||||
"in": "path",
|
||||
"description": """Resource to query, e.g. users, assets, permissions, acls, user-groups, policies, nodes, hosts,
|
||||
devices, clouds, webs, databases,
|
||||
gpts, ds, customs, platforms, zones, gateways, protocol-settings, labels, virtual-accounts,
|
||||
gathered-accounts, account-templates, account-template-secrets, account-backups, account-backup-executions,
|
||||
change-secret-automations, change-secret-executions, change-secret-records, gather-account-automations,
|
||||
gather-account-executions, push-account-automations, push-account-executions, push-account-records,
|
||||
check-account-automations, check-account-executions, account-risks, integration-apps, asset-permissions,
|
||||
zones, gateways, virtual-accounts, gathered-accounts, account-templates, account-template-secrets,,
|
||||
GET /api/v1/resources/ to get full supported resource.
|
||||
""",
|
||||
"required": True,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "X-JMS-ORG",
|
||||
"in": "header",
|
||||
"description": "The organization ID to use for the request. Organization is the namespace for resources, if not set, use default org",
|
||||
"required": False,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
73
apps/jumpserver/api/aggregate/detail.py
Normal file
73
apps/jumpserver/api/aggregate/detail.py
Normal file
@ -0,0 +1,73 @@
|
||||
# views.py
|
||||
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .const import common_params
|
||||
from .proxy import ProxyMixin
|
||||
from .utils import param_dic_to_param
|
||||
|
||||
one_param = [
|
||||
{
|
||||
'name': 'id',
|
||||
'in': 'path',
|
||||
'required': True,
|
||||
'description': 'Resource ID',
|
||||
'type': 'string',
|
||||
}
|
||||
]
|
||||
|
||||
object_params = [
|
||||
param_dic_to_param(d)
|
||||
for d in common_params + one_param
|
||||
]
|
||||
|
||||
|
||||
class ResourceDetailApi(ProxyMixin, APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_id="get_resource_detail",
|
||||
operation_summary="Get resource detail",
|
||||
manual_parameters=object_params,
|
||||
operation_description="""
|
||||
Get resource detail.
|
||||
{resource} is the resource name, GET /api/v1/resources/ to get full supported resource.
|
||||
|
||||
""", )
|
||||
def get(self, request, resource, pk=None):
|
||||
return self._proxy(request, resource, pk=pk, action='retrieve')
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_id="delete_resource",
|
||||
operation_summary="Delete the resource ",
|
||||
manual_parameters=object_params,
|
||||
operation_description="Delete the resource, and can not be restored",
|
||||
)
|
||||
def delete(self, request, resource, pk=None):
|
||||
return self._proxy(request, resource, pk, action='destroy')
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_id="update_resource",
|
||||
operation_summary="Update the resource property",
|
||||
manual_parameters=object_params,
|
||||
operation_description="""
|
||||
Update the resource property, all property will be update,
|
||||
{resource} is the resource name, GET /api/v1/resources/ to get full supported resource.
|
||||
|
||||
OPTION /api/v1/resources/{resource}/{id}/?action=put to get field type and helptext.
|
||||
""")
|
||||
def put(self, request, resource, pk=None):
|
||||
return self._proxy(request, resource, pk, action='update')
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_id="partial_update_resource",
|
||||
operation_summary="Update the resource property",
|
||||
manual_parameters=object_params,
|
||||
operation_description="""
|
||||
Partial update the resource property, only request property will be update,
|
||||
OPTION /api/v1/resources/{resource}/{id}/?action=patch to get field type and helptext.
|
||||
""")
|
||||
def patch(self, request, resource, pk=None):
|
||||
return self._proxy(request, resource, pk, action='partial_update')
|
86
apps/jumpserver/api/aggregate/list.py
Normal file
86
apps/jumpserver/api/aggregate/list.py
Normal file
@ -0,0 +1,86 @@
|
||||
# views.py
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .const import list_params, common_params
|
||||
from .proxy import ProxyMixin
|
||||
from .utils import param_dic_to_param
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
BASE_URL = "http://localhost:8080"
|
||||
|
||||
list_params = [
|
||||
param_dic_to_param(d)
|
||||
for d in list_params + common_params
|
||||
]
|
||||
|
||||
create_params = [
|
||||
param_dic_to_param(d)
|
||||
for d in common_params
|
||||
]
|
||||
|
||||
list_schema = {
|
||||
"required": [
|
||||
"count",
|
||||
"results"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"next": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"x-nullable": True
|
||||
},
|
||||
"previous": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"x-nullable": True
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list_response = openapi.Response("Resource list response", schema=openapi.Schema(**list_schema))
|
||||
|
||||
|
||||
class ResourceListApi(ProxyMixin, APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_id="get_resource_list",
|
||||
operation_summary="Get resource list",
|
||||
manual_parameters=list_params,
|
||||
responses={200: list_response},
|
||||
operation_description="""
|
||||
Get resource list, you should set the resource name in the url.
|
||||
OPTIONS /api/v1/resources/{resource}/?action=get to get every type resource's field type and help text.
|
||||
""", )
|
||||
# ↓↓↓ Swagger 自动文档 ↓↓↓
|
||||
def get(self, request, resource):
|
||||
return self._proxy(request, resource)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_id="create_resource_by_type",
|
||||
operation_summary="Create resource",
|
||||
manual_parameters=create_params,
|
||||
operation_description="""
|
||||
Create resource,
|
||||
OPTIONS /api/v1/resources/{resource}/?action=post to get every resource type field type and helptext, and
|
||||
you will know how to create it.
|
||||
""")
|
||||
def post(self, request, resource, pk=None):
|
||||
if not resource:
|
||||
resource = request.data.pop('resource', '')
|
||||
return self._proxy(request, resource, pk, action='create')
|
||||
|
||||
def options(self, request, resource, pk=None):
|
||||
return self._proxy(request, resource, pk, action='metadata')
|
68
apps/jumpserver/api/aggregate/proxy.py
Normal file
68
apps/jumpserver/api/aggregate/proxy.py
Normal file
@ -0,0 +1,68 @@
|
||||
# views.py
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import requests
|
||||
from rest_framework.exceptions import NotFound, APIException
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .utils import get_full_resource_map
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
BASE_URL = "http://localhost:8080"
|
||||
|
||||
|
||||
class ProxyMixin(APIView):
|
||||
"""
|
||||
通用资源代理 API,支持动态路径、自动文档生成
|
||||
"""
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def _build_url(self, resource_name: str, pk: str = None, query_params=None):
|
||||
resource_map = get_full_resource_map()
|
||||
resource = resource_map.get(resource_name)
|
||||
if not resource:
|
||||
raise NotFound(f"Unknown resource: {resource_name}")
|
||||
|
||||
base_path = resource['path']
|
||||
if pk:
|
||||
base_path += f"{pk}/"
|
||||
|
||||
if query_params:
|
||||
base_path += f"?{urlencode(query_params)}"
|
||||
|
||||
return f"{BASE_URL}{base_path}"
|
||||
|
||||
def _proxy(self, request, resource: str, pk: str = None, action='list'):
|
||||
method = request.method.lower()
|
||||
if method not in ['get', 'post', 'put', 'patch', 'delete', 'options']:
|
||||
raise APIException("Unsupported method")
|
||||
|
||||
if not resource or resource == '{resource}':
|
||||
if request.data:
|
||||
resource = request.data.get('resource')
|
||||
|
||||
query_params = request.query_params.dict()
|
||||
if action == 'list':
|
||||
query_params['limit'] = 10
|
||||
|
||||
url = self._build_url(resource, pk, query_params)
|
||||
headers = {k: v for k, v in request.headers.items() if k.lower() != 'host'}
|
||||
cookies = request.COOKIES
|
||||
body = request.body if method in ['post', 'put', 'patch'] else None
|
||||
|
||||
try:
|
||||
resp = requests.request(
|
||||
method=method,
|
||||
url=url,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
data=body,
|
||||
timeout=10,
|
||||
)
|
||||
return resp
|
||||
except requests.RequestException as e:
|
||||
raise APIException(f"Proxy request failed: {str(e)}")
|
43
apps/jumpserver/api/aggregate/supported.py
Normal file
43
apps/jumpserver/api/aggregate/supported.py
Normal file
@ -0,0 +1,43 @@
|
||||
# views.py
|
||||
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework import serializers
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.views import APIView
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
BASE_URL = "http://localhost:8080"
|
||||
|
||||
|
||||
class ResourceTypeResourceSerializer(serializers.Serializer):
|
||||
name = serializers.CharField()
|
||||
path = serializers.CharField()
|
||||
app = serializers.CharField()
|
||||
verbose_name = serializers.CharField()
|
||||
description = serializers.CharField()
|
||||
|
||||
|
||||
class ResourceTypeListApi(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_id="get_supported_resources",
|
||||
operation_summary="Get-all-support-resources",
|
||||
operation_description="Get all support resources, name, path, verbose_name description",
|
||||
responses={200: ResourceTypeResourceSerializer(many=True)}, # Specify the response serializer
|
||||
)
|
||||
def get(self, request):
|
||||
result = []
|
||||
resource_map = get_full_resource_map()
|
||||
for name, desc in resource_map.items():
|
||||
desc = resource_map.get(name, {})
|
||||
resource = {
|
||||
"name": name,
|
||||
**desc,
|
||||
"path": f'/api/v1/resources/{name}/',
|
||||
}
|
||||
result.append(resource)
|
||||
return Response(result)
|
128
apps/jumpserver/api/aggregate/utils.py
Normal file
128
apps/jumpserver/api/aggregate/utils.py
Normal file
@ -0,0 +1,128 @@
|
||||
# views.py
|
||||
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from typing import Dict
|
||||
|
||||
from django.urls import URLPattern
|
||||
from django.urls import URLResolver
|
||||
from drf_yasg import openapi
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
BASE_URL = "http://localhost:8080"
|
||||
|
||||
|
||||
def clean_path(path: str) -> str:
|
||||
"""
|
||||
清理掉 DRF 自动生成的正则格式内容,让其变成普通 RESTful URL path。
|
||||
"""
|
||||
|
||||
# 去掉格式后缀匹配: \.(?P<format>xxx)
|
||||
path = re.sub(r'\\\.\(\?P<format>[^)]+\)', '', path)
|
||||
|
||||
# 去掉括号式格式匹配
|
||||
path = re.sub(r'\(\?P<format>[^)]+\)', '', path)
|
||||
|
||||
# 移除 DRF 中正则参数的部分 (?P<param>pattern)
|
||||
path = re.sub(r'\(\?P<\w+>[^)]+\)', '{param}', path)
|
||||
|
||||
# 如果有多个括号包裹的正则(比如前缀路径),去掉可选部分包装
|
||||
path = re.sub(r'\(\(([^)]+)\)\?\)', r'\1', path) # ((...))? => ...
|
||||
|
||||
# 去掉中间和两边的 ^ 和 $
|
||||
path = path.replace('^', '').replace('$', '')
|
||||
|
||||
# 去掉尾部 ?/
|
||||
path = re.sub(r'\?/?$', '', path)
|
||||
|
||||
# 去掉反斜杠
|
||||
path = path.replace('\\', '')
|
||||
|
||||
# 替换多重斜杠
|
||||
path = re.sub(r'/+', '/', path)
|
||||
|
||||
# 添加开头斜杠,移除多余空格
|
||||
path = path.strip()
|
||||
if not path.startswith('/'):
|
||||
path = '/' + path
|
||||
if not path.endswith('/'):
|
||||
path += '/'
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def extract_resource_paths(urlpatterns, prefix='/api/v1/') -> Dict[str, Dict[str, str]]:
|
||||
resource_map = {}
|
||||
|
||||
for pattern in urlpatterns:
|
||||
if isinstance(pattern, URLResolver):
|
||||
nested_prefix = prefix + str(pattern.pattern)
|
||||
resource_map.update(extract_resource_paths(pattern.url_patterns, nested_prefix))
|
||||
|
||||
elif isinstance(pattern, URLPattern):
|
||||
callback = pattern.callback
|
||||
actions = getattr(callback, 'actions', {})
|
||||
if not actions:
|
||||
continue
|
||||
|
||||
if 'get' in actions and actions['get'] == 'list':
|
||||
path = clean_path(prefix + str(pattern.pattern))
|
||||
|
||||
# 尝试获取资源名称
|
||||
name = pattern.name
|
||||
if name and name.endswith('-list'):
|
||||
resource = name[:-5]
|
||||
else:
|
||||
resource = path.strip('/').split('/')[-1]
|
||||
|
||||
# 不强行加 s,资源名保持原状即可
|
||||
resource = resource if resource.endswith('s') else resource + 's'
|
||||
|
||||
# 获取 View 类和 model 的 verbose_name
|
||||
view_cls = getattr(callback, 'cls', None)
|
||||
model = None
|
||||
|
||||
if view_cls:
|
||||
queryset = getattr(view_cls, 'queryset', None)
|
||||
if queryset is not None:
|
||||
model = getattr(queryset, 'model', None)
|
||||
else:
|
||||
# 有些 View 用 get_queryset()
|
||||
try:
|
||||
instance = view_cls()
|
||||
qs = instance.get_queryset()
|
||||
model = getattr(qs, 'model', None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not model:
|
||||
continue
|
||||
|
||||
app = str(getattr(model._meta, 'app_label', ''))
|
||||
verbose_name = str(getattr(model._meta, 'verbose_name', ''))
|
||||
resource_map[resource] = {
|
||||
'path': path,
|
||||
'app': app,
|
||||
'verbose_name': verbose_name,
|
||||
'description': model.__doc__.__str__()
|
||||
}
|
||||
|
||||
print("Extracted resource paths:", list(resource_map.keys()))
|
||||
return resource_map
|
||||
|
||||
|
||||
def param_dic_to_param(d):
|
||||
return openapi.Parameter(
|
||||
d['name'], d['in'],
|
||||
description=d['description'], type=d['type'], required=d.get('required', False)
|
||||
)
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_full_resource_map():
|
||||
from apps.jumpserver.urls import resource_api
|
||||
resource_map = extract_resource_paths(resource_api)
|
||||
print("Building URL for resource:", resource_map)
|
||||
return resource_map
|
@ -1,15 +1,11 @@
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Count, Max, F, CharField
|
||||
from django.db.models.functions import Cast
|
||||
from django.http.response import JsonResponse, HttpResponse
|
||||
from django.http.response import JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.timesince import timesince
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from assets.const import AllTypes
|
||||
@ -25,8 +21,6 @@ from orgs.caches import OrgResourceStatisticsCache
|
||||
from orgs.utils import current_org
|
||||
from terminal.const import RiskLevelChoices
|
||||
from terminal.models import Session, Command
|
||||
from terminal.utils import ComponentsPrometheusMetricsUtil
|
||||
from users.models import User
|
||||
|
||||
__all__ = ['IndexApi']
|
||||
|
||||
@ -466,61 +460,3 @@ class IndexApi(DateTimeMixin, DatesLoginMetricMixin, APIView):
|
||||
})
|
||||
|
||||
return JsonResponse(data, status=200)
|
||||
|
||||
|
||||
class HealthApiMixin(APIView):
|
||||
pass
|
||||
|
||||
|
||||
class HealthCheckView(HealthApiMixin):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
@staticmethod
|
||||
def get_db_status():
|
||||
t1 = time.time()
|
||||
try:
|
||||
ok = User.objects.first() is not None
|
||||
t2 = time.time()
|
||||
return ok, t2 - t1
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
@staticmethod
|
||||
def get_redis_status():
|
||||
key = 'HEALTH_CHECK'
|
||||
|
||||
t1 = time.time()
|
||||
try:
|
||||
value = '1'
|
||||
cache.set(key, '1', 10)
|
||||
got = cache.get(key)
|
||||
t2 = time.time()
|
||||
|
||||
if value == got:
|
||||
return True, t2 - t1
|
||||
return False, 'Value not match'
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def get(self, request):
|
||||
redis_status, redis_time = self.get_redis_status()
|
||||
db_status, db_time = self.get_db_status()
|
||||
status = all([redis_status, db_status])
|
||||
data = {
|
||||
'status': status,
|
||||
'db_status': db_status,
|
||||
'db_time': db_time,
|
||||
'redis_status': redis_status,
|
||||
'redis_time': redis_time,
|
||||
'time': int(time.time()),
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
|
||||
class PrometheusMetricsApi(HealthApiMixin):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
util = ComponentsPrometheusMetricsUtil()
|
||||
metrics_text = util.get_prometheus_metrics_text()
|
||||
return HttpResponse(metrics_text, content_type='text/plain; version=0.0.4; charset=utf-8')
|
68
apps/jumpserver/api/health.py
Normal file
68
apps/jumpserver/api/health.py
Normal file
@ -0,0 +1,68 @@
|
||||
import time
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.http.response import HttpResponse
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from terminal.utils import ComponentsPrometheusMetricsUtil
|
||||
from users.models import User
|
||||
|
||||
|
||||
class HealthApiMixin(APIView):
|
||||
pass
|
||||
|
||||
|
||||
class HealthCheckView(HealthApiMixin):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
@staticmethod
|
||||
def get_db_status():
|
||||
t1 = time.time()
|
||||
try:
|
||||
ok = User.objects.first() is not None
|
||||
t2 = time.time()
|
||||
return ok, t2 - t1
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
@staticmethod
|
||||
def get_redis_status():
|
||||
key = 'HEALTH_CHECK'
|
||||
|
||||
t1 = time.time()
|
||||
try:
|
||||
value = '1'
|
||||
cache.set(key, '1', 10)
|
||||
got = cache.get(key)
|
||||
t2 = time.time()
|
||||
|
||||
if value == got:
|
||||
return True, t2 - t1
|
||||
return False, 'Value not match'
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def get(self, request):
|
||||
redis_status, redis_time = self.get_redis_status()
|
||||
db_status, db_time = self.get_db_status()
|
||||
status = all([redis_status, db_status])
|
||||
data = {
|
||||
'status': status,
|
||||
'db_status': db_status,
|
||||
'db_time': db_time,
|
||||
'redis_status': redis_status,
|
||||
'redis_time': redis_time,
|
||||
'time': int(time.time()),
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
|
||||
class PrometheusMetricsApi(HealthApiMixin):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
util = ComponentsPrometheusMetricsUtil()
|
||||
metrics_text = util.get_prometheus_metrics_text()
|
||||
return HttpResponse(metrics_text, content_type='text/plain; version=0.0.4; charset=utf-8')
|
@ -12,7 +12,7 @@ from django.views.i18n import JavaScriptCatalog
|
||||
|
||||
from . import views, api
|
||||
|
||||
api_v1 = [
|
||||
resource_api = [
|
||||
path('index/', api.IndexApi.as_view()),
|
||||
path('users/', include('users.urls.api_urls', namespace='api-users')),
|
||||
path('assets/', include('assets.urls.api_urls', namespace='api-assets')),
|
||||
@ -30,7 +30,13 @@ api_v1 = [
|
||||
path('notifications/', include('notifications.urls.api_urls', namespace='api-notifications')),
|
||||
path('rbac/', include('rbac.urls.api_urls', namespace='api-rbac')),
|
||||
path('labels/', include('labels.urls', namespace='api-label')),
|
||||
]
|
||||
|
||||
api_v1 = resource_api + [
|
||||
path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()),
|
||||
path('resources/', api.ResourceTypeListApi.as_view(), name='resource-list'),
|
||||
path('resources/<str:resource>/', api.ResourceListApi.as_view()),
|
||||
path('resources/<str:resource>/<str:pk>/', api.ResourceDetailApi.as_view()),
|
||||
]
|
||||
|
||||
app_view_patterns = [
|
||||
|
@ -19,41 +19,78 @@ class CustomSchemaGenerator(OpenAPISchemaGenerator):
|
||||
'/report/', '/render-to-json/', '/suggestions/',
|
||||
'executions', 'automations', 'change-secret-records',
|
||||
'change-secret-dashboard', '/copy-to-assets/',
|
||||
'/move-to-assets/', 'dashboard',
|
||||
|
||||
'/move-to-assets/', 'dashboard', 'index', 'countries',
|
||||
'/resources/cache/', 'profile/mfa', 'profile/password',
|
||||
'profile/permissions', 'prometheus', 'constraints'
|
||||
]
|
||||
for p in excludes:
|
||||
if path.find(p) >= 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def exclude_some_app(self, path):
|
||||
def exclude_some_app_model(self, path):
|
||||
parts = path.split('/')
|
||||
if len(parts) < 4:
|
||||
if len(parts) < 5:
|
||||
return False
|
||||
|
||||
apps = []
|
||||
if self.from_mcp:
|
||||
apps = [
|
||||
'ops', 'tickets', 'common', 'authentication',
|
||||
'settings', 'xpack', 'terminal', 'rbac'
|
||||
'ops', 'tickets', 'authentication',
|
||||
'settings', 'xpack', 'terminal', 'rbac',
|
||||
'notifications', 'promethues', 'acls'
|
||||
]
|
||||
|
||||
app_name = parts[3]
|
||||
if app_name in apps:
|
||||
return True
|
||||
models = []
|
||||
model = parts[4]
|
||||
if self.from_mcp:
|
||||
models = [
|
||||
'users', 'user-groups', 'users-groups-relations', 'assets', 'hosts', 'devices', 'databases',
|
||||
'webs', 'clouds', 'gpts', 'ds', 'customs', 'platforms', 'nodes', 'zones', 'gateways',
|
||||
'protocol-settings', 'labels', 'virtual-accounts', 'gathered-accounts', 'account-templates',
|
||||
'account-template-secrets', 'account-backups', 'account-backup-executions',
|
||||
'change-secret-automations', 'change-secret-executions', 'change-secret-records',
|
||||
'gather-account-automations', 'gather-account-executions', 'push-account-automations',
|
||||
'push-account-executions', 'push-account-records', 'check-account-automations',
|
||||
'check-account-executions', 'account-risks', 'integration-apps', 'asset-permissions',
|
||||
'asset-permissions-users-relations', 'asset-permissions-user-groups-relations',
|
||||
'asset-permissions-assets-relations', 'asset-permissions-nodes-relations', 'terminal-status',
|
||||
'terminals', 'tasks', 'status', 'replay-storages', 'command-storages', 'session-sharing-records',
|
||||
'endpoints', 'endpoint-rules', 'applets', 'applet-hosts', 'applet-publications',
|
||||
'applet-host-deployments', 'virtual-apps', 'app-providers', 'virtual-app-publications',
|
||||
'celery-period-tasks', 'task-executions', 'adhocs', 'playbooks', 'variables', 'ftp-logs',
|
||||
'login-logs', 'operate-logs', 'password-change-logs', 'job-logs', 'jobs', 'user-sessions',
|
||||
'service-access-logs', 'chatai-prompts', 'super-connection-tokens', 'flows',
|
||||
'apply-assets', 'apply-nodes', 'login-acls', 'login-asset-acls', 'command-filter-acls',
|
||||
'command-groups', 'connect-method-acls', 'system-msg-subscriptions', 'roles', 'role-bindings',
|
||||
'system-roles', 'system-role-bindings', 'org-roles', 'org-role-bindings', 'content-types',
|
||||
'labeled-resources', 'account-backup-plans', 'account-check-engines', 'account-secrets',
|
||||
'change-secret', 'integration-applications', 'push-account', 'directories', 'connection-token',
|
||||
'groups', 'accounts', 'resource-types', 'favorite-assets', 'activities', 'platform-automation-methods',
|
||||
]
|
||||
if model in models:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_operation(self, view, path, prefix, method, components, request):
|
||||
# 这里可以对 path 进行处理
|
||||
if self.exclude_some_paths(path):
|
||||
return None
|
||||
if self.exclude_some_app(path):
|
||||
if self.exclude_some_app_model(path):
|
||||
return None
|
||||
operation = super().get_operation(view, path, prefix, method, components, request)
|
||||
operation_id = operation.get('operationId')
|
||||
if 'bulk' in operation_id:
|
||||
return None
|
||||
exclude_operations = [
|
||||
'orgs_orgs_read', 'orgs_orgs_update', 'orgs_orgs_delete', 'orgs_orgs_create',
|
||||
'orgs_orgs_partial_update',
|
||||
]
|
||||
if operation_id in exclude_operations:
|
||||
return None
|
||||
return operation
|
||||
|
||||
|
||||
@ -82,7 +119,8 @@ class CustomSwaggerAutoSchema(SwaggerAutoSchema):
|
||||
|
||||
def get_operation(self, operation_keys):
|
||||
operation = super().get_operation(operation_keys)
|
||||
operation.summary = operation.operation_id
|
||||
if not getattr(operation, 'summary', ''):
|
||||
operation.summary = operation.operation_id
|
||||
return operation
|
||||
|
||||
def get_filter_parameters(self):
|
||||
|
@ -37,7 +37,7 @@ class JMSInventory:
|
||||
from assets.models import Asset
|
||||
asset_ids = [asset.id for asset in assets]
|
||||
assets = Asset.objects.filter(id__in=asset_ids, is_active=True) \
|
||||
.prefetch_related('platform', 'domain', 'accounts')
|
||||
.prefetch_related('platform', 'zone', 'accounts')
|
||||
return assets
|
||||
|
||||
@staticmethod
|
||||
@ -236,8 +236,8 @@ class JMSInventory:
|
||||
host.update(ansible_config)
|
||||
|
||||
gateway = None
|
||||
if not asset.is_gateway and asset.domain:
|
||||
gateway = asset.domain.select_gateway()
|
||||
if not asset.is_gateway and asset.zone:
|
||||
gateway = asset.zone.select_gateway()
|
||||
|
||||
self.make_account_vars(
|
||||
host, asset, account, automation, protocol, platform, gateway, path_dir, ansible_config
|
||||
|
@ -7,7 +7,7 @@ from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
|
||||
from assets.models import (
|
||||
Asset, Domain, Label, Node,
|
||||
Asset, Zone, Label, Node,
|
||||
)
|
||||
from common.api import JMSBulkModelViewSet
|
||||
from common.permissions import IsValidUser
|
||||
@ -24,7 +24,7 @@ logger = get_logger(__file__)
|
||||
|
||||
# 部分 org 相关的 model,需要清空这些数据之后才能删除该组织
|
||||
org_related_models = [
|
||||
User, UserGroup, Asset, Node, Label, Domain, AssetPermission
|
||||
User, UserGroup, Asset, Node, Label, Zone, AssetPermission
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
from django.db.transaction import on_commit
|
||||
|
||||
from orgs.models import Organization
|
||||
from orgs.tasks import refresh_org_cache_task
|
||||
from orgs.utils import current_org, tmp_to_org
|
||||
from accounts.models import Account
|
||||
from assets.models import Node, Zone, Asset
|
||||
from common.cache import Cache, IntegerField
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_zero_hour, local_monday
|
||||
from users.models import UserGroup, User
|
||||
from assets.models import Node, Domain, Asset
|
||||
from accounts.models import Account
|
||||
from terminal.models import Session
|
||||
from orgs.models import Organization
|
||||
from orgs.tasks import refresh_org_cache_task
|
||||
from orgs.utils import current_org, tmp_to_org
|
||||
from perms.models import AssetPermission
|
||||
from terminal.models import Session
|
||||
from users.models import UserGroup, User
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
@ -57,7 +57,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
|
||||
new_users_amount_this_week = IntegerField()
|
||||
new_assets_amount_this_week = IntegerField()
|
||||
nodes_amount = IntegerField(queryset=Node.objects)
|
||||
domains_amount = IntegerField(queryset=Domain.objects)
|
||||
zones_amount = IntegerField(queryset=Zone.objects)
|
||||
groups_amount = IntegerField(queryset=UserGroup.objects)
|
||||
accounts_amount = IntegerField(queryset=Account.objects)
|
||||
asset_perms_amount = IntegerField(queryset=AssetPermission.objects)
|
||||
|
@ -12,7 +12,7 @@ class ResourceStatisticsSerializer(serializers.Serializer):
|
||||
|
||||
assets_amount = serializers.IntegerField(required=False, label=_('Assets amount'))
|
||||
nodes_amount = serializers.IntegerField(required=False, label=_('Nodes amount'))
|
||||
domains_amount = serializers.IntegerField(required=False, label=_('Domains amount'))
|
||||
zones_amount = serializers.IntegerField(required=False, label=_('Domains amount'))
|
||||
gateways_amount = serializers.IntegerField(required=False, label=_('Gateways amount'))
|
||||
|
||||
asset_perms_amount = serializers.IntegerField(required=False, label=_('Asset permissions amount'))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user