提交 99b4c66b 编写于 作者: baltery's avatar baltery

[Feature] 添加signals 解耦代码

上级 cbc00069
......@@ -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):
......
......@@ -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()
# -*- coding: utf-8 -*-
#
ADMIN_USER_CONN_CACHE_KEY_PREFIX = "ADMIN_USER_CONN_"
SYSTEM_USER_CONN_CACHE_KEY_PREFIX = 'SYSTEM_USER_CONN_'
......@@ -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')
}
......
......@@ -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
......
......@@ -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'))
......
......@@ -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 {
......
# -*- 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
......
# -*- 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()
......@@ -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)
......
......@@ -5,7 +5,7 @@
{% block help_message %}
<div class="alert alert-info help-message">
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等
管理用户是 服务器上已存在的特权用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。可以设置主机级别管理用户,也设置集群级别管理用户,这样资产可以不用再单独设置
</div>
{% endblock %}
......
......@@ -14,20 +14,18 @@
<h3>{% trans 'Basic' %}</h3>
{% 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" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% 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" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Cluster and group' %}</h3>
{% bootstrap_field form.cluster layout="horizontal" %}
<h3>{% trans 'Group' %}</h3>
{% bootstrap_field form.groups layout="horizontal" %}
<div class="hr-line-dashed"></div>
......@@ -50,11 +48,11 @@
<script>
$(document).ready(function () {
$('.select2').select2();
$("#id_tags").select2({
tags: true,
maximumSelectionLength: 8 //最多能够选择的个数
//closeOnSelect: false
});
{# $("#id_tags").select2({#}
{# tags: true,#}
{# maximumSelectionLength: 8 //最多能够选择的个数#}
{# //closeOnSelect: false#}
{# });#}
})
</script>
{% endblock %}
\ No newline at end of file
......@@ -19,9 +19,6 @@
<li class="active">
<a href="{% url 'assets:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset detail' %} </a>
</li>
<li>
<a href="{% url 'assets:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Auth' %} </a>
</li>
{% if user.is_superuser %}
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>Update</a>
......@@ -198,14 +195,6 @@
</span>
</td>
</tr>
<tr>
<td>{% trans 'Reset auth' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn_test_admin_user" style="width: 54px;">{% trans 'Reset' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
......@@ -239,7 +228,7 @@
<tr>
<td ><b class="bdg_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger pull-right btn-xs btn-leave-group" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
......@@ -274,7 +263,7 @@ function updateAssetGroups(groups) {
$('#add-asset2group tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_group" type="button"><i class="fa fa-minus"></i></button></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-group" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
......@@ -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(
'<tr>' +
'<td><b class="bdg_group" data-sid="' + index + '">' + name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// 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(
'<option value="' + sid + '" id="opt_' + sid + '">' + name + '</option>'
);
$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 () {
})
})
</script>
{% endblock %}
......@@ -67,16 +67,16 @@
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select assets' %}" class="select2 system-user-select" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users %}
<option value="{{ system_user.id }}"> {{ system_user.name }} </option>
<select data-placeholder="{% trans 'Select assets' %}" class="select2 asset-select" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}"> {{ asset.hostname }} </option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm btn-push-system-user">{% trans 'Push' %}</button>
<button type="button" class="btn btn-primary btn-sm btn-add-asset">{% trans 'Add' %}</button>
</td>
</tr>
</form>
......@@ -93,28 +93,30 @@
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.assets_selected = {};
function updateGroupAssets(assets) {
jumpserver.assets_selected = {};
function updateGroupAssets(assets) {
var the_url = "{}";
var body = {
assets: Object.assign([], assets)
assets: Object.assign([], assets)
};
var $data_table = $("#asset_list_table").DataTable();
var success = function(data) {
$('.select2-selection__rendered').empty();
$.map(jumpserver.assets_selected, function(asset_ip, index) {
$('#opt_' + index).remove();
$data_table.ajax.reload();
});
jumpserver.groups_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PUT',
success: success
$('.select2-selection__rendered').empty();
$.map(jumpserver.assets_selected, function(asset_ip, index) {
$('#opt_' + index).remove();
$data_table.ajax.reload();
});
}
jumpserver.groups_selected = {};
};
console.log(body);
//APIUpdateAttr({
// url: the_url,
// body: JSON.stringify(body),
// method: 'PUT',
// success: success
//});
}
function leaveGroup(obj, name, url, data) {
function doDelete() {
......@@ -203,20 +205,21 @@ $(document).ready(function () {
$('.select2').select2();
$('.select2.asset-select').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
console.log(jumpserver.assets_selected)
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id]
});
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
console.log(jumpserver.assets_selected)
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id]
});
initTable();
})
.on('click', ".btn-asset-group-add-asset", function () {
.on('click', ".btn-add-asset", function () {
if (Object.keys(jumpserver.assets_selected).length === 0) {
return false;
}
......@@ -225,16 +228,8 @@ $(document).ready(function () {
})
.on('click', '.btn-push-system-user', function () {
var data = $('.system-user-select').select2();
var system_id = data.val()[0];
if (!system_id) {
return false
}
pushSystemUser(system_id)
})
.on('click', '.btn_asset_delete', function () {
.on('click', '.btn-leave-group', function () {
var $this = $(this);
var the_url = "{% url 'api-assets:asset-groups-update' pk=asset_group.id %}";
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
......
......@@ -33,8 +33,8 @@
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Env' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Valid' %}</th>
<th class="text-center">{% trans 'Alive' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Connective' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
......@@ -47,7 +47,7 @@
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
......@@ -114,8 +114,8 @@ $(document).ready(function(){
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active" }, {data: "is_online"}, {data: "id" }],
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"},
{data: "is_active" }, {data: "is_connective"}, {data: "id" }],
op_html: $('#actions').html()
};
var table = jumpserver.initDataTable(options);
......@@ -180,24 +180,28 @@ $(document).ready(function(){
$data_table.rows({selected: true}).every(function(){
id_list.push(this.data().id);
});
if (id_list.length == 0) {
if (id_list.length === 0) {
return false;
}
var the_url = "{% url 'api-assets:asset-list' %}";
function doDeactive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = false;
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": false};
data.push(obj);
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doActive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = true;
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": true};
data.push(obj);
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
......@@ -220,8 +224,13 @@ $(document).ready(function(){
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
var url_delete = the_url + '?id__in=' + JSON.stringify(id_list);
APIUpdateAttr({
url: url_delete,
method: 'DELETE',
success: success,
error: fail
});
$data_table.ajax.reload();
jumpserver.checked = false;
});
......@@ -248,65 +257,6 @@ $(document).ready(function(){
break;
}
});
{##}
{#.on('click', '#btn_asset_bulk_update', function () {#}
{# var json_data = $("#fm_asset_bulk_update").serializeObject();#}
{# var body = {};#}
{# body.enable_otp = (json_data.enable_otp === 'on')? true: false;#}
{# if (json_data.type != '') {#}
{# body.type = json_data.type;#}
{# }#}
{# if (json_data.groups != undefined) {#}
{# body.groups = json_data.groups;#}
{# }#}
{# if (typeof body.groups === 'string') {#}
{# body.groups = [parseInt(body.groups)]#}
{# } else if(typeof body.groups === 'array') {#}
{# var new_groups = body.groups.map(Number);#}
{# body.groups = new_groups;#}
{# }#}
{##}
{# if (json_data.system_users != undefined) {#}
{# body.system_users = json_data.system_users;#}
{# }#}
{# if (typeof body.system_users === 'string') {#}
{# body.system_users = [parseInt(body.system_users)]#}
{# } else if(typeof body.system_users === 'array') {#}
{# var new_users = body.system_users.map(Number);#}
{# body.system_users = new_users;#}
{# }#}
{##}
{# if (json_data.tags != undefined) {#}
{# body.tags = json_data.tags;#}
{# }#}
{# if (typeof body.tags == 'string') {#}
{# body.tags = [parseInt(body.tags)];#}
{# } else if (typeof body.tags === 'array') {#}
{# var new_tags = body.tags.map(Number);#}
{# body.tags = new_tags;#}
{# }#}
{##}
{# var $data_table = $('#asset_list_table').DataTable();#}
{# var post_list = [];#}
{# $data_table.rows({selected: true}).every(function(){#}
{# var content = Object.assign({id: this.data().id}, body);#}
{# post_list.push(content);#}
{# });#}
{# if (post_list === []) {#}
{# return false#}
{# }#}
{# var the_url = "{% url 'api-assets:asset-list' %}";#}
{# var success = function() {#}
{# var msg = "{% trans 'The selected assets has been updated successfully.' %}";#}
{# swal("{% trans 'Asset Updated' %}", msg, "success");#}
{# $('#asset_list_table').DataTable().ajax.reload();#}
{# jumpserver.checked = false;#}
{# };#}
{# console.log(JSON.stringify(post_list));#}
{# console.log(the_url);#}
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
{# $('#asset_bulk_update_modal').modal('hide');#}
{#})#}
</script>
{% endblock %}
......@@ -19,13 +19,18 @@
<h3>{% trans 'Basic' %}</h3>
{% 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" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Cluster and group' %}</h3>
{% bootstrap_field form.cluster layout="horizontal" %}
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Group' %}</h3>
{% bootstrap_field form.groups layout="horizontal" %}
<div class="hr-line-dashed"></div>
......
......@@ -30,30 +30,31 @@
<div class="panel blank-panel">
<div class="panel-body">
<div class="tab-content">
<div id="tab-1" class="ibox float-e-margins tab-pane active"></div>
<form id="ClusterForm" method="post" class="form-horizontal">
{% csrf_token %}
<h3 class="widget-head-color-box">基本信息</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.admin_user layout="horizontal" %}
{% bootstrap_field form.address layout="horizontal" %}
{% bootstrap_field form.contact layout="horizontal" %}
{% bootstrap_field form.phone layout="horizontal" %}
<form id="ClusterForm" method="post" class="form-horizontal">
{% csrf_token %}
<h3 class="widget-head-color-box">{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.address layout="horizontal" %}
{% bootstrap_field form.contact layout="horizontal" %}
{% bootstrap_field form.phone layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3 class="widget-head-color-box">IP段</h3>
{% bootstrap_field form.operator layout="horizontal" %}
{% bootstrap_field form.intranet layout="horizontal" %}
{% bootstrap_field form.extranet layout="horizontal" %}
<h3 class="widget-head-color-box">{% trans 'Settings' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
<div class="hr-line-dashed"></div>
<h3 class="widget-head-color-box">{% trans 'Other' %}</h3>
{% bootstrap_field form.operator layout="horizontal" %}
{% bootstrap_field form.intranet layout="horizontal" %}
{% bootstrap_field form.extranet layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</form>
</div>
</form>
</div>
</div>
</div>
......
# 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'
......
......@@ -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
......@@ -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):
......
......@@ -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):
......
......@@ -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={
......
# 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
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))
......@@ -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 = {
......
......@@ -21,6 +21,7 @@ def is_uuid(s):
return False
def record_adhoc(func):
def _deco(adhoc, **options):
record = AdHocRunHistory(adhoc=adhoc)
......
......@@ -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,
......
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}" style="margin: 20px auto 0px">
<div class="alert alert-{{ message.tags }} help-message" >
{{ message|safe }}
</div>
{% endfor %}
......
......@@ -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()
# -*- coding: utf-8 -*-
#
ASSETS_CACHE_KEY = "terminal__session__assets"
USERS_CACHE_KEY = "terminal__session__users"
SYSTEM_USER_CACHE_KEY = "terminal__session__system_users"
# -*- 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
# -*- 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()
# -*- 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():
......
......@@ -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,)
......
......@@ -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({
......
......@@ -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
......
# -*- 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)
......@@ -23,17 +23,18 @@ urlpatterns = [
# User view
url(r'^user$', views.UserListView.as_view(), name='user-list'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]+)$', views.UserDetailView.as_view(), name='user-detail'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission$', views.UserAssetPermissionView.as_view(), name='user-asset-permission'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission/create$', views.UserAssetPermissionCreateView.as_view(), name='user-asset-permission-create'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[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<pk>[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<pk>[0-9a-zA-Z\-]+)$', views.UserDetailView.as_view(), name='user-detail'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission$', views.UserAssetPermissionView.as_view(), name='user-asset-permission'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]+)/asset-permission/create$', views.UserAssetPermissionCreateView.as_view(), name='user-asset-permission-create'),
url(r'^user/(?P<pk>[0-9a-zA-Z\-]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[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'),
......
......@@ -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)
......
......@@ -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:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册