diff --git a/apps/assets/__init__.py b/apps/assets/__init__.py
index e69de29bb..a3128cc9a 100644
--- a/apps/assets/__init__.py
+++ b/apps/assets/__init__.py
@@ -0,0 +1 @@
+from . import signals
diff --git a/apps/assets/api.py b/apps/assets/api.py
index d2ba7ca61..6df0c184e 100644
--- a/apps/assets/api.py
+++ b/apps/assets/api.py
@@ -181,10 +181,11 @@ class AssetRefreshHardwareView(generics.RetrieveAPIView):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
summary = update_assets_hardware_info([asset])
- if len(summary['failed']) == 0:
- return super(AssetRefreshHardwareView, self).retrieve(request, *args, **kwargs)
+ print(summary)
+ if summary.get('dark'):
+ return Response(summary['dark'].values(), status=501)
else:
- return Response('', status=502)
+ return Response({"msg": "ok"})
class AssetAdminUserTestView(AssetRefreshHardwareView):
diff --git a/apps/assets/apps.py b/apps/assets/apps.py
index 89c61e1db..41eead508 100644
--- a/apps/assets/apps.py
+++ b/apps/assets/apps.py
@@ -5,3 +5,8 @@ from django.apps import AppConfig
class AssetsConfig(AppConfig):
name = 'assets'
+
+ def ready(self):
+ from .signals import on_app_ready
+ on_app_ready.send(self.__class__)
+ super().ready()
diff --git a/apps/assets/const.py b/apps/assets/const.py
new file mode 100644
index 000000000..36a539c20
--- /dev/null
+++ b/apps/assets/const.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+#
+
+ADMIN_USER_CONN_CACHE_KEY_PREFIX = "ADMIN_USER_CONN_"
+SYSTEM_USER_CONN_CACHE_KEY_PREFIX = 'SYSTEM_USER_CONN_'
diff --git a/apps/assets/forms.py b/apps/assets/forms.py
index 5b7d30d5a..a067a4a3c 100644
--- a/apps/assets/forms.py
+++ b/apps/assets/forms.py
@@ -10,52 +10,26 @@ 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',
- 'cluster', 'groups', 'status', 'env', 'is_active', 'username',
+ 'cluster', 'groups', 'status', 'env', 'is_active',
+ 'admin_user'
]
widgets = {
- 'groups': forms.SelectMultiple(
- attrs={'class': 'select2',
- 'data-placeholder': _('Select asset groups')}),
+ 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
+ 'cluster': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select cluster')}),
+ 'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select admin user')}),
}
help_texts = {
'hostname': '* required',
'ip': '* required',
+ 'port': '* required',
+ 'cluster': '* required',
+ 'admin_user': _('Host level admin user, If not set using cluster admin user default')
}
@@ -65,16 +39,18 @@ class AssetUpdateForm(forms.ModelForm):
fields = [
'hostname', 'ip', 'port', 'groups', "cluster", 'is_active',
'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no',
- 'cabinet_pos', 'number', 'comment'
+ 'cabinet_pos', 'number', 'comment', 'admin_user',
]
widgets = {
- 'groups': forms.SelectMultiple(
- attrs={'class': 'select2',
- 'data-placeholder': _('Select asset groups')}),
+ 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
+ 'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _("Default using cluster admin user")})
}
help_texts = {
'hostname': '* required',
'ip': '* required',
+ 'port': '* required',
+ 'cluster': '* required',
+ 'admin_user': _('Host level admin user, If not set using cluster admin user default')
}
diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py
index 430183d16..c2622511b 100644
--- a/apps/assets/models/asset.py
+++ b/apps/assets/models/asset.py
@@ -3,17 +3,13 @@
#
import uuid
-import os
import logging
-from hashlib import md5
from django.db import models
-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 ..const import ADMIN_USER_CONN_CACHE_KEY_PREFIX
from .cluster import Cluster
from .group import AssetGroup
from .user import AdminUser, SystemUser
@@ -59,9 +55,7 @@ class Asset(models.Model):
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, ])
+ admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
@@ -105,39 +99,22 @@ class Asset(models.Model):
return False, warning
@property
- def password(self):
- if self._password:
- return signer.unsign(self._password)
+ def hardware_info(self):
+ if self.cpu_count:
+ return '{} Core {} {}'.format(
+ self.cpu_count * self.cpu_cores,
+ self.memory, self.disk_total
+ )
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)
+ def is_connective(self):
+ val = cache.get(ADMIN_USER_CONN_CACHE_KEY_PREFIX + self.hostname)
+ if val == 1:
+ return True
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
+ return False
def to_json(self):
return {
@@ -148,25 +125,28 @@ class Asset(models.Model):
'groups': [group.name for group in self.groups.all()],
}
- def is_connective(self):
- return cache.get(self.hostname)
-
def _to_secret_json(self):
"""
- Ansible use it create inventory
+ Ansible use it create inventory, First using asset user,
+ otherwise using cluster admin user
Todo: May be move to ops implements it
"""
data = self.to_json()
- if self.cluster and self.cluster.admin_user:
+ admin_user = None
+ if self.admin_user:
+ admin_user = self.admin_user
+ elif self.cluster and self.cluster.admin_user:
+ admin_user = self.cluster.admin_user
+ if admin_user:
data.update({
- 'username': self.cluster.admin_user.username,
- 'password': self.cluster.admin_user.password,
- 'private_key': self.cluster.admin_user.private_key_file,
+ 'username': admin_user.username,
+ 'password': admin_user.password,
+ 'private_key': admin_user.private_key_file,
'become': {
- 'method': self.cluster.admin_user.become_method,
- 'user': self.cluster.admin_user.become_user,
- 'pass': self.cluster.admin_user.become_pass,
+ 'method': admin_user.become_method,
+ 'user': admin_user.become_user,
+ 'pass': admin_user.become_pass,
}
})
return data
diff --git a/apps/assets/models/cluster.py b/apps/assets/models/cluster.py
index dd30516a4..be90a1be6 100644
--- a/apps/assets/models/cluster.py
+++ b/apps/assets/models/cluster.py
@@ -2,8 +2,6 @@
# -*- coding: utf-8 -*-
#
-from __future__ import unicode_literals
-
import logging
import uuid
@@ -18,7 +16,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"))
+ admin_user = models.ForeignKey('assets.AdminUser', null=True, on_delete=models.SET_NULL, 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/user.py b/apps/assets/models/user.py
index fb8a98ede..e7df0f151 100644
--- a/apps/assets/models/user.py
+++ b/apps/assets/models/user.py
@@ -96,12 +96,16 @@ class AdminUser(models.Model):
def become_pass(self, password):
self._become_pass = signer.sign(password)
+ def get_related_assets(self):
+ assets = []
+ for cluster in self.cluster_set.all():
+ assets.extend(cluster.assets.all())
+ assets.extend(self.asset_set.all())
+ return list(set(assets))
+
@property
def assets_amount(self):
- amount = 0
- for cluster in self.cluster_set.all():
- amount += cluster.assets.all().count()
- return amount
+ return len(self.get_related_assets())
class Meta:
ordering = ['name']
@@ -209,9 +213,14 @@ class SystemUser(models.Model):
'private_key_file': self.private_key_file,
}
+ def get_clusters_assets(self):
+ from .asset import Asset
+ clusters = self.cluster.all()
+ return Asset.objects.filter(cluster__in=clusters)
+
@property
def assets_amount(self):
- return self.assets.count()
+ return len(self.get_clusters_assets())
def to_json(self):
return {
diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py
index cfd6d1700..a22de9ef9 100644
--- a/apps/assets/serializers.py
+++ b/apps/assets/serializers.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from django.core.cache import cache
from rest_framework import viewsets, serializers, generics
-from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
+from rest_framework_bulk.serializers import BulkListSerializer
+from common.mixins import BulkSerializerMixin
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser
from .tasks import SYSTEM_USER_CONN_CACHE_KEY_PREFIX, ADMIN_USER_CONN_CACHE_KEY_PREFIX
@@ -140,36 +141,18 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
- # system_users = SystemUserSerializer(many=True, read_only=True)
- # admin_user = AdminUserSerializer(many=False, read_only=True)
- hardware = serializers.SerializerMethodField()
- is_online = serializers.SerializerMethodField()
-
class Meta(object):
model = Asset
list_serializer_class = BulkListSerializer
fields = '__all__'
-
- @staticmethod
- def get_hardware(obj):
- if obj.cpu_count:
- return '{} Core {} {}'.format(obj.cpu_count*obj.cpu_cores, obj.memory, obj.disk_total)
- else:
- return ''
-
- @staticmethod
- def get_is_online(obj):
- hostname = obj.hostname
- if cache.get(hostname) == '1':
- return True
- elif cache.get(hostname) == '0':
- return False
- else:
- return 'Unknown'
+ validators = [] # If not set to [], partial bulk update will be error
def get_field_names(self, declared_fields, info):
- fields = super(AssetSerializer, self).get_field_names(declared_fields, info)
- fields.extend(['get_type_display', 'get_env_display'])
+ fields = super().get_field_names(declared_fields, info)
+ fields.extend([
+ 'get_type_display', 'get_env_display',
+ 'hardware_info', 'is_connective',
+ ])
return fields
diff --git a/apps/assets/signals.py b/apps/assets/signals.py
new file mode 100644
index 000000000..d8223eaba
--- /dev/null
+++ b/apps/assets/signals.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+#
+
+from django.dispatch import Signal, receiver
+
+from common.utils import get_logger
+
+logger = get_logger(__file__)
+on_asset_created = Signal(providing_args=['asset'])
+on_app_ready = Signal()
+
+
+@receiver(on_asset_created)
+def update_asset_info(sender, asset=None, **kwargs):
+ from .tasks import update_assets_hardware_info
+ logger.debug("Receive asset create signal, update asset hardware info")
+ update_assets_hardware_info.delay([asset])
+
+
+@receiver(on_asset_created)
+def test_admin_user_connective(sender, asset=None, **kwargs):
+ from .tasks import test_admin_user_connectability_manual
+ logger.debug("Receive asset create signal, test admin user connectability")
+ test_admin_user_connectability_manual.delay(asset)
+
+
+@receiver(on_app_ready)
+def test_admin_user_on_app_ready(sender, **kwargs):
+ from .tasks import test_admin_user_connectability_period
+ logger.debug("Receive app ready signal, test admin connectability")
+ test_admin_user_connectability_period.delay()
diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py
index 5ac899abd..46d85db5d 100644
--- a/apps/assets/tasks.py
+++ b/apps/assets/tasks.py
@@ -4,16 +4,14 @@ import json
from celery import shared_task
from django.core.cache import cache
-from assets.models import SystemUser, AdminUser
from common.utils import get_object_or_none, capacity_convert, sum_capacity, encrypt_password, get_logger
-from .models import Asset
+from .models import SystemUser, AdminUser, Asset
+from .const import ADMIN_USER_CONN_CACHE_KEY_PREFIX, SYSTEM_USER_CONN_CACHE_KEY_PREFIX
FORKS = 10
TIMEOUT = 60
logger = get_logger(__file__)
-ADMIN_USER_CONN_CACHE_KEY_PREFIX = "ADMIN_USER_CONN_"
-SYSTEM_USER_CONN_CACHE_KEY_PREFIX = 'SYSTEM_USER_CONN_'
@shared_task
@@ -75,6 +73,12 @@ def update_assets_hardware_info(assets):
if k.startswith('___'):
setattr(asset, k.strip('_'), v)
asset.save()
+
+ for hostname, task in summary['dark'].items():
+ logger.warn("Update {} hardware info error: {}".format(
+ hostname, task[name],
+ ))
+
return summary
@@ -96,8 +100,7 @@ def test_admin_user_connectability(admin_user):
:return:
"""
from ops.utils import run_adhoc
- assets = admin_user.assets.all()
- # assets = Asset.objects.filter(type__in=['Server', 'VM'])
+ assets = admin_user.get_related_assets()
hosts = [asset.hostname for asset in assets]
tasks = [
{
@@ -126,6 +129,7 @@ def test_admin_user_connectability_period():
cache.set(ADMIN_USER_CONN_CACHE_KEY_PREFIX + i, 0, 60*60*60)
+@shared_task
def test_admin_user_connectability_manual(asset):
from ops.utils import run_adhoc
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
@@ -140,8 +144,10 @@ def test_admin_user_connectability_manual(asset):
]
result = run_adhoc(hosts, tasks=tasks, pattern="all", run_as_admin=True)
if result.results_summary['dark']:
+ cache.set(ADMIN_USER_CONN_CACHE_KEY_PREFIX + asset.hostname, 0, 60*60*60)
return False
else:
+ cache.set(ADMIN_USER_CONN_CACHE_KEY_PREFIX + asset.hostname, 1, 60*60* 60)
return True
@@ -153,7 +159,7 @@ def test_system_user_connectability(system_user):
:return:
"""
from ops.utils import run_adhoc
- assets = system_user.assets.all()
+ assets = system_user.get_clusters_assets()
hosts = [asset.hostname for asset in assets]
tasks = [
{
@@ -171,7 +177,7 @@ def test_system_user_connectability(system_user):
def test_system_user_connectability_period():
for system_user in SystemUser.objects.all():
summary = test_system_user_connectability(system_user)
- cache.set(SYSTEM_USER_CONN_CACHE_KEY_PREFIX + system_user.name , summary, 60*60*60)
+ cache.set(SYSTEM_USER_CONN_CACHE_KEY_PREFIX + system_user.name, summary, 60*60*60)
def get_push_system_user_tasks(system_user):
@@ -207,19 +213,12 @@ def get_push_system_user_tasks(system_user):
)
}
}
-
]
return tasks
-PUSH_SYSTEM_USER_PERIOD_TASK_NAME = 'PUSH SYSTEM USER {} PERIOD...'
-PUSH_SYSTEM_USER_TASK_NAME = 'PUSH SYSTEM USER {} ASSETS'
-
-
-def get_push_system_user_task(system_user):
- from ops.utils import get_task_by_name
- task = get_task_by_name(PUSH_SYSTEM_USER_PERIOD_TASK_NAME.format(system_user.name))
- return task
+PUSH_SYSTEM_USER_PERIOD_TASK_NAME = 'PUSH SYSTEM USER [{}] PERIOD...'
+PUSH_SYSTEM_USER_TASK_NAME = 'PUSH SYSTEM USER [{}] ASSETS'
def push_system_user(system_user, assets, name):
@@ -246,7 +245,7 @@ def push_system_user(system_user, assets, name):
def push_system_user_period():
logger.debug("Push system user period")
for s in SystemUser.objects.filter(auto_push=True):
- assets = s.assets.all()
+ assets = s.get_clusters_assets()
name = PUSH_SYSTEM_USER_PERIOD_TASK_NAME.format(s.name)
push_system_user(s, assets, name)
diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html
index 37874ac3c..0db237b46 100644
--- a/apps/assets/templates/assets/admin_user_list.html
+++ b/apps/assets/templates/assets/admin_user_list.html
@@ -5,7 +5,7 @@
{% block help_message %}
- 管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等
+ 管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。可以设置主机级别管理用户,也设置集群级别管理用户,这样资产可以不用再单独设置
{% endblock %}
diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html
index 2860e61c9..ac0e4d744 100644
--- a/apps/assets/templates/assets/asset_create.html
+++ b/apps/assets/templates/assets/asset_create.html
@@ -14,20 +14,18 @@
{% trans 'Basic' %}
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
- {% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
+ {% bootstrap_field form.cluster layout="horizontal" %}
+ {% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.env layout="horizontal" %}
{% trans 'Auth' %}
- {% bootstrap_field form.username layout="horizontal" %}
- {% bootstrap_field form.password layout="horizontal" %}
- {% bootstrap_field form.private_key_file layout="horizontal" %}
+ {% bootstrap_field form.admin_user layout="horizontal" %}
- {% trans 'Cluster and group' %}
- {% bootstrap_field form.cluster layout="horizontal" %}
+ {% trans 'Group' %}
{% bootstrap_field form.groups layout="horizontal" %}
@@ -50,11 +48,11 @@
{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html
index 9fd887cb9..78b21023b 100644
--- a/apps/assets/templates/assets/asset_detail.html
+++ b/apps/assets/templates/assets/asset_detail.html
@@ -19,9 +19,6 @@
{% trans 'Asset detail' %}
-
- {% trans 'Auth' %}
-
{% if user.is_superuser %}
Update
@@ -198,14 +195,6 @@
-
- {% trans 'Reset auth' %}: |
-
-
-
-
- |
-
@@ -239,7 +228,7 @@
{{ asset_group.name }} |
-
+
|
{% endfor %}
@@ -274,7 +263,7 @@ function updateAssetGroups(groups) {
$('#add-asset2group tbody').append(
'' +
'' + group_name + ' | ' +
- ' | ' +
+ ' | ' +
'
'
)
});
@@ -288,44 +277,20 @@ function updateAssetGroups(groups) {
});
}
-function updateAssetSystemUser(system_users) {
- var the_url = "{% url 'api-assets:asset-update-system-users' pk=asset.id %}";
- var body = {
- system_users: Object.assign([], system_users)
- };
- var success = function(data) {
- $('.select2-selection__rendered').empty();
- $('#groups_selected').val('');
- $.map(jumpserver.system_user_selected, function(name, index) {
- $('#opt_' + index).remove();
-
- $('#add-asset2systemuser tbody').append(
- '' +
- '' + name + ' | ' +
- ' | ' +
- '
'
- )
- });
- // clear jumpserver.groups_selected
- jumpserver.system_user_selected = {};
- };
- APIUpdateAttr({
- url: the_url,
- body: JSON.stringify(body),
- success: success
- });
-}
-
function refreshAssetHardware() {
var the_url = "{% url 'api-assets:asset-refresh' pk=asset.id %}";
var success = function (data) {
location.reload();
};
+ var error = function (data) {
+ alert(data)
+ };
APIUpdateAttr({
url: the_url,
success: success,
+ error: error,
method: 'GET'
- })
+ });
}
@@ -337,13 +302,6 @@ $(document).ready(function () {
var data = evt.params.data;
delete jumpserver.groups_selected[data.id]
});
- $('.select2.system-user').select2().on('select2:select', function(evt) {
- var data = evt.params.data;
- jumpserver.system_user_selected[data.id] = data.text;
- }).on('select2:unselect', function(evt) {
- var data = evt.params.data;
- delete jumpserver.system_user_selected[data.id]
- })
}).on('click', '#is_active', function () {
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
var checked = $(this).prop('checked');
@@ -370,11 +328,11 @@ $(document).ready(function () {
return $(this).data('gid');
}).get();
$.map(jumpserver.groups_selected, function(value, index) {
- groups.push(parseInt(index));
+ groups.push(index);
$('#opt_' + index).remove();
});
updateAssetGroups(groups)
-}).on('click', '.btn_leave_group', function() {
+}).on('click', '.btn-leave-group', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_group');
@@ -388,34 +346,6 @@ $(document).ready(function () {
return $(this).data('gid');
}).get();
updateAssetGroups(groups)
-}).on('click', '.btn-system-user', function () {
- if (Object.keys(jumpserver.system_user_selected).length === 0) {
- return false;
- }
- var system_users = $('.bdg_group').map(function() {
- return $(this).data('sid');
- }).get();
- $.map(jumpserver.system_user_selected, function(value, index) {
- system_users.push(parseInt(index));
- $('#opt_' + index).remove();
- });
- updateAssetSystemUser(system_users)
-
-}).on('click', '.btn_leave_system', function () {
- var $this = $(this);
- var $tr = $this.closest('tr');
- var $badge = $tr.find('.bdg_group');
- var sid = $badge.data('sid');
- var name = $badge.html() || $badge.text();
- $('#groups_selected').append(
- ''
- );
- $tr.remove();
- var system_users = $('.bdg_group').map(function () {
- return $(this).data('sid');
- }).get();
- updateAssetSystemUser(system_users)
-
}).on('click', '.btn-delete-asset', function () {
var $this = $(this);
var name = "{{ asset.hostname }}";
@@ -424,7 +354,7 @@ $(document).ready(function () {
var redirect_url = "{% url 'assets:asset-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#btn_refresh_asset', function () {
- alert('请等待几秒, 等待完成');
+ alert('关闭alert, 等待完成, 自动刷新页面');
refreshAssetHardware()
}).on('click', '#btn_test_admin_user', function () {
$.ajax({
@@ -436,6 +366,5 @@ $(document).ready(function () {
})
})
-
{% endblock %}
diff --git a/apps/assets/templates/assets/asset_group_detail.html b/apps/assets/templates/assets/asset_group_detail.html
index b6639f40d..a21f5a744 100644
--- a/apps/assets/templates/assets/asset_group_detail.html
+++ b/apps/assets/templates/assets/asset_group_detail.html
@@ -67,16 +67,16 @@
@@ -93,28 +93,30 @@
{% endblock %}
{% block custom_foot_js %}
{% endblock %}
diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html
index 4dddf3f38..af5f46566 100644
--- a/apps/assets/templates/assets/asset_update.html
+++ b/apps/assets/templates/assets/asset_update.html
@@ -19,13 +19,18 @@
{% trans 'Basic' %}
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
- {% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
+ {% bootstrap_field form.cluster layout="horizontal" %}
+ {% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
+ {% bootstrap_field form.env layout="horizontal" %}
- {% trans 'Cluster and group' %}
- {% bootstrap_field form.cluster layout="horizontal" %}
+ {% trans 'Auth' %}
+ {% bootstrap_field form.admin_user layout="horizontal" %}
+
+
+ {% trans 'Group' %}
{% bootstrap_field form.groups layout="horizontal" %}
diff --git a/apps/assets/templates/assets/cluster_create_update.html b/apps/assets/templates/assets/cluster_create_update.html
index 14175a4d4..d9c262fb1 100644
--- a/apps/assets/templates/assets/cluster_create_update.html
+++ b/apps/assets/templates/assets/cluster_create_update.html
@@ -30,30 +30,31 @@
diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py
index 171a1d0ef..de0b234bd 100644
--- a/apps/assets/urls/api_urls.py
+++ b/apps/assets/urls/api_urls.py
@@ -1,7 +1,6 @@
# coding:utf-8
from django.conf.urls import url
from .. import api
-from rest_framework import routers
from rest_framework_bulk.routes import BulkRouter
app_name = 'assets'
diff --git a/apps/assets/utils.py b/apps/assets/utils.py
index f7efb50ff..e21a2120e 100644
--- a/apps/assets/utils.py
+++ b/apps/assets/utils.py
@@ -15,3 +15,5 @@ def get_assets_by_hostname_list(hostname_list):
def get_system_user_by_name(name):
system_user = get_object_or_none(SystemUser, name=name)
return system_user
+
+
diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py
index f72618f54..a774c127a 100644
--- a/apps/assets/views/asset.py
+++ b/apps/assets/views/asset.py
@@ -28,6 +28,7 @@ from .. import forms
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
from ..hands import AdminUserRequiredMixin
from ..tasks import update_assets_hardware_info
+from ..signals import on_asset_created
__all__ = [
@@ -73,11 +74,12 @@ class AssetCreateView(AdminUserRequiredMixin, CreateView):
success_url = reverse_lazy('assets:asset-list')
def form_valid(self, form):
- self.asset = asset = form.save()
+ asset = form.save()
asset.created_by = self.request.user.username or 'Admin'
asset.date_created = timezone.now()
asset.save()
- return super(AssetCreateView, self).form_valid(form)
+ on_asset_created.send(sender=self.__class__, asset=asset)
+ return super().form_valid(form)
def get_context_data(self, **kwargs):
context = {
@@ -85,11 +87,11 @@ class AssetCreateView(AdminUserRequiredMixin, CreateView):
'action': 'Create asset',
}
kwargs.update(context)
- return super(AssetCreateView, self).get_context_data(**kwargs)
+ return super().get_context_data(**kwargs)
def get_success_url(self):
- update_assets_hardware_info.delay([self.asset._to_secret_json()])
- return super(AssetCreateView, self).get_success_url()
+ # update_assets_hardware_info.delay([self.asset._to_secret_json()])
+ return super().get_success_url()
class AssetModalListView(AdminUserRequiredMixin, ListView):
diff --git a/apps/assets/views/group.py b/apps/assets/views/group.py
index 679e71283..414380522 100644
--- a/apps/assets/views/group.py
+++ b/apps/assets/views/group.py
@@ -66,20 +66,15 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
context_object_name = 'asset_group'
def get_context_data(self, **kwargs):
- assets_remain = Asset.objects.exclude(id__in=self.object.assets.all())
- system_users = SystemUser.objects.all()
- system_users_remain = SystemUser.objects.exclude(id__in=system_users)
+ assets_remain = Asset.objects.exclude(groups__in=[self.object])
context = {
'app': _('Assets'),
'action': _('Asset group detail'),
'assets_remain': assets_remain,
- 'assets': [asset for asset in Asset.objects.all()
- if asset not in assets_remain],
- 'system_users': system_users,
- 'system_users_remain': system_users_remain,
+ 'assets': self.object.assets.all(),
}
kwargs.update(context)
- return super(AssetGroupDetailView, self).get_context_data(**kwargs)
+ return super().get_context_data(**kwargs)
class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
diff --git a/apps/common/celery.py b/apps/common/celery.py
index 98270a12c..ebf1a549e 100644
--- a/apps/common/celery.py
+++ b/apps/common/celery.py
@@ -17,8 +17,7 @@ app = Celery('jumpserver')
# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
-app.autodiscover_tasks(lambda: [app_config.split('.')[0]
- for app_config in settings.INSTALLED_APPS])
+app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])
app.conf.update(
CELERYBEAT_SCHEDULE={
diff --git a/apps/common/mixins.py b/apps/common/mixins.py
index f32bbd68b..764d8e50a 100644
--- a/apps/common/mixins.py
+++ b/apps/common/mixins.py
@@ -1,11 +1,13 @@
# coding: utf-8
+import inspect
from django.db import models
from django.http import JsonResponse
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
+
class NoDeleteQuerySet(models.query.QuerySet):
def delete(self):
@@ -58,3 +60,31 @@ class IDInFilterMixin(object):
if isinstance(ids, list):
queryset = queryset.filter(id__in=ids)
return queryset
+
+
+class BulkSerializerMixin(object):
+ """
+ Become rest_framework_bulk not support uuid as a primary key
+ so rewrite it. https://github.com/miki725/django-rest-framework-bulk/issues/66
+ """
+ def to_internal_value(self, data):
+ from rest_framework_bulk import BulkListSerializer
+ ret = super(BulkSerializerMixin, self).to_internal_value(data)
+
+ id_attr = getattr(self.Meta, 'update_lookup_field', 'id')
+ request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
+ # add update_lookup_field field back to validated data
+ # since super by default strips out read-only fields
+ # hence id will no longer be present in validated_data
+ if all((isinstance(self.root, BulkListSerializer),
+ id_attr,
+ request_method in ('PUT', 'PATCH'))):
+ id_field = self.fields[id_attr]
+ if data.get("id"):
+ id_value = id_field.to_internal_value(data.get("id"))
+ else:
+ id_value = id_field.to_internal_value(data.get("pk"))
+ ret[id_attr] = id_value
+
+ return ret
+
diff --git a/apps/common/tasks.py b/apps/common/tasks.py
index 11d0d711d..4e6e33fc4 100644
--- a/apps/common/tasks.py
+++ b/apps/common/tasks.py
@@ -1,9 +1,10 @@
-from __future__ import absolute_import
-
-# from celery import shared_task
from django.core.mail import send_mail
from django.conf import settings
from common import celery_app as app
+from .utils import get_logger
+
+
+logger = get_logger(__file__)
@app.task
@@ -26,4 +27,7 @@ def send_mail_async(*args, **kwargs):
args.insert(2, settings.EMAIL_HOST_USER)
args = tuple(args)
- send_mail(*args, **kwargs)
+ try:
+ send_mail(*args, **kwargs)
+ except Exception as e:
+ logger.error("Sending mail error: {}".format(e))
diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py
index 596563981..d464adc92 100644
--- a/apps/jumpserver/settings.py
+++ b/apps/jumpserver/settings.py
@@ -331,20 +331,16 @@ BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
}
+CELERY_TASK_SERIALIZER = 'pickle'
+CELERY_RESULT_SERIALIZER = 'pickle'
CELERY_RESULT_BACKEND = BROKER_URL
-
+CELERY_ACCEPT_CONTENT = ['json', 'pickle']
+CELERY_TASK_RESULT_EXPIRES = 3600
+CELERYD_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
+CELERYD_TASK_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
+CELERY_TIMEZONE = TIME_ZONE
# TERMINAL_HEATBEAT_INTERVAL = CONFIG.TERMINAL_HEATBEAT_INTERVAL or 30
-# crontab job
-# CELERYBEAT_SCHEDULE = {
-# Check applications is alive every 10m
-# 'check_terminal_alive': {
-# 'task': 'applications.tasks.check_terminal_alive',
-# 'schedule': timedelta(seconds=TERMINAL_HEATBEAT_INTERVAL),
-# 'args': (),
-# },
-# }
-
# Cache use redis
CACHES = {
diff --git a/apps/ops/utils.py b/apps/ops/utils.py
index 1bace8351..61feca1e4 100644
--- a/apps/ops/utils.py
+++ b/apps/ops/utils.py
@@ -21,6 +21,7 @@ def is_uuid(s):
return False
+
def record_adhoc(func):
def _deco(adhoc, **options):
record = AdHocRunHistory(adhoc=adhoc)
diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js
index 360c4ad3e..e8840e9a1 100644
--- a/apps/static/js/jumpserver.js
+++ b/apps/static/js/jumpserver.js
@@ -156,7 +156,7 @@ function activeNav() {
function APIUpdateAttr(props) {
// props = {url: .., body: , success: , error: , method: ,}
props = props || {};
- var success_message = props.success_message || 'Update Successfully!';
+ var success_message = props.success_message || 'Update successfully!';
var fail_message = props.fail_message || 'Error occurred while updating.';
$.ajax({
url: props.url,
@@ -169,11 +169,10 @@ function APIUpdateAttr(props) {
if (typeof props.success === 'function') {
return props.success(data);
}
-
- }).fail(function(jqXHR, textStatue, errorThrown) {
+ }).fail(function(jqXHR, textStatus, errorThrown) {
toastr.error(fail_message);
if (typeof props.error === 'function') {
- return props.error(errorThrown);
+ return props.error(jqXHR.responseText);
}
});
// return true;
@@ -265,7 +264,7 @@ jumpserver.initDataTable = function (options) {
language: {
url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json"
},
- order: options.order || [[ 1, 'asc' ]],
+ order: options.order || [],
select: options.select || 'multi',
buttons: [],
columnDefs: columnDefs,
diff --git a/apps/templates/_message.html b/apps/templates/_message.html
index 8eb061661..6f0811ae2 100644
--- a/apps/templates/_message.html
+++ b/apps/templates/_message.html
@@ -1,6 +1,6 @@
{% if messages %}
{% for message in messages %}
-
+
{{ message|safe }}
{% endfor %}
diff --git a/apps/terminal/apps.py b/apps/terminal/apps.py
index aae1c57b5..53e0c69ae 100644
--- a/apps/terminal/apps.py
+++ b/apps/terminal/apps.py
@@ -5,3 +5,8 @@ from django.apps import AppConfig
class ApplicationsConfig(AppConfig):
name = 'terminal'
+
+ def ready(self):
+ from .signals import on_app_ready
+ on_app_ready.send(self.__class__)
+ super().ready()
diff --git a/apps/terminal/const.py b/apps/terminal/const.py
new file mode 100644
index 000000000..2d74e00c1
--- /dev/null
+++ b/apps/terminal/const.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+#
+
+ASSETS_CACHE_KEY = "terminal__session__assets"
+USERS_CACHE_KEY = "terminal__session__users"
+SYSTEM_USER_CACHE_KEY = "terminal__session__system_users"
+
diff --git a/apps/terminal/signals.py b/apps/terminal/signals.py
new file mode 100644
index 000000000..832a51cd8
--- /dev/null
+++ b/apps/terminal/signals.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+#
+import threading
+import time
+
+from django.core.cache import cache
+from django.dispatch import Signal, receiver
+from django.db.utils import ProgrammingError, OperationalError
+
+from common.utils import get_logger
+from .const import ASSETS_CACHE_KEY, USERS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
+
+RUNNING = False
+logger = get_logger(__file__)
+
+on_app_ready = Signal()
+
+
+@receiver(on_app_ready)
+def on_app_ready_set_cache(sender, **kwargs):
+ from .utils import get_session_asset_list, get_session_user_list, \
+ get_session_system_user_list
+ global RUNNING
+
+ def set_cache():
+ while True:
+ try:
+ assets = get_session_asset_list()
+ users = get_session_user_list()
+ system_users = get_session_system_user_list()
+
+ cache.set(ASSETS_CACHE_KEY, assets)
+ cache.set(USERS_CACHE_KEY, users)
+ cache.set(SYSTEM_USER_CACHE_KEY, system_users)
+ except (ProgrammingError, OperationalError):
+ pass
+ finally:
+ time.sleep(10)
+ if RUNNING:
+ return
+ threads = []
+ thread = threading.Thread(target=set_cache)
+ threads.append(thread)
+
+ logger.debug("Receive app ready signal: set cache task start")
+ for t in threads:
+ t.daemon = True
+ t.start()
+ RUNNING = True
diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py
index c286b1db7..87a90bfcb 100644
--- a/apps/terminal/tasks.py
+++ b/apps/terminal/tasks.py
@@ -1,19 +1,15 @@
# -*- coding: utf-8 -*-
#
-import threading
import time
from celery import shared_task
from django.core.cache import cache
-from django.db.utils import ProgrammingError
+from django.db.utils import ProgrammingError, OperationalError
from .models import Session
-ASSETS_CACHE_KEY = "terminal__session__assets"
-USERS_CACHE_KEY = "terminal__session__users"
-SYSTEM_USER_CACHE_KEY = "terminal__session__system_users"
CACHE_REFRESH_INTERVAL = 10
RUNNING = False
@@ -24,46 +20,7 @@ def clean_terminal_history():
pass
-def get_session_asset_list():
- return set(list(Session.objects.values_list('asset', flat=True)))
-def get_session_user_list():
- return set(list(Session.objects.values_list('user', flat=True)))
-def get_session_system_user_list():
- return set(list(Session.objects.values_list('system_user', flat=True)))
-
-
-def set_cache():
- while True:
- try:
- assets = get_session_asset_list()
- users = get_session_user_list()
- system_users = get_session_system_user_list()
-
- cache.set(ASSETS_CACHE_KEY, assets)
- cache.set(USERS_CACHE_KEY, users)
- cache.set(SYSTEM_USER_CACHE_KEY, system_users)
- except ProgrammingError:
- pass
- finally:
- time.sleep(10)
-
-
-def main():
- global RUNNING
- if RUNNING:
- return
- threads = []
- thread = threading.Thread(target=set_cache)
- threads.append(thread)
-
- for t in threads:
- t.daemon = True
- t.start()
- RUNNING = True
-
-# Todo: 不能migrations了
-main()
diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py
index c24571c77..e81ea0e3b 100644
--- a/apps/terminal/utils.py
+++ b/apps/terminal/utils.py
@@ -1,8 +1,23 @@
# -*- coding: utf-8 -*-
#
from django.core.cache import cache
+from .models import Session
+
+from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
+
+
+def get_session_asset_list():
+ return set(list(Session.objects.values_list('asset', flat=True)))
+
+
+def get_session_user_list():
+ return set(list(Session.objects.values_list('user', flat=True)))
+
+
+def get_session_system_user_list():
+ return set(list(Session.objects.values_list('system_user', flat=True)))
+
-from .tasks import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
def get_user_list_from_cache():
diff --git a/apps/users/api.py b/apps/users/api.py
index 7ec00b836..160c19028 100644
--- a/apps/users/api.py
+++ b/apps/users/api.py
@@ -4,11 +4,11 @@ from rest_framework import generics
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
-from rest_framework import viewsets
from rest_framework_bulk import BulkModelViewSet
-from django_filters.rest_framework import DjangoFilterBackend
-from . import serializers
+from .serializers import UserSerializer, UserGroupSerializer, \
+ UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
+ UserUpdateGroupSerializer
from .tasks import write_login_log_async
from .models import User, UserGroup
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly
@@ -20,49 +20,23 @@ from common.utils import get_logger
logger = get_logger(__name__)
-# class UserListView(generics.ListAPIView):
-# queryset = User.objects.all()
-# serializer_class = serializers.UserSerializer
-# filter_fields = ('username', 'email', 'name', 'id')
-
-
-class UserViewSet(viewsets.ModelViewSet):
- # class UserViewSet(IDInFilterMixin, BulkModelViewSet):
- """
- retrieve:
- Return a user instance .
-
- list:
- Return all users except app user, ordered by most recently joined.
-
- create:
- Create a new user.
-
- delete:
- Remove an existing user.
-
- partial_update:
- Update one or more fields on an existing user.
-
- update:
- Update a user.
- """
+class UserViewSet(BulkModelViewSet):
queryset = User.objects.all()
# queryset = User.objects.all().exclude(role="App").order_by("date_joined")
- serializer_class = serializers.UserSerializer
+ serializer_class = UserSerializer
permission_classes = (IsSuperUser,)
filter_fields = ('username', 'email', 'name', 'id')
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
- serializer_class = serializers.UserUpdateGroupSerializer
+ serializer_class = UserUpdateGroupSerializer
permission_classes = (IsSuperUser,)
class UserResetPasswordApi(generics.UpdateAPIView):
queryset = User.objects.all()
- serializer_class = serializers.UserSerializer
+ serializer_class = UserSerializer
def perform_update(self, serializer):
# Note: we are not updating the user object here.
@@ -77,7 +51,7 @@ class UserResetPasswordApi(generics.UpdateAPIView):
class UserResetPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
- serializer_class = serializers.UserSerializer
+ serializer_class = UserSerializer
def perform_update(self, serializer):
from .utils import send_reset_ssh_key_mail
@@ -89,7 +63,7 @@ class UserResetPKApi(generics.UpdateAPIView):
class UserUpdatePKApi(generics.UpdateAPIView):
queryset = User.objects.all()
- serializer_class = serializers.UserPKUpdateSerializer
+ serializer_class = UserPKUpdateSerializer
permission_classes = (IsCurrentUserOrReadOnly,)
def perform_update(self, serializer):
@@ -100,12 +74,12 @@ class UserUpdatePKApi(generics.UpdateAPIView):
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all()
- serializer_class = serializers.UserGroupSerializer
+ serializer_class = UserGroupSerializer
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
queryset = UserGroup.objects.all()
- serializer_class = serializers.UserGroupUpdateMemeberSerializer
+ serializer_class = UserGroupUpdateMemeberSerializer
permission_classes = (IsSuperUser,)
diff --git a/apps/users/models/user.py b/apps/users/models/user.py
index 47d701006..1c60ed710 100644
--- a/apps/users/models/user.py
+++ b/apps/users/models/user.py
@@ -189,7 +189,7 @@ class User(AbstractUser):
return 'https://www.gravatar.com/avatar/c6812ab450230979465d7bf288eadce2a?s=120&d=identicon'
def generate_reset_token(self):
- return signer.sign_t({'reset': self.id, 'email': self.email}, expires_in=3600)
+ return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600)
def to_json(self):
return OrderedDict({
diff --git a/apps/users/serializers.py b/apps/users/serializers.py
index fd52d99c7..f8e1d6044 100644
--- a/apps/users/serializers.py
+++ b/apps/users/serializers.py
@@ -3,9 +3,10 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
-from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
+from rest_framework_bulk import BulkListSerializer
from common.utils import signer, validate_ssh_public_key
+from common.mixins import BulkSerializerMixin
from .models import User, UserGroup
diff --git a/apps/users/signals.py b/apps/users/signals.py
new file mode 100644
index 000000000..15bd56c55
--- /dev/null
+++ b/apps/users/signals.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+#
+
+from django.dispatch import Signal, receiver
+
+from common.utils import get_logger
+
+logger = get_logger(__file__)
+on_user_created = Signal(providing_args=['user'])
+
+
+@receiver(on_user_created)
+def send_user_add_mail_to_user(sender, user=None, **kwargs):
+ from .utils import send_user_created_mail
+ logger.debug("Receive asset create signal, update asset hardware info")
+ send_user_created_mail(user)
+
diff --git a/apps/users/urls/views_urls.py b/apps/users/urls/views_urls.py
index b8196434f..19081026a 100644
--- a/apps/users/urls/views_urls.py
+++ b/apps/users/urls/views_urls.py
@@ -23,17 +23,18 @@ urlpatterns = [
# User view
url(r'^user$', views.UserListView.as_view(), name='user-list'),
- url(r'^user/(?P
[0-9a-zA-Z\-]+)$', views.UserDetailView.as_view(), name='user-detail'),
- url(r'^user/(?P[0-9a-zA-Z\-]+)/asset-permission$', views.UserAssetPermissionView.as_view(), name='user-asset-permission'),
- url(r'^user/(?P[0-9a-zA-Z\-]+)/asset-permission/create$', views.UserAssetPermissionCreateView.as_view(), name='user-asset-permission-create'),
- url(r'^user/(?P[0-9a-zA-Z\-]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
- url(r'^user/(?P[0-9a-zA-Z\-]+)/login-history', views.UserDetailView.as_view(), name='user-login-history'),
url(r'^user/export/', views.UserExportView.as_view(), name='user-export'),
url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
url(r'^user/import/$', views.UserBulkImportView.as_view(), name='user-import'),
url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'),
url(r'^user/(?P[0-9a-zA-Z\-]{36})/update$', views.UserUpdateView.as_view(), name='user-update'),
url(r'^user/update$', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
+ url(r'^user/(?P[0-9a-zA-Z\-]+)$', views.UserDetailView.as_view(), name='user-detail'),
+ url(r'^user/(?P[0-9a-zA-Z\-]+)/asset-permission$', views.UserAssetPermissionView.as_view(), name='user-asset-permission'),
+ url(r'^user/(?P[0-9a-zA-Z\-]+)/asset-permission/create$', views.UserAssetPermissionCreateView.as_view(), name='user-asset-permission-create'),
+ url(r'^user/(?P[0-9a-zA-Z\-]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
+ url(r'^user/(?P[0-9a-zA-Z\-]+)/login-history', views.UserDetailView.as_view(), name='user-login-history'),
+
# User group view
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
diff --git a/apps/users/utils.py b/apps/users/utils.py
index f5abf658f..02ab846f5 100644
--- a/apps/users/utils.py
+++ b/apps/users/utils.py
@@ -31,7 +31,7 @@ class AdminUserRequiredMixin(UserPassesTestMixin):
return True
-def user_add_success_next(user):
+def send_user_created_mail(user):
subject = _('Create account successfully')
recipient_list = [user.email]
message = _("""
@@ -58,6 +58,8 @@ def user_add_success_next(user):
'email': user.email,
'login_url': reverse('users:login', external=True),
}
+ if settings.DEBUG:
+ print(message)
send_mail_async.delay(subject, message, recipient_list, html_message=message)
diff --git a/apps/users/views/user.py b/apps/users/views/user.py
index e853c230e..b65c0c223 100644
--- a/apps/users/views/user.py
+++ b/apps/users/views/user.py
@@ -29,7 +29,8 @@ from django.contrib.auth import logout as auth_logout
from .. import forms
from ..models import User, UserGroup
-from ..utils import AdminUserRequiredMixin, user_add_success_next
+from ..utils import AdminUserRequiredMixin, send_user_created_mail
+from ..signals import on_user_created
from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none
from perms.models import AssetPermission
@@ -74,7 +75,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
user = form.save(commit=False)
user.created_by = self.request.user.username or 'System'
user.save()
- user_add_success_next(user)
+ on_user_created.send(self.__class__, user=user)
return super(UserCreateView, self).form_valid(form)
def get_success_message(self, cleaned_data):
@@ -281,7 +282,7 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
user = User.objects.create(**user_dict)
user.groups.set(groups)
created.append(user_dict['username'])
- user_add_success_next(user)
+ on_user_created.send(self.__class__, user=user)
except Exception as e:
failed.append('%s: %s' % (user_dict['username'], str(e)))
else: