mirror of https://github.com/jumpserver/jumpserver
Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
commit
a255bd21b4
|
@ -1,17 +1,28 @@
|
|||
import os
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
from copy import deepcopy
|
||||
from openpyxl import Workbook
|
||||
from collections import defaultdict
|
||||
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import lazyproperty, gen_key_pair
|
||||
from common.utils.timezone import local_now_display
|
||||
from common.utils.file import encrypt_and_compress_zip_file
|
||||
from common.utils import get_logger, lazyproperty, gen_key_pair
|
||||
from users.models import User
|
||||
from assets.models import ChangeSecretRecord
|
||||
from assets.notifications import ChangeSecretExecutionTaskMsg
|
||||
from assets.serializers import ChangeSecretRecordBackUpSerializer
|
||||
from assets.const import (
|
||||
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
|
||||
)
|
||||
from ..base.manager import BasePlaybookManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ChangeSecretManager(BasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -125,7 +136,7 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||
new_secret = self.get_secret()
|
||||
|
||||
recorder = ChangeSecretRecord(
|
||||
account=account, execution=self.execution,
|
||||
asset=asset, account=account, execution=self.execution,
|
||||
old_secret=account.secret, new_secret=new_secret,
|
||||
)
|
||||
records.append(recorder)
|
||||
|
@ -172,4 +183,49 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||
recorder.save()
|
||||
|
||||
def on_runner_failed(self, runner, e):
|
||||
pass
|
||||
logger.error("Change secret error: ", e)
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
super().run(*args, **kwargs)
|
||||
recorders = self.name_recorder_mapper.values()
|
||||
recorders = list(recorders)
|
||||
self.send_recorder_mail(recorders)
|
||||
|
||||
def send_recorder_mail(self, recorders):
|
||||
recipients = self.execution.recipients
|
||||
if not recorders or not recipients:
|
||||
return
|
||||
recipients = User.objects.filter(id__in=list(recipients))
|
||||
|
||||
name = self.execution.snapshot['name']
|
||||
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||
filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx')
|
||||
if not self.create_file(recorders, filename):
|
||||
return
|
||||
|
||||
for user in recipients:
|
||||
attachments = []
|
||||
if user.secret_key:
|
||||
password = user.secret_key.encode('utf8')
|
||||
attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
|
||||
encrypt_and_compress_zip_file(attachment, password, [filename])
|
||||
attachments = [attachment]
|
||||
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
|
||||
os.remove(filename)
|
||||
|
||||
@staticmethod
|
||||
def create_file(recorders, filename):
|
||||
serializer_cls = ChangeSecretRecordBackUpSerializer
|
||||
serializer = serializer_cls(recorders, many=True)
|
||||
header = [v.label for v in serializer.child.fields.values()]
|
||||
rows = [list(row.values()) for row in serializer.data]
|
||||
if not rows:
|
||||
return False
|
||||
|
||||
rows.insert(0, header)
|
||||
wb = Workbook(filename)
|
||||
ws = wb.create_sheet('Sheet1')
|
||||
for row in rows:
|
||||
ws.append(row)
|
||||
wb.save(filename)
|
||||
return True
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
# Generated by Django 3.2.14 on 2022-11-02 12:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0108_auto_20221027_1053'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='account',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True, verbose_name='Is active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='account',
|
||||
name='updated_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='accounttemplate',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True, verbose_name='Is active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='accounttemplate',
|
||||
name='updated_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gateway',
|
||||
name='updated_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='account',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accounttemplate',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 3.2.14 on 2022-11-03 08:44
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0108_auto_20221027_1053'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='accountbackupplan',
|
||||
old_name='categories',
|
||||
new_name='types',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 3.2.14 on 2022-11-03 13:57
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0109_auto_20221102_2017'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='accountbackupplan',
|
||||
old_name='categories',
|
||||
new_name='types',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='changesecretrecord',
|
||||
name='asset',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.asset'),
|
||||
),
|
||||
]
|
|
@ -30,11 +30,13 @@ class AccountHistoricalRecords(HistoricalRecords):
|
|||
return
|
||||
super().post_save(instance, created, using=using, **kwargs)
|
||||
|
||||
def fields_included(self, model):
|
||||
if self.included_fields:
|
||||
fields = [i for i in model._meta.fields if i.name in self.included_fields]
|
||||
return fields
|
||||
return super().fields_included(model)
|
||||
def create_history_model(self, model, inherited):
|
||||
if self.included_fields and not self.excluded_fields:
|
||||
self.excluded_fields = [
|
||||
field.name for field in model._meta.fields
|
||||
if field.name not in self.included_fields
|
||||
]
|
||||
return super().create_history_model(model, inherited)
|
||||
|
||||
|
||||
class Account(AbsConnectivity, BaseAccount):
|
||||
|
|
|
@ -111,6 +111,13 @@ class AutomationExecution(OrgModelMixin):
|
|||
def manager_type(self):
|
||||
return self.snapshot['type']
|
||||
|
||||
@property
|
||||
def recipients(self):
|
||||
recipients = self.snapshot.get('recipients')
|
||||
if not recipients:
|
||||
return []
|
||||
return recipients.values()
|
||||
|
||||
def start(self):
|
||||
from assets.automations.endpoint import ExecutionManager
|
||||
manager = ExecutionManager(execution=self)
|
||||
|
|
|
@ -51,6 +51,7 @@ class ChangeSecretAutomation(BaseAutomation):
|
|||
|
||||
class ChangeSecretRecord(JMSBaseModel):
|
||||
execution = models.ForeignKey('assets.AutomationExecution', on_delete=models.CASCADE)
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
|
||||
account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, null=True)
|
||||
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
|
||||
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#
|
||||
import io
|
||||
import os
|
||||
import uuid
|
||||
import sshpubkeys
|
||||
from hashlib import md5
|
||||
|
||||
|
@ -12,13 +11,13 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.conf import settings
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from common.db import fields
|
||||
from common.utils import (
|
||||
ssh_key_string_to_obj, ssh_key_gen, get_logger,
|
||||
random_string, ssh_pubkey_gen, lazyproperty
|
||||
)
|
||||
from common.db import fields
|
||||
from orgs.mixins.models import JMSOrgBaseModel
|
||||
from assets.const import Connectivity, SecretType
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -47,8 +46,7 @@ class AbsConnectivity(models.Model):
|
|||
abstract = True
|
||||
|
||||
|
||||
class BaseAccount(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
class BaseAccount(JMSOrgBaseModel):
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
|
||||
secret_type = models.CharField(
|
||||
|
@ -56,9 +54,8 @@ class BaseAccount(OrgModelMixin):
|
|||
)
|
||||
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
@property
|
||||
|
@ -116,7 +113,7 @@ class BaseAccount(OrgModelMixin):
|
|||
|
||||
@property
|
||||
def private_key_path(self):
|
||||
if not self.secret_type != 'ssh_key' or not self.secret:
|
||||
if not self.secret_type != SecretType.ssh_key or not self.secret:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
|
@ -158,7 +155,6 @@ class BaseAccount(OrgModelMixin):
|
|||
return {
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
'public_key': self.public_key,
|
||||
}
|
||||
|
||||
|
|
|
@ -15,11 +15,35 @@ class AccountBackupExecutionTaskMsg(object):
|
|||
def message(self):
|
||||
name = self.name
|
||||
if self.user.secret_key:
|
||||
return _('{} - The account backup passage task has been completed. See the attachment for details').format(name)
|
||||
return _('{} - The account backup passage task has been completed. See the attachment for details').format(
|
||||
name)
|
||||
return _("{} - The account backup passage task has been completed: the encryption password has not been set - "
|
||||
"please go to personal information -> file encryption password to set the encryption password").format(name)
|
||||
"please go to personal information -> file encryption password to set the encryption password").format(
|
||||
name)
|
||||
|
||||
def publish(self, attachment_list=None):
|
||||
send_mail_attachment_async.delay(
|
||||
send_mail_attachment_async(
|
||||
self.subject, self.message, [self.user.email], attachment_list
|
||||
)
|
||||
|
||||
|
||||
class ChangeSecretExecutionTaskMsg(object):
|
||||
subject = _('Notification of implementation result of encryption change plan')
|
||||
|
||||
def __init__(self, name: str, user: User):
|
||||
self.name = name
|
||||
self.user = user
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
name = self.name
|
||||
if self.user.secret_key:
|
||||
return _('{} - The encryption change task has been completed. See the attachment for details').format(name)
|
||||
return _("{} - The encryption change task has been completed: the encryption password has not been set - "
|
||||
"please go to personal information -> file encryption password to set the encryption password").format(
|
||||
name)
|
||||
|
||||
def publish(self, attachments=None):
|
||||
send_mail_attachment_async(
|
||||
self.subject, self.message, [self.user.email], attachments
|
||||
)
|
||||
|
|
|
@ -11,3 +11,4 @@ from .account import *
|
|||
from assets.serializers.account.backup import *
|
||||
from .platform import *
|
||||
from .cagegory import *
|
||||
from .automation import *
|
||||
|
|
|
@ -3,6 +3,7 @@ from rest_framework import serializers
|
|||
|
||||
from common.drf.serializers import SecretReadableMixin
|
||||
from common.drf.fields import ObjectRelatedField
|
||||
from assets.tasks import push_accounts_to_assets
|
||||
from assets.models import Account, AccountTemplate, Asset
|
||||
from .base import BaseAccountSerializer
|
||||
|
||||
|
@ -47,8 +48,7 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer):
|
|||
def create(self, validated_data):
|
||||
instance = super().create(validated_data)
|
||||
if self.push_now:
|
||||
# Todo: push it
|
||||
print("Start push account to asset")
|
||||
push_accounts_to_assets.delay([instance.id], [instance.asset_id])
|
||||
return instance
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models import AccountTemplate
|
||||
from .base import BaseAccountSerializer
|
||||
|
||||
|
@ -9,15 +6,14 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||
class Meta(BaseAccountSerializer.Meta):
|
||||
model = AccountTemplate
|
||||
|
||||
@classmethod
|
||||
def validate_required(cls, attrs):
|
||||
# Todo: why ?
|
||||
required_field_dict = {}
|
||||
error = _('This field is required.')
|
||||
for k, v in cls().fields.items():
|
||||
if v.required and k not in attrs:
|
||||
required_field_dict[k] = error
|
||||
if not required_field_dict:
|
||||
return
|
||||
raise serializers.ValidationError(required_field_dict)
|
||||
|
||||
# @classmethod
|
||||
# def validate_required(cls, attrs):
|
||||
# # TODO 选择模版后检查一些必填项
|
||||
# required_field_dict = {}
|
||||
# error = _('This field is required.')
|
||||
# for k, v in cls().fields.items():
|
||||
# if v.required and k not in attrs:
|
||||
# required_field_dict[k] = error
|
||||
# if not required_field_dict:
|
||||
# return
|
||||
# raise serializers.ValidationError(required_field_dict)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.utils import get_logger
|
||||
|
||||
from assets.models import ChangeSecretRecord
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
|
||||
asset = serializers.SerializerMethodField(label=_('Asset'))
|
||||
account = serializers.SerializerMethodField(label=_('Account'))
|
||||
is_success = serializers.SerializerMethodField(label=_('Is success'))
|
||||
|
||||
class Meta:
|
||||
model = ChangeSecretRecord
|
||||
fields = [
|
||||
'id', 'asset', 'account', 'old_secret', 'new_secret',
|
||||
'status', 'error', 'is_success'
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_asset(instance):
|
||||
return str(instance.asset)
|
||||
|
||||
@staticmethod
|
||||
def get_account(instance):
|
||||
return str(instance.account)
|
||||
|
||||
@staticmethod
|
||||
def get_is_success(obj):
|
||||
if obj.status == 'success':
|
||||
return _("Success")
|
||||
return _("Failed")
|
|
@ -9,8 +9,6 @@ logger = get_logger(__name__)
|
|||
|
||||
@receiver(pre_save, sender=Account)
|
||||
def on_account_pre_create(sender, instance, **kwargs):
|
||||
# Todo: 是否只有更改密码的时候才有版本增加, bitwarden 只有再改密码的时候,
|
||||
# 才会有版本,代表的是 password_version
|
||||
# 升级版本号
|
||||
instance.version += 1
|
||||
# 即使在 root 组织也不怕
|
||||
|
|
|
@ -11,8 +11,9 @@ __all__ = [
|
|||
|
||||
|
||||
@org_aware_func("assets")
|
||||
def push_accounts_to_assets_util(accounts, assets, task_name):
|
||||
def push_accounts_to_assets_util(accounts, assets):
|
||||
from assets.models import PushAccountAutomation
|
||||
task_name = gettext_noop("Push accounts to assets")
|
||||
task_name = PushAccountAutomation.generate_unique_name(task_name)
|
||||
account_usernames = list(accounts.values_list('username', flat=True))
|
||||
|
||||
|
@ -33,5 +34,4 @@ def push_accounts_to_assets(account_ids, asset_ids):
|
|||
assets = Asset.objects.get(id=asset_ids)
|
||||
accounts = Account.objects.get(id=account_ids)
|
||||
|
||||
task_name = gettext_noop("Push accounts to assets")
|
||||
return push_accounts_to_assets_util(accounts, assets, task_name)
|
||||
return push_accounts_to_assets_util(accounts, assets)
|
||||
|
|
|
@ -13,8 +13,8 @@ from .mixin import BulkListSerializerMixin, BulkSerializerMixin
|
|||
__all__ = [
|
||||
'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer',
|
||||
'AdaptedBulkListSerializer', 'CeleryTaskExecutionSerializer',
|
||||
'WritableNestedModelSerializer',
|
||||
'GroupedChoiceSerializer',
|
||||
'WritableNestedModelSerializer', 'GroupedChoiceSerializer',
|
||||
'FileSerializer'
|
||||
]
|
||||
|
||||
|
||||
|
@ -88,3 +88,7 @@ class GroupedChoiceSerializer(ChoiceSerializer):
|
|||
|
||||
class WritableNestedModelSerializer(NestedModelSerializer):
|
||||
pass
|
||||
|
||||
|
||||
class FileSerializer(serializers.Serializer):
|
||||
file = serializers.FileField(label=_("File"))
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c4c889e251a4de3161f462e042882ba3c4ab40eaf34799e2d49d4788ad961586
|
||||
size 119171
|
||||
oid sha256:07f1cfd07039142f4847b4139586bf815467f266119eae57476c073130f0ac92
|
||||
size 118098
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-01 15:30+0800\n"
|
||||
"POT-Creation-Date: 2022-11-03 16:00+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"
|
||||
|
@ -25,7 +25,7 @@ msgstr "Acls"
|
|||
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48
|
||||
#: applications/models.py:10 assets/models/_user.py:33
|
||||
#: assets/models/asset/common.py:81 assets/models/asset/common.py:91
|
||||
#: assets/models/base.py:59 assets/models/cmd_filter.py:25
|
||||
#: assets/models/base.py:57 assets/models/cmd_filter.py:25
|
||||
#: assets/models/domain.py:24 assets/models/group.py:20
|
||||
#: assets/models/label.py:17 assets/models/platform.py:22
|
||||
#: assets/models/platform.py:68 assets/serializers/asset/common.py:86
|
||||
|
@ -60,7 +60,7 @@ msgstr "アクティブ"
|
|||
|
||||
#: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40
|
||||
#: assets/models/asset/common.py:100 assets/models/automations/base.py:26
|
||||
#: assets/models/backup.py:30 assets/models/base.py:66
|
||||
#: assets/models/backup.py:30 assets/models/base.py:65
|
||||
#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88
|
||||
#: assets/models/domain.py:25 assets/models/domain.py:69
|
||||
#: assets/models/group.py:23 assets/models/label.py:22
|
||||
|
@ -68,7 +68,7 @@ msgstr "アクティブ"
|
|||
#: ops/models/playbook.py:25 orgs/models.py:74
|
||||
#: perms/models/asset_permission.py:84 rbac/models/role.py:37
|
||||
#: settings/models.py:38 terminal/models/applet/applet.py:28
|
||||
#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:34
|
||||
#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:104
|
||||
#: terminal/models/component/endpoint.py:24
|
||||
#: terminal/models/component/endpoint.py:97
|
||||
#: terminal/models/component/storage.py:28
|
||||
|
@ -129,14 +129,14 @@ msgstr "レビュー担当者"
|
|||
msgid "Login acl"
|
||||
msgstr "ログインacl"
|
||||
|
||||
#: acls/models/login_asset_acl.py:21 assets/models/account.py:57
|
||||
#: acls/models/login_asset_acl.py:21 assets/models/account.py:59
|
||||
#: authentication/models/connection_token.py:33 ops/models/base.py:18
|
||||
#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87
|
||||
#: xpack/plugins/cloud/serializers/task.py:65
|
||||
msgid "Account"
|
||||
msgstr "アカウント"
|
||||
|
||||
#: acls/models/login_asset_acl.py:22 assets/models/account.py:47
|
||||
#: acls/models/login_asset_acl.py:22 assets/models/account.py:49
|
||||
#: assets/models/asset/common.py:83 assets/models/asset/common.py:227
|
||||
#: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14
|
||||
#: assets/serializers/account/account.py:58 assets/serializers/label.py:30
|
||||
|
@ -164,7 +164,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること
|
|||
|
||||
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18
|
||||
#: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34
|
||||
#: assets/models/base.py:60 assets/models/gathered_user.py:15
|
||||
#: assets/models/base.py:58 assets/models/gathered_user.py:15
|
||||
#: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27
|
||||
#: authentication/models/temp_token.py:9
|
||||
#: authentication/templates/authentication/_msg_different_city.html:9
|
||||
|
@ -210,7 +210,7 @@ msgstr ""
|
|||
"ション: {}"
|
||||
|
||||
#: acls/serializers/login_asset_acl.py:84
|
||||
#: tickets/serializers/ticket/ticket.py:85
|
||||
#: tickets/serializers/ticket/ticket.py:86
|
||||
msgid "The organization `{}` does not exist"
|
||||
msgstr "組織 '{}'は存在しません"
|
||||
|
||||
|
@ -255,7 +255,7 @@ msgstr "カテゴリ"
|
|||
#: assets/models/platform.py:70 assets/serializers/asset/common.py:63
|
||||
#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24
|
||||
#: terminal/models/component/storage.py:57
|
||||
#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20
|
||||
#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33
|
||||
#: tickets/models/comment.py:26 tickets/models/flow.py:57
|
||||
#: tickets/models/ticket/apply_application.py:17
|
||||
#: tickets/models/ticket/general.py:273
|
||||
|
@ -303,7 +303,7 @@ msgstr "アプリ資産"
|
|||
msgid "{} disabled"
|
||||
msgstr "無効"
|
||||
|
||||
#: assets/const/account.py:6 assets/tasks/const.py:51 audits/const.py:5
|
||||
#: assets/const/account.py:6 audits/const.py:5
|
||||
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
|
||||
#: common/utils/ip/utils.py:84
|
||||
msgid "Unknown"
|
||||
|
@ -313,14 +313,14 @@ msgstr "不明"
|
|||
msgid "Ok"
|
||||
msgstr "OK"
|
||||
|
||||
#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:18
|
||||
#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:19
|
||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:190
|
||||
#: xpack/plugins/cloud/const.py:33
|
||||
msgid "Failed"
|
||||
msgstr "失敗しました"
|
||||
|
||||
#: assets/const/account.py:12 assets/models/_user.py:35
|
||||
#: assets/models/base.py:53 assets/models/domain.py:71
|
||||
#: assets/models/base.py:52 assets/models/domain.py:71
|
||||
#: assets/serializers/base.py:15 audits/signal_handlers.py:50
|
||||
#: authentication/confirm/password.py:9 authentication/forms.py:32
|
||||
#: authentication/templates/authentication/login.html:228
|
||||
|
@ -337,19 +337,19 @@ msgstr "失敗しました"
|
|||
msgid "Password"
|
||||
msgstr "パスワード"
|
||||
|
||||
#: assets/const/account.py:13 assets/models/base.py:54
|
||||
#: assets/const/account.py:13 assets/models/base.py:53
|
||||
#, fuzzy
|
||||
#| msgid "SSH Key"
|
||||
msgid "SSH key"
|
||||
msgstr "SSHキー"
|
||||
|
||||
#: assets/const/account.py:14 assets/models/base.py:55
|
||||
#: assets/const/account.py:14 assets/models/base.py:54
|
||||
#: authentication/models/access_key.py:31
|
||||
msgid "Access key"
|
||||
msgstr "アクセスキー"
|
||||
|
||||
#: assets/const/account.py:15 assets/models/_user.py:38
|
||||
#: assets/models/base.py:56 authentication/models/sso_token.py:13
|
||||
#: assets/models/base.py:55 authentication/models/sso_token.py:13
|
||||
msgid "Token"
|
||||
msgstr "トークン"
|
||||
|
||||
|
@ -417,7 +417,7 @@ msgid "Replace (The key generated by JumpServer) "
|
|||
msgstr "置換(JumpServerによって生成された鍵)"
|
||||
|
||||
#: assets/const/category.py:11 settings/serializers/auth/radius.py:14
|
||||
#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:56
|
||||
#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:59
|
||||
#: terminal/models/component/endpoint.py:12
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:72
|
||||
msgid "Host"
|
||||
|
@ -499,22 +499,20 @@ msgstr "SSH秘密鍵"
|
|||
msgid "SSH public key"
|
||||
msgstr "SSHパブリックキー"
|
||||
|
||||
#: assets/models/_user.py:41 assets/models/automations/base.py:87
|
||||
#: assets/models/base.py:67 assets/models/domain.py:26
|
||||
#: assets/models/gathered_user.py:19 assets/models/group.py:22
|
||||
#: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53
|
||||
#: orgs/models.py:73 perms/models/asset_permission.py:82
|
||||
#: assets/models/_user.py:41 assets/models/automations/base.py:96
|
||||
#: assets/models/domain.py:26 assets/models/gathered_user.py:19
|
||||
#: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50
|
||||
#: ops/models/base.py:53 orgs/models.py:73 perms/models/asset_permission.py:82
|
||||
#: users/models/group.py:18 users/models/user.py:927
|
||||
msgid "Date created"
|
||||
msgstr "作成された日付"
|
||||
|
||||
#: assets/models/_user.py:42 assets/models/base.py:68
|
||||
#: assets/models/gathered_user.py:20 common/db/models.py:77
|
||||
#: common/mixins/models.py:51
|
||||
#: assets/models/_user.py:42 assets/models/gathered_user.py:20
|
||||
#: common/db/models.py:77 common/mixins/models.py:51
|
||||
msgid "Date updated"
|
||||
msgstr "更新日"
|
||||
|
||||
#: assets/models/_user.py:43 assets/models/base.py:69
|
||||
#: assets/models/_user.py:43 assets/models/base.py:66
|
||||
#: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91
|
||||
#: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49
|
||||
#: orgs/models.py:71 perms/models/asset_permission.py:81
|
||||
|
@ -582,34 +580,34 @@ msgstr "システムユーザー"
|
|||
msgid "Can match system user"
|
||||
msgstr "システムユーザーに一致できます"
|
||||
|
||||
#: assets/models/account.py:51
|
||||
#: assets/models/account.py:53
|
||||
#, fuzzy
|
||||
#| msgid "Switch from"
|
||||
msgid "Su from"
|
||||
msgstr "から切り替え"
|
||||
|
||||
#: assets/models/account.py:53 settings/serializers/auth/cas.py:18
|
||||
#: assets/models/account.py:55 settings/serializers/auth/cas.py:18
|
||||
#: terminal/models/applet/applet.py:22
|
||||
msgid "Version"
|
||||
msgstr "バージョン"
|
||||
|
||||
#: assets/models/account.py:63
|
||||
#: assets/models/account.py:65
|
||||
msgid "Can view asset account secret"
|
||||
msgstr "資産アカウントの秘密を表示できます"
|
||||
|
||||
#: assets/models/account.py:64
|
||||
#: assets/models/account.py:66
|
||||
msgid "Can change asset account secret"
|
||||
msgstr "資産口座の秘密を変更できます"
|
||||
|
||||
#: assets/models/account.py:65
|
||||
#: assets/models/account.py:67
|
||||
msgid "Can view asset history account"
|
||||
msgstr "資産履歴アカウントを表示できます"
|
||||
|
||||
#: assets/models/account.py:66
|
||||
#: assets/models/account.py:68
|
||||
msgid "Can view asset history account secret"
|
||||
msgstr "資産履歴アカウントパスワードを表示できます"
|
||||
|
||||
#: assets/models/account.py:89 assets/serializers/account/account.py:13
|
||||
#: assets/models/account.py:91 assets/serializers/account/account.py:13
|
||||
#, fuzzy
|
||||
#| msgid "Account name"
|
||||
msgid "Account template"
|
||||
|
@ -642,9 +640,9 @@ msgid "Nodes"
|
|||
msgstr "ノード"
|
||||
|
||||
#: assets/models/asset/common.py:98 assets/models/automations/base.py:25
|
||||
#: assets/models/cmd_filter.py:39 assets/models/domain.py:70
|
||||
#: assets/models/label.py:21 terminal/models/applet/applet.py:25
|
||||
#: users/serializers/user.py:147
|
||||
#: assets/models/base.py:64 assets/models/cmd_filter.py:39
|
||||
#: assets/models/domain.py:70 assets/models/label.py:21
|
||||
#: terminal/models/applet/applet.py:25 users/serializers/user.py:147
|
||||
msgid "Is active"
|
||||
msgstr "アクティブです。"
|
||||
|
||||
|
@ -661,7 +659,9 @@ msgid "Can test asset connectivity"
|
|||
msgstr "資産接続をテストできます"
|
||||
|
||||
#: assets/models/asset/common.py:232
|
||||
msgid "Can push system user to asset"
|
||||
#, fuzzy
|
||||
#| msgid "Can push system user to asset"
|
||||
msgid "Can push account to asset"
|
||||
msgstr "システムユーザーを資産にプッシュできます"
|
||||
|
||||
#: assets/models/asset/common.py:233
|
||||
|
@ -677,6 +677,7 @@ msgid "Move asset to node"
|
|||
msgstr "アセットをノードに移動する"
|
||||
|
||||
#: assets/models/asset/web.py:9 audits/models.py:111
|
||||
#: terminal/serializers/applet_host.py:24
|
||||
msgid "Disabled"
|
||||
msgstr "無効"
|
||||
|
||||
|
@ -723,15 +724,15 @@ msgstr "アカウント"
|
|||
msgid "Assets"
|
||||
msgstr "資産"
|
||||
|
||||
#: assets/models/automations/base.py:77 assets/models/automations/base.py:84
|
||||
#: assets/models/automations/base.py:86 assets/models/automations/base.py:93
|
||||
#, fuzzy
|
||||
#| msgid "Automatic managed"
|
||||
msgid "Automation task"
|
||||
msgstr "自動管理"
|
||||
|
||||
#: assets/models/automations/base.py:88 assets/models/backup.py:77
|
||||
#: assets/models/automations/base.py:97 assets/models/backup.py:77
|
||||
#: audits/models.py:44 ops/models/base.py:54
|
||||
#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:32
|
||||
#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:102
|
||||
#: terminal/models/session/session.py:43
|
||||
#: tickets/models/ticket/apply_application.py:28
|
||||
#: tickets/models/ticket/apply_asset.py:21
|
||||
|
@ -741,32 +742,32 @@ msgstr "自動管理"
|
|||
msgid "Date start"
|
||||
msgstr "開始日"
|
||||
|
||||
#: assets/models/automations/base.py:89
|
||||
#: assets/models/automations/base.py:98
|
||||
#: assets/models/automations/change_secret.py:58 ops/models/base.py:55
|
||||
#: terminal/models/applet/host.py:33
|
||||
#: terminal/models/applet/host.py:103
|
||||
msgid "Date finished"
|
||||
msgstr "終了日"
|
||||
|
||||
#: assets/models/automations/base.py:91
|
||||
#: assets/models/automations/base.py:100
|
||||
#, fuzzy
|
||||
#| msgid "Relation snapshot"
|
||||
msgid "Automation snapshot"
|
||||
msgstr "製造オーダスナップショット"
|
||||
|
||||
#: assets/models/automations/base.py:95 assets/models/backup.py:88
|
||||
#: assets/models/automations/base.py:104 assets/models/backup.py:88
|
||||
#: assets/serializers/account/backup.py:36
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:121
|
||||
#: xpack/plugins/change_auth_plan/serializers/base.py:78
|
||||
msgid "Trigger mode"
|
||||
msgstr "トリガーモード"
|
||||
|
||||
#: assets/models/automations/base.py:99
|
||||
#: assets/models/automations/base.py:108
|
||||
#, fuzzy
|
||||
#| msgid "Command execution"
|
||||
msgid "Automation task execution"
|
||||
msgstr "コマンド実行"
|
||||
|
||||
#: assets/models/automations/change_secret.py:15 assets/models/base.py:62
|
||||
#: assets/models/automations/change_secret.py:15 assets/models/base.py:60
|
||||
#, fuzzy
|
||||
#| msgid "Secret key"
|
||||
msgid "Secret type"
|
||||
|
@ -779,7 +780,7 @@ msgid "Secret strategy"
|
|||
msgstr "SSHキー戦略"
|
||||
|
||||
#: assets/models/automations/change_secret.py:21
|
||||
#: assets/models/automations/change_secret.py:56 assets/models/base.py:64
|
||||
#: assets/models/automations/change_secret.py:56 assets/models/base.py:62
|
||||
#: assets/serializers/account/base.py:17
|
||||
#: authentication/models/connection_token.py:34
|
||||
#: authentication/models/temp_token.py:10
|
||||
|
@ -825,7 +826,7 @@ msgstr "ひみつ"
|
|||
msgid "Date started"
|
||||
msgstr "開始日"
|
||||
|
||||
#: assets/models/automations/change_secret.py:60 common/const/choices.py:19
|
||||
#: assets/models/automations/change_secret.py:60 common/const/choices.py:20
|
||||
#, fuzzy
|
||||
#| msgid "WeCom Error"
|
||||
msgid "Error"
|
||||
|
@ -855,6 +856,12 @@ msgstr "資産ユーザーの収集"
|
|||
msgid "Gather asset facts"
|
||||
msgstr "資産ユーザーの収集"
|
||||
|
||||
#: assets/models/automations/ping.py:15
|
||||
#, fuzzy
|
||||
#| msgid "Login asset"
|
||||
msgid "Ping asset"
|
||||
msgstr "ログイン資産"
|
||||
|
||||
#: assets/models/automations/push_account.py:16
|
||||
#, fuzzy
|
||||
#| msgid "Is service account"
|
||||
|
@ -902,15 +909,15 @@ msgstr "成功は"
|
|||
msgid "Account backup execution"
|
||||
msgstr "アカウントバックアップの実行"
|
||||
|
||||
#: assets/models/base.py:30 assets/serializers/domain.py:42
|
||||
#: assets/models/base.py:29 assets/serializers/domain.py:42
|
||||
msgid "Connectivity"
|
||||
msgstr "接続性"
|
||||
|
||||
#: assets/models/base.py:32 authentication/models/temp_token.py:12
|
||||
#: assets/models/base.py:31 authentication/models/temp_token.py:12
|
||||
msgid "Date verified"
|
||||
msgstr "確認済みの日付"
|
||||
|
||||
#: assets/models/base.py:65
|
||||
#: assets/models/base.py:63
|
||||
msgid "Privileged"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1079,6 +1086,7 @@ msgid "Setting"
|
|||
msgstr "設定"
|
||||
|
||||
#: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37
|
||||
#: terminal/serializers/applet_host.py:25
|
||||
msgid "Enabled"
|
||||
msgstr "有効化"
|
||||
|
||||
|
@ -1415,60 +1423,18 @@ msgstr "パスワードには `'` を含まない"
|
|||
msgid "Password can not contains `\"` "
|
||||
msgstr "パスワードには `\"` を含まない"
|
||||
|
||||
#: assets/tasks/account_connectivity.py:30
|
||||
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
||||
msgstr ""
|
||||
"資産 {} システムプラットフォーム {} はAnsibleタスクの実行をサポートしていませ"
|
||||
"ん。"
|
||||
|
||||
#: assets/tasks/account_connectivity.py:108
|
||||
msgid "Test account connectivity: "
|
||||
msgstr "テストアカウント接続:"
|
||||
|
||||
#: assets/tasks/asset_connectivity.py:49
|
||||
msgid "Test assets connectivity. "
|
||||
msgstr "資産の接続性をテストします。"
|
||||
|
||||
#: assets/tasks/asset_connectivity.py:94 assets/tasks/asset_connectivity.py:107
|
||||
msgid "Test assets connectivity: "
|
||||
msgstr "資産の接続性のテスト:"
|
||||
|
||||
#: assets/tasks/asset_connectivity.py:121
|
||||
msgid "Test if the assets under the node are connectable: "
|
||||
msgstr "ノードの下のアセットが接続可能かどうかをテストします。"
|
||||
|
||||
#: assets/tasks/const.py:49
|
||||
msgid "Unreachable"
|
||||
msgstr "達成できない"
|
||||
|
||||
#: assets/tasks/const.py:50
|
||||
msgid "Reachable"
|
||||
msgstr "接続可能"
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:46
|
||||
msgid "Get asset info failed: {}"
|
||||
msgstr "資産情報の取得に失敗しました: {}"
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:97
|
||||
#: assets/tasks/gather_facts.py:25
|
||||
msgid "Update some assets hardware info. "
|
||||
msgstr "一部の資産ハードウェア情報を更新します。"
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:118
|
||||
msgid "Update asset hardware info: "
|
||||
msgstr "資産ハードウェア情報の更新:"
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:124
|
||||
#: assets/tasks/gather_facts.py:48
|
||||
msgid "Update assets hardware info: "
|
||||
msgstr "資産のハードウェア情報を更新する:"
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:146
|
||||
#: assets/tasks/gather_facts.py:58
|
||||
msgid "Update node asset hardware information: "
|
||||
msgstr "ノード資産のハードウェア情報を更新します。"
|
||||
|
||||
#: assets/tasks/gather_asset_users.py:110
|
||||
msgid "Gather assets users"
|
||||
msgstr "資産ユーザーの収集"
|
||||
|
||||
#: assets/tasks/nodes_amount.py:29
|
||||
msgid ""
|
||||
"The task of self-checking is already running and cannot be started repeatedly"
|
||||
|
@ -1476,6 +1442,24 @@ msgstr ""
|
|||
"セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ"
|
||||
"ん"
|
||||
|
||||
#: assets/tasks/ping.py:20 assets/tasks/ping.py:38
|
||||
#, fuzzy
|
||||
#| msgid "Test assets connectivity. "
|
||||
msgid "Test assets connectivity "
|
||||
msgstr "資産の接続性をテストします。"
|
||||
|
||||
#: assets/tasks/ping.py:48
|
||||
#, fuzzy
|
||||
#| msgid "Test if the assets under the node are connectable: "
|
||||
msgid "Test if the assets under the node are connectable "
|
||||
msgstr "ノードの下のアセットが接続可能かどうかをテストします。"
|
||||
|
||||
#: assets/tasks/push_account.py:36
|
||||
#, fuzzy
|
||||
#| msgid "Create account successfully"
|
||||
msgid "Push accounts to assets"
|
||||
msgstr "アカウントを正常に作成"
|
||||
|
||||
#: assets/tasks/utils.py:17
|
||||
msgid "Asset has been disabled, skipped: {}"
|
||||
msgstr "資産が無効化されました。スキップ: {}"
|
||||
|
@ -1492,6 +1476,12 @@ msgstr "セキュリティのために、ユーザー {} をプッシュしな
|
|||
msgid "No assets matched, stop task"
|
||||
msgstr "一致する資産がない、タスクを停止"
|
||||
|
||||
#: assets/tasks/verify_account.py:36
|
||||
#, fuzzy
|
||||
#| msgid "Test account connectivity: "
|
||||
msgid "Verify accounts connectivity"
|
||||
msgstr "テストアカウント接続:"
|
||||
|
||||
#: audits/apps.py:9
|
||||
msgid "Audits"
|
||||
msgstr "監査"
|
||||
|
@ -1539,7 +1529,7 @@ msgstr "操作"
|
|||
msgid "Filename"
|
||||
msgstr "ファイル名"
|
||||
|
||||
#: audits/models.py:43 audits/models.py:117 common/const/choices.py:17
|
||||
#: audits/models.py:43 audits/models.py:117 common/const/choices.py:18
|
||||
#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114
|
||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:189
|
||||
msgid "Success"
|
||||
|
@ -1619,8 +1609,8 @@ msgid "MFA"
|
|||
msgstr "MFA"
|
||||
|
||||
#: audits/models.py:128 ops/models/base.py:48
|
||||
#: terminal/models/applet/applet.py:57 terminal/models/applet/host.py:19
|
||||
#: terminal/models/applet/host.py:31 terminal/models/component/status.py:33
|
||||
#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:101
|
||||
#: terminal/models/component/status.py:33 terminal/serializers/applet.py:22
|
||||
#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171
|
||||
#: xpack/plugins/cloud/models.py:223
|
||||
msgid "Status"
|
||||
|
@ -2144,13 +2134,13 @@ msgstr "アセットがアクティブ化されていません"
|
|||
msgid "No account"
|
||||
msgstr "ログインacl"
|
||||
|
||||
#: authentication/models/connection_token.py:103
|
||||
#: authentication/models/connection_token.py:101
|
||||
msgid "User has no permission to access asset or permission expired"
|
||||
msgstr ""
|
||||
"ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて"
|
||||
"います"
|
||||
|
||||
#: authentication/models/connection_token.py:145
|
||||
#: authentication/models/connection_token.py:144
|
||||
msgid "Super connection token"
|
||||
msgstr "スーパー接続トークン"
|
||||
|
||||
|
@ -2576,15 +2566,19 @@ msgstr "手動トリガー"
|
|||
msgid "Timing trigger"
|
||||
msgstr "タイミングトリガー"
|
||||
|
||||
#: common/const/choices.py:15 tickets/const.py:29 tickets/const.py:37
|
||||
#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183
|
||||
msgid "Ready"
|
||||
msgstr "の準備を"
|
||||
|
||||
#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:37
|
||||
msgid "Pending"
|
||||
msgstr "未定"
|
||||
|
||||
#: common/const/choices.py:16
|
||||
#: common/const/choices.py:17
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: common/const/choices.py:20
|
||||
#: common/const/choices.py:21
|
||||
#, fuzzy
|
||||
#| msgid "Cancel"
|
||||
msgid "Canceled"
|
||||
|
@ -2653,6 +2647,12 @@ msgstr "解析ファイルエラー: {}"
|
|||
msgid "Children"
|
||||
msgstr ""
|
||||
|
||||
#: common/drf/serializers/common.py:94
|
||||
#, fuzzy
|
||||
#| msgid "Filename"
|
||||
msgid "File"
|
||||
msgstr "ファイル名"
|
||||
|
||||
#: common/exceptions.py:15
|
||||
#, python-format
|
||||
msgid "%s object does not exist."
|
||||
|
@ -3091,7 +3091,7 @@ msgstr "アプリ組織"
|
|||
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88
|
||||
#: rbac/const.py:7 rbac/models/rolebinding.py:48
|
||||
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
|
||||
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71
|
||||
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:72
|
||||
msgid "Organization"
|
||||
msgstr "組織"
|
||||
|
||||
|
@ -4928,50 +4928,52 @@ msgstr "認証アドレス"
|
|||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/models/applet/applet.py:29 terminal/serializers/storage.py:157
|
||||
#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157
|
||||
msgid "Hosts"
|
||||
msgstr "ホスト"
|
||||
|
||||
#: terminal/models/applet/applet.py:55 terminal/models/applet/host.py:21
|
||||
#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:28
|
||||
#, fuzzy
|
||||
#| msgid "Apply assets"
|
||||
msgid "Applet"
|
||||
msgstr "資産の適用"
|
||||
|
||||
#: terminal/models/applet/host.py:14
|
||||
#, fuzzy
|
||||
#| msgid "Verify auth"
|
||||
msgid "Account automation"
|
||||
msgstr "パスワード/キーの確認"
|
||||
|
||||
#: terminal/models/applet/host.py:15 terminal/serializers/applet.py:66
|
||||
#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:36
|
||||
#, fuzzy
|
||||
#| msgid "More login options"
|
||||
msgid "Deploy options"
|
||||
msgstr "その他のログインオプション"
|
||||
|
||||
#: terminal/models/applet/host.py:16
|
||||
#: terminal/models/applet/host.py:20
|
||||
msgid "Inited"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/models/applet/host.py:17
|
||||
#: terminal/models/applet/host.py:21
|
||||
#, fuzzy
|
||||
#| msgid "Date finished"
|
||||
msgid "Date inited"
|
||||
msgstr "終了日"
|
||||
|
||||
#: terminal/models/applet/host.py:18
|
||||
#: terminal/models/applet/host.py:22
|
||||
#, fuzzy
|
||||
#| msgid "Date sync"
|
||||
msgid "Date synced"
|
||||
msgstr "日付の同期"
|
||||
|
||||
#: terminal/models/applet/host.py:30
|
||||
#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:183
|
||||
msgid "Terminal"
|
||||
msgstr "ターミナル"
|
||||
|
||||
#: terminal/models/applet/host.py:99
|
||||
#, fuzzy
|
||||
#| msgid "Host"
|
||||
msgid "Hosting"
|
||||
msgstr "ホスト"
|
||||
|
||||
#: terminal/models/applet/host.py:100
|
||||
msgid "Initial"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/models/component/endpoint.py:14
|
||||
msgid "HTTPS Port"
|
||||
msgstr "HTTPS ポート"
|
||||
|
@ -5076,10 +5078,6 @@ msgstr "再生ストレージ"
|
|||
msgid "type"
|
||||
msgstr "タイプ"
|
||||
|
||||
#: terminal/models/component/terminal.py:183
|
||||
msgid "Terminal"
|
||||
msgstr "ターミナル"
|
||||
|
||||
#: terminal/models/component/terminal.py:185
|
||||
msgid "Can view terminal config"
|
||||
msgstr "ターミナル構成を表示できます"
|
||||
|
@ -5196,49 +5194,61 @@ msgstr "レベル"
|
|||
msgid "Batch danger command alert"
|
||||
msgstr "一括危険コマンド警告"
|
||||
|
||||
#: terminal/serializers/applet.py:19
|
||||
#: terminal/serializers/applet.py:16
|
||||
#, fuzzy
|
||||
#| msgid "Public key"
|
||||
msgid "Published"
|
||||
msgstr "公開キー"
|
||||
|
||||
#: terminal/serializers/applet.py:17
|
||||
#, fuzzy
|
||||
#| msgid "Finished"
|
||||
msgid "Unpublished"
|
||||
msgstr "終了"
|
||||
|
||||
#: terminal/serializers/applet.py:18
|
||||
#, fuzzy
|
||||
#| msgid "Phone not set"
|
||||
msgid "Not match"
|
||||
msgstr "電話が設定されていない"
|
||||
|
||||
#: terminal/serializers/applet.py:32
|
||||
msgid "Icon"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/serializers/applet.py:53
|
||||
#, fuzzy
|
||||
#| msgid "Phone not set"
|
||||
msgid "Not set"
|
||||
msgstr "電話が設定されていない"
|
||||
|
||||
#: terminal/serializers/applet.py:54
|
||||
#: terminal/serializers/applet_host.py:20
|
||||
#, fuzzy
|
||||
#| msgid "Session"
|
||||
msgid "Per Session"
|
||||
msgstr "セッション"
|
||||
|
||||
#: terminal/serializers/applet.py:55
|
||||
#: terminal/serializers/applet_host.py:21
|
||||
msgid "Per Device"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/serializers/applet.py:57
|
||||
#: terminal/serializers/applet_host.py:27
|
||||
#, fuzzy
|
||||
#| msgid "License"
|
||||
msgid "RDS Licensing"
|
||||
msgstr "ライセンス"
|
||||
|
||||
#: terminal/serializers/applet.py:58
|
||||
#: terminal/serializers/applet_host.py:28
|
||||
msgid "RDS License Server"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/serializers/applet.py:59
|
||||
#: terminal/serializers/applet_host.py:29
|
||||
msgid "RDS Licensing Mode"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/serializers/applet.py:60
|
||||
#: terminal/serializers/applet_host.py:30
|
||||
msgid "RDS fSingleSessionPerUser"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/serializers/applet.py:61
|
||||
#: terminal/serializers/applet_host.py:31
|
||||
msgid "RDS Max Disconnection Time"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/serializers/applet.py:62
|
||||
#: terminal/serializers/applet_host.py:32
|
||||
msgid "RDS Remote App Logoff Time Limit"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5673,7 +5683,7 @@ msgstr "有効期限は開始日より大きくする必要があります"
|
|||
msgid "Permission named `{}` already exists"
|
||||
msgstr "'{}'という名前の権限は既に存在します"
|
||||
|
||||
#: tickets/serializers/ticket/ticket.py:99
|
||||
#: tickets/serializers/ticket/ticket.py:101
|
||||
msgid "The ticket flow `{}` does not exist"
|
||||
msgstr "チケットフロー '{}'が存在しない"
|
||||
|
||||
|
@ -6377,10 +6387,6 @@ msgstr "公開鍵をnull、exitに設定することはできません。"
|
|||
msgid "Change auth plan snapshot"
|
||||
msgstr "計画スナップショットの暗号化"
|
||||
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:183
|
||||
msgid "Ready"
|
||||
msgstr "の準備を"
|
||||
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:184
|
||||
msgid "Preflight check"
|
||||
msgstr "プリフライトチェック"
|
||||
|
@ -6991,11 +6997,11 @@ msgstr "テーマ"
|
|||
msgid "Interface setting"
|
||||
msgstr "インターフェイスの設定"
|
||||
|
||||
#: xpack/plugins/license/api.py:50
|
||||
#: xpack/plugins/license/api.py:53
|
||||
msgid "License import successfully"
|
||||
msgstr "ライセンスのインポートに成功"
|
||||
|
||||
#: xpack/plugins/license/api.py:51
|
||||
#: xpack/plugins/license/api.py:54
|
||||
msgid "License is invalid"
|
||||
msgstr "ライセンスが無効です"
|
||||
|
||||
|
@ -7019,6 +7025,34 @@ msgstr "究極のエディション"
|
|||
msgid "Community edition"
|
||||
msgstr "コミュニティ版"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Verify auth"
|
||||
#~ msgid "Account automation"
|
||||
#~ msgstr "パスワード/キーの確認"
|
||||
|
||||
#~ msgid "The asset {} system platform {} does not support run Ansible tasks"
|
||||
#~ msgstr ""
|
||||
#~ "資産 {} システムプラットフォーム {} はAnsibleタスクの実行をサポートしてい"
|
||||
#~ "ません。"
|
||||
|
||||
#~ msgid "Test assets connectivity: "
|
||||
#~ msgstr "資産の接続性のテスト:"
|
||||
|
||||
#~ msgid "Unreachable"
|
||||
#~ msgstr "達成できない"
|
||||
|
||||
#~ msgid "Reachable"
|
||||
#~ msgstr "接続可能"
|
||||
|
||||
#~ msgid "Get asset info failed: {}"
|
||||
#~ msgstr "資産情報の取得に失敗しました: {}"
|
||||
|
||||
#~ msgid "Update asset hardware info: "
|
||||
#~ msgstr "資産ハードウェア情報の更新:"
|
||||
|
||||
#~ msgid "Gather assets users"
|
||||
#~ msgstr "資産ユーザーの収集"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Automatic managed"
|
||||
#~ msgid "Push automation"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08529907ac3879f60c2026f91e7ba3f48a3a7d288f7b29cd35c0f73bc3999c21
|
||||
size 103630
|
||||
oid sha256:0b396cc9a485f6474d14ca30a1a7ba4f954b07754148b964efbb21519c55b280
|
||||
size 102849
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-01 15:30+0800\n"
|
||||
"POT-Creation-Date: 2022-11-03 16:00+0800\n"
|
||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
|
@ -24,7 +24,7 @@ msgstr "访问控制"
|
|||
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48
|
||||
#: applications/models.py:10 assets/models/_user.py:33
|
||||
#: assets/models/asset/common.py:81 assets/models/asset/common.py:91
|
||||
#: assets/models/base.py:59 assets/models/cmd_filter.py:25
|
||||
#: assets/models/base.py:57 assets/models/cmd_filter.py:25
|
||||
#: assets/models/domain.py:24 assets/models/group.py:20
|
||||
#: assets/models/label.py:17 assets/models/platform.py:22
|
||||
#: assets/models/platform.py:68 assets/serializers/asset/common.py:86
|
||||
|
@ -59,7 +59,7 @@ msgstr "激活中"
|
|||
|
||||
#: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40
|
||||
#: assets/models/asset/common.py:100 assets/models/automations/base.py:26
|
||||
#: assets/models/backup.py:30 assets/models/base.py:66
|
||||
#: assets/models/backup.py:30 assets/models/base.py:65
|
||||
#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88
|
||||
#: assets/models/domain.py:25 assets/models/domain.py:69
|
||||
#: assets/models/group.py:23 assets/models/label.py:22
|
||||
|
@ -67,7 +67,7 @@ msgstr "激活中"
|
|||
#: ops/models/playbook.py:25 orgs/models.py:74
|
||||
#: perms/models/asset_permission.py:84 rbac/models/role.py:37
|
||||
#: settings/models.py:38 terminal/models/applet/applet.py:28
|
||||
#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:34
|
||||
#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:104
|
||||
#: terminal/models/component/endpoint.py:24
|
||||
#: terminal/models/component/endpoint.py:97
|
||||
#: terminal/models/component/storage.py:28
|
||||
|
@ -128,14 +128,14 @@ msgstr "审批人"
|
|||
msgid "Login acl"
|
||||
msgstr "登录访问控制"
|
||||
|
||||
#: acls/models/login_asset_acl.py:21 assets/models/account.py:57
|
||||
#: acls/models/login_asset_acl.py:21 assets/models/account.py:59
|
||||
#: authentication/models/connection_token.py:33 ops/models/base.py:18
|
||||
#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87
|
||||
#: xpack/plugins/cloud/serializers/task.py:65
|
||||
msgid "Account"
|
||||
msgstr "账号"
|
||||
|
||||
#: acls/models/login_asset_acl.py:22 assets/models/account.py:47
|
||||
#: acls/models/login_asset_acl.py:22 assets/models/account.py:49
|
||||
#: assets/models/asset/common.py:83 assets/models/asset/common.py:227
|
||||
#: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14
|
||||
#: assets/serializers/account/account.py:58 assets/serializers/label.py:30
|
||||
|
@ -163,7 +163,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
|
|||
|
||||
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18
|
||||
#: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34
|
||||
#: assets/models/base.py:60 assets/models/gathered_user.py:15
|
||||
#: assets/models/base.py:58 assets/models/gathered_user.py:15
|
||||
#: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27
|
||||
#: authentication/models/temp_token.py:9
|
||||
#: authentication/templates/authentication/_msg_different_city.html:9
|
||||
|
@ -206,7 +206,7 @@ msgid ""
|
|||
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}"
|
||||
|
||||
#: acls/serializers/login_asset_acl.py:84
|
||||
#: tickets/serializers/ticket/ticket.py:85
|
||||
#: tickets/serializers/ticket/ticket.py:86
|
||||
msgid "The organization `{}` does not exist"
|
||||
msgstr "组织 `{}` 不存在"
|
||||
|
||||
|
@ -250,7 +250,7 @@ msgstr "类别"
|
|||
#: assets/models/platform.py:70 assets/serializers/asset/common.py:63
|
||||
#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24
|
||||
#: terminal/models/component/storage.py:57
|
||||
#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20
|
||||
#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33
|
||||
#: tickets/models/comment.py:26 tickets/models/flow.py:57
|
||||
#: tickets/models/ticket/apply_application.py:17
|
||||
#: tickets/models/ticket/general.py:273
|
||||
|
@ -296,7 +296,7 @@ msgstr "资产管理"
|
|||
msgid "{} disabled"
|
||||
msgstr "{} 已禁用"
|
||||
|
||||
#: assets/const/account.py:6 assets/tasks/const.py:51 audits/const.py:5
|
||||
#: assets/const/account.py:6 audits/const.py:5
|
||||
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
|
||||
#: common/utils/ip/utils.py:84
|
||||
msgid "Unknown"
|
||||
|
@ -306,14 +306,14 @@ msgstr "未知"
|
|||
msgid "Ok"
|
||||
msgstr "成功"
|
||||
|
||||
#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:18
|
||||
#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:19
|
||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:190
|
||||
#: xpack/plugins/cloud/const.py:33
|
||||
msgid "Failed"
|
||||
msgstr "失败"
|
||||
|
||||
#: assets/const/account.py:12 assets/models/_user.py:35
|
||||
#: assets/models/base.py:53 assets/models/domain.py:71
|
||||
#: assets/models/base.py:52 assets/models/domain.py:71
|
||||
#: assets/serializers/base.py:15 audits/signal_handlers.py:50
|
||||
#: authentication/confirm/password.py:9 authentication/forms.py:32
|
||||
#: authentication/templates/authentication/login.html:228
|
||||
|
@ -330,17 +330,17 @@ msgstr "失败"
|
|||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
#: assets/const/account.py:13 assets/models/base.py:54
|
||||
#: assets/const/account.py:13 assets/models/base.py:53
|
||||
msgid "SSH key"
|
||||
msgstr "SSH 密钥"
|
||||
|
||||
#: assets/const/account.py:14 assets/models/base.py:55
|
||||
#: assets/const/account.py:14 assets/models/base.py:54
|
||||
#: authentication/models/access_key.py:31
|
||||
msgid "Access key"
|
||||
msgstr "Access key"
|
||||
|
||||
#: assets/const/account.py:15 assets/models/_user.py:38
|
||||
#: assets/models/base.py:56 authentication/models/sso_token.py:13
|
||||
#: assets/models/base.py:55 authentication/models/sso_token.py:13
|
||||
msgid "Token"
|
||||
msgstr "Token"
|
||||
|
||||
|
@ -398,7 +398,7 @@ msgid "Replace (The key generated by JumpServer) "
|
|||
msgstr "替换 (由 JumpServer 生成的密钥)"
|
||||
|
||||
#: assets/const/category.py:11 settings/serializers/auth/radius.py:14
|
||||
#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:56
|
||||
#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:59
|
||||
#: terminal/models/component/endpoint.py:12
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:72
|
||||
msgid "Host"
|
||||
|
@ -474,22 +474,20 @@ msgstr "SSH 密钥"
|
|||
msgid "SSH public key"
|
||||
msgstr "SSH 公钥"
|
||||
|
||||
#: assets/models/_user.py:41 assets/models/automations/base.py:87
|
||||
#: assets/models/base.py:67 assets/models/domain.py:26
|
||||
#: assets/models/gathered_user.py:19 assets/models/group.py:22
|
||||
#: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53
|
||||
#: orgs/models.py:73 perms/models/asset_permission.py:82
|
||||
#: assets/models/_user.py:41 assets/models/automations/base.py:96
|
||||
#: assets/models/domain.py:26 assets/models/gathered_user.py:19
|
||||
#: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50
|
||||
#: ops/models/base.py:53 orgs/models.py:73 perms/models/asset_permission.py:82
|
||||
#: users/models/group.py:18 users/models/user.py:927
|
||||
msgid "Date created"
|
||||
msgstr "创建日期"
|
||||
|
||||
#: assets/models/_user.py:42 assets/models/base.py:68
|
||||
#: assets/models/gathered_user.py:20 common/db/models.py:77
|
||||
#: common/mixins/models.py:51
|
||||
#: assets/models/_user.py:42 assets/models/gathered_user.py:20
|
||||
#: common/db/models.py:77 common/mixins/models.py:51
|
||||
msgid "Date updated"
|
||||
msgstr "更新日期"
|
||||
|
||||
#: assets/models/_user.py:43 assets/models/base.py:69
|
||||
#: assets/models/_user.py:43 assets/models/base.py:66
|
||||
#: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91
|
||||
#: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49
|
||||
#: orgs/models.py:71 perms/models/asset_permission.py:81
|
||||
|
@ -557,32 +555,32 @@ msgstr "系统用户"
|
|||
msgid "Can match system user"
|
||||
msgstr "可以匹配系统用户"
|
||||
|
||||
#: assets/models/account.py:51
|
||||
#: assets/models/account.py:53
|
||||
msgid "Su from"
|
||||
msgstr "切换自"
|
||||
|
||||
#: assets/models/account.py:53 settings/serializers/auth/cas.py:18
|
||||
#: assets/models/account.py:55 settings/serializers/auth/cas.py:18
|
||||
#: terminal/models/applet/applet.py:22
|
||||
msgid "Version"
|
||||
msgstr "版本"
|
||||
|
||||
#: assets/models/account.py:63
|
||||
#: assets/models/account.py:65
|
||||
msgid "Can view asset account secret"
|
||||
msgstr "可以查看资产账号密码"
|
||||
|
||||
#: assets/models/account.py:64
|
||||
#: assets/models/account.py:66
|
||||
msgid "Can change asset account secret"
|
||||
msgstr "可以更改资产账号密码"
|
||||
|
||||
#: assets/models/account.py:65
|
||||
#: assets/models/account.py:67
|
||||
msgid "Can view asset history account"
|
||||
msgstr "可以查看资产历史账号"
|
||||
|
||||
#: assets/models/account.py:66
|
||||
#: assets/models/account.py:68
|
||||
msgid "Can view asset history account secret"
|
||||
msgstr "可以查看资产历史账号密码"
|
||||
|
||||
#: assets/models/account.py:89 assets/serializers/account/account.py:13
|
||||
#: assets/models/account.py:91 assets/serializers/account/account.py:13
|
||||
msgid "Account template"
|
||||
msgstr "账号模版"
|
||||
|
||||
|
@ -613,9 +611,9 @@ msgid "Nodes"
|
|||
msgstr "节点"
|
||||
|
||||
#: assets/models/asset/common.py:98 assets/models/automations/base.py:25
|
||||
#: assets/models/cmd_filter.py:39 assets/models/domain.py:70
|
||||
#: assets/models/label.py:21 terminal/models/applet/applet.py:25
|
||||
#: users/serializers/user.py:147
|
||||
#: assets/models/base.py:64 assets/models/cmd_filter.py:39
|
||||
#: assets/models/domain.py:70 assets/models/label.py:21
|
||||
#: terminal/models/applet/applet.py:25 users/serializers/user.py:147
|
||||
msgid "Is active"
|
||||
msgstr "激活"
|
||||
|
||||
|
@ -632,7 +630,9 @@ msgid "Can test asset connectivity"
|
|||
msgstr "可以测试资产连接性"
|
||||
|
||||
#: assets/models/asset/common.py:232
|
||||
msgid "Can push system user to asset"
|
||||
#, fuzzy
|
||||
#| msgid "Can push system user to asset"
|
||||
msgid "Can push account to asset"
|
||||
msgstr "可以推送系统用户到资产"
|
||||
|
||||
#: assets/models/asset/common.py:233
|
||||
|
@ -648,6 +648,7 @@ msgid "Move asset to node"
|
|||
msgstr "移动资产到节点"
|
||||
|
||||
#: assets/models/asset/web.py:9 audits/models.py:111
|
||||
#: terminal/serializers/applet_host.py:24
|
||||
msgid "Disabled"
|
||||
msgstr "禁用"
|
||||
|
||||
|
@ -688,13 +689,13 @@ msgstr "账号管理"
|
|||
msgid "Assets"
|
||||
msgstr "资产"
|
||||
|
||||
#: assets/models/automations/base.py:77 assets/models/automations/base.py:84
|
||||
#: assets/models/automations/base.py:86 assets/models/automations/base.py:93
|
||||
msgid "Automation task"
|
||||
msgstr "自动化任务"
|
||||
|
||||
#: assets/models/automations/base.py:88 assets/models/backup.py:77
|
||||
#: assets/models/automations/base.py:97 assets/models/backup.py:77
|
||||
#: audits/models.py:44 ops/models/base.py:54
|
||||
#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:32
|
||||
#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:102
|
||||
#: terminal/models/session/session.py:43
|
||||
#: tickets/models/ticket/apply_application.py:28
|
||||
#: tickets/models/ticket/apply_asset.py:21
|
||||
|
@ -704,28 +705,28 @@ msgstr "自动化任务"
|
|||
msgid "Date start"
|
||||
msgstr "开始日期"
|
||||
|
||||
#: assets/models/automations/base.py:89
|
||||
#: assets/models/automations/base.py:98
|
||||
#: assets/models/automations/change_secret.py:58 ops/models/base.py:55
|
||||
#: terminal/models/applet/host.py:33
|
||||
#: terminal/models/applet/host.py:103
|
||||
msgid "Date finished"
|
||||
msgstr "结束日期"
|
||||
|
||||
#: assets/models/automations/base.py:91
|
||||
#: assets/models/automations/base.py:100
|
||||
msgid "Automation snapshot"
|
||||
msgstr "自动化快照"
|
||||
|
||||
#: assets/models/automations/base.py:95 assets/models/backup.py:88
|
||||
#: assets/models/automations/base.py:104 assets/models/backup.py:88
|
||||
#: assets/serializers/account/backup.py:36
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:121
|
||||
#: xpack/plugins/change_auth_plan/serializers/base.py:78
|
||||
msgid "Trigger mode"
|
||||
msgstr "触发模式"
|
||||
|
||||
#: assets/models/automations/base.py:99
|
||||
#: assets/models/automations/base.py:108
|
||||
msgid "Automation task execution"
|
||||
msgstr "自动化任务执行"
|
||||
|
||||
#: assets/models/automations/change_secret.py:15 assets/models/base.py:62
|
||||
#: assets/models/automations/change_secret.py:15 assets/models/base.py:60
|
||||
msgid "Secret type"
|
||||
msgstr "密文类型"
|
||||
|
||||
|
@ -734,7 +735,7 @@ msgid "Secret strategy"
|
|||
msgstr "密钥策略"
|
||||
|
||||
#: assets/models/automations/change_secret.py:21
|
||||
#: assets/models/automations/change_secret.py:56 assets/models/base.py:64
|
||||
#: assets/models/automations/change_secret.py:56 assets/models/base.py:62
|
||||
#: assets/serializers/account/base.py:17
|
||||
#: authentication/models/connection_token.py:34
|
||||
#: authentication/models/temp_token.py:10
|
||||
|
@ -772,7 +773,7 @@ msgstr "原来密码"
|
|||
msgid "Date started"
|
||||
msgstr "开始日期"
|
||||
|
||||
#: assets/models/automations/change_secret.py:60 common/const/choices.py:19
|
||||
#: assets/models/automations/change_secret.py:60 common/const/choices.py:20
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
|
@ -794,6 +795,12 @@ msgstr "收集资产信息"
|
|||
msgid "Gather asset facts"
|
||||
msgstr "收集资产信息"
|
||||
|
||||
#: assets/models/automations/ping.py:15
|
||||
#, fuzzy
|
||||
#| msgid "Login asset"
|
||||
msgid "Ping asset"
|
||||
msgstr "登录资产"
|
||||
|
||||
#: assets/models/automations/push_account.py:16
|
||||
#, fuzzy
|
||||
#| msgid "Is service account"
|
||||
|
@ -841,15 +848,15 @@ msgstr "是否成功"
|
|||
msgid "Account backup execution"
|
||||
msgstr "账号备份执行"
|
||||
|
||||
#: assets/models/base.py:30 assets/serializers/domain.py:42
|
||||
#: assets/models/base.py:29 assets/serializers/domain.py:42
|
||||
msgid "Connectivity"
|
||||
msgstr "可连接性"
|
||||
|
||||
#: assets/models/base.py:32 authentication/models/temp_token.py:12
|
||||
#: assets/models/base.py:31 authentication/models/temp_token.py:12
|
||||
msgid "Date verified"
|
||||
msgstr "校验日期"
|
||||
|
||||
#: assets/models/base.py:65
|
||||
#: assets/models/base.py:63
|
||||
msgid "Privileged"
|
||||
msgstr "特权的"
|
||||
|
||||
|
@ -1015,6 +1022,7 @@ msgid "Setting"
|
|||
msgstr "设置"
|
||||
|
||||
#: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37
|
||||
#: terminal/serializers/applet_host.py:25
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
|
||||
|
@ -1306,63 +1314,41 @@ msgstr "密码不能包含 `'` 字符"
|
|||
msgid "Password can not contains `\"` "
|
||||
msgstr "密码不能包含 `\"` 字符"
|
||||
|
||||
#: assets/tasks/account_connectivity.py:30
|
||||
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
||||
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
||||
|
||||
#: assets/tasks/account_connectivity.py:108
|
||||
msgid "Test account connectivity: "
|
||||
msgstr "测试账号可连接性: "
|
||||
|
||||
#: assets/tasks/asset_connectivity.py:49
|
||||
msgid "Test assets connectivity. "
|
||||
msgstr "测试资产可连接性. "
|
||||
|
||||
#: assets/tasks/asset_connectivity.py:94 assets/tasks/asset_connectivity.py:107
|
||||
msgid "Test assets connectivity: "
|
||||
msgstr "测试资产可连接性: "
|
||||
|
||||
#: assets/tasks/asset_connectivity.py:121
|
||||
msgid "Test if the assets under the node are connectable: "
|
||||
msgstr "测试节点下资产是否可连接: "
|
||||
|
||||
#: assets/tasks/const.py:49
|
||||
msgid "Unreachable"
|
||||
msgstr "不可达"
|
||||
|
||||
#: assets/tasks/const.py:50
|
||||
msgid "Reachable"
|
||||
msgstr "可连接"
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:46
|
||||
msgid "Get asset info failed: {}"
|
||||
msgstr "获取资产信息失败:{}"
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:97
|
||||
#: assets/tasks/gather_facts.py:25
|
||||
msgid "Update some assets hardware info. "
|
||||
msgstr "更新资产硬件信息. "
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:118
|
||||
msgid "Update asset hardware info: "
|
||||
msgstr "更新资产硬件信息: "
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:124
|
||||
#: assets/tasks/gather_facts.py:48
|
||||
msgid "Update assets hardware info: "
|
||||
msgstr "更新资产硬件信息: "
|
||||
|
||||
#: assets/tasks/gather_asset_hardware_info.py:146
|
||||
#: assets/tasks/gather_facts.py:58
|
||||
msgid "Update node asset hardware information: "
|
||||
msgstr "更新节点资产硬件信息: "
|
||||
|
||||
#: assets/tasks/gather_asset_users.py:110
|
||||
msgid "Gather assets users"
|
||||
msgstr "收集资产上的用户"
|
||||
|
||||
#: assets/tasks/nodes_amount.py:29
|
||||
msgid ""
|
||||
"The task of self-checking is already running and cannot be started repeatedly"
|
||||
msgstr "自检程序已经在运行,不能重复启动"
|
||||
|
||||
#: assets/tasks/ping.py:20 assets/tasks/ping.py:38
|
||||
#, fuzzy
|
||||
#| msgid "Test assets connectivity. "
|
||||
msgid "Test assets connectivity "
|
||||
msgstr "测试资产可连接性. "
|
||||
|
||||
#: assets/tasks/ping.py:48
|
||||
#, fuzzy
|
||||
#| msgid "Test if the assets under the node are connectable: "
|
||||
msgid "Test if the assets under the node are connectable "
|
||||
msgstr "测试节点下资产是否可连接: "
|
||||
|
||||
#: assets/tasks/push_account.py:36
|
||||
#, fuzzy
|
||||
#| msgid "Push account method"
|
||||
msgid "Push accounts to assets"
|
||||
msgstr "推送方式"
|
||||
|
||||
#: assets/tasks/utils.py:17
|
||||
msgid "Asset has been disabled, skipped: {}"
|
||||
msgstr "资产已经被禁用, 跳过: {}"
|
||||
|
@ -1379,6 +1365,12 @@ msgstr "为了安全,禁止推送用户 {}"
|
|||
msgid "No assets matched, stop task"
|
||||
msgstr "没有匹配到资产,结束任务"
|
||||
|
||||
#: assets/tasks/verify_account.py:36
|
||||
#, fuzzy
|
||||
#| msgid "Test account connectivity: "
|
||||
msgid "Verify accounts connectivity"
|
||||
msgstr "测试账号可连接性: "
|
||||
|
||||
#: audits/apps.py:9
|
||||
msgid "Audits"
|
||||
msgstr "日志审计"
|
||||
|
@ -1426,7 +1418,7 @@ msgstr "操作"
|
|||
msgid "Filename"
|
||||
msgstr "文件名"
|
||||
|
||||
#: audits/models.py:43 audits/models.py:117 common/const/choices.py:17
|
||||
#: audits/models.py:43 audits/models.py:117 common/const/choices.py:18
|
||||
#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114
|
||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:189
|
||||
msgid "Success"
|
||||
|
@ -1506,8 +1498,8 @@ msgid "MFA"
|
|||
msgstr "MFA"
|
||||
|
||||
#: audits/models.py:128 ops/models/base.py:48
|
||||
#: terminal/models/applet/applet.py:57 terminal/models/applet/host.py:19
|
||||
#: terminal/models/applet/host.py:31 terminal/models/component/status.py:33
|
||||
#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:101
|
||||
#: terminal/models/component/status.py:33 terminal/serializers/applet.py:22
|
||||
#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171
|
||||
#: xpack/plugins/cloud/models.py:223
|
||||
msgid "Status"
|
||||
|
@ -2018,11 +2010,11 @@ msgstr "资产未激活"
|
|||
msgid "No account"
|
||||
msgstr "登录账号"
|
||||
|
||||
#: authentication/models/connection_token.py:103
|
||||
#: authentication/models/connection_token.py:101
|
||||
msgid "User has no permission to access asset or permission expired"
|
||||
msgstr "用户没有权限访问资产或权限已过期"
|
||||
|
||||
#: authentication/models/connection_token.py:145
|
||||
#: authentication/models/connection_token.py:144
|
||||
msgid "Super connection token"
|
||||
msgstr "超级连接令牌"
|
||||
|
||||
|
@ -2439,15 +2431,19 @@ msgstr "手动触发"
|
|||
msgid "Timing trigger"
|
||||
msgstr "定时触发"
|
||||
|
||||
#: common/const/choices.py:15 tickets/const.py:29 tickets/const.py:37
|
||||
#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183
|
||||
msgid "Ready"
|
||||
msgstr "准备"
|
||||
|
||||
#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:37
|
||||
msgid "Pending"
|
||||
msgstr "待定的"
|
||||
|
||||
#: common/const/choices.py:16
|
||||
#: common/const/choices.py:17
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: common/const/choices.py:20
|
||||
#: common/const/choices.py:21
|
||||
#, fuzzy
|
||||
#| msgid "Cancel"
|
||||
msgid "Canceled"
|
||||
|
@ -2515,6 +2511,12 @@ msgstr "解析文件错误: {}"
|
|||
msgid "Children"
|
||||
msgstr ""
|
||||
|
||||
#: common/drf/serializers/common.py:94
|
||||
#, fuzzy
|
||||
#| msgid "Filename"
|
||||
msgid "File"
|
||||
msgstr "文件名"
|
||||
|
||||
#: common/exceptions.py:15
|
||||
#, python-format
|
||||
msgid "%s object does not exist."
|
||||
|
@ -2925,7 +2927,7 @@ msgstr "组织管理"
|
|||
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88
|
||||
#: rbac/const.py:7 rbac/models/rolebinding.py:48
|
||||
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
|
||||
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71
|
||||
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:72
|
||||
msgid "Organization"
|
||||
msgstr "组织"
|
||||
|
||||
|
@ -4725,42 +4727,46 @@ msgstr "作者"
|
|||
msgid "Tags"
|
||||
msgstr "标签"
|
||||
|
||||
#: terminal/models/applet/applet.py:29 terminal/serializers/storage.py:157
|
||||
#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157
|
||||
msgid "Hosts"
|
||||
msgstr "主机"
|
||||
|
||||
#: terminal/models/applet/applet.py:55 terminal/models/applet/host.py:21
|
||||
#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:28
|
||||
msgid "Applet"
|
||||
msgstr "远程应用"
|
||||
|
||||
#: terminal/models/applet/host.py:14
|
||||
msgid "Account automation"
|
||||
msgstr "账号自动化"
|
||||
|
||||
#: terminal/models/applet/host.py:15 terminal/serializers/applet.py:66
|
||||
#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:36
|
||||
#, fuzzy
|
||||
#| msgid "More login options"
|
||||
msgid "Deploy options"
|
||||
msgstr "其他方式登录"
|
||||
|
||||
#: terminal/models/applet/host.py:16
|
||||
#: terminal/models/applet/host.py:20
|
||||
msgid "Inited"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/models/applet/host.py:17
|
||||
#: terminal/models/applet/host.py:21
|
||||
#, fuzzy
|
||||
#| msgid "Date finished"
|
||||
msgid "Date inited"
|
||||
msgstr "结束日期"
|
||||
|
||||
#: terminal/models/applet/host.py:18
|
||||
#: terminal/models/applet/host.py:22
|
||||
msgid "Date synced"
|
||||
msgstr "最后同步日期"
|
||||
|
||||
#: terminal/models/applet/host.py:30
|
||||
#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:183
|
||||
msgid "Terminal"
|
||||
msgstr "终端"
|
||||
|
||||
#: terminal/models/applet/host.py:99
|
||||
msgid "Hosting"
|
||||
msgstr "主机"
|
||||
|
||||
#: terminal/models/applet/host.py:100
|
||||
msgid "Initial"
|
||||
msgstr ""
|
||||
|
||||
#: terminal/models/component/endpoint.py:14
|
||||
msgid "HTTPS Port"
|
||||
msgstr "HTTPS 端口"
|
||||
|
@ -4865,10 +4871,6 @@ msgstr "录像存储"
|
|||
msgid "type"
|
||||
msgstr "类型"
|
||||
|
||||
#: terminal/models/component/terminal.py:183
|
||||
msgid "Terminal"
|
||||
msgstr "终端"
|
||||
|
||||
#: terminal/models/component/terminal.py:185
|
||||
msgid "Can view terminal config"
|
||||
msgstr "可以查看终端配置"
|
||||
|
@ -4985,43 +4987,51 @@ msgstr "级别"
|
|||
msgid "Batch danger command alert"
|
||||
msgstr "批量危险命令告警"
|
||||
|
||||
#: terminal/serializers/applet.py:19
|
||||
#: terminal/serializers/applet.py:16
|
||||
msgid "Published"
|
||||
msgstr "已安装"
|
||||
|
||||
#: terminal/serializers/applet.py:17
|
||||
msgid "Unpublished"
|
||||
msgstr "未安装"
|
||||
|
||||
#: terminal/serializers/applet.py:18
|
||||
msgid "Not match"
|
||||
msgstr "不匹配"
|
||||
|
||||
#: terminal/serializers/applet.py:32
|
||||
msgid "Icon"
|
||||
msgstr "图标"
|
||||
|
||||
#: terminal/serializers/applet.py:53
|
||||
msgid "Not set"
|
||||
msgstr "不设置"
|
||||
|
||||
#: terminal/serializers/applet.py:54
|
||||
#: terminal/serializers/applet_host.py:20
|
||||
msgid "Per Session"
|
||||
msgstr "按会话"
|
||||
|
||||
#: terminal/serializers/applet.py:55
|
||||
#: terminal/serializers/applet_host.py:21
|
||||
msgid "Per Device"
|
||||
msgstr "按设备"
|
||||
|
||||
#: terminal/serializers/applet.py:57
|
||||
#: terminal/serializers/applet_host.py:27
|
||||
msgid "RDS Licensing"
|
||||
msgstr "部署 RDS 许可服务"
|
||||
|
||||
#: terminal/serializers/applet.py:58
|
||||
#: terminal/serializers/applet_host.py:28
|
||||
msgid "RDS License Server"
|
||||
msgstr "RDS 许可服务主机"
|
||||
|
||||
#: terminal/serializers/applet.py:59
|
||||
#: terminal/serializers/applet_host.py:29
|
||||
msgid "RDS Licensing Mode"
|
||||
msgstr "RDS 许可模式"
|
||||
|
||||
#: terminal/serializers/applet.py:60
|
||||
#: terminal/serializers/applet_host.py:30
|
||||
msgid "RDS fSingleSessionPerUser"
|
||||
msgstr "RDS 会话用户数"
|
||||
|
||||
#: terminal/serializers/applet.py:61
|
||||
#: terminal/serializers/applet_host.py:31
|
||||
msgid "RDS Max Disconnection Time"
|
||||
msgstr "RDS 会话断开时间"
|
||||
|
||||
#: terminal/serializers/applet.py:62
|
||||
#: terminal/serializers/applet_host.py:32
|
||||
msgid "RDS Remote App Logoff Time Limit"
|
||||
msgstr "RDS 远程应用注销时间"
|
||||
|
||||
|
@ -5447,7 +5457,7 @@ msgstr "过期时间要大于开始时间"
|
|||
msgid "Permission named `{}` already exists"
|
||||
msgstr "授权名称 `{}` 已存在"
|
||||
|
||||
#: tickets/serializers/ticket/ticket.py:99
|
||||
#: tickets/serializers/ticket/ticket.py:101
|
||||
msgid "The ticket flow `{}` does not exist"
|
||||
msgstr "工单流程 `{}` 不存在"
|
||||
|
||||
|
@ -6136,10 +6146,6 @@ msgstr "公钥不能设置为空, 退出. "
|
|||
msgid "Change auth plan snapshot"
|
||||
msgstr "改密计划快照"
|
||||
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:183
|
||||
msgid "Ready"
|
||||
msgstr "准备"
|
||||
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:184
|
||||
msgid "Preflight check"
|
||||
msgstr "改密前的校验"
|
||||
|
@ -6747,11 +6753,11 @@ msgstr "主题"
|
|||
msgid "Interface setting"
|
||||
msgstr "界面设置"
|
||||
|
||||
#: xpack/plugins/license/api.py:50
|
||||
#: xpack/plugins/license/api.py:53
|
||||
msgid "License import successfully"
|
||||
msgstr "许可证导入成功"
|
||||
|
||||
#: xpack/plugins/license/api.py:51
|
||||
#: xpack/plugins/license/api.py:54
|
||||
msgid "License is invalid"
|
||||
msgstr "无效的许可证"
|
||||
|
||||
|
@ -6775,6 +6781,30 @@ msgstr "旗舰版"
|
|||
msgid "Community edition"
|
||||
msgstr "社区版"
|
||||
|
||||
#~ msgid "Account automation"
|
||||
#~ msgstr "账号自动化"
|
||||
|
||||
#~ msgid "The asset {} system platform {} does not support run Ansible tasks"
|
||||
#~ msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
||||
|
||||
#~ msgid "Test assets connectivity: "
|
||||
#~ msgstr "测试资产可连接性: "
|
||||
|
||||
#~ msgid "Unreachable"
|
||||
#~ msgstr "不可达"
|
||||
|
||||
#~ msgid "Reachable"
|
||||
#~ msgstr "可连接"
|
||||
|
||||
#~ msgid "Get asset info failed: {}"
|
||||
#~ msgstr "获取资产信息失败:{}"
|
||||
|
||||
#~ msgid "Update asset hardware info: "
|
||||
#~ msgstr "更新资产硬件信息: "
|
||||
|
||||
#~ msgid "Gather assets users"
|
||||
#~ msgstr "收集资产上的用户"
|
||||
|
||||
#~ msgid "Push automation"
|
||||
#~ msgstr "自动化推送"
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@ from rest_framework.response import Response
|
|||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from common.utils import is_uuid
|
||||
from common.drf.serializers import FileSerializer
|
||||
from terminal import serializers
|
||||
from terminal.models import AppletPublication, Applet
|
||||
from terminal.serializers import AppletUploadSerializer
|
||||
|
||||
|
||||
__all__ = ['AppletViewSet', 'AppletPublicationViewSet']
|
||||
|
@ -59,7 +59,7 @@ class DownloadUploadMixin:
|
|||
raise ValidationError({'error': 'Missing name in manifest.yml'})
|
||||
return manifest, tmp_dir
|
||||
|
||||
@action(detail=False, methods=['post'], serializer_class=AppletUploadSerializer)
|
||||
@action(detail=False, methods=['post'], serializer_class=FileSerializer)
|
||||
def upload(self, request, *args, **kwargs):
|
||||
manifest, tmp_dir = self.extract_and_check_file(request)
|
||||
name = manifest['name']
|
||||
|
|
|
@ -2,6 +2,8 @@ from rest_framework import viewsets
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.drf.api import JMSModelViewSet
|
||||
from orgs.utils import tmp_to_builtin_org
|
||||
from terminal import serializers
|
||||
from terminal.models import AppletHost, Applet, AppletHostDeployment
|
||||
from terminal.tasks import run_applet_host_deployment
|
||||
|
@ -10,9 +12,34 @@ from terminal.tasks import run_applet_host_deployment
|
|||
__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet']
|
||||
|
||||
|
||||
class AppletHostViewSet(viewsets.ModelViewSet):
|
||||
class AppletHostViewSet(JMSModelViewSet):
|
||||
serializer_class = serializers.AppletHostSerializer
|
||||
queryset = AppletHost.objects.all()
|
||||
rbac_perms = {
|
||||
'accounts': 'terminal.view_applethost',
|
||||
'reports': '*'
|
||||
}
|
||||
|
||||
@action(methods=['post'], detail=True, serializer_class=serializers.AppletHostReportSerializer)
|
||||
def reports(self, request, *args, **kwargs):
|
||||
# 1. Host 和 Terminal 关联
|
||||
# 2. 上报 安装的 Applets 每小时
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
data = serializer.validated_data
|
||||
instance.check_terminal_binding(request)
|
||||
instance.check_applets_state(data['applets'])
|
||||
return Response({'msg': 'ok'})
|
||||
|
||||
@action(methods=['get'], detail=True, serializer_class=serializers.AppletHostAccountSerializer)
|
||||
def accounts(self, request, *args, **kwargs):
|
||||
host = self.get_object()
|
||||
with tmp_to_builtin_org(system=1):
|
||||
accounts = host.accounts.all().filter(privileged=False)
|
||||
response = self.get_paginated_response_from_queryset(accounts)
|
||||
return response
|
||||
|
||||
|
||||
class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
|
||||
|
|
|
@ -21,7 +21,7 @@ __all__ = ['StatusViewSet', 'ComponentsMetricsAPIView']
|
|||
|
||||
class StatusViewSet(viewsets.ModelViewSet):
|
||||
queryset = Status.objects.all()
|
||||
serializer_class = serializers.StatusSerializer
|
||||
serializer_class = serializers.StatSerializer
|
||||
session_serializer_class = serializers.SessionSerializer
|
||||
task_serializer_class = serializers.TaskSerializer
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet):
|
|||
if not filterset.is_valid():
|
||||
raise utils.translate_validation(filterset.errors)
|
||||
command_qs = filterset.qs
|
||||
if storage.type == const.CommandStorageTypeChoices.es:
|
||||
if storage.type == const.CommandStorageType.es:
|
||||
command_count = command_qs.count(limit_to_max_result_window=False)
|
||||
else:
|
||||
command_count = command_qs.count()
|
||||
|
|
|
@ -42,43 +42,12 @@ class TerminalViewSet(JMSBulkModelViewSet):
|
|||
self.perform_destroy(instance)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
if isinstance(request.data, list):
|
||||
raise exceptions.BulkCreateNotSupport()
|
||||
|
||||
name = request.data.get('name')
|
||||
remote_ip = request.META.get('REMOTE_ADDR')
|
||||
x_real_ip = request.META.get('X-Real-IP')
|
||||
remote_addr = x_real_ip or remote_ip
|
||||
|
||||
terminal = get_object_or_none(Terminal, name=name, is_deleted=False)
|
||||
if terminal:
|
||||
msg = 'Terminal name %s already used' % name
|
||||
return Response({'msg': msg}, status=409)
|
||||
|
||||
serializer = self.serializer_class(data={
|
||||
'name': name, 'remote_addr': remote_addr
|
||||
})
|
||||
|
||||
if serializer.is_valid():
|
||||
terminal = serializer.save()
|
||||
|
||||
# App should use id, token get access key, if accepted
|
||||
token = uuid.uuid4().hex
|
||||
cache.set(token, str(terminal.id), 3600)
|
||||
data = {"id": str(terminal.id), "token": token, "msg": "Need accept"}
|
||||
return Response(data, status=201)
|
||||
else:
|
||||
data = serializer.errors
|
||||
logger.error("Register terminal error: {}".format(data))
|
||||
return Response(data, status=400)
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
s = self.request.query_params.get('status')
|
||||
if not s:
|
||||
return queryset
|
||||
filtered_queryset_id = [str(q.id) for q in queryset if q.latest_status == s]
|
||||
filtered_queryset_id = [str(q.id) for q in queryset if q.load == s]
|
||||
queryset = queryset.filter(id__in=filtered_queryset_id)
|
||||
return queryset
|
||||
|
||||
|
|
|
@ -27,13 +27,19 @@ class DeployAppletHostManager:
|
|||
|
||||
def generate_playbook(self):
|
||||
playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml')
|
||||
base_site_url = settings.BASE_SITE_URL
|
||||
bootstrap_token = settings.BOOTSTRAP_TOKEN
|
||||
host_id = str(self.deployment.host.id)
|
||||
if not base_site_url:
|
||||
base_site_url = "localhost:8080"
|
||||
with open(playbook_src) as f:
|
||||
plays = yaml.safe_load(f)
|
||||
for play in plays:
|
||||
play['vars'].update(self.deployment.host.deploy_options)
|
||||
play['vars']['DownloadHost'] = settings.BASE_URL + '/download/'
|
||||
play['vars']['CORE_HOST'] = settings.BASE_URL
|
||||
play['vars']['BOOTSTRAP_TOKEN'] = settings.BOOSTRAP_TOKEN
|
||||
play['vars']['DownloadHost'] = base_site_url + '/download/'
|
||||
play['vars']['CORE_HOST'] = base_site_url
|
||||
play['vars']['BOOTSTRAP_TOKEN'] = bootstrap_token
|
||||
play['vars']['HOST_ID'] = host_id
|
||||
play['vars']['HOST_NAME'] = self.deployment.host.name
|
||||
|
||||
playbook_dir = os.path.join(self.run_dir, 'playbook')
|
||||
|
@ -70,6 +76,3 @@ class DeployAppletHostManager:
|
|||
self.deployment.date_finished = timezone.now()
|
||||
with safe_db_connection():
|
||||
self.deployment.save()
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
DownloadHost: https://demo.jumpserver.org/download
|
||||
Initial: 0
|
||||
HOST_NAME: test
|
||||
HOST_ID: 00000000-0000-0000-0000-000000000000
|
||||
CORE_HOST: https://demo.jumpserver.org
|
||||
BOOTSTRAP_TOKEN: PleaseChangeMe
|
||||
RDS_Licensing: true
|
||||
|
@ -38,7 +39,7 @@
|
|||
- name: Install JumpServer Remoteapp agent (jumpserver)
|
||||
ansible.windows.win_package:
|
||||
path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
|
||||
args:
|
||||
arguments:
|
||||
- /VERYSILENT
|
||||
- /SUPPRESSMSGBOXES
|
||||
- /NORESTART
|
||||
|
@ -154,7 +155,8 @@
|
|||
|
||||
- name: Generate component config
|
||||
ansible.windows.win_shell:
|
||||
"remoteapp-server config --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}"
|
||||
"remoteapp-server config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
|
||||
--token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}"
|
||||
|
||||
- name: Install remoteapp-server service
|
||||
ansible.windows.win_shell:
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
# --------------------------------
|
||||
|
||||
|
||||
class ReplayStorageTypeChoices(TextChoices):
|
||||
class ReplayStorageType(TextChoices):
|
||||
null = 'null', 'Null',
|
||||
server = 'server', 'Server'
|
||||
s3 = 's3', 'S3'
|
||||
|
@ -20,7 +20,7 @@ class ReplayStorageTypeChoices(TextChoices):
|
|||
cos = 'cos', 'COS'
|
||||
|
||||
|
||||
class CommandStorageTypeChoices(TextChoices):
|
||||
class CommandStorageType(TextChoices):
|
||||
null = 'null', 'Null',
|
||||
server = 'server', 'Server'
|
||||
es = 'es', 'Elasticsearch'
|
||||
|
@ -29,7 +29,7 @@ class CommandStorageTypeChoices(TextChoices):
|
|||
# Component Status Choices
|
||||
# ------------------------
|
||||
|
||||
class ComponentStatusChoices(TextChoices):
|
||||
class ComponentLoad(TextChoices):
|
||||
critical = 'critical', _('Critical')
|
||||
high = 'high', _('High')
|
||||
normal = 'normal', _('Normal')
|
||||
|
@ -40,7 +40,7 @@ class ComponentStatusChoices(TextChoices):
|
|||
return set(dict(cls.choices).keys())
|
||||
|
||||
|
||||
class TerminalTypeChoices(TextChoices):
|
||||
class TerminalType(TextChoices):
|
||||
koko = 'koko', 'KoKo'
|
||||
guacamole = 'guacamole', 'Guacamole'
|
||||
omnidb = 'omnidb', 'OmniDB'
|
||||
|
@ -50,7 +50,7 @@ class TerminalTypeChoices(TextChoices):
|
|||
celery = 'celery', 'Celery'
|
||||
magnus = 'magnus', 'Magnus'
|
||||
razor = 'razor', 'Razor'
|
||||
tinker = 'tinker', 'Tinker'
|
||||
tinker = 'tinker', 'Tinker'
|
||||
|
||||
@classmethod
|
||||
def types(cls):
|
||||
|
|
|
@ -13,6 +13,10 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='terminal',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'), ('xrdp', 'Xrdp'), ('lion', 'Lion'), ('core', 'Core'), ('celery', 'Celery'), ('magnus', 'Magnus'), ('razor', 'Razor')], default='koko', max_length=64, verbose_name='type'),
|
||||
field=models.CharField(choices=[
|
||||
('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'),
|
||||
('xrdp', 'Xrdp'), ('lion', 'Lion'), ('core', 'Core'), ('celery', 'Celery'),
|
||||
('magnus', 'Magnus'), ('razor', 'Razor'), ('tinker', 'Tinker'),
|
||||
], default='koko', max_length=64, verbose_name='type'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -39,7 +39,6 @@ class Migration(migrations.Migration):
|
|||
name='AppletHost',
|
||||
fields=[
|
||||
('host_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.host')),
|
||||
('account_automation', models.BooleanField(default=False, verbose_name='Account automation')),
|
||||
('date_synced', models.DateTimeField(blank=True, null=True, verbose_name='Date synced')),
|
||||
('status', models.CharField(max_length=16, verbose_name='Status')),
|
||||
],
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 3.2.14 on 2022-11-02 11:41
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('terminal', '0056_auto_20221101_1353'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='applethost',
|
||||
name='terminal',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='applet_host', to='terminal.terminal', verbose_name='Terminal'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='appletpublication',
|
||||
name='status',
|
||||
field=models.CharField(default='ready', max_length=16, verbose_name='Status'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 3.2.14 on 2022-11-03 08:24
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('terminal', '0057_auto_20221102_1941'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='terminal',
|
||||
options={'permissions': (('view_terminalconfig', 'Can view terminal config'),), 'verbose_name': 'Terminal'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='terminal',
|
||||
name='http_port',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='terminal',
|
||||
name='is_accepted',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='terminal',
|
||||
name='ssh_port',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='applethost',
|
||||
name='status',
|
||||
),
|
||||
]
|
|
@ -1,7 +1,14 @@
|
|||
import os
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from simple_history.utils import bulk_create_with_history
|
||||
|
||||
from common.db.models import JMSBaseModel
|
||||
from common.utils import random_string
|
||||
from assets.models import Host
|
||||
|
||||
|
||||
|
@ -9,22 +16,90 @@ __all__ = ['AppletHost', 'AppletHostDeployment']
|
|||
|
||||
|
||||
class AppletHost(Host):
|
||||
LOCKING_ORG = '00000000-0000-0000-0000-000000000004'
|
||||
|
||||
account_automation = models.BooleanField(default=False, verbose_name=_('Account automation'))
|
||||
deploy_options = models.JSONField(default=dict, verbose_name=_('Deploy options'))
|
||||
inited = models.BooleanField(default=False, verbose_name=_('Inited'))
|
||||
date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date inited'))
|
||||
date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced'))
|
||||
status = models.CharField(max_length=16, verbose_name=_('Status'))
|
||||
terminal = models.OneToOneField(
|
||||
'terminal.Terminal', on_delete=models.PROTECT, null=True, blank=True,
|
||||
related_name='applet_host', verbose_name=_('Terminal')
|
||||
)
|
||||
applets = models.ManyToManyField(
|
||||
'Applet', verbose_name=_('Applet'),
|
||||
through='AppletPublication', through_fields=('host', 'applet'),
|
||||
)
|
||||
LOCKING_ORG = '00000000-0000-0000-0000-000000000004'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
if self.terminal:
|
||||
return 'online'
|
||||
return self.terminal.status
|
||||
|
||||
def check_terminal_binding(self, request):
|
||||
request_terminal = getattr(request.user, 'terminal', None)
|
||||
if not request_terminal:
|
||||
raise ValidationError('Request user has no terminal')
|
||||
|
||||
self.date_synced = timezone.now()
|
||||
if not self.terminal:
|
||||
self.terminal = request_terminal
|
||||
self.save(update_fields=['terminal', 'date_synced'])
|
||||
elif self.terminal and self.terminal != request_terminal:
|
||||
raise ValidationError('Terminal has been set')
|
||||
else:
|
||||
self.save(update_fields=['date_synced'])
|
||||
|
||||
def check_applets_state(self, applets_value_list):
|
||||
applets = self.applets.all()
|
||||
name_version_mapper = {
|
||||
value['name']: value['version']
|
||||
for value in applets_value_list
|
||||
}
|
||||
|
||||
status_applets = defaultdict(list)
|
||||
for applet in applets:
|
||||
if applet.name not in name_version_mapper:
|
||||
status_applets['unpublished'].append(applet)
|
||||
elif applet.version != name_version_mapper[applet.name]:
|
||||
status_applets['not_match'].append(applet)
|
||||
else:
|
||||
status_applets['published'].append(applet)
|
||||
|
||||
for status, applets in status_applets.items():
|
||||
self.publications.filter(applet__in=applets)\
|
||||
.exclude(status=status)\
|
||||
.update(status=status)
|
||||
|
||||
@staticmethod
|
||||
def random_username():
|
||||
return 'jms_' + random_string(8)
|
||||
|
||||
@staticmethod
|
||||
def random_password():
|
||||
return random_string(16, special_char=True)
|
||||
|
||||
def generate_accounts(self):
|
||||
amount = int(os.getenv('TERMINAL_ACCOUNTS_AMOUNT', 100))
|
||||
now_count = self.accounts.filter(privileged=False).count()
|
||||
need = amount - now_count
|
||||
|
||||
accounts = []
|
||||
account_model = self.accounts.model
|
||||
for i in range(need):
|
||||
username = self.random_username()
|
||||
password = self.random_password()
|
||||
account = account_model(
|
||||
username=username, secret=password, name=username,
|
||||
asset_id=self.id, secret_type='password', version=1,
|
||||
org_id=self.LOCKING_ORG
|
||||
)
|
||||
accounts.append(account)
|
||||
bulk_create_with_history(accounts, account_model, batch_size=20)
|
||||
|
||||
|
||||
class AppletHostDeployment(JMSBaseModel):
|
||||
host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting'))
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.forms.models import model_to_dict
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import get_logger
|
||||
|
@ -25,53 +21,9 @@ class Status(models.Model):
|
|||
terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.CASCADE)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
CACHE_KEY = 'TERMINAL_STATUS_{}'
|
||||
|
||||
class Meta:
|
||||
db_table = 'terminal_status'
|
||||
get_latest_by = 'date_created'
|
||||
verbose_name = _("Status")
|
||||
|
||||
def save_to_cache(self):
|
||||
if not self.terminal:
|
||||
return
|
||||
key = self.CACHE_KEY.format(self.terminal.id)
|
||||
data = model_to_dict(self)
|
||||
cache.set(key, data, 60*3)
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def get_terminal_latest_status(cls, terminal):
|
||||
from ...utils import ComputeStatUtil
|
||||
stat = cls.get_terminal_latest_stat(terminal)
|
||||
return ComputeStatUtil.compute_component_status(stat)
|
||||
|
||||
@classmethod
|
||||
def get_terminal_latest_stat(cls, terminal):
|
||||
key = cls.CACHE_KEY.format(terminal.id)
|
||||
data = cache.get(key)
|
||||
if not data:
|
||||
return None
|
||||
data.pop('terminal', None)
|
||||
stat = cls(**data)
|
||||
stat.terminal = terminal
|
||||
stat.is_alive = terminal.is_alive
|
||||
stat.keep_one_decimal_place()
|
||||
return stat
|
||||
|
||||
def keep_one_decimal_place(self):
|
||||
keys = ['cpu_load', 'memory_used', 'disk_used']
|
||||
for key in keys:
|
||||
value = getattr(self, key, 0)
|
||||
if not isinstance(value, (int, float)):
|
||||
continue
|
||||
value = '%.1f' % value
|
||||
setattr(self, key, float(value))
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
self.terminal.set_alive(ttl=120)
|
||||
return self.save_to_cache()
|
||||
# return super().save()
|
||||
|
||||
|
||||
|
|
|
@ -53,21 +53,21 @@ class CommonStorageModelMixin(models.Model):
|
|||
|
||||
class CommandStorage(CommonStorageModelMixin, CommonModelMixin):
|
||||
type = models.CharField(
|
||||
max_length=16, choices=const.CommandStorageTypeChoices.choices,
|
||||
default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'),
|
||||
max_length=16, choices=const.CommandStorageType.choices,
|
||||
default=const.CommandStorageType.server.value, verbose_name=_('Type'),
|
||||
)
|
||||
|
||||
@property
|
||||
def type_null(self):
|
||||
return self.type == const.CommandStorageTypeChoices.null.value
|
||||
return self.type == const.CommandStorageType.null.value
|
||||
|
||||
@property
|
||||
def type_server(self):
|
||||
return self.type == const.CommandStorageTypeChoices.server.value
|
||||
return self.type == const.CommandStorageType.server.value
|
||||
|
||||
@property
|
||||
def type_es(self):
|
||||
return self.type == const.CommandStorageTypeChoices.es.value
|
||||
return self.type == const.CommandStorageType.es.value
|
||||
|
||||
@property
|
||||
def type_null_or_server(self):
|
||||
|
@ -138,17 +138,17 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin):
|
|||
|
||||
class ReplayStorage(CommonStorageModelMixin, CommonModelMixin):
|
||||
type = models.CharField(
|
||||
max_length=16, choices=const.ReplayStorageTypeChoices.choices,
|
||||
default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type')
|
||||
max_length=16, choices=const.ReplayStorageType.choices,
|
||||
default=const.ReplayStorageType.server.value, verbose_name=_('Type')
|
||||
)
|
||||
|
||||
@property
|
||||
def type_null(self):
|
||||
return self.type == const.ReplayStorageTypeChoices.null.value
|
||||
return self.type == const.ReplayStorageType.null.value
|
||||
|
||||
@property
|
||||
def type_server(self):
|
||||
return self.type == const.ReplayStorageTypeChoices.server.value
|
||||
return self.type == const.ReplayStorageType.server.value
|
||||
|
||||
@property
|
||||
def type_null_or_server(self):
|
||||
|
@ -156,11 +156,11 @@ class ReplayStorage(CommonStorageModelMixin, CommonModelMixin):
|
|||
|
||||
@property
|
||||
def type_swift(self):
|
||||
return self.type == const.ReplayStorageTypeChoices.swift.value
|
||||
return self.type == const.ReplayStorageType.swift.value
|
||||
|
||||
@property
|
||||
def type_ceph(self):
|
||||
return self.type == const.ReplayStorageTypeChoices.ceph.value
|
||||
return self.type == const.ReplayStorageType.ceph.value
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
|
@ -168,7 +168,7 @@ class ReplayStorage(CommonStorageModelMixin, CommonModelMixin):
|
|||
|
||||
# add type config
|
||||
if self.type_ceph:
|
||||
_type = const.ReplayStorageTypeChoices.s3.value
|
||||
_type = const.ReplayStorageType.s3.value
|
||||
else:
|
||||
_type = self.type
|
||||
_config.update({'TYPE': _type})
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import uuid
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db import models
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from users.models import User
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from .status import Status
|
||||
from terminal import const
|
||||
from terminal.const import ComponentStatusChoices as StatusChoice
|
||||
from terminal.const import TerminalType as TypeChoices, ComponentLoad as StatusChoice
|
||||
from ..session import Session
|
||||
|
||||
|
||||
|
@ -18,42 +17,24 @@ logger = get_logger(__file__)
|
|||
|
||||
|
||||
class TerminalStatusMixin:
|
||||
ALIVE_KEY = 'TERMINAL_ALIVE_{}'
|
||||
id: str
|
||||
ALIVE_KEY = 'TERMINAL_ALIVE_{}'
|
||||
status_set: models.Manager
|
||||
|
||||
@property
|
||||
def latest_status(self):
|
||||
return Status.get_terminal_latest_status(self)
|
||||
@lazyproperty
|
||||
def last_stat(self):
|
||||
return self.status_set.order_by('date_created').last()
|
||||
|
||||
@property
|
||||
def latest_status_display(self):
|
||||
return self.latest_status.label
|
||||
|
||||
@property
|
||||
def latest_stat(self):
|
||||
return Status.get_terminal_latest_stat(self)
|
||||
|
||||
@property
|
||||
def is_normal(self):
|
||||
return self.latest_status == StatusChoice.normal
|
||||
|
||||
@property
|
||||
def is_high(self):
|
||||
return self.latest_status == StatusChoice.high
|
||||
|
||||
@property
|
||||
def is_critical(self):
|
||||
return self.latest_status == StatusChoice.critical
|
||||
@lazyproperty
|
||||
def load(self):
|
||||
from ...utils import ComputeLoadUtil
|
||||
return ComputeLoadUtil.compute_load(self.last_stat)
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
key = self.ALIVE_KEY.format(self.id)
|
||||
# return self.latest_status != StatusChoice.offline
|
||||
return cache.get(key, False)
|
||||
|
||||
def set_alive(self, ttl=120):
|
||||
key = self.ALIVE_KEY.format(self.id)
|
||||
cache.set(key, True, ttl)
|
||||
if not self.last_stat:
|
||||
return False
|
||||
return self.last_stat.date_created > timezone.now() - timezone.timedelta(seconds=120)
|
||||
|
||||
|
||||
class StorageMixin:
|
||||
|
@ -99,16 +80,13 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model):
|
|||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
type = models.CharField(
|
||||
choices=const.TerminalTypeChoices.choices, default=const.TerminalTypeChoices.koko.value,
|
||||
choices=TypeChoices.choices, default=TypeChoices.koko,
|
||||
max_length=64, verbose_name=_('type')
|
||||
)
|
||||
remote_addr = models.CharField(max_length=128, blank=True, verbose_name=_('Remote Address'))
|
||||
ssh_port = models.IntegerField(verbose_name=_('SSH Port'), default=2222)
|
||||
http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000)
|
||||
command_storage = models.CharField(max_length=128, verbose_name=_("Command storage"), default='default')
|
||||
replay_storage = models.CharField(max_length=128, verbose_name=_("Replay storage"), default='default')
|
||||
user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE)
|
||||
is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted')
|
||||
is_deleted = models.BooleanField(default=False)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
@ -167,9 +145,7 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model):
|
|||
|
||||
def __str__(self):
|
||||
status = "Active"
|
||||
if not self.is_accepted:
|
||||
status = "NotAccept"
|
||||
elif self.is_deleted:
|
||||
if self.is_deleted:
|
||||
status = "Deleted"
|
||||
elif not self.is_active:
|
||||
status = "Disable"
|
||||
|
@ -178,7 +154,6 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model):
|
|||
return '%s: %s' % (self.name, status)
|
||||
|
||||
class Meta:
|
||||
ordering = ('is_accepted',)
|
||||
db_table = "terminal"
|
||||
verbose_name = _("Terminal")
|
||||
permissions = (
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from common.const.choices import Status
|
||||
from ..models import Applet, AppletPublication, AppletHost
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AppletSerializer', 'AppletPublicationSerializer',
|
||||
'AppletUploadSerializer',
|
||||
]
|
||||
|
||||
|
||||
class AppletUploadSerializer(serializers.Serializer):
|
||||
file = serializers.FileField()
|
||||
|
||||
|
||||
class AppletPublicationSerializer(serializers.ModelSerializer):
|
||||
class Status(models.TextChoices):
|
||||
PUBLISHED = 'published', _('Published')
|
||||
UNPUBLISHED = 'unpublished', _('Unpublished')
|
||||
NOT_MATCH = 'not_match', _('Not match')
|
||||
|
||||
applet = ObjectRelatedField(attrs=('id', 'display_name', 'icon', 'version'), queryset=Applet.objects.all())
|
||||
host = ObjectRelatedField(queryset=AppletHost.objects.all())
|
||||
status = LabeledChoiceField(choices=Status.choices, label=_("Status"))
|
||||
|
|
|
@ -2,7 +2,8 @@ from rest_framework import serializers
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from assets.models import Platform
|
||||
from common.drf.fields import ObjectRelatedField
|
||||
from assets.models import Platform, Account
|
||||
from assets.serializers import HostSerializer
|
||||
from ..models import AppletHost, AppletHostDeployment, Applet
|
||||
from .applet import AppletSerializer
|
||||
|
@ -10,6 +11,7 @@ from .applet import AppletSerializer
|
|||
|
||||
__all__ = [
|
||||
'AppletHostSerializer', 'AppletHostDeploymentSerializer',
|
||||
'AppletHostAccountSerializer', 'AppletHostReportSerializer'
|
||||
]
|
||||
|
||||
|
||||
|
@ -36,7 +38,7 @@ class AppletHostSerializer(HostSerializer):
|
|||
class Meta(HostSerializer.Meta):
|
||||
model = AppletHost
|
||||
fields = HostSerializer.Meta.fields + [
|
||||
'account_automation', 'status', 'date_synced', 'deploy_options'
|
||||
'status', 'date_synced', 'deploy_options'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'status': {'read_only': True},
|
||||
|
@ -86,3 +88,13 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer):
|
|||
'date_start', 'date_finished'
|
||||
]
|
||||
fields = fields_mini + ['comment'] + read_only_fields
|
||||
|
||||
|
||||
class AppletHostAccountSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = ['id', 'username', 'secret', 'date_updated']
|
||||
|
||||
|
||||
class AppletHostReportSerializer(serializers.Serializer):
|
||||
applets = ObjectRelatedField(attrs=('id', 'name', 'version'), queryset=Applet.objects.all(), many=True)
|
||||
|
|
|
@ -118,13 +118,13 @@ class ReplayStorageTypeAzureSerializer(serializers.Serializer):
|
|||
|
||||
# mapping
|
||||
replay_storage_type_serializer_classes_mapping = {
|
||||
const.ReplayStorageTypeChoices.s3.value: ReplayStorageTypeS3Serializer,
|
||||
const.ReplayStorageTypeChoices.ceph.value: ReplayStorageTypeCephSerializer,
|
||||
const.ReplayStorageTypeChoices.swift.value: ReplayStorageTypeSwiftSerializer,
|
||||
const.ReplayStorageTypeChoices.oss.value: ReplayStorageTypeOSSSerializer,
|
||||
const.ReplayStorageTypeChoices.azure.value: ReplayStorageTypeAzureSerializer,
|
||||
const.ReplayStorageTypeChoices.obs.value: ReplayStorageTypeOBSSerializer,
|
||||
const.ReplayStorageTypeChoices.cos.value: ReplayStorageTypeCOSSerializer
|
||||
const.ReplayStorageType.s3.value: ReplayStorageTypeS3Serializer,
|
||||
const.ReplayStorageType.ceph.value: ReplayStorageTypeCephSerializer,
|
||||
const.ReplayStorageType.swift.value: ReplayStorageTypeSwiftSerializer,
|
||||
const.ReplayStorageType.oss.value: ReplayStorageTypeOSSSerializer,
|
||||
const.ReplayStorageType.azure.value: ReplayStorageTypeAzureSerializer,
|
||||
const.ReplayStorageType.obs.value: ReplayStorageTypeOBSSerializer,
|
||||
const.ReplayStorageType.cos.value: ReplayStorageTypeCOSSerializer
|
||||
}
|
||||
|
||||
|
||||
|
@ -172,7 +172,7 @@ class CommandStorageTypeESSerializer(serializers.Serializer):
|
|||
|
||||
# mapping
|
||||
command_storage_type_serializer_classes_mapping = {
|
||||
const.CommandStorageTypeChoices.es.value: CommandStorageTypeESSerializer
|
||||
const.CommandStorageType.es.value: CommandStorageTypeESSerializer
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,28 +2,26 @@ from rest_framework import serializers
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.drf.serializers import BulkModelSerializer
|
||||
from common.utils import is_uuid
|
||||
from common.drf.fields import LabeledChoiceField
|
||||
from common.utils import get_request_ip, pretty_string, is_uuid
|
||||
from users.serializers import ServiceAccountSerializer
|
||||
from common.utils import get_request_ip, pretty_string
|
||||
from .. import const
|
||||
|
||||
from ..models import (
|
||||
Terminal, Status, Task, CommandStorage, ReplayStorage
|
||||
)
|
||||
from ..models import Terminal, Status, Task, CommandStorage, ReplayStorage
|
||||
|
||||
|
||||
class StatusSerializer(serializers.ModelSerializer):
|
||||
class StatSerializer(serializers.ModelSerializer):
|
||||
sessions = serializers.ListSerializer(
|
||||
child=serializers.CharField(max_length=36), write_only=True
|
||||
child=serializers.CharField(max_length=36),
|
||||
write_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Status
|
||||
fields_mini = ['id']
|
||||
fields_write_only = ['sessions', ]
|
||||
fields_small = fields_mini + fields_write_only + [
|
||||
'cpu_load', 'memory_used', 'disk_used',
|
||||
'session_online',
|
||||
'date_created'
|
||||
'session_online', 'date_created'
|
||||
]
|
||||
fields_fk = ['terminal']
|
||||
fields = fields_small + fields_fk
|
||||
|
@ -32,30 +30,28 @@ class StatusSerializer(serializers.ModelSerializer):
|
|||
"memory_used": {'default': 0},
|
||||
"disk_used": {'default': 0},
|
||||
}
|
||||
model = Status
|
||||
|
||||
|
||||
class TerminalSerializer(BulkModelSerializer):
|
||||
session_online = serializers.ReadOnlyField(source='get_online_session_count')
|
||||
is_alive = serializers.BooleanField(read_only=True)
|
||||
is_active = serializers.BooleanField(read_only=True, label='Is active')
|
||||
status = serializers.ChoiceField(
|
||||
read_only=True, choices=const.ComponentStatusChoices.choices,
|
||||
source='latest_status', label=_('Load status')
|
||||
load = LabeledChoiceField(
|
||||
read_only=True, choices=const.ComponentLoad.choices,
|
||||
label=_('Load status')
|
||||
)
|
||||
status_display = serializers.CharField(read_only=True, source='latest_status_display')
|
||||
stat = StatusSerializer(read_only=True, source='latest_stat')
|
||||
stat = StatSerializer(read_only=True, source='last_stat')
|
||||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields_mini = ['id', 'name']
|
||||
fields_small = fields_mini + [
|
||||
'type', 'remote_addr', 'http_port', 'ssh_port',
|
||||
'session_online', 'command_storage', 'replay_storage',
|
||||
'is_accepted', "is_active", 'is_alive',
|
||||
'type', 'remote_addr', 'session_online',
|
||||
'command_storage', 'replay_storage',
|
||||
'is_active', 'is_alive',
|
||||
'date_created', 'comment',
|
||||
]
|
||||
fields_fk = ['status', 'status_display', 'stat']
|
||||
fields_fk = ['load', 'stat']
|
||||
fields = fields_small + fields_fk
|
||||
read_only_fields = ['type', 'date_created']
|
||||
extra_kwargs = {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
|
||||
from .models import Applet, AppletHost
|
||||
|
||||
|
||||
|
@ -14,6 +13,7 @@ def on_applet_host_create(sender, instance, created=False, **kwargs):
|
|||
return
|
||||
applets = Applet.objects.all()
|
||||
instance.applets.set(applets)
|
||||
instance.generate_accounts()
|
||||
|
||||
|
||||
@receiver(post_save, sender=Applet)
|
||||
|
|
|
@ -9,8 +9,8 @@ from common.db.utils import close_old_connections
|
|||
from common.decorator import Singleton
|
||||
from common.utils import get_disk_usage, get_cpu_load, get_memory_usage, get_logger
|
||||
|
||||
from .serializers.terminal import TerminalRegistrationSerializer, StatusSerializer
|
||||
from .const import TerminalTypeChoices
|
||||
from .serializers.terminal import TerminalRegistrationSerializer, StatSerializer
|
||||
from .const import TerminalType
|
||||
from .models import Terminal
|
||||
|
||||
__all__ = ['CoreTerminal', 'CeleryTerminal']
|
||||
|
@ -51,16 +51,18 @@ class BaseTerminal(object):
|
|||
'disk_used': get_disk_usage(path=settings.BASE_DIR),
|
||||
'sessions': [],
|
||||
}
|
||||
status_serializer = StatusSerializer(data=heartbeat_data)
|
||||
status_serializer = StatSerializer(data=heartbeat_data)
|
||||
status_serializer.is_valid()
|
||||
status_serializer.validated_data.pop('sessions', None)
|
||||
terminal = self.get_or_register_terminal()
|
||||
status_serializer.validated_data['terminal'] = terminal
|
||||
|
||||
try:
|
||||
status_serializer.save()
|
||||
status = status_serializer.save()
|
||||
print("Save status ok: ", status)
|
||||
time.sleep(self.interval)
|
||||
except OperationalError:
|
||||
print("Save status error, close old connections")
|
||||
close_old_connections()
|
||||
|
||||
def get_or_register_terminal(self):
|
||||
|
@ -90,8 +92,8 @@ class CoreTerminal(BaseTerminal):
|
|||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
suffix_name=TerminalTypeChoices.core.label,
|
||||
_type=TerminalTypeChoices.core.value
|
||||
suffix_name=TerminalType.core.label,
|
||||
_type=TerminalType.core.value
|
||||
)
|
||||
|
||||
|
||||
|
@ -99,6 +101,6 @@ class CoreTerminal(BaseTerminal):
|
|||
class CeleryTerminal(BaseTerminal):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
suffix_name=TerminalTypeChoices.celery.label,
|
||||
_type=TerminalTypeChoices.celery.value
|
||||
suffix_name=TerminalType.celery.label,
|
||||
_type=TerminalType.celery.value
|
||||
)
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import os
|
||||
import time
|
||||
from itertools import groupby, chain
|
||||
from collections import defaultdict
|
||||
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
import jms_storage
|
||||
|
||||
from common.utils import get_logger
|
||||
from tickets.models import TicketSession
|
||||
from . import const
|
||||
from .models import ReplayStorage
|
||||
from tickets.models import TicketSession
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -76,16 +78,16 @@ def get_session_replay_url(session):
|
|||
return local_path, url
|
||||
|
||||
|
||||
class ComputeStatUtil:
|
||||
class ComputeLoadUtil:
|
||||
# system status
|
||||
@staticmethod
|
||||
def _common_compute_system_status(value, thresholds):
|
||||
if thresholds[0] <= value <= thresholds[1]:
|
||||
return const.ComponentStatusChoices.normal.value
|
||||
return const.ComponentLoad.normal.value
|
||||
elif thresholds[1] < value <= thresholds[2]:
|
||||
return const.ComponentStatusChoices.high.value
|
||||
return const.ComponentLoad.high.value
|
||||
else:
|
||||
return const.ComponentStatusChoices.critical.value
|
||||
return const.ComponentLoad.critical.value
|
||||
|
||||
@classmethod
|
||||
def _compute_system_stat_status(cls, stat):
|
||||
|
@ -106,16 +108,16 @@ class ComputeStatUtil:
|
|||
return system_status
|
||||
|
||||
@classmethod
|
||||
def compute_component_status(cls, stat):
|
||||
if not stat:
|
||||
return const.ComponentStatusChoices.offline
|
||||
def compute_load(cls, stat):
|
||||
if not stat or time.time() - stat.date_created.timestamp() > 150:
|
||||
return const.ComponentLoad.offline
|
||||
system_status_values = cls._compute_system_stat_status(stat).values()
|
||||
if const.ComponentStatusChoices.critical in system_status_values:
|
||||
return const.ComponentStatusChoices.critical
|
||||
elif const.ComponentStatusChoices.high in system_status_values:
|
||||
return const.ComponentStatusChoices.high
|
||||
if const.ComponentLoad.critical in system_status_values:
|
||||
return const.ComponentLoad.critical
|
||||
elif const.ComponentLoad.high in system_status_values:
|
||||
return const.ComponentLoad.high
|
||||
else:
|
||||
return const.ComponentStatusChoices.normal
|
||||
return const.ComponentLoad.normal
|
||||
|
||||
|
||||
class TypedComponentsStatusMetricsUtil(object):
|
||||
|
@ -135,31 +137,15 @@ class TypedComponentsStatusMetricsUtil(object):
|
|||
def get_metrics(self):
|
||||
metrics = []
|
||||
for _tp, components in self.grouped_components:
|
||||
normal_count = high_count = critical_count = 0
|
||||
total_count = offline_count = session_online_total = 0
|
||||
|
||||
metric = {
|
||||
'normal': 0, 'high': 0, 'critical': 0, 'offline': 0,
|
||||
'total': 0, 'session_active': 0, 'type': _tp
|
||||
}
|
||||
for component in components:
|
||||
total_count += 1
|
||||
if not component.is_alive:
|
||||
offline_count += 1
|
||||
continue
|
||||
if component.is_normal:
|
||||
normal_count += 1
|
||||
elif component.is_high:
|
||||
high_count += 1
|
||||
else:
|
||||
# critical
|
||||
critical_count += 1
|
||||
session_online_total += component.get_online_session_count()
|
||||
metrics.append({
|
||||
'total': total_count,
|
||||
'normal': normal_count,
|
||||
'high': high_count,
|
||||
'critical': critical_count,
|
||||
'offline': offline_count,
|
||||
'session_active': session_online_total,
|
||||
'type': _tp,
|
||||
})
|
||||
metric[component.load] += 1
|
||||
metric['total'] += 1
|
||||
metric['session_active'] += component.get_online_session_count()
|
||||
metrics.append(metric)
|
||||
return metrics
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue