Merge branch 'v3' of github.com:jumpserver/jumpserver into v3

pull/9023/head
Jiangjie.Bai 2022-11-04 18:47:00 +08:00
commit a255bd21b4
43 changed files with 889 additions and 587 deletions

View File

@ -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

View File

@ -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'),
),
]

View File

@ -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',
),
]

View File

@ -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'),
),
]

View File

@ -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):

View File

@ -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)

View File

@ -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'))

View File

@ -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,
}

View File

@ -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
)

View File

@ -11,3 +11,4 @@ from .account import *
from assets.serializers.account.backup import *
from .platform import *
from .cagegory import *
from .automation import *

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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 组织也不怕

View File

@ -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)

View File

@ -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"))

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4c889e251a4de3161f462e042882ba3c4ab40eaf34799e2d49d4788ad961586
size 119171
oid sha256:07f1cfd07039142f4847b4139586bf815467f266119eae57476c073130f0ac92
size 118098

View File

@ -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"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:08529907ac3879f60c2026f91e7ba3f48a3a7d288f7b29cd35c0f73bc3999c21
size 103630
oid sha256:0b396cc9a485f6474d14ca30a1a7ba4f954b07754148b964efbb21519c55b280
size 102849

View File

@ -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 "自动化推送"

View File

@ -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']

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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):

View File

@ -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'),
),
]

View File

@ -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')),
],

View File

@ -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'),
),
]

View File

@ -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',
),
]

View File

@ -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'))

View File

@ -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()

View File

@ -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})

View File

@ -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 = (

View File

@ -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"))

View File

@ -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)

View File

@ -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
}

View File

@ -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 = {

View File

@ -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)

View File

@ -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
)

View File

@ -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