From cbc000696eca9c6e920204f98b2eacae3b2976ca Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 11 Dec 2017 17:08:43 +0800 Subject: [PATCH] =?UTF-8?q?[Feature]=20=E4=BF=AE=E6=94=B9adminuser=20syste?= =?UTF-8?q?muser=20cluster=E5=8F=8A=E4=BB=96=E4=BB=AC=E7=9A=84=E5=85=B3?= =?UTF-8?q?=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api.py | 17 +- apps/assets/forms.py | 120 ++++---- apps/assets/models/asset.py | 63 +++- apps/assets/models/cluster.py | 1 + apps/assets/models/group.py | 3 +- apps/assets/models/user.py | 22 +- apps/assets/models/utils.py | 14 +- apps/assets/serializers.py | 32 +- .../assets/templates/assets/_system_user.html | 1 + .../templates/assets/admin_user_assets.html | 287 ++++-------------- .../templates/assets/admin_user_detail.html | 202 +++--------- .../templates/assets/admin_user_list.html | 9 +- .../assets/templates/assets/asset_create.html | 12 +- .../assets/templates/assets/asset_detail.html | 66 ++-- .../templates/assets/asset_group_create.html | 56 ++-- .../templates/assets/asset_group_detail.html | 73 ++--- .../templates/assets/asset_group_list.html | 2 +- apps/assets/templates/assets/asset_list.html | 2 +- .../assets/templates/assets/asset_update.html | 6 +- .../templates/assets/cluster_assets.html | 44 ++- .../assets/cluster_create_update.html | 1 + .../templates/assets/cluster_detail.html | 2 +- .../assets/templates/assets/cluster_list.html | 7 +- .../templates/assets/system_user_asset.html | 142 +++------ .../templates/assets/system_user_detail.html | 32 +- .../templates/assets/system_user_list.html | 2 +- .../templates/assets/system_user_update.html | 40 +-- apps/assets/views/admin_user.py | 13 +- apps/assets/views/asset.py | 2 - apps/assets/views/system_user.py | 25 +- apps/ops/serializers.py | 9 + apps/ops/templates/ops/task_adhoc.html | 13 +- apps/ops/templates/ops/task_history.html | 28 +- apps/static/css/jumpserver.css | 13 + apps/static/css/style.css | 8 +- apps/static/js/jumpserver.js | 35 +-- apps/templates/base.html | 5 +- .../users/user_asset_permission.html | 4 +- .../users/user_group_granted_asset.html | 4 +- apps/users/templates/users/user_list.html | 2 +- 40 files changed, 513 insertions(+), 906 deletions(-) diff --git a/apps/assets/api.py b/apps/assets/api.py index 3cc9e0773..d2ba7ca61 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -39,21 +39,20 @@ class AssetViewSet(IDInFilterMixin, BulkModelViewSet): def get_queryset(self): if self.request.user.is_superuser: - queryset = super(AssetViewSet, self).get_queryset() + queryset = super().get_queryset() else: queryset = get_user_granted_assets(self.request.user) - cluster_id = self.request.query_params.get('cluster_id', '') - system_users_id = self.request.query_params.get('system_user_id', '') - asset_group_id = self.request.query_params.get('asset_group_id', '') - admin_user_id = self.request.query_params.get('admin_user_id', '') + cluster_id = self.request.query_params.get('cluster_id') + asset_group_id = self.request.query_params.get('asset_group_id') + admin_user_id = self.request.query_params.get('admin_user_id') if cluster_id: queryset = queryset.filter(cluster__id=cluster_id) - if system_users_id: - queryset = queryset.filter(system_users__id=system_users_id) - if admin_user_id: - queryset = queryset.filter(admin_user__id=admin_user_id) if asset_group_id: queryset = queryset.filter(groups__id=asset_group_id) + if admin_user_id: + admin_user = get_object_or_404(AdminUser, id=admin_user_id) + clusters = [cluster.id for cluster in admin_user.cluster_set.all()] + queryset = queryset.filter(cluster__id__in=clusters) return queryset diff --git a/apps/assets/forms.py b/apps/assets/forms.py index d7d014a15..5b7d30d5a 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -10,40 +10,60 @@ logger = get_logger(__file__) class AssetCreateForm(forms.ModelForm): + # Form field name can not start with `_`, so redefine it, + password = forms.CharField( + widget=forms.PasswordInput, max_length=100, + strip=True, required=False, + help_text=_('If also set private key, use that first'), + ) + # Need use upload private key file except paste private key content + private_key_file = forms.FileField(required=False) + + def save(self, commit=True): + # Because we define custom field, so we need rewrite :method: `save` + obj = super().save(commit=commit) + password = self.cleaned_data['password'] + private_key = self.cleaned_data['private_key_file'] + + if password: + obj.password = password + if private_key: + obj.private_key = private_key + obj.save() + return obj + + def clean_private_key_file(self): + private_key_file = self.cleaned_data['private_key_file'] + if private_key_file: + private_key = private_key_file.read() + if not validate_ssh_private_key(private_key): + raise forms.ValidationError(_('Invalid private key')) + return private_key + return private_key_file + class Meta: model = Asset fields = [ 'hostname', 'ip', 'public_ip', 'port', 'type', 'comment', - 'admin_user', "cluster", 'groups', 'status', 'env', 'is_active' + 'cluster', 'groups', 'status', 'env', 'is_active', 'username', + ] widgets = { 'groups': forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), - 'admin_user': forms.Select( - attrs={'class': 'select2', - 'data-placeholder': _('Select asset admin user')}), } help_texts = { 'hostname': '* required', 'ip': '* required', - 'system_users': _('System user will be granted for user to login ' - 'assets (using ansible create automatic)'), - 'admin_user': _('Admin user should be exist on asset already, ' - 'And have sudo ALL permission'), } - def clean_admin_user(self): - if not self.cleaned_data['admin_user']: - raise forms.ValidationError(_('Select admin user')) - return self.cleaned_data['admin_user'] - class AssetUpdateForm(forms.ModelForm): class Meta: model = Asset fields = [ - 'hostname', 'ip', 'port', 'groups', 'admin_user', "cluster", 'is_active', + 'hostname', 'ip', 'port', 'groups', "cluster", 'is_active', 'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no', 'cabinet_pos', 'number', 'comment' ] @@ -51,17 +71,10 @@ class AssetUpdateForm(forms.ModelForm): 'groups': forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), - 'admin_user': forms.Select( - attrs={'class': 'select2', - 'data-placeholder': _('Select asset admin user')}), } help_texts = { 'hostname': '* required', 'ip': '* required', - 'system_users': _('System user will be granted for user ' - 'to login assets (using ansible create automatic)'), - 'admin_user': _('Admin user should be exist on asset ' - 'already, And have sudo ALL permission'), } @@ -77,22 +90,16 @@ class AssetBulkUpdateForm(forms.ModelForm): } ) ) - port = forms.IntegerField(min_value=1, max_value=65535, - required=False, label=_('Port')) + port = forms.IntegerField(min_value=1, max_value=65535, required=False, label=_('Port')) class Meta: model = Asset fields = [ - 'assets', 'port', 'groups', 'admin_user', "cluster", + 'assets', 'port', 'groups', "cluster", 'type', 'env', 'status', ] widgets = { - 'groups': forms.SelectMultiple( - attrs={'class': 'select2', - 'data-placeholder': _('Select asset groups')}), - 'admin_user': forms.Select( - attrs={'class': 'select2', - 'data-placeholder': _('Select asset admin user')}), + 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), } def save(self, commit=True): @@ -140,40 +147,19 @@ class AssetGroupForm(forms.ModelForm): class ClusterForm(forms.ModelForm): - # See AdminUserForm comment same it - assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.all(), - label=_('Asset'), - required=False, - widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select assets')}) - ) - - def __init__(self, *args, **kwargs): - if kwargs.get('instance'): - initial = kwargs.get('initial', {}) - initial['assets'] = kwargs['instance'].assets.all() - super(ClusterForm, self).__init__(*args, **kwargs) - - def _save_m2m(self): - super(ClusterForm, self)._save_m2m() - assets = self.cleaned_data['assets'] - self.instance.assets.clear() - self.instance.assets.add(*tuple(assets)) class Meta: model = Cluster - fields = ['name', "bandwidth", "operator", 'contact', + fields = ['name', "bandwidth", "operator", 'contact', 'admin_user', 'phone', 'address', 'intranet', 'extranet', 'comment'] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'intranet': forms.Textarea( - attrs={'placeholder': 'IP段之间用逗号隔开,如:192.168.1.0/24,192.168.1.0/24'}), - 'extranet': forms.Textarea( - attrs={'placeholder': 'IP段之间用逗号隔开,如:201.1.32.1/24,202.2.32.1/24'}) + 'intranet': forms.Textarea(attrs={'placeholder': 'IP段之间用逗号隔开,如:192.168.1.0/24,192.168.1.0/24'}), + 'extranet': forms.Textarea(attrs={'placeholder': 'IP段之间用逗号隔开,如:201.1.32.1/24,202.2.32.1/24'}) } help_texts = { - 'name': '* required' + 'name': '* required', + 'admin_user': 'The assets of this cluster will use this admin user as his admin user', } @@ -237,8 +223,7 @@ class SystemUserForm(forms.ModelForm): # Admin user assets define, let user select, save it in form not in view auto_generate_key = forms.BooleanField(initial=True, required=False) # Form field name can not start with `_`, so redefine it, - password = forms.CharField(widget=forms.PasswordInput, required=False, - max_length=100, strip=True) + password = forms.CharField(widget=forms.PasswordInput, required=False, max_length=100, strip=True) # Need use upload private key file except paste private key content private_key_file = forms.FileField(required=False) @@ -289,15 +274,19 @@ class SystemUserForm(forms.ModelForm): fields = [ 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auth_method', 'auto_push', 'sudo', - 'comment', 'shell' + 'comment', 'shell', 'cluster' ] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), + 'cluster': forms.SelectMultiple( + attrs={'class': 'select2', + 'data-placeholder': _(' Select clusters')}), } help_texts = { 'name': '* required', 'username': '* required', + 'cluster': 'If auto push checked, then push system user to that cluster assets', 'auto_push': 'Auto push system user to asset', } @@ -306,8 +295,7 @@ class SystemUserUpdateForm(forms.ModelForm): # Admin user assets define, let user select, save it in form not in view auto_generate_key = forms.BooleanField(initial=False, required=False) # Form field name can not start with `_`, so redefine it, - password = forms.CharField(widget=forms.PasswordInput, required=False, - max_length=100, strip=True) + password = forms.CharField(widget=forms.PasswordInput, required=False, max_length=100, strip=True) # Need use upload private key file except paste private key content private_key_file = forms.FileField(required=False) @@ -341,17 +329,21 @@ class SystemUserUpdateForm(forms.ModelForm): class Meta: model = SystemUser fields = [ - 'name', 'username', 'protocol', 'auto_generate_key', 'password', - 'private_key_file', 'auth_method', 'auto_push', 'sudo', - 'comment', 'shell' + 'name', 'username', 'protocol', + 'auth_method', 'auto_push', 'sudo', + 'comment', 'shell', 'cluster' ] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), + 'cluster': forms.SelectMultiple( + attrs={'class': 'select2', + 'data-placeholder': _(' Select clusters')}), } help_texts = { 'name': '* required', 'username': '* required', + 'cluster': 'If auto push checked, then push system user to that cluster assets', 'auto_push': 'Auto push system user to asset', } diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index e5656ca97..430183d16 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -3,12 +3,17 @@ # import uuid +import os +import logging +from hashlib import md5 from django.db import models -import logging +from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.core.cache import cache +from common.utils import signer, ssh_key_string_to_obj +from .utils import private_key_validator from .cluster import Cluster from .group import AssetGroup from .user import AdminUser, SystemUser @@ -47,14 +52,17 @@ class Asset(models.Model): hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) port = models.IntegerField(default=22, verbose_name=_('Port')) groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) - admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_("Admin user")) - system_users = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User")) cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster'),) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),) env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),) status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True, default='In use', verbose_name=_('Asset status')) + # Auth + username = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('Username')) + _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) + # Some information public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote control card IP')) @@ -96,6 +104,41 @@ class Asset(models.Model): return True, '' return False, warning + @property + def password(self): + if self._password: + return signer.unsign(self._password) + else: + return '' + + @password.setter + def password(self, password_raw): + self._password = signer.sign(password_raw) + + @property + def private_key(self): + if self._private_key: + key_str = signer.unsign(self._private_key) + return ssh_key_string_to_obj(key_str) + else: + return None + + @private_key.setter + def private_key(self, private_key_raw): + self._private_key = signer.sign(private_key_raw) + + @property + def private_key_file(self): + if not self.private_key: + return None + project_dir = settings.PROJECT_DIR + tmp_dir = os.path.join(project_dir, 'tmp') + key_name = md5(self._private_key.encode()).hexdigest() + key_path = os.path.join(tmp_dir, key_name) + if not os.path.exists(key_path): + self.private_key.write_private_key_file(key_path) + return key_path + def to_json(self): return { 'id': self.id, @@ -115,15 +158,15 @@ class Asset(models.Model): Todo: May be move to ops implements it """ data = self.to_json() - if self.admin_user: + if self.cluster and self.cluster.admin_user: data.update({ - 'username': self.admin_user.username, - 'password': self.admin_user.password, - 'private_key': self.admin_user.private_key_file, + 'username': self.cluster.admin_user.username, + 'password': self.cluster.admin_user.password, + 'private_key': self.cluster.admin_user.private_key_file, 'become': { - 'method': self.admin_user.become_method, - 'user': self.admin_user.become_user, - 'pass': self.admin_user.become_pass, + 'method': self.cluster.admin_user.become_method, + 'user': self.cluster.admin_user.become_user, + 'pass': self.cluster.admin_user.become_pass, } }) return data diff --git a/apps/assets/models/cluster.py b/apps/assets/models/cluster.py index 54ce1be4e..dd30516a4 100644 --- a/apps/assets/models/cluster.py +++ b/apps/assets/models/cluster.py @@ -18,6 +18,7 @@ logger = logging.getLogger(__name__) class Cluster(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=32, verbose_name=_('Name')) + admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.CASCADE, verbose_name=_("Admin user")) bandwidth = models.CharField(max_length=32, blank=True, verbose_name=_('Bandwidth')) contact = models.CharField(max_length=128, blank=True, verbose_name=_('Contact')) phone = models.CharField(max_length=32, blank=True, verbose_name=_('Phone')) diff --git a/apps/assets/models/group.py b/apps/assets/models/group.py index 31a1cd2d5..965ceb1f3 100644 --- a/apps/assets/models/group.py +++ b/apps/assets/models/group.py @@ -23,9 +23,8 @@ class AssetGroup(models.Model): date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) comment = models.TextField(blank=True, verbose_name=_('Comment')) - def __unicode__(self): + def __str__(self): return self.name - __str__ = __unicode__ class Meta: ordering = ['name'] diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 47bc41ce5..fb8a98ede 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -8,25 +8,18 @@ import logging import uuid from hashlib import md5 -from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from common.utils import signer, validate_ssh_private_key, ssh_key_string_to_obj +from common.utils import signer, ssh_key_string_to_obj +from .utils import private_key_validator -__all__ = ['AdminUser', 'SystemUser', 'private_key_validator'] + +__all__ = ['AdminUser', 'SystemUser',] logger = logging.getLogger(__name__) -def private_key_validator(value): - if not validate_ssh_private_key(value): - raise ValidationError( - _('%(value)s is not an even number'), - params={'value': value}, - ) - - class AdminUser(models.Model): """ A privileged user that ansible can use it to push system user and so on @@ -103,10 +96,12 @@ class AdminUser(models.Model): def become_pass(self, password): self._become_pass = signer.sign(password) - @property def assets_amount(self): - return self.assets.count() + amount = 0 + for cluster in self.cluster_set.all(): + amount += cluster.assets.all().count() + return amount class Meta: ordering = ['name'] @@ -143,6 +138,7 @@ class SystemUser(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) username = models.CharField(max_length=16, verbose_name=_('Username')) + cluster = models.ManyToManyField('assets.Cluster', verbose_name=_("Cluster")) _password = models.CharField(max_length=256, blank=True, verbose_name=_('Password')) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) _private_key = models.TextField(max_length=8192, blank=True, verbose_name=_('SSH private key')) diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index 8b1c94502..a819715ab 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -2,22 +2,34 @@ # -*- coding: utf-8 -*- # -from . import Cluster, SystemUser, AdminUser, AssetGroup, Asset +from django.core.exceptions import ValidationError +from common.utils import validate_ssh_private_key + __all__ = ['init_model', 'generate_fake'] def init_model(): + from . import Cluster, SystemUser, AdminUser, AssetGroup, Asset for cls in [Cluster, SystemUser, AdminUser, AssetGroup, Asset]: if hasattr(cls, 'initial'): cls.initial() def generate_fake(): + from . import Cluster, SystemUser, AdminUser, AssetGroup, Asset for cls in [Cluster, SystemUser, AdminUser, AssetGroup, Asset]: if hasattr(cls, 'generate_fake'): cls.generate_fake() +def private_key_validator(value): + if not validate_ssh_private_key(value): + raise ValidationError( + _('%(value)s is not an even number'), + params={'value': value}, + ) + + if __name__ == '__main__': pass diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 0a51adfb7..cfd6d1700 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -63,7 +63,7 @@ class ClusterUpdateAssetsSerializer(serializers.ModelSerializer): class AdminUserSerializer(serializers.ModelSerializer): - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) + assets_amount = serializers.SerializerMethodField() unreachable_amount = serializers.SerializerMethodField() class Meta: @@ -78,14 +78,18 @@ class AdminUserSerializer(serializers.ModelSerializer): else: return 'Unknown' - def get_field_names(self, declared_fields, info): - fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info) - fields.append('assets_amount') - return fields + @staticmethod + def get_assets_amount(obj): + amount = 0 + clusters = obj.cluster_set.all() + for cluster in clusters: + amount += len(cluster.assets.all()) + return amount class SystemUserSerializer(serializers.ModelSerializer): unreachable_amount = serializers.SerializerMethodField() + assets_amount = serializers.SerializerMethodField() class Meta: model = SystemUser @@ -99,10 +103,12 @@ class SystemUserSerializer(serializers.ModelSerializer): else: return "Unknown" - def get_field_names(self, declared_fields, info): - fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) - fields.extend(['assets_amount']) - return fields + @staticmethod + def get_assets_amount(obj): + amount = 0 + for cluster in obj.cluster.all(): + amount += cluster.assets.all().count() + return amount class AssetSystemUserSerializer(serializers.ModelSerializer): @@ -200,6 +206,7 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer): class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer): assets_amount = serializers.SerializerMethodField() + admin_user_name = serializers.SerializerMethodField() assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) class Meta: @@ -210,10 +217,9 @@ class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer): def get_assets_amount(obj): return obj.assets.count() - def get_field_names(self, declared_fields, info): - fields = super(ClusterSerializer, self).get_field_names(declared_fields, info) - fields.append('assets_amount') - return fields + @staticmethod + def get_admin_user_name(obj): + return obj.admin_user.name class AssetGroupGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html index cb3e8dd91..41c6dda51 100644 --- a/apps/assets/templates/assets/_system_user.html +++ b/apps/assets/templates/assets/_system_user.html @@ -38,6 +38,7 @@ {% bootstrap_field form.name layout="horizontal" %} {% bootstrap_field form.username layout="horizontal" %} {% bootstrap_field form.protocol layout="horizontal" %} + {% bootstrap_field form.cluster layout="horizontal" %}

{% trans 'Auth' %}

{% bootstrap_field form.auth_method layout="horizontal" %} {% block auth %} diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index 62244b5f0..cf086379b 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -50,37 +50,22 @@
- +
+ - + + - {% for asset in page_obj %} - - - - - {% if asset.is_connective == '1' %} - - {% else %} - - {% endif %} - - {% endfor %}
+ + {% trans 'Hostname' %} {% trans 'IP' %} {% trans 'Port' %}{% trans 'Alive' %}{% trans 'Type' %}{% trans 'Valid' %}
{{ asset.hostname }}{{ asset.ip }}{{ asset.port }} - - - -
-
- {% include '_pagination.html' %} -
@@ -104,69 +89,12 @@ -
-
- {% trans 'Replace asset admin user with this' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Replace asset group admin user with this' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- - {% endblock %} {% block custom_foot_js %} {% endblock %} \ No newline at end of file diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py index 0cc4f66f2..57ef646a1 100644 --- a/apps/assets/views/admin_user.py +++ b/apps/assets/views/admin_user.py @@ -89,7 +89,10 @@ class AdminUserDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView): return super(AdminUserDetailView, self).get(request, *args, **kwargs) def get_queryset(self): - return self.object.assets.all() + queryset = [] + for cluster in self.object.cluster_set.all(): + queryset.extend(list(cluster.assets.all())) + return queryset def get_context_data(self, **kwargs): asset_groups = AssetGroup.objects.all() @@ -114,9 +117,11 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): return super().get(request, *args, **kwargs) def get_queryset(self): - self.queryset = self.object.assets.all() - sorted(self.queryset, key=lambda x: x.is_connective() is False) - return self.queryset + queryset = [] + for cluster in self.object.cluster_set.all(): + queryset.extend(list(cluster.assets.all())) + self.queryset = queryset + return queryset def get_context_data(self, **kwargs): context = { diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 10b4219af..f72618f54 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -183,7 +183,6 @@ class AssetDetailView(DetailView): def get_context_data(self, **kwargs): asset_groups = self.object.groups.all() - system_users = self.object.system_users.all() context = { 'app': 'Assets', 'action': 'Asset detail', @@ -191,7 +190,6 @@ class AssetDetailView(DetailView): if asset_group not in asset_groups], 'asset_groups': asset_groups, 'system_users_all': SystemUser.objects.all(), - 'system_users': system_users, } kwargs.update(context) return super(AssetDetailView, self).get_context_data(**kwargs) diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py index 0d3ccfe8b..2a42dfee0 100644 --- a/apps/assets/views/system_user.py +++ b/apps/assets/views/system_user.py @@ -10,7 +10,7 @@ from django.urls import reverse_lazy from django.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView, SingleObjectMixin -from .. import forms +from ..forms import SystemUserForm, SystemUserUpdateForm from ..models import Asset, AssetGroup, SystemUser from ..hands import AdminUserRequiredMixin from perms.utils import associate_system_users_and_assets @@ -36,7 +36,7 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView): class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): model = SystemUser - form_class = forms.SystemUserForm + form_class = SystemUserForm template_name = 'assets/system_user_create.html' success_url = reverse_lazy('assets:system-user-list') @@ -65,7 +65,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView): model = SystemUser - form_class = forms.SystemUserUpdateForm + form_class = SystemUserUpdateForm template_name = 'assets/system_user_update.html' def get_context_data(self, **kwargs): @@ -110,31 +110,16 @@ class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView): success_url = reverse_lazy('assets:system-user-list') -class SystemUserAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView): - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE +class SystemUserAssetView(AdminUserRequiredMixin, DetailView): + model = SystemUser template_name = 'assets/system_user_asset.html' context_object_name = 'system_user' - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=SystemUser.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - return self.object.assets.all() - def get_context_data(self, **kwargs): - assets = self.get_queryset() context = { 'app': 'assets', 'action': 'System user asset', - 'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets], - 'asset_groups': AssetGroup.objects.all(), } kwargs.update(context) return super(SystemUserAssetView, self).get_context_data(**kwargs) - - - - - diff --git a/apps/ops/serializers.py b/apps/ops/serializers.py index 2ffb21c06..86a029c12 100644 --- a/apps/ops/serializers.py +++ b/apps/ops/serializers.py @@ -25,6 +25,7 @@ class AdHocSerializer(serializers.ModelSerializer): class AdHocRunHistorySerializer(serializers.ModelSerializer): task = serializers.SerializerMethodField() adhoc_short_id = serializers.SerializerMethodField() + stat = serializers.SerializerMethodField() class Meta: model = AdHocRunHistory @@ -38,6 +39,14 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer): def get_task(obj): return obj.adhoc.task.id + @staticmethod + def get_stat(obj): + return { + "total": len(obj.adhoc.hosts), + "success": len(obj.summary["contacted"]), + "failed": len(obj.summary["dark"]), + } + def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) fields.extend(['summary', 'short_id']) diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html index 21232ad82..8eb47c8fd 100644 --- a/apps/ops/templates/ops/task_adhoc.html +++ b/apps/ops/templates/ops/task_adhoc.html @@ -58,7 +58,7 @@ {% trans 'Run as' %} {% trans 'Become' %} {% trans 'Datetime' %} - + {% trans 'Action' %} @@ -104,12 +104,17 @@ } else { $(td).html(cellData.user) } - - }} + }}, + {targets: 7, createdCell: function (td, cellData, rowData) { + var detail_btn = '{% trans "Detail" %}'.replace('99991937', cellData); + if (cellData) { + $(td).html(detail_btn); + } + }} ], ajax_url: '{% url "api-ops:adhoc-list" %}?task={{ object.pk }}', columns: [{data: function(){return ""}}, {data: "short_id" }, {data: "hosts"}, {data: "pattern"}, - {data: "run_as"}, {data: "become"}, {data: "date_created"}] + {data: "run_as"}, {data: "become"}, {data: "date_created"}, {data: "id"}] }; jumpserver.initDataTable(options); }) diff --git a/apps/ops/templates/ops/task_history.html b/apps/ops/templates/ops/task_history.html index 6fde3b742..772455429 100644 --- a/apps/ops/templates/ops/task_history.html +++ b/apps/ops/templates/ops/task_history.html @@ -30,7 +30,7 @@
- {% trans 'Versions of ' %} {{ object.name }} + {% trans 'History of ' %} {{ object.name }}
@@ -57,7 +57,8 @@ {% trans 'Is finished' %} {% trans 'Is success' %} {% trans 'Time' %} - + {% trans 'Version' %} + {% trans 'Action' %} @@ -86,10 +87,12 @@ {# var detail_btn = '" + cellData.total + ""; + var success = "" + cellData.success + ""; + var failed = "" + cellData.failed + ""; + $(td).html(failed + '/' + success + '/' + total ); + }}, {targets: 3, createdCell: function (td, cellData) { if (!cellData) { $(td).html('') @@ -110,12 +113,17 @@ } else { $(td).html("0" + ' s') } - - }} + }}, + {targets: 7, createdCell: function (td, cellData) { + var run_btn = '{% trans "Detail" %}'.replace('99991937', cellData); + if (cellData) { + $(td).html(run_btn); + } + }} ], ajax_url: '{% url "api-ops:history-list" %}?task={{ object.pk }}', - columns: [{data: function(){return ""}}, {data: "date_start"}, {data: "adhoc_short_id"}, - {data: "adhoc_short_id"}, {data: "adhoc_short_id"}, {data: "timedelta"}] + columns: [{data: function(){return ""}}, {data: "date_start"}, {data: "stat"}, {data: "adhoc_short_id"}, + {data: "adhoc_short_id"}, {data: "timedelta"}, {data: 'adhoc_short_id'}, {data: "id"}] }; jumpserver.initDataTable(options); }) diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index a7a6d0af4..501eecee3 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -277,3 +277,16 @@ div.dataTables_wrapper div.dataTables_filter { margin-top: 5px; padding: 5px 12px; } + +#op.col-md-6{ + padding-left: 0; +} + +.help-message { + padding-left: 10px; + font-size: 12px; + line-height: 18px; + margin-bottom:0; + margin-left: 10px; + margin-right: 10px +} \ No newline at end of file diff --git a/apps/static/css/style.css b/apps/static/css/style.css index a4c62fe97..3e391e352 100644 --- a/apps/static/css/style.css +++ b/apps/static/css/style.css @@ -591,7 +591,7 @@ body.canvas-menu.mini-navbar nav.navbar-static-side { border-radius: 3px; } .float-e-margins .btn { - margin-bottom: 5px; + /*margin-bottom: 5px;*/ } .btn-w-m { min-width: 120px; @@ -3440,13 +3440,13 @@ video { overflow-x: hidden; } .wrapper { - padding: 0 20px; + padding: 0 10px; } .wrapper-content { - padding: 20px 10px 40px; + padding: 10px 10px 10px; } #page-wrapper { - padding: 0 15px; + padding: 0 10px; min-height: 568px; position: relative !important; } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index c9b091338..360c4ad3e 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -98,30 +98,6 @@ function move_left(from, to, from_o, to_o) { }); } -//function move_all(from, to) { -// $("#" + from).children().each(function () { -// $("#" + to).append(this); -// }); -//} -// - -//function selectAllOption(){ -// var checklist = document.getElementsByName ("selected"); -// if(document.getElementById("select_all").checked) -// { -// for(var i=0;i'.replace('99991937', cellData)); }}, - {className: 'text-center', targets: '_all'} + {className: 'text-center', targets: '_all'} ]; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; var table = ele.DataTable({ diff --git a/apps/templates/base.html b/apps/templates/base.html index cc2525b0d..58bd303a1 100644 --- a/apps/templates/base.html +++ b/apps/templates/base.html @@ -22,7 +22,7 @@ {% include '_message.html' %} {% block first_login_message %} {% if user.is_authenticated and user.is_first_login %} -
+
{% url 'users:user-first-login' as first_login_url %} {% blocktrans %} Your information was incomplete. Please click this link to complete your information. @@ -32,7 +32,7 @@ {% endblock %} {% block update_public_key_message %} {% if user.is_authenticated and not user.is_public_key_valid %} -
+
{% url 'users:user-pubkey-update' as user_pubkey_update %} {% blocktrans %} Your ssh public key not set or expired. Please click this link to update your @@ -40,6 +40,7 @@
{% endif %} {% endblock %} + {% block help_message %}{% endblock %} {% block content %}{% endblock %} {% include '_footer.html' %}
diff --git a/apps/users/templates/users/user_asset_permission.html b/apps/users/templates/users/user_asset_permission.html index 609c4a69f..d70d259c3 100644 --- a/apps/users/templates/users/user_asset_permission.html +++ b/apps/users/templates/users/user_asset_permission.html @@ -26,7 +26,7 @@
-
+
{% trans 'Asset permission of ' %} {{ user.name }} @@ -65,7 +65,7 @@
-
+
{% trans 'Quick create permission for user' %} diff --git a/apps/users/templates/users/user_group_granted_asset.html b/apps/users/templates/users/user_group_granted_asset.html index afdcef5c0..ce3a37121 100644 --- a/apps/users/templates/users/user_group_granted_asset.html +++ b/apps/users/templates/users/user_group_granted_asset.html @@ -26,7 +26,7 @@
-
+
{% trans 'Assets granted of ' %} {{ user_group.name }} @@ -62,7 +62,7 @@
-
+
{% trans 'Asset groups granted of ' %} {{ user_group.name }} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index a551495ea..7f0bda0e5 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -13,7 +13,7 @@
{% endblock %} {% block table_container %} - +