diff --git a/apps/applications/migrations/0009_applicationuser.py b/apps/applications/migrations/0009_applicationuser.py new file mode 100644 index 000000000..7b3368ef9 --- /dev/null +++ b/apps/applications/migrations/0009_applicationuser.py @@ -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',), + ), + ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 15e579624..3d1ae71fe 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 593fea0b1..483f95fa3 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 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" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -23,9 +23,9 @@ msgstr "" #: 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 #: 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/task.py:16 terminal/models/terminal.py:100 -#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:550 +#: terminal/models/storage.py:23 terminal/models/task.py:16 +#: terminal/models/terminal.py:100 users/forms/profile.py:32 +#: users/models/group.py:15 users/models/user.py:550 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: 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/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 -#: terminal/models/storage.py:29 terminal/models/storage.py:96 -#: terminal/models/terminal.py:114 tickets/models/ticket.py:73 -#: users/models/group.py:16 users/models/user.py:583 -#: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:35 -#: xpack/plugins/cloud/models.py:98 xpack/plugins/gathered_user/models.py:26 +#: terminal/models/storage.py:26 terminal/models/terminal.py:114 +#: tickets/models/ticket.py:73 users/models/group.py:16 +#: users/models/user.py:583 xpack/plugins/change_auth_plan/models.py:77 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:98 +#: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" @@ -257,7 +257,7 @@ msgstr "类别" #: perms/models/application_permission.py:23 #: perms/serializers/application/permission.py:17 #: 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/serializers/ticket/meta/ticket_type/apply_application.py:27 msgid "Type" @@ -1202,7 +1202,7 @@ msgstr "主机 (显示名称)" msgid "Result" msgstr "结果" -#: audits/serializers.py:92 terminal/serializers/storage.py:189 +#: audits/serializers.py:92 terminal/serializers/storage.py:195 msgid "Hosts" msgstr "主机" @@ -3252,6 +3252,10 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" +#: terminal/models/storage.py:25 +msgid "Default storage" +msgstr "默认存储" + #: terminal/models/task.py:17 msgid "Args" msgstr "参数" @@ -3426,27 +3430,27 @@ msgstr "账户密钥" msgid "Endpoint suffix" msgstr "端点后缀" -#: terminal/serializers/storage.py:166 +#: terminal/serializers/storage.py:172 msgid "The address format is incorrect" msgstr "地址格式不正确" -#: terminal/serializers/storage.py:173 +#: terminal/serializers/storage.py:179 msgid "Host invalid" msgstr "主机无效" -#: terminal/serializers/storage.py:176 +#: terminal/serializers/storage.py:182 msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:192 +#: terminal/serializers/storage.py:198 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:194 +#: terminal/serializers/storage.py:200 msgid "Doc type" msgstr "文档类型" -#: terminal/serializers/storage.py:196 +#: terminal/serializers/storage.py:202 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" @@ -5114,6 +5118,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" - -#~ msgid "Terminal command alert" -#~ msgstr "终端命令告警" diff --git a/apps/terminal/api/storage.py b/apps/terminal/api/storage.py index 4aa3fecbb..db4470f75 100644 --- a/apps/terminal/api/storage.py +++ b/apps/terminal/api/storage.py @@ -36,7 +36,7 @@ class BaseStorageViewSetMixin: class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet): - search_fields = ('name', 'type',) + search_fields = ('name', 'type') queryset = CommandStorage.objects.all() serializer_class = CommandStorageSerializer permission_classes = (IsSuperUser,) @@ -103,7 +103,7 @@ class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet): class ReplayStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet): - filterset_fields = ('name', 'type',) + filterset_fields = ('name', 'type', 'is_default') search_fields = filterset_fields queryset = ReplayStorage.objects.all() serializer_class = ReplayStorageSerializer diff --git a/apps/terminal/filters.py b/apps/terminal/filters.py index a102c149c..81f548162 100644 --- a/apps/terminal/filters.py +++ b/apps/terminal/filters.py @@ -71,7 +71,7 @@ class CommandStorageFilter(filters.FilterSet): class Meta: model = CommandStorage - fields = ['real', 'name', 'type'] + fields = ['real', 'name', 'type', 'is_default'] def filter_real(self, queryset, name, value): if value: diff --git a/apps/terminal/migrations/0037_auto_20210623_1748.py b/apps/terminal/migrations/0037_auto_20210623_1748.py new file mode 100644 index 000000000..fde10b3b7 --- /dev/null +++ b/apps/terminal/migrations/0037_auto_20210623_1748.py @@ -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) + ] diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/storage.py index 883e5f67a..7d4efcfbc 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/storage.py @@ -19,17 +19,41 @@ from .. import const logger = get_logger(__file__) -class CommandStorage(CommonModelMixin): +class CommonStorageModelMixin(models.Model): 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( max_length=16, choices=const.CommandStorageTypeChoices.choices, 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 def type_null(self): @@ -86,17 +110,11 @@ class CommandStorage(CommonModelMixin): backend.pre_use_check() -class ReplayStorage(CommonModelMixin): - name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True) +class ReplayStorage(CommonStorageModelMixin, CommonModelMixin): type = models.CharField( max_length=16, choices=const.ReplayStorageTypeChoices.choices, 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 def type_null(self): diff --git a/apps/terminal/models/terminal.py b/apps/terminal/models/terminal.py index 77c9b1ce8..0c0c36771 100644 --- a/apps/terminal/models/terminal.py +++ b/apps/terminal/models/terminal.py @@ -176,6 +176,12 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): self.save() 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): status = "Active" if not self.is_accepted: diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index cdd6e75a3..d0f803928 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -119,44 +119,6 @@ replay_storage_type_serializer_classes_mapping = { 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 # --------------------------- @@ -204,15 +166,17 @@ command_storage_type_serializer_classes_mapping = { const.CommandStorageTypeChoices.es.value: CommandStorageTypeESSerializer } -# CommandStorageSerializer + +# BaseStorageSerializer -class CommandStorageSerializer(serializers.ModelSerializer): +class BaseStorageSerializer(serializers.ModelSerializer): + storage_type_serializer_classes_mapping = {} meta = MethodSerializer() class Meta: - model = CommandStorage - fields = ['id', 'name', 'type', 'meta', 'comment'] + model = None + fields = ['id', 'name', 'type', 'meta', 'is_default', 'comment'] def validate_meta(self, meta): _meta = self.instance.meta if self.instance else {} @@ -222,13 +186,13 @@ class CommandStorageSerializer(serializers.ModelSerializer): def get_meta_serializer(self): default_serializer = serializers.Serializer(read_only=True) - if isinstance(self.instance, CommandStorage): + if isinstance(self.instance, self.__class__.Meta.model): _type = self.instance.type else: _type = self.context['request'].query_params.get('type') if _type: - serializer_class = command_storage_type_serializer_classes_mapping.get(_type) + serializer_class = self.storage_type_serializer_classes_mapping.get(_type) else: serializer_class = default_serializer @@ -240,3 +204,30 @@ class CommandStorageSerializer(serializers.ModelSerializer): else: serializer = serializer_class 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 +