feat: 支持设置默认存储(命令、录像) (#6336)

* fix: 修改LDAP用户导入的组织为当前组织

* fix: 修改翻译信息

* feat: 支持设置默认存储

* feat: 支持设置默认存储(2)

* feat: 支持设置默认存储(3)
pull/6343/head
Jiangjie.Bai 2021-06-28 10:32:59 +08:00 committed by GitHub
parent aa6e550ba2
commit c0ec0f1343
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 158 additions and 80 deletions

View File

@ -0,0 +1,25 @@
# Generated by Django 3.1.6 on 2021-06-23 09:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0070_auto_20210426_1515'),
('applications', '0008_auto_20210104_0435'),
]
operations = [
migrations.CreateModel(
name='ApplicationUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('assets.systemuser',),
),
]

Binary file not shown.

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-06-22 19:04+0800\n" "POT-Creation-Date: 2021-06-25 17:12+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -23,9 +23,9 @@ msgstr ""
#: assets/models/cmd_filter.py:21 assets/models/domain.py:21 #: assets/models/cmd_filter.py:21 assets/models/domain.py:21
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29 #: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29
#: terminal/models/storage.py:23 terminal/models/storage.py:90 #: terminal/models/storage.py:23 terminal/models/task.py:16
#: terminal/models/task.py:16 terminal/models/terminal.py:100 #: terminal/models/terminal.py:100 users/forms/profile.py:32
#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:550 #: users/models/group.py:15 users/models/user.py:550
#: users/templates/users/_select_user_modal.html:13 #: users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:37
#: users/templates/users/user_asset_permission.html:154 #: users/templates/users/user_asset_permission.html:154
@ -59,11 +59,11 @@ msgstr "激活中"
#: assets/models/domain.py:22 assets/models/domain.py:56 #: assets/models/domain.py:22 assets/models/domain.py:56
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 #: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
#: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34 #: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34
#: terminal/models/storage.py:29 terminal/models/storage.py:96 #: terminal/models/storage.py:26 terminal/models/terminal.py:114
#: terminal/models/terminal.py:114 tickets/models/ticket.py:73 #: tickets/models/ticket.py:73 users/models/group.py:16
#: users/models/group.py:16 users/models/user.py:583 #: users/models/user.py:583 xpack/plugins/change_auth_plan/models.py:77
#: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:98
#: xpack/plugins/cloud/models.py:98 xpack/plugins/gathered_user/models.py:26 #: xpack/plugins/gathered_user/models.py:26
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
@ -257,7 +257,7 @@ msgstr "类别"
#: perms/models/application_permission.py:23 #: perms/models/application_permission.py:23
#: perms/serializers/application/permission.py:17 #: perms/serializers/application/permission.py:17
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:26 terminal/models/storage.py:93 #: terminal/models/storage.py:47 terminal/models/storage.py:108
#: tickets/models/ticket.py:38 #: tickets/models/ticket.py:38
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:27 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:27
msgid "Type" msgid "Type"
@ -1202,7 +1202,7 @@ msgstr "主机 (显示名称)"
msgid "Result" msgid "Result"
msgstr "结果" msgstr "结果"
#: audits/serializers.py:92 terminal/serializers/storage.py:189 #: audits/serializers.py:92 terminal/serializers/storage.py:195
msgid "Hosts" msgid "Hosts"
msgstr "主机" msgstr "主机"
@ -3252,6 +3252,10 @@ msgstr "线程数"
msgid "Boot Time" msgid "Boot Time"
msgstr "运行时间" msgstr "运行时间"
#: terminal/models/storage.py:25
msgid "Default storage"
msgstr "默认存储"
#: terminal/models/task.py:17 #: terminal/models/task.py:17
msgid "Args" msgid "Args"
msgstr "参数" msgstr "参数"
@ -3426,27 +3430,27 @@ msgstr "账户密钥"
msgid "Endpoint suffix" msgid "Endpoint suffix"
msgstr "端点后缀" msgstr "端点后缀"
#: terminal/serializers/storage.py:166 #: terminal/serializers/storage.py:172
msgid "The address format is incorrect" msgid "The address format is incorrect"
msgstr "地址格式不正确" msgstr "地址格式不正确"
#: terminal/serializers/storage.py:173 #: terminal/serializers/storage.py:179
msgid "Host invalid" msgid "Host invalid"
msgstr "主机无效" msgstr "主机无效"
#: terminal/serializers/storage.py:176 #: terminal/serializers/storage.py:182
msgid "Port invalid" msgid "Port invalid"
msgstr "端口无效" msgstr "端口无效"
#: terminal/serializers/storage.py:192 #: terminal/serializers/storage.py:198
msgid "Index" msgid "Index"
msgstr "索引" msgstr "索引"
#: terminal/serializers/storage.py:194 #: terminal/serializers/storage.py:200
msgid "Doc type" msgid "Doc type"
msgstr "文档类型" msgstr "文档类型"
#: terminal/serializers/storage.py:196 #: terminal/serializers/storage.py:202
msgid "Ignore Certificate Verification" msgid "Ignore Certificate Verification"
msgstr "忽略证书认证" msgstr "忽略证书认证"
@ -5114,6 +5118,3 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid "Terminal command alert"
#~ msgstr "终端命令告警"

View File

@ -36,7 +36,7 @@ class BaseStorageViewSetMixin:
class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet): class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet):
search_fields = ('name', 'type',) search_fields = ('name', 'type')
queryset = CommandStorage.objects.all() queryset = CommandStorage.objects.all()
serializer_class = CommandStorageSerializer serializer_class = CommandStorageSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
@ -103,7 +103,7 @@ class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet):
class ReplayStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet): class ReplayStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet):
filterset_fields = ('name', 'type',) filterset_fields = ('name', 'type', 'is_default')
search_fields = filterset_fields search_fields = filterset_fields
queryset = ReplayStorage.objects.all() queryset = ReplayStorage.objects.all()
serializer_class = ReplayStorageSerializer serializer_class = ReplayStorageSerializer

View File

@ -71,7 +71,7 @@ class CommandStorageFilter(filters.FilterSet):
class Meta: class Meta:
model = CommandStorage model = CommandStorage
fields = ['real', 'name', 'type'] fields = ['real', 'name', 'type', 'is_default']
def filter_real(self, queryset, name, value): def filter_real(self, queryset, name, value):
if value: if value:

View File

@ -0,0 +1,37 @@
# Generated by Django 3.1.6 on 2021-06-23 09:48
from django.db import migrations, models
def set_default_storage(apps, schema_editor):
command_storage_model = apps.get_model("terminal", "CommandStorage")
command_storage = command_storage_model.objects.filter(name='default', type='server').first()
if command_storage:
command_storage.is_default = True
command_storage.save()
replay_storage_model = apps.get_model("terminal", "ReplayStorage")
replay_storage = replay_storage_model.objects.filter(name='default', type='server').first()
if replay_storage:
replay_storage.is_default = True
replay_storage.save()
class Migration(migrations.Migration):
dependencies = [
('terminal', '0036_auto_20210604_1124'),
]
operations = [
migrations.AddField(
model_name='commandstorage',
name='is_default',
field=models.BooleanField(default=False, verbose_name='Default storage'),
),
migrations.AddField(
model_name='replaystorage',
name='is_default',
field=models.BooleanField(default=False, verbose_name='Default storage'),
),
migrations.RunPython(set_default_storage)
]

View File

@ -19,17 +19,41 @@ from .. import const
logger = get_logger(__file__) logger = get_logger(__file__)
class CommandStorage(CommonModelMixin): class CommonStorageModelMixin(models.Model):
name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True) name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True)
meta = EncryptJsonDictTextField(default={})
is_default = models.BooleanField(default=False, verbose_name=_('Default storage'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
class Meta:
abstract = True
def __str__(self):
return self.name
def set_to_default(self):
self.is_default = True
self.save()
self.__class__.objects.select_for_update()\
.filter(is_default=True)\
.exclude(id=self.id)\
.update(is_default=False)
@classmethod
def default(cls):
objs = cls.objects.filter(is_default=True)
if not objs:
objs = cls.objects.filter(name='default', type='server')
if not objs:
objs = cls.objects.all()
return objs.first()
class CommandStorage(CommonStorageModelMixin, CommonModelMixin):
type = models.CharField( type = models.CharField(
max_length=16, choices=const.CommandStorageTypeChoices.choices, max_length=16, choices=const.CommandStorageTypeChoices.choices,
default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'), default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'),
) )
meta = EncryptJsonDictTextField(default={})
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name
@property @property
def type_null(self): def type_null(self):
@ -86,17 +110,11 @@ class CommandStorage(CommonModelMixin):
backend.pre_use_check() backend.pre_use_check()
class ReplayStorage(CommonModelMixin): class ReplayStorage(CommonStorageModelMixin, CommonModelMixin):
name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True)
type = models.CharField( type = models.CharField(
max_length=16, choices=const.ReplayStorageTypeChoices.choices, max_length=16, choices=const.ReplayStorageTypeChoices.choices,
default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type') default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type')
) )
meta = EncryptJsonDictTextField(default={})
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name
@property @property
def type_null(self): def type_null(self):

View File

@ -176,6 +176,12 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model):
self.save() self.save()
return return
def save(self, **kwargs):
from .storage import CommandStorage, ReplayStorage
self.command_storage = CommandStorage.default().name
self.replay_storage = ReplayStorage.default().name
return super().save(**kwargs)
def __str__(self): def __str__(self):
status = "Active" status = "Active"
if not self.is_accepted: if not self.is_accepted:

View File

@ -119,44 +119,6 @@ replay_storage_type_serializer_classes_mapping = {
const.ReplayStorageTypeChoices.obs.value: ReplayStorageTypeOBSSerializer const.ReplayStorageTypeChoices.obs.value: ReplayStorageTypeOBSSerializer
} }
# ReplayStorageSerializer
class ReplayStorageSerializer(serializers.ModelSerializer):
meta = MethodSerializer()
class Meta:
model = ReplayStorage
fields = ['id', 'name', 'type', 'meta', 'comment']
def validate_meta(self, meta):
_meta = self.instance.meta if self.instance else {}
_meta.update(meta)
return _meta
def get_meta_serializer(self):
default_serializer = serializers.Serializer(read_only=True)
if isinstance(self.instance, ReplayStorage):
_type = self.instance.type
else:
_type = self.context['request'].query_params.get('type')
if _type:
serializer_class = replay_storage_type_serializer_classes_mapping.get(_type)
else:
serializer_class = default_serializer
if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class()
else:
serializer = serializer_class
return serializer
# Command storage serializers # Command storage serializers
# --------------------------- # ---------------------------
@ -204,15 +166,17 @@ command_storage_type_serializer_classes_mapping = {
const.CommandStorageTypeChoices.es.value: CommandStorageTypeESSerializer const.CommandStorageTypeChoices.es.value: CommandStorageTypeESSerializer
} }
# CommandStorageSerializer
# BaseStorageSerializer
class CommandStorageSerializer(serializers.ModelSerializer): class BaseStorageSerializer(serializers.ModelSerializer):
storage_type_serializer_classes_mapping = {}
meta = MethodSerializer() meta = MethodSerializer()
class Meta: class Meta:
model = CommandStorage model = None
fields = ['id', 'name', 'type', 'meta', 'comment'] fields = ['id', 'name', 'type', 'meta', 'is_default', 'comment']
def validate_meta(self, meta): def validate_meta(self, meta):
_meta = self.instance.meta if self.instance else {} _meta = self.instance.meta if self.instance else {}
@ -222,13 +186,13 @@ class CommandStorageSerializer(serializers.ModelSerializer):
def get_meta_serializer(self): def get_meta_serializer(self):
default_serializer = serializers.Serializer(read_only=True) default_serializer = serializers.Serializer(read_only=True)
if isinstance(self.instance, CommandStorage): if isinstance(self.instance, self.__class__.Meta.model):
_type = self.instance.type _type = self.instance.type
else: else:
_type = self.context['request'].query_params.get('type') _type = self.context['request'].query_params.get('type')
if _type: if _type:
serializer_class = command_storage_type_serializer_classes_mapping.get(_type) serializer_class = self.storage_type_serializer_classes_mapping.get(_type)
else: else:
serializer_class = default_serializer serializer_class = default_serializer
@ -240,3 +204,30 @@ class CommandStorageSerializer(serializers.ModelSerializer):
else: else:
serializer = serializer_class serializer = serializer_class
return serializer return serializer
def save(self, **kwargs):
instance = super().save(**kwargs)
if self.validated_data.get('is_default', False):
instance.set_to_default()
return instance
# CommandStorageSerializer
class CommandStorageSerializer(BaseStorageSerializer):
storage_type_serializer_classes_mapping = command_storage_type_serializer_classes_mapping
class Meta(BaseStorageSerializer.Meta):
model = CommandStorage
# ReplayStorageSerializer
class ReplayStorageSerializer(BaseStorageSerializer):
storage_type_serializer_classes_mapping = replay_storage_type_serializer_classes_mapping
class Meta(BaseStorageSerializer.Meta):
model = ReplayStorage