Commit 046c7e21 authored by yumaojun03's avatar yumaojun03

Merge remote-tracking branch 'origin/master' into ops_dev

# Conflicts:
#	requirements.txt
parents 47d87e38 87eed2e5
...@@ -6,17 +6,18 @@ from rest_framework.views import APIView ...@@ -6,17 +6,18 @@ from rest_framework.views import APIView
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from common.mixins import BulkDeleteApiMixin from common.mixins import IDInFilterMixin
from common.utils import get_object_or_none, signer from common.utils import get_object_or_none, signer
from .hands import IsSuperUserOrTerminalUser, IsSuperUser from .hands import IsSuperUserOrTerminalUser, IsSuperUser
from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser
from . import serializers from . import serializers
class AssetViewSet(viewsets.ModelViewSet): class AssetViewSet(IDInFilterMixin, viewsets.ModelViewSet):
"""API endpoint that allows Asset to be viewed or edited.""" """API endpoint that allows Asset to be viewed or edited."""
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
filter_fields = ('id', 'ip', 'hostname')
def get_queryset(self): def get_queryset(self):
queryset = super(AssetViewSet, self).get_queryset() queryset = super(AssetViewSet, self).get_queryset()
...@@ -27,7 +28,6 @@ class AssetViewSet(viewsets.ModelViewSet): ...@@ -27,7 +28,6 @@ class AssetViewSet(viewsets.ModelViewSet):
if asset_group_id: if asset_group_id:
queryset = queryset.filter(groups__id=asset_group_id) queryset = queryset.filter(groups__id=asset_group_id)
return queryset return queryset
...@@ -38,6 +38,10 @@ class AssetGroupViewSet(viewsets.ModelViewSet): ...@@ -38,6 +38,10 @@ class AssetGroupViewSet(viewsets.ModelViewSet):
queryset = AssetGroup.objects.all() queryset = AssetGroup.objects.all()
serializer_class = serializers.AssetGroupSerializer serializer_class = serializers.AssetGroupSerializer
class AssetUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = Asset.objects.all()
serializer_class = serializers.AssetUpdateGroupSerializer
permission_classes = (IsSuperUser,)
class IDCViewSet(viewsets.ModelViewSet): class IDCViewSet(viewsets.ModelViewSet):
"""API endpoint that allows IDC to be viewed or edited.""" """API endpoint that allows IDC to be viewed or edited."""
...@@ -45,18 +49,21 @@ class IDCViewSet(viewsets.ModelViewSet): ...@@ -45,18 +49,21 @@ class IDCViewSet(viewsets.ModelViewSet):
serializer_class = serializers.IDCSerializer serializer_class = serializers.IDCSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
class AdminUserViewSet(viewsets.ModelViewSet): class AdminUserViewSet(viewsets.ModelViewSet):
queryset = AdminUser.objects.all() queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer serializer_class = serializers.AdminUserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
class SystemUserViewSet(viewsets.ModelViewSet): class SystemUserViewSet(viewsets.ModelViewSet):
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer serializer_class = serializers.SystemUserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
class SystemUserUpdateApi(generics.RetrieveUpdateAPIView):
queryset = Asset.objects.all()
serializer_class = serializers.AssetUpdateSystemUserSerializer
permission_classes = (IsSuperUser,)
# class IDCAssetsApi(generics.ListAPIView): # class IDCAssetsApi(generics.ListAPIView):
# model = IDC # model = IDC
...@@ -71,7 +78,7 @@ class SystemUserViewSet(viewsets.ModelViewSet): ...@@ -71,7 +78,7 @@ class SystemUserViewSet(viewsets.ModelViewSet):
# return self.object.assets.all() # return self.object.assets.all()
class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
......
...@@ -310,3 +310,7 @@ class AssetTagForm(forms.ModelForm): ...@@ -310,3 +310,7 @@ class AssetTagForm(forms.ModelForm):
help_texts = { help_texts = {
'name': '* required', 'name': '* required',
} }
class FileForm(forms.Form):
file = forms.FileField()
\ No newline at end of file
...@@ -60,38 +60,6 @@ class IDC(models.Model): ...@@ -60,38 +60,6 @@ class IDC(models.Model):
continue continue
class AssetExtend(models.Model):
key = models.CharField(max_length=64, verbose_name=_('KEY'))
value = models.CharField(max_length=64, verbose_name=_('VALUE'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_("Created by"))
date_created = models.DateTimeField(auto_now_add=True, null=True)
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return '%(key)s: %(value)s' % {'key': self.key, 'value': self.value}
@classmethod
def initial(cls):
for k, v in (
(_('status'), _('In use')),
(_('status'), _('Out of use')),
(_('type'), _('Server')),
(_('type'), _('VM')),
(_('type'), _('Switch')),
(_('type'), _('Router')),
(_('type'), _('Firewall')),
(_('type'), _('Storage')),
(_('env'), _('Production')),
(_('env'), _('Development')),
(_('env'), _('Testing')),
):
cls.objects.create(key=k, value=v, created_by='System')
class Meta:
db_table = 'asset_extend'
unique_together = ('key', 'value')
def private_key_validator(value): def private_key_validator(value):
if not validate_ssh_private_key(value): if not validate_ssh_private_key(value):
raise ValidationError( raise ValidationError(
...@@ -233,6 +201,10 @@ class SystemUser(models.Model): ...@@ -233,6 +201,10 @@ class SystemUser(models.Model):
def assets_amount(self): def assets_amount(self):
return self.assets.count() return self.assets.count()
@property
def asset_group_amount(self):
return self.asset_groups.count()
class Meta: class Meta:
db_table = 'system_user' db_table = 'system_user'
...@@ -294,18 +266,29 @@ class AssetGroup(models.Model): ...@@ -294,18 +266,29 @@ class AssetGroup(models.Model):
continue continue
def get_default_extend(key, value):
try:
return AssetExtend.objects.get_or_create(key=key, value=value)[0]
except:
return None
def get_default_idc(): def get_default_idc():
return IDC.initial() return IDC.initial()
class Asset(models.Model): class Asset(models.Model):
STATUS_CHOICES = (
('In use', _('In use')),
('Out of use', _('Out of use')),
)
TYPE_CHOICES = (
('Server', _('Server')),
('VM', _('VM')),
('Switch', _('Switch')),
('Router', _('Router')),
('Firewall', _('Firewall')),
('Storage', _("Storage")),
)
ENV_CHOICES = (
('Prod', 'Production'),
('Dev', 'Development'),
('Test', 'Testing'),
)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
other_ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Other IP')) other_ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Other IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote card IP')) remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote card IP'))
...@@ -326,15 +309,12 @@ class Asset(models.Model): ...@@ -326,15 +309,12 @@ class Asset(models.Model):
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number')) cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position')) cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number')) number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
status = models.ForeignKey(AssetExtend, null=True, blank=True, status = models.CharField(choices=STATUS_CHOICES, max_length=8, null=True, blank=True,
related_name="status_asset", verbose_name=_('Asset status'),) default='In use', verbose_name=_('Asset status'))
# default=functools.partial(get_default_extend, 'status', 'In use')) type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
type = models.ForeignKey(AssetExtend, blank=True,null=True, limit_choices_to={'key': 'type'}, default='Server', verbose_name=_('Asset type'),)
related_name="type_asset", verbose_name=_('Asset type'),) env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
# default=functools.partial(get_default_extend, 'type','Server')) default='Prod', verbose_name=_('Asset environment'),)
env = models.ForeignKey(AssetExtend, blank=True, null=True, limit_choices_to={'key': 'env'},
related_name="env_asset", verbose_name=_('Asset environment'),)
# default=functools.partial(get_default_extend, 'env', 'Production'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
...@@ -400,7 +380,7 @@ class Tag(models.Model): ...@@ -400,7 +380,7 @@ class Tag(models.Model):
def init_all_models(): def init_all_models():
for cls in (AssetExtend, AssetGroup): for cls in (AssetGroup,):
cls.initial() cls.initial()
...@@ -410,5 +390,5 @@ def generate_fake(): ...@@ -410,5 +390,5 @@ def generate_fake():
def flush_all(): def flush_all():
for cls in (AssetGroup, AssetExtend, IDC, AdminUser, SystemUser, Asset): for cls in (AssetGroup, IDC, AdminUser, SystemUser, Asset):
cls.objects.all().delete() cls.objects.all().delete()
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import viewsets, serializers,generics from rest_framework import viewsets, serializers,generics
from .models import AssetGroup, Asset, IDC, AssetExtend, AdminUser, SystemUser from .models import AssetGroup, Asset, IDC, AdminUser, SystemUser
from common.mixins import BulkDeleteApiMixin from common.mixins import IDInFilterMixin
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
...@@ -17,6 +17,19 @@ class AssetGroupSerializer(serializers.ModelSerializer): ...@@ -17,6 +17,19 @@ class AssetGroupSerializer(serializers.ModelSerializer):
def get_assets_amount(obj): def get_assets_amount(obj):
return obj.assets.count() return obj.assets.count()
class AssetUpdateGroupSerializer(serializers.ModelSerializer):
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all())
class Meta:
model = Asset
fields = ['id', 'groups']
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all())
class Meta:
model = Asset
fields = ['id', 'system_users']
class AdminUserSerializer(serializers.ModelSerializer): class AdminUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
...@@ -35,7 +48,7 @@ class SystemUserSerializer(serializers.ModelSerializer): ...@@ -35,7 +48,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
def get_field_names(self, declared_fields, info): def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.append('assets_amount') fields.extend(['assets_amount'])
return fields return fields
...@@ -43,7 +56,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -43,7 +56,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
# system_users = SystemUserSerializer(many=True, read_only=True) # system_users = SystemUserSerializer(many=True, read_only=True)
# admin_user = AdminUserSerializer(many=False, read_only=True) # admin_user = AdminUserSerializer(many=False, read_only=True)
hardware = serializers.SerializerMethodField() hardware = serializers.SerializerMethodField()
type_display = serializers.SerializerMethodField()
class Meta(object): class Meta(object):
model = Asset model = Asset
...@@ -51,15 +63,16 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -51,15 +63,16 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@staticmethod @staticmethod
def get_hardware(obj): def get_hardware(obj):
if obj.cpu:
return '%s %s %s' % (obj.cpu, obj.memory, obj.disk) return '%s %s %s' % (obj.cpu, obj.memory, obj.disk)
@staticmethod
def get_type_display(obj):
if obj.type:
return obj.type.value
else: else:
return '' return ''
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'])
return fields
class AssetGrantedSerializer(serializers.ModelSerializer): class AssetGrantedSerializer(serializers.ModelSerializer):
system_users = SystemUserSerializer(many=True, read_only=True) system_users = SystemUserSerializer(many=True, read_only=True)
......
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_import_modal{% endblock %}
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
{% block modal_body %}
<p class="text-success">{% trans "Download template or use export excel format" %}</p>
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
<a href="{{ MEDIA_URL }}files/asset_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Asset excel file" %}</label>
<input id="id_assets" type="file" name="file" />
</div>
</form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %}
{% block modal_confirm_id %}btn_asset_import{% endblock %}
...@@ -39,13 +39,6 @@ $(document).ready(function(){ ...@@ -39,13 +39,6 @@ $(document).ready(function(){
var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData; var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData;
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>'); $(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
}}, }},
{# {targets: 6, createdCell: function (td, cellData) {#}
{# if (!cellData) {#}
{# $(td).html('<i class="fa fa-times text-danger"></i>')#}
{# } else {#}
{# $(td).html('<i class="fa fa-check text-navy"></i>')#}
{# }#}
{# }},#}
{targets: 6, createdCell: function (td, cellData, rowData) { {targets: 6, createdCell: function (td, cellData, rowData) {
var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData); var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);
var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData); var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
...@@ -55,7 +48,6 @@ $(document).ready(function(){ ...@@ -55,7 +48,6 @@ $(document).ready(function(){
ajax_url: '{% url "api-assets:admin-user-list" %}', ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () {return 'lost'} }, columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () {return 'lost'} },
{data: "comment" }, {data: "id" }], {data: "comment" }, {data: "id" }],
op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initDataTable(options);
}); });
......
...@@ -205,9 +205,9 @@ ...@@ -205,9 +205,9 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Join asset groups' %}" class="select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Join asset groups' %}" id="groups_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups_remain %} {% for asset_group in asset_groups_remain %}
<option value="{{ asset_group.id }}" >{{ asset_group.name }}</option> <option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}" >{{ asset_group.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
...@@ -221,9 +221,9 @@ ...@@ -221,9 +221,9 @@
{% for asset_group in asset_groups %} {% for asset_group in asset_groups %}
<tr> <tr>
<td ><b data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td> <td ><b class="bdg_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
<td> <td>
<button class="btn btn-danger pull-right btn-xs " 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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -243,22 +243,22 @@ ...@@ -243,22 +243,22 @@
<td colspan="2"> <td colspan="2">
<select data-placeholder="{% trans 'Select system user' %}" class="select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select system user' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_remain %} {% for system_user in system_users_remain %}
<option value="{{ system_user.id }}">{{ system_user.name }}</option> <option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td colspan="2"> <td colspan="2">
<button type="button" class="btn btn-warning btn-sm">{% trans 'Associate' %}</button> <button type="button" class="btn btn-warning btn-sm btn-system-user">{% trans 'Associate' %}</button>
</td> </td>
</tr> </tr>
</form> </form>
{% for system_user in system_users %} {% for system_user in system_users %}
<tr> <tr>
<td ><b>{{ system_user.name }}</b></td> <td ><b class="bdg_group" data-sid={{ system_user.id }}>{{ system_user.name }}</b></td>
<td> <td>
<button class="btn btn-danger btn-xs" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -275,8 +275,154 @@ ...@@ -275,8 +275,154 @@
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
jumpserver.groups_selected = {};
function updateAssetGroups(groups) {
var the_url = "{% url 'api-assets:asset-update-group' pk=asset.id %}";
var body = {
groups: Object.assign([], groups)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.groups_selected, function(group_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('.group_edit 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>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.groups_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function updateAssetSystem(system_users) {
var the_url = "{% url 'api-assets:asset-update-systemusers' pk=asset.id %}";
var body = {
system_users: Object.assign([], system_users)
};
var success = function(data) {
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.groups_selected, function(name, index) {
$('#opt_' + index).remove();
$('.group_edit 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.groups_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.groups_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.groups_selected[data.id]
})
})
.on('click', '#is_active', function () {
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
var checked = $(this).prop('checked');
var body = {
'is_active': checked
};
var success = '{% trans "Update Successfully!" %}';
var status = $(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").text();
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success_message: success
});
if (status == "False") {
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
}else{
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
}
})
.on('click', '#btn_add_user_group', function () {
if (Object.keys(jumpserver.groups_selected).length === 0) {
return false;
}
var groups = $('.bdg_group').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.groups_selected, function(value, index) {
groups.push(parseInt(index));
$('#opt_' + index).remove();
});
updateAssetGroups(groups)
})
.on('click', '.btn_leave_group', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_group');
var gid = $badge.data('gid');
var group_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
);
$tr.remove();
var groups = $('.bdg_group').map(function () {
return $(this).data('gid');
}).get();
updateAssetGroups(groups)
}) })
.on('click', '.btn-system-user', function () {
if (Object.keys(jumpserver.groups_selected).length === 0) {
return false;
}
var system_users = $('.bdg_group').map(function() {
return $(this).data('sid');
}).get();
$.map(jumpserver.groups_selected, function(value, index) {
system_users.push(parseInt(index));
$('#opt_' + index).remove();
});
updateAssetSystem(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();
console.log(system_users);
updateAssetSystem(system_users)
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<div class="uc pull-left m-l-5 m-r-5"> <div class="uc pull-left m-l-5 m-r-5">
<a href="{% url "assets:asset-group-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset group" %} </a> <a href="{% url "assets:asset-group-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset group" %} </a>
</div> </div>
<table class="table table-striped table-bordered table-hover " id="admin_user_list_table" > <table class="table table-striped table-bordered table-hover " id="asset_groups_list_table" >
<thead> <thead>
<tr> <tr>
<th class="text-center"> <th class="text-center">
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<script> <script>
$(document).ready(function(){ $(document).ready(function(){
var options = { var options = {
ele: $('#admin_user_list_table'), ele: $('#asset_groups_list_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=99991937 %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=99991937 %}">' + cellData + '</a>';
...@@ -46,7 +46,14 @@ $(document).ready(function(){ ...@@ -46,7 +46,14 @@ $(document).ready(function(){
columns: [{data: "id"}, {data: "name" }, {data: "assets_amount" }, {data: "comment" }, {data: "id"}] columns: [{data: "id"}, {data: "name" }, {data: "assets_amount" }, {data: "comment" }, {data: "id"}]
}; };
jumpserver.initDataTable(options); jumpserver.initDataTable(options);
}); })
.on('click', '.btn_asset_group_delete', function () {
var $this = $(this);
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-group-detail" pk=99991937 %}'.replace('99991937', uid);
objectDelete($this, name, the_url);
});
</script> </script>
{% endblock %} {% endblock %}
...@@ -18,10 +18,11 @@ ...@@ -18,10 +18,11 @@
{% block table_search %} {% block table_search %}
<div class="html5buttons"> <div class="html5buttons">
<div class="dt-buttons btn-group"> <div class="dt-buttons btn-group">
<a class="btn btn-default buttons-pdf" tabindex="0" href="#"> <a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
<span>PDF</span></a> <span>{% trans "Import" %}</span>
<a class="btn btn-default buttons-excel" tabindex="0" href="#"> </a>
<span>Excel</span> <a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a> </a>
</div> </div>
</div> </div>
...@@ -32,7 +33,7 @@ ...@@ -32,7 +33,7 @@
<div class="tagBtnList"> <div class="tagBtnList">
{% for tag in tag_list %} {% for tag in tag_list %}
<a href="{% url 'assets:asset-tags' tag_id=tag.0 %}" <a href="{% url 'assets:asset-tags' tag_id=tag.0 %}"
{% if tag.0|IntToStr == tag_id %} {% if tag.0|int_to_str == tag_id %}
class="tagBtn2 label label-warning" name="tag_on"> class="tagBtn2 label label-warning" name="tag_on">
{% else %} {% else %}
class="tagBtn2 label label-default"> class="tagBtn2 label label-default">
...@@ -47,7 +48,6 @@ ...@@ -47,7 +48,6 @@
{% block table_container %} {% block table_container %}
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div> <div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
<div class="uc pull-left"><a href="javascript:void(0);" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#asset_import_modal"> {% trans "Import asset" %} </a></div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" > <table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead> <thead>
<tr> <tr>
...@@ -80,11 +80,13 @@ ...@@ -80,11 +80,13 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_asset_import_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script type="text/javascript"> <script type="text/javascript">
window.onload=function (){ window.onload = function (){
var tag_on = document.getElementsByName("tag_on"); var tag_on = document.getElementsByName("tag_on");
var oDiv = document.getElementById("ydxbd"); var oDiv = document.getElementById("ydxbd");
if(tag_on.length > 0){ if(tag_on.length > 0){
...@@ -99,7 +101,7 @@ ...@@ -99,7 +101,7 @@
}else{ }else{
oDiv.style.display = "none"; oDiv.style.display = "none";
} }
}; //onload; } //onload;
$(document).ready(function(){ $(document).ready(function(){
var options = { var options = {
...@@ -125,17 +127,67 @@ ...@@ -125,17 +127,67 @@
}}, }},
{targets: 9, createdCell: function (td, cellData, rowData) { {targets: 9, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData); var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
}} }}
], ],
ajax_url: '{% url "api-assets:asset-list" %}', ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "type_display" }, {data: "env"}, {data: "hardware"}, {data: "is_active" }, {data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active"}, {data: "id" }], {data: "is_active" }, {data: "is_active"}, {data: "id" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
var table = jumpserver.initDataTable(options); var table = jumpserver.initDataTable(options);
$('.btn_export').click(function () {
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
});
$('#btn_asset_import').click(function() {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
objectDelete($this, name, the_url);
}); });
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
{% extends '_base_list.html' %} {% extends '_base_list.html' %}
{% load i18n %} {% load i18n %}
{% load common_tags %} {% load common_tags %}
{% block content_left_head %}
<a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a> {% block table_search %}
{% endblock %} {% endblock %}
{% block table_head %} {% block table_container %}
<th class="text-center">{% trans 'ID' %}</th> <div class="uc pull-left m-l-5 m-r-5">
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=name">{% trans 'Name' %}</a></th> <a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=username">{% trans 'Username' %}</a></th> </div>
<th class="text-center">{% trans 'Asset num' %}</th> <table class="table table-striped table-bordered table-hover " id="system_user_list_table" >
<th class="text-center">{% trans 'Asset group num' %}</th> <thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th> <th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center"></th> <th class="text-center">{% trans 'Action' %}</th>
{% endblock %}
{% block table_body %}
{% for system_user in system_user_list %}
<tr class="gradeX">
<td class="text-center">{{ system_user.id }}</td>
<td>
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}">
{{ system_user.name }}
</a>
</td>
<td class="text-center">{{ system_user.username }}</td>
<td class="text-center">{{ system_user.get_assets|length }}</td>
<td class="text-center">{{ system_user.asset_groups.count }}</td>
<td class="text-center">{{ system_user.assets.count }}</td>
<td class="text-center">{{ system_user.comment|truncatewords:4 }}</td>
<td class="text-center">
<!-- Todo: Click script button will paste a url to clipboard like: curl http://url/system_user_create.sh | bash -->
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-primary">{% trans 'Script' %}</a>
<!-- Todo: Click refresh button will run a task to test admin user could connect asset or not immediately -->
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-warning">{% trans 'Refresh' %}</a>
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-info">{% trans 'Update' %}</a>
<a onclick="obj_del(this,'{{ system_user.name }}','{% url 'assets:system-user-delete' system_user.id %}')" class="btn btn-xs btn-danger del">{% trans 'Delete' %}</a>
</td>
</tr> </tr>
{% endfor %} </thead>
<tbody>
</tbody>
</table>
{% endblock %} {% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function(){
var options = {
ele: $('#system_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var script_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);
var update_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
$(td).html(script_btn + update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () { return "3"}},
{data: "comment" }, {data: "id" }],
};
jumpserver.initDataTable(options);
});
</script>
{% endblock %}
...@@ -17,6 +17,11 @@ urlpatterns = [ ...@@ -17,6 +17,11 @@ urlpatterns = [
url(r'^v1/assets_bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), url(r'^v1/assets_bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
# url(r'^v1/idc/(?P<pk>[0-9]+)/assets/$', api.IDCAssetsApi.as_view(), name='api-idc-assets'), # url(r'^v1/idc/(?P<pk>[0-9]+)/assets/$', api.IDCAssetsApi.as_view(), name='api-idc-assets'),
url(r'^v1/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'), url(r'^v1/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'),
url(r'^v1/assets/(?P<pk>\d+)/groups/$',
api.AssetUpdateGroupApi.as_view(), name='asset-update-group'),
url(r'^v1/assets/(?P<pk>\d+)/system-users/$',
api.SystemUserUpdateApi.as_view(), name='asset-update-systemusers'),
] ]
urlpatterns += router.urls urlpatterns += router.urls
......
...@@ -9,6 +9,8 @@ urlpatterns = [ ...@@ -9,6 +9,8 @@ urlpatterns = [
url(r'^$', views.AssetListView.as_view(), name='asset-index'), url(r'^$', views.AssetListView.as_view(), name='asset-index'),
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'), url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'), url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
url(r'^asset/(?P<pk>[0-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'), url(r'^asset/(?P<pk>[0-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'),
url(r'^asset/(?P<pk>[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'), url(r'^asset/(?P<pk>[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'), url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
......
This diff is collapsed.
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
{% load common_tags %} {% load common_tags %}
{% block content_left_head %} {% block content_left_head %}
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<style> <style>
#search_btn { #search_btn {
margin-bottom: 0; margin-bottom: 0;
...@@ -91,6 +92,7 @@ ...@@ -91,6 +92,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script> <script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.footable').footable(); $('.footable').footable();
......
...@@ -40,14 +40,13 @@ class NoDeleteModelMixin(models.Model): ...@@ -40,14 +40,13 @@ class NoDeleteModelMixin(models.Model):
class JSONResponseMixin(object): class JSONResponseMixin(object):
"""JSON mixin""" """JSON mixin"""
@staticmethod
def render_json_response(self, context): def render_json_response(context):
return JsonResponse(context) return JsonResponse(context)
class BulkDeleteApiMixin(object): class IDInFilterMixin(object):
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
id_list = self.request.query_params.get('id__in') id_list = self.request.query_params.get('id__in')
......
...@@ -44,6 +44,7 @@ def join_attr(seq, attr=None, sep=None): ...@@ -44,6 +44,7 @@ def join_attr(seq, attr=None, sep=None):
print(seq) print(seq)
return sep.join(seq) return sep.join(seq)
@register.filter @register.filter
def IntToStr(value): def int_to_str(value):
return str(value) return str(value)
\ No newline at end of file
This diff is collapsed.
[{"model": "users.usergroup", "pk": 1, "fields": {"is_discard": false, "discard_time": null, "name": "Default", "comment": "Default user group for all user", "date_created": "2016-11-02T14:49:50Z", "created_by": "System"}}, {"model": "assets.assetextend", "pk": 1, "fields": {"key": "status", "value": "In use", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 2, "fields": {"key": "status", "value": "Out of use", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 3, "fields": {"key": "type", "value": "Server", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 4, "fields": {"key": "type", "value": "VM", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 5, "fields": {"key": "type", "value": "Switch", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 6, "fields": {"key": "type", "value": "Router", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 7, "fields": {"key": "type", "value": "Firewall", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 8, "fields": {"key": "type", "value": "Storage", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 9, "fields": {"key": "env", "value": "Production", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 10, "fields": {"key": "env", "value": "Development", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 11, "fields": {"key": "env", "value": "Testing", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetgroup", "pk": 1, "fields": {"name": "Default", "created_by": "", "date_created": "2016-11-02T14:51:53Z", "comment": "Default asset group", "system_users": []}}, {"model": "users.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$30000$y45nsj5IDfVi$KUXoECb9rZJZ2ZosQSxi9anmj2oY5LAr1MdJby/xEzU=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2016-11-02T14:51:45Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": "", "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2086-10-16T14:51:45Z", "created_by": "System", "user_permissions": [], "groups": [1]}}] [{"model": "users.usergroup", "pk": 1, "fields": {"is_discard": false, "discard_time": null, "name": "Default", "comment": "Default user group for all user", "date_created": "2016-11-25T06:50:28.410Z", "created_by": "System"}}, {"model": "assets.assetgroup", "pk": 1, "fields": {"name": "Default", "created_by": "", "date_created": "2016-11-25T06:50:28.627Z", "comment": "Default asset group", "system_users": []}}, {"model": "users.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$30000$RwSpXYAYQGbQ$PADpsQmM+cO5Y/R1CVSx3qNV4EbGIm2k+iMBXUtwvNc=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2016-11-25T06:50:28.412Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": null, "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2086-11-08T06:50:28.412Z", "created_by": "System", "user_permissions": [], "groups": [1]}}]
\ No newline at end of file
...@@ -98,6 +98,7 @@ TEMPLATES = [ ...@@ -98,6 +98,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'django.template.context_processors.static', 'django.template.context_processors.static',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.template.context_processors.media',
], ],
}, },
}, },
...@@ -272,7 +273,6 @@ REST_FRAMEWORK = { ...@@ -272,7 +273,6 @@ REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions, # Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users. # or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': ( 'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated',
'users.backends.IsValidUser', 'users.backends.IsValidUser',
), ),
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
...@@ -282,6 +282,7 @@ REST_FRAMEWORK = { ...@@ -282,6 +282,7 @@ REST_FRAMEWORK = {
'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
), ),
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
} }
# Custom User Auth model # Custom User Auth model
......
...@@ -214,9 +214,7 @@ function APIUpdateAttr(props) { ...@@ -214,9 +214,7 @@ function APIUpdateAttr(props) {
// Sweet Alert for Delete // Sweet Alert for Delete
function objectDelete(obj, name, url) { function objectDelete(obj, name, url) {
var $this = $(this);
function doDelete() { function doDelete() {
var uid = $this.data('uid');
var body = {}; var body = {};
var success = function() { var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success"); swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
...@@ -292,7 +290,7 @@ jumpserver.initDataTable = function (options) { ...@@ -292,7 +290,7 @@ jumpserver.initDataTable = function (options) {
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var table = ele.DataTable({ var table = ele.DataTable({
pageLength: options.pageLength || 15, pageLength: options.pageLength || 15,
dom: options.dom || '<"#uc.pull-left"><"html5buttons"B>flti<"row m-t"<"#op.col-md-6"><"col-md-6"p>>', dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
language: { language: {
url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json" url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json"
}, },
......
...@@ -24,10 +24,10 @@ class TerminalSerializer(serializers.ModelSerializer): ...@@ -24,10 +24,10 @@ class TerminalSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_is_alive(obj): def get_is_alive(obj):
log = obj.terminalheatbeat_set.last() log = obj.terminalheatbeat_set.last()
if timezone.now() - log.date_created > timezone.timedelta(seconds=600): if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
return False
else:
return True return True
else:
return False
class TerminalHeatbeatSerializer(serializers.ModelSerializer): class TerminalHeatbeatSerializer(serializers.ModelSerializer):
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
import base64
from django.shortcuts import get_object_or_404
from django.core.cache import cache from django.core.cache import cache
from django.conf import settings from django.conf import settings
from rest_framework import generics, status, viewsets from rest_framework import generics, viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView, BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework import authentication from django_filters.rest_framework import DjangoFilterBackend
from common.mixins import BulkDeleteApiMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger from common.utils import get_logger
from .utils import check_user_valid, token_gen from .utils import check_user_valid, token_gen
from .models import User, UserGroup from .models import User, UserGroup
...@@ -24,10 +22,12 @@ from . import serializers ...@@ -24,10 +22,12 @@ from . import serializers
logger = get_logger(__name__) logger = get_logger(__name__)
class UserViewSet(BulkModelViewSet): class UserViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
filter_backends = (DjangoFilterBackend,)
filter_fields = ('username', 'email', 'name', 'id')
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView): class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
...@@ -73,7 +73,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): ...@@ -73,7 +73,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user.save() user.save()
class UserGroupViewSet(viewsets.ModelViewSet): class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all() queryset = UserGroup.objects.all()
serializer_class = serializers.UserGroupSerializer serializer_class = serializers.UserGroupSerializer
...@@ -83,52 +83,7 @@ class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): ...@@ -83,52 +83,7 @@ class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
serializer_class = serializers.UserGroupUpdateMemeberSerializer serializer_class = serializers.UserGroupUpdateMemeberSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
# class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView):
# queryset = UserGroup.objects.all()
# serializer_class = serializers.GroupDetailSerializer
#
# def perform_update(self, serializer):
# users = serializer.validated_data.get('users')
# if users:
# group = self.get_object()
# Note: use `list` method to force hitting the db.
# group_users = list(group.users.all())
# serializer.save()
# group.users.set(users + group_users)
# group.save()
# return
# serializer.save()
# class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
# queryset = User.objects.all()
# serializer_class = serializers.UserBulkUpdateSerializer
# permission_classes = (IsSuperUserOrTerminalUser,)
#
# def get(self, request, *args, **kwargs):
# return super(UserListUpdateApi, self).get(request, *args, **kwargs)
#
# class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
# queryset = UserGroup.objects.all()
# serializer_class = serializers.GroupBulkUpdateSerializer
#
# class DeleteUserFromGroupApi(generics.DestroyAPIView):
# queryset = UserGroup.objects.all()
# serializer_class = serializers.GroupDetailSerializer
#
# def destroy(self, request, *args, **kwargs):
# group = self.get_object()
# self.perform_destroy(group, **kwargs)
# return Response(status=status.HTTP_204_NO_CONTENT)
#
# def perform_destroy(self, instance, **kwargs):
# user_id = kwargs.get('uid')
# user = get_object_or_404(User, id=user_id)
# instance.users.remove(user)
#
#
class UserAuthApi(APIView): class UserAuthApi(APIView):
permission_classes = () permission_classes = ()
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
......
...@@ -141,4 +141,4 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm): ...@@ -141,4 +141,4 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
class FileForm(forms.Form): class FileForm(forms.Form):
excel = forms.FileField() file = forms.FileField()
...@@ -79,7 +79,7 @@ class User(AbstractUser): ...@@ -79,7 +79,7 @@ class User(AbstractUser):
role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role')) role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role'))
avatar = models.ImageField(upload_to="avatar", verbose_name=_('Avatar')) avatar = models.ImageField(upload_to="avatar", verbose_name=_('Avatar'))
wechat = models.CharField(max_length=30, blank=True, verbose_name=_('Wechat')) wechat = models.CharField(max_length=30, blank=True, verbose_name=_('Wechat'))
phone = models.CharField(max_length=20, blank=True, verbose_name=_('Phone')) phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP')) enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP'))
secret_key_otp = models.CharField(max_length=16, blank=True) secret_key_otp = models.CharField(max_length=16, blank=True)
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key')) _private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key'))
......
...@@ -9,12 +9,6 @@ from common.utils import signer, validate_ssh_public_key ...@@ -9,12 +9,6 @@ from common.utils import signer, validate_ssh_public_key
from .models import User, UserGroup from .models import User, UserGroup
# class UserDetailSerializer(BulkSerializerMixin, serializers.ModelSerializer):
# class Meta:
# model = User
# fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
groups_display = serializers.SerializerMethodField() groups_display = serializers.SerializerMethodField()
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all()) groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
...@@ -33,10 +27,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -33,10 +27,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
def get_groups_display(obj): def get_groups_display(obj):
return " ".join([group.name for group in obj.groups.all()]) return " ".join([group.name for group in obj.groups.all()])
# @staticmethod
# def get_active_display(obj):
# return not (obj.is_expired and obj.is_active)
class UserPKUpdateSerializer(serializers.ModelSerializer): class UserPKUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-9 col-lg-9 col-sm-offset-2"> <div class="col-sm-9 col-lg-9 col-sm-offset-2">
<div class="checkbox checkbox-success"> <div class="checkbox">
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label> <input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label>
</div> </div>
</div> </div>
......
{% extends '_modal.html' %} {% extends '_modal.html' %}
{% load i18n %} {% load i18n %}
{% block modal_id %}user_import_modal{% endblock %} {% block modal_id %}user_import_modal{% endblock %}
{% block modal_title%}{% trans "Import User" %}{% endblock %} {% block modal_title%}{% trans "Import user" %}{% endblock %}
{% block modal_body %} {% block modal_body %}
<p class="text-success text-center">{% trans "Hint: your excel should organized in the following format." %}</p> <p class="text-success">{% trans "Download template or use export excel format" %}</p>
<p class="text-success text-center">{% trans "* You should have a very worksheet named `users`." %}</p> <form method="post" action="{% url 'users:user-import' %}" id="fm_user_import" enctype="multipart/form-data">
<p class="text-success text-center">{% trans "* Rows in this worksheet: username, email, enable_opt(0, 1), role(one of ['Admin', 'User'])" %}</p>
<form method="post" class="form-horizontal" action="{% url 'users:user-import' %}" id="fm_user_import" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2 col-lg-2 " for="id_excel">{% trans "Excel" %}</label> <label class="control-label" for="id_users">{% trans "Template" %}</label>
<div class=" col-sm-9 col-lg-9 "> <a href="{{ MEDIA_URL }}files/user_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
<input id="id_excel" type="file" name="excel" />
</div> </div>
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Users excel file" %}</label>
<input id="id_users" type="file" name="file" />
</div> </div>
</form> </form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %} {% endblock %}
{% block modal_confirm_id %}btn_user_import{% endblock %} {% block modal_confirm_id %}btn_user_import{% endblock %}
...@@ -169,7 +169,7 @@ ...@@ -169,7 +169,7 @@
id: $this.attr('id'), id: $this.attr('id'),
user_id: {{ user.id }} user_id: {{ user.id }}
}; };
var the_url = "{% url 'perms:revoke-user-asset-permission' %}"; var the_url = "{% url 'api-perms:revoke-user-asset-permission' %}";
var success = function () { var success = function () {
$this.closest('tr').remove(); $this.closest('tr').remove();
}; };
......
...@@ -255,16 +255,19 @@ function updateUserGroups(groups) { ...@@ -255,16 +255,19 @@ function updateUserGroups(groups) {
success: success success: success
}); });
} }
$(document).ready(function() { $(document).ready(function() {
$('.select2').select2() $('.select2').select2()
.on('select2:select', function(evt) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.groups_selected[data.id] = data.text; jumpserver.groups_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) { })
.on('select2:unselect', function(evt) {
var data = evt.params.data; var data = evt.params.data;
delete jumpserver.groups_selected[data.id] delete jumpserver.groups_selected[data.id]
}) })
}).on('click', '#is_active', function() { })
.on('click', '#is_active', function() {
var the_url = "{% url 'api-users:user-detail' pk=user.id %}"; var the_url = "{% url 'api-users:user-detail' pk=user.id %}";
var checked = $(this).prop('checked'); var checked = $(this).prop('checked');
var body = { var body = {
...@@ -276,7 +279,8 @@ $(document).ready(function() { ...@@ -276,7 +279,8 @@ $(document).ready(function() {
body: JSON.stringify(body), body: JSON.stringify(body),
success_message: success success_message: success
}); });
}).on('click', '#enable_otp', function() { })
.on('click', '#enable_otp', function() {
var the_url = "{% url 'api-users:user-detail' pk=user.id %}"; var the_url = "{% url 'api-users:user-detail' pk=user.id %}";
var checked = $(this).prop('checked'); var checked = $(this).prop('checked');
var body = { var body = {
...@@ -288,7 +292,8 @@ $(document).ready(function() { ...@@ -288,7 +292,8 @@ $(document).ready(function() {
body: JSON.stringify(body), body: JSON.stringify(body),
success_message: success success_message: success
}); });
}).on('click', '#btn_join_group', function() { })
.on('click', '#btn_join_group', function() {
if (Object.keys(jumpserver.groups_selected).length === 0) { if (Object.keys(jumpserver.groups_selected).length === 0) {
return false; return false;
} }
......
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
id: $this.attr('id'), id: $this.attr('id'),
user_group_id: {{ user_group.id }} user_group_id: {{ user_group.id }}
}; };
var the_url = "{% url 'perms:revoke-user-group-asset-permission' %}"; var the_url = "{% url 'api-perms:revoke-user-group-asset-permission' %}";
var success = function () { var success = function () {
$this.closest('tr').remove(); $this.closest('tr').remove();
}; };
......
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
} }
}} }}
], ],
ajax_url: '{% url "perms:api-user-group-assets" pk=user_group.id %}', ajax_url: '{% url "api-perms:user-group-assets" pk=user_group.id %}',
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port"}, columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port"},
{data: "system_users_join"}, {data: "is_active"}] {data: "system_users_join"}, {data: "is_active"}]
}; };
......
...@@ -47,8 +47,11 @@ $(document).ready(function() { ...@@ -47,8 +47,11 @@ $(document).ready(function() {
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}}, }},
{targets: 4, createdCell: function (td, cellData, rowData) { {targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "users:user-group-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData); var update_btn = '<a href="{% url "users:user-group-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_delete_user_group" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData); .replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_delete_user_group" data-gid="99991937" data-name="99991938">{% trans "Delete" %}</a>'
.replace('99991937', cellData)
.replace('99991938', rowData.name);
if (rowData.id === 1) { if (rowData.id === 1) {
$(td).html(update_btn) $(td).html(update_btn)
} else { } else {
...@@ -63,38 +66,10 @@ $(document).ready(function() { ...@@ -63,38 +66,10 @@ $(document).ready(function() {
jumpserver.initDataTable(options); jumpserver.initDataTable(options);
}).on('click', '.btn_delete_user_group', function(){ }).on('click', '.btn_delete_user_group', function(){
var $this = $(this); var $this = $(this);
function doDelete() {
var group_id = $this.data('gid'); var group_id = $this.data('gid');
var name = $this.data('name');
var the_url = "{% url 'api-users:user-group-detail' pk=99991937 %}".replace('99991937', group_id); var the_url = "{% url 'api-users:user-group-detail' pk=99991937 %}".replace('99991937', group_id);
var body = {}; objectDelete($this, name, the_url)
var success = function() {
var msg = "{% trans 'Group Deleted.' %}";
swal("{% trans 'Group Delete' %}", msg, "success");
$this.closest('tr.gradeX').remove();
};
var fail = function() {
var msg = "{% trans 'Group Deleting failed.' %}";
swal("{% trans 'Group Delete' %}", msg, "error");
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'DELETE',
success: success,
error: fail
});
}
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected group, but will not remove any user in this group.' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
doDelete();
});
}).on('click', '#btn_bulk_update', function(){ }).on('click', '#btn_bulk_update', function(){
var action = $('#slct_bulk_update').val(); var action = $('#slct_bulk_update').val();
var $data_table = $('#group_list_table').DataTable() var $data_table = $('#group_list_table').DataTable()
......
...@@ -3,17 +3,18 @@ ...@@ -3,17 +3,18 @@
{% block table_search %} {% block table_search %}
<div class="html5buttons"> <div class="html5buttons">
<div class="dt-buttons btn-group"> <div class="dt-buttons btn-group">
<a class="btn btn-default buttons-pdf" tabindex="0" href="#"> <a class="btn btn-default btn_import" data-toggle="modal" data-target="#user_import_modal" tabindex="0">
<span>PDF</span></a> <span>{% trans "Import" %}</span>
<a class="btn btn-default buttons-excel" tabindex="0" href="#"> </a>
<span>Excel</span> <a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a> </a>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block table_container %} {% block table_container %}
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div> <div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>
<div class="uc pull-left"><a href="javascript:void(0);" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#user_import_modal"> {% trans "Import user" %} </a></div> {#<div class="uc pull-left"><a href="javascript:void(0);" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#user_import_modal"> {% trans "Import user" %} </a></div>#}
<table class="table table-striped table-bordered table-hover " id="user_list_table" > <table class="table table-striped table-bordered table-hover " id="user_list_table" >
<thead> <thead>
<tr> <tr>
...@@ -38,6 +39,7 @@ ...@@ -38,6 +39,7 @@
<option value="delete">{% trans 'Delete selected' %}</option> <option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option> <option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option> <option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select> </select>
<div class="input-group-btn pull-left" style="padding-left: 5px;"> <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"> <button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
...@@ -74,7 +76,9 @@ $(document).ready(function(){ ...@@ -74,7 +76,9 @@ $(document).ready(function(){
}}, }},
{targets: 6, createdCell: function (td, cellData, rowData) { {targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "users:user-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData); var update_btn = '<a href="{% url "users:user-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937" data-name="99991938">{% trans "Delete" %}</a>'
.replace('99991937', cellData)
.replace('99991938', rowData.name);
if (rowData.id === 1 || rowData.username == "admin") { if (rowData.id === 1 || rowData.username == "admin") {
$(td).html(update_btn) $(td).html(update_btn)
} else { } else {
...@@ -82,21 +86,54 @@ $(document).ready(function(){ ...@@ -82,21 +86,54 @@ $(document).ready(function(){
} }
}}], }}],
ajax_url: '{% url "api-users:user-list" %}', ajax_url: '{% url "api-users:user-list" %}',
columns: [{data: "id"}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, columns: [{data: "id"}, {data: "name" }, {data: "username" }, {data: "get_role_display" },
{data: "groups_display" }, {data: "is_valid" }, {data: "id" }], {data: "groups_display" }, {data: "is_valid" }, {data: "id" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
var table = jumpserver.initDataTable(options); var table = jumpserver.initDataTable(options);
$('.buttons-pdf').click(function () { $('.btn_export').click(function () {
var users = []; var users = [];
var rows = table.rows('.selected').data(); var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) { $.each(rows, function (index, obj) {
users.push(obj.id) users.push(obj.id)
});
$.ajax({
url: "{% url 'users:user-export' %}",
method: 'POST',
data: JSON.stringify({users_id: users}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
}) })
}); });
}).on('click', '#btn_bulk_update', function(){ $('#btn_user_import').click(function() {
var $form = $('#fm_user_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#user_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
})
.on('click', '#btn_bulk_update', function(){
var action = $('#slct_bulk_update').val(); var action = $('#slct_bulk_update').val();
var $data_table = $('#user_list_table').DataTable(); var $data_table = $('#user_list_table').DataTable();
var id_list = []; var id_list = [];
...@@ -117,6 +154,14 @@ $(document).ready(function(){ ...@@ -117,6 +154,14 @@ $(document).ready(function(){
$data_table.ajax.reload(); $data_table.ajax.reload();
jumpserver.checked = false; jumpserver.checked = false;
} }
function doActive() {
var body = $.each(id_list, function(index, user_object) {
user_object['is_active'] = true;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doDelete() { function doDelete() {
swal({ swal({
title: "{% trans 'Are you sure?' %}", title: "{% trans 'Are you sure?' %}",
...@@ -154,44 +199,21 @@ $(document).ready(function(){ ...@@ -154,44 +199,21 @@ $(document).ready(function(){
case 'update': case 'update':
doUpdate(); doUpdate();
break; break;
case 'active':
doActive();
break;
default: default:
break; break;
} }
}).on('click', '.btn_user_delete', function(){ })
.on('click', '.btn_user_delete', function(){
var $this = $(this); var $this = $(this);
function doDelete() { var name = $this.data('name');
var uid = $this.data('uid'); var uid = $this.data('uid');
var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid); var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid);
var body = {}; objectDelete($this, name, the_url);
var success = function() { })
var msg = "{% trans 'User Deleted.' %}"; .on('click', '#btn_user_bulk_update', function(){
swal("{% trans 'User Delete' %}", msg, "success");
$('#user_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'User Deleting failed.' %}";
swal("{% trans 'User Delete' %}", msg, "error");
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'DELETE',
success: success,
error: fail
});
}
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected user.' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
doDelete();
});
}).on('click', '#btn_user_bulk_update', function(){
var json_data = $('#fm_user_bulk_update').serializeObject(); var json_data = $('#fm_user_bulk_update').serializeObject();
var body = {}; var body = {};
body.enable_otp = (json_data.enable_otp === 'on')? true: false; body.enable_otp = (json_data.enable_otp === 'on')? true: false;
...@@ -204,10 +226,10 @@ $(document).ready(function(){ ...@@ -204,10 +226,10 @@ $(document).ready(function(){
if (typeof body.groups === 'string') { if (typeof body.groups === 'string') {
body.groups = [parseInt(body.groups)] body.groups = [parseInt(body.groups)]
} else if(typeof body.groups === 'array') { } else if(typeof body.groups === 'array') {
new_groups = body.groups.map(Number); var new_groups = body.groups.map(Number);
body.groups = new_groups; body.groups = new_groups;
} }
var $data_table = $('#user_list_table').DataTable() var $data_table = $('#user_list_table').DataTable();
var post_list = []; var post_list = [];
$data_table.rows({selected: true}).every(function(){ $data_table.rows({selected: true}).every(function(){
var content = Object.assign({id: this.data().id}, body); var content = Object.assign({id: this.data().id}, body);
...@@ -225,22 +247,7 @@ $(document).ready(function(){ ...@@ -225,22 +247,7 @@ $(document).ready(function(){
}; };
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success}); APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
$('#user_bulk_update_modal').modal('hide'); $('#user_bulk_update_modal').modal('hide');
}).on('click', '#btn_user_import', function() { });
var $form = $('#fm_user_import');
$form.find('.help-block').remove();
function success (data) {
if (data.success === false) {
var $help = $form.find('.help-block');
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_excel'));
} else {
$('#user_import_modal').modal('hide');
var $data_table = $('#user_list_table').DataTable();
toastr.success("{% trans 'Import User Success.' %}");
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -23,8 +23,9 @@ urlpatterns = [ ...@@ -23,8 +23,9 @@ urlpatterns = [
name='user-asset-permission-create'), name='user-asset-permission-create'),
url(r'^user/(?P<pk>[0-9]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), url(r'^user/(?P<pk>[0-9]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[0-9]+)/login-history', views.UserDetailView.as_view(), name='user-login-history'), url(r'^user/(?P<pk>[0-9]+)/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'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
url(r'^import/$', views.BulkImportUserView.as_view(), name='user-import'), url(r'^user/import/$', views.BulkImportUserView.as_view(), name='user-import'),
# url(r'^user/(?P<pk>[0-9]+)/assets-perm$', views.UserDetailView.as_view(), name='user-detail'), # url(r'^user/(?P<pk>[0-9]+)/assets-perm$', views.UserDetailView.as_view(), name='user-detail'),
url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'), url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'),
url(r'^user/(?P<pk>[0-9]+)/update$', views.UserUpdateView.as_view(), name='user-update'), url(r'^user/(?P<pk>[0-9]+)/update$', views.UserUpdateView.as_view(), name='user-update'),
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
import json
import uuid
import csv from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook
from openpyxl import load_workbook
from django import forms from django import forms
from django.conf import settings from django.utils import timezone
from django.core.cache import cache
from django.db import IntegrityError
from django.contrib.auth import login as auth_login, logout as auth_logout from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
from django.shortcuts import reverse, redirect from django.shortcuts import reverse, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views import View
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic.list import ListView from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView, SingleObjectMixin, \ from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView, SingleObjectMixin, FormMixin
FormMixin
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from formtools.wizard.views import SessionWizardView from formtools.wizard.views import SessionWizardView
...@@ -32,7 +38,6 @@ from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_pas ...@@ -32,7 +38,6 @@ from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_pas
from .hands import write_login_log_async from .hands import write_login_log_async
from . import forms from . import forms
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -90,7 +95,11 @@ class UserListView(AdminUserRequiredMixin, TemplateView): ...@@ -90,7 +95,11 @@ class UserListView(AdminUserRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UserListView, self).get_context_data(**kwargs) context = super(UserListView, self).get_context_data(**kwargs)
context.update({'app': _('Users'), 'action': _('User list'), 'groups': UserGroup.objects.all()}) context.update({
'app': _('Users'),
'action': _('User list'),
'groups': UserGroup.objects.all()
})
return context return context
...@@ -231,10 +240,6 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView): ...@@ -231,10 +240,6 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
return super(UserGroupDetailView, self).get_context_data(**kwargs) return super(UserGroupDetailView, self).get_context_data(**kwargs)
class UserGroupDeleteView(DeleteView):
pass
class UserForgotPasswordView(TemplateView): class UserForgotPasswordView(TemplateView):
template_name = 'users/forgot_password.html' template_name = 'users/forgot_password.html'
...@@ -490,55 +495,99 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView): ...@@ -490,55 +495,99 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
return self.render_json_response(data) return self.render_json_response(data)
def form_valid(self, form): def form_valid(self, form):
from openpyxl import load_workbook
try: try:
wb = load_workbook(form.cleaned_data['excel']) wb = load_workbook(form.cleaned_data['file'])
ws = wb['users'] ws = wb.get_active_sheet()
except Exception as e: except Exception as e:
print e print(e)
error = _('Not a valid Excel file.') data = {'valid': False, 'msg': 'Not a valid Excel file'}
data = {
'success': False,
'msg': error
}
return self.render_json_response(data) return self.render_json_response(data)
errors = [] rows = ws.rows
for index, row in enumerate(ws.rows): header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
user_data = [cell.value for cell in row] header = [col.value for col in next(rows)]
if len(user_data) != 4: print(header)
errors.append("Row {}: invalid user data format.".format(index)) if header != header_need:
continue data = {'valid': False, 'msg': 'Must be same format as template or export file'}
username, email, enable_otp, role = user_data return self.render_json_response(data)
data = {
'username': username, created = []
'email': email, updated = []
'enable_otp': True if enable_otp in ['T', '1', 1, True] else False, failed = []
'role': role for row in rows:
} user_dict = dict(zip(header, [col.value for col in row]))
form = forms.UserBulkImportForm(data, auto_id=False) groups_name = user_dict.pop('groups')
if form.is_valid(): if groups_name:
form.save() groups_name = groups_name.split(',')
groups = UserGroup.objects.filter(name__in=groups_name)
else: else:
form_errors = form.errors.as_data() groups = None
for key, err_list in form_errors.iteritems(): try:
error_line = "{} :".format(key) user = User.objects.create(**user_dict)
for errs in err_list: user_add_success_next(user)
error_line = "{}{}".format(error_line, ";".join([err for err in errs.messages])) created.append(user_dict['username'])
errors.append("Row {}: {}".format(index, error_line)) except IntegrityError as e:
user = User.objects.filter(username=user_dict['username'])
if not user:
failed.append(user_dict['username'])
continue
user.update(**user_dict)
user = user[0]
updated.append(user_dict['username'])
except TypeError as e:
print(e)
failed.append(user_dict['username'])
user = None
if user and groups:
user.groups.add(*tuple(groups))
user.save()
data = { data = {
'success': True if not errors else False, 'created': created,
'msg': 'ok' if not errors else '<br />'.join(errors) 'created_info': 'Created {}'.format(len(created)),
'updated': updated,
'updated_info': 'Updated {}'.format(len(updated)),
'failed': failed,
'failed_info': 'Failed {}'.format(len(failed)),
'valid': True,
'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(failed))
} }
return self.render_json_response(data) return self.render_json_response(data)
def down_csv(request, xx): @method_decorator(csrf_exempt, name='dispatch')
print(xx) class UserExportView(View):
response = HttpResponse(content_type='application/csv') def get(self, request, *args, **kwargs):
response['Content-Disposition'] = 'attachment; filename="somefile.csv"' spm = request.GET.get('spm', '')
writer = csv.writer(response) users_id = cache.get(spm)
writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) if not users_id and not isinstance(users_id, list):
writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) return HttpResponse('May be expired', status=404)
users = User.objects.filter(id__in=users_id)
wb = Workbook()
ws = wb.active
ws.title = 'User'
header = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
ws.append(header)
for user in users:
ws.append([user.name, user.username, user.email,
','.join([group.name for group in user.groups.all()]),
user.role, user.phone, user.wechat, user.comment])
filename = 'users-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
response = HttpResponse(save_virtual_workbook(wb), content_type='application/vnd.ms-excel')
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
return response return response
def post(self, request, *args, **kwargs):
try:
users_id = json.loads(request.body).get('users_id', [])
except ValueError:
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().get_hex()
cache.set(spm, users_id, 300)
url = reverse('users:user-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url})
ibtiff4-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk
\ No newline at end of file
...@@ -18,3 +18,5 @@ itsdangerous==0.24 ...@@ -18,3 +18,5 @@ itsdangerous==0.24
python-gssapi==0.6.4 python-gssapi==0.6.4
tornado==4.4.2 tornado==4.4.2
eventlet==0.19.0 eventlet==0.19.0
unicodecsv==0.14.1
django-filter==1.0.0
libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel
\ No newline at end of file
#!/bin/bash
#
for app in users assets perms audits teminal ops;do
rm -f ../apps/$app/migrations/000*
done
#!/bin/bash
#
python ../apps/manage.py loaddata init
#!/bin/bash
#
python ../apps/manage.py shell << EOF
from users.models import *
generate_fake()
from assets.models import *
generate_fake()
EOF
python ../apps/manage.py dbshell << EOF
delete from django_content_type;
delete from auth_permission;
EOF
python ../apps/manage.py dumpdata > ../apps/fixtures/fake.json
#!/bin/bash
#
python ../apps/manage.py shell << EOF
from users.models import *
init_all_models()
from assets.models import *
init_all_models()
EOF
python ../apps/manage.py dbshell << EOF
delete from django_content_type;
delete from auth_permission;
EOF
python ../apps/manage.py dumpdata > ../apps/fixtures/init.json
#!/bin/bash
#
python ../apps/manage.py makemigrations
python ../apps/manage.py migrate
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment