mirror of https://github.com/jumpserver/jumpserver
Merge branch 'audits'
commit
0e4804b59f
|
@ -6,17 +6,18 @@ from rest_framework.views import APIView
|
|||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView
|
||||
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 .hands import IsSuperUserOrTerminalUser, IsSuperUser
|
||||
from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser
|
||||
from . import serializers
|
||||
|
||||
|
||||
class AssetViewSet(viewsets.ModelViewSet):
|
||||
class AssetViewSet(IDInFilterMixin, viewsets.ModelViewSet):
|
||||
"""API endpoint that allows Asset to be viewed or edited."""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
filter_fields = ('id', 'ip', 'hostname')
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(AssetViewSet, self).get_queryset()
|
||||
|
@ -27,7 +28,6 @@ class AssetViewSet(viewsets.ModelViewSet):
|
|||
|
||||
if asset_group_id:
|
||||
queryset = queryset.filter(groups__id=asset_group_id)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
|
@ -71,7 +71,7 @@ class SystemUserViewSet(viewsets.ModelViewSet):
|
|||
# return self.object.assets.all()
|
||||
|
||||
|
||||
class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
|
|
@ -309,4 +309,8 @@ class AssetTagForm(forms.ModelForm):
|
|||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField()
|
|
@ -60,38 +60,6 @@ class IDC(models.Model):
|
|||
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):
|
||||
if not validate_ssh_private_key(value):
|
||||
raise ValidationError(
|
||||
|
@ -233,6 +201,10 @@ class SystemUser(models.Model):
|
|||
def assets_amount(self):
|
||||
return self.assets.count()
|
||||
|
||||
@property
|
||||
def asset_group_amount(self):
|
||||
return self.asset_groups.count()
|
||||
|
||||
class Meta:
|
||||
db_table = 'system_user'
|
||||
|
||||
|
@ -294,18 +266,29 @@ class AssetGroup(models.Model):
|
|||
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():
|
||||
return IDC.initial()
|
||||
|
||||
|
||||
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)
|
||||
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'))
|
||||
|
@ -326,15 +309,12 @@ class Asset(models.Model):
|
|||
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'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
status = models.ForeignKey(AssetExtend, null=True, blank=True,
|
||||
related_name="status_asset", verbose_name=_('Asset status'),)
|
||||
# default=functools.partial(get_default_extend, 'status', 'In use'))
|
||||
type = models.ForeignKey(AssetExtend, blank=True,null=True, limit_choices_to={'key': 'type'},
|
||||
related_name="type_asset", verbose_name=_('Asset type'),)
|
||||
# default=functools.partial(get_default_extend, 'type','Server'))
|
||||
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'))
|
||||
status = models.CharField(choices=STATUS_CHOICES, max_length=8, null=True, blank=True,
|
||||
default='In use', verbose_name=_('Asset status'))
|
||||
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
|
||||
default='Server', verbose_name=_('Asset type'),)
|
||||
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
|
||||
default='Prod', verbose_name=_('Asset environment'),)
|
||||
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'))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
@ -400,7 +380,7 @@ class Tag(models.Model):
|
|||
|
||||
|
||||
def init_all_models():
|
||||
for cls in (AssetExtend, AssetGroup):
|
||||
for cls in (AssetGroup,):
|
||||
cls.initial()
|
||||
|
||||
|
||||
|
@ -410,5 +390,5 @@ def generate_fake():
|
|||
|
||||
|
||||
def flush_all():
|
||||
for cls in (AssetGroup, AssetExtend, IDC, AdminUser, SystemUser, Asset):
|
||||
for cls in (AssetGroup, IDC, AdminUser, SystemUser, Asset):
|
||||
cls.objects.all().delete()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import viewsets, serializers,generics
|
||||
from .models import AssetGroup, Asset, IDC, AssetExtend, AdminUser, SystemUser
|
||||
from common.mixins import BulkDeleteApiMixin
|
||||
from .models import AssetGroup, Asset, IDC, AdminUser, SystemUser
|
||||
from common.mixins import IDInFilterMixin
|
||||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
|
||||
|
||||
|
||||
|
@ -35,7 +35,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.append('assets_amount')
|
||||
fields.extend(['assets_amount'])
|
||||
return fields
|
||||
|
||||
|
||||
|
@ -43,7 +43,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
# system_users = SystemUserSerializer(many=True, read_only=True)
|
||||
# admin_user = AdminUserSerializer(many=False, read_only=True)
|
||||
hardware = serializers.SerializerMethodField()
|
||||
type_display = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
|
@ -51,15 +50,16 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
|
||||
@staticmethod
|
||||
def get_hardware(obj):
|
||||
return '%s %s %s' % (obj.cpu, obj.memory, obj.disk)
|
||||
|
||||
@staticmethod
|
||||
def get_type_display(obj):
|
||||
if obj.type:
|
||||
return obj.type.value
|
||||
if obj.cpu:
|
||||
return '%s %s %s' % (obj.cpu, obj.memory, obj.disk)
|
||||
else:
|
||||
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):
|
||||
system_users = SystemUserSerializer(many=True, read_only=True)
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
{% 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(){
|
|||
var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData;
|
||||
$(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) {
|
||||
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);
|
||||
|
@ -55,7 +48,6 @@ $(document).ready(function(){
|
|||
ajax_url: '{% url "api-assets:admin-user-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () {return 'lost'} },
|
||||
{data: "comment" }, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
});
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset status' %}:</td>
|
||||
<td><b>{{ asset.status }}</b></td>
|
||||
<td><b>{{ asset.get_status_display() }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Is active' %}:</td>
|
||||
|
|
|
@ -18,10 +18,11 @@
|
|||
{% block table_search %}
|
||||
<div class="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default buttons-pdf" tabindex="0" href="#">
|
||||
<span>PDF</span></a>
|
||||
<a class="btn btn-default buttons-excel" tabindex="0" href="#">
|
||||
<span>Excel</span>
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -32,7 +33,7 @@
|
|||
<div class="tagBtnList">
|
||||
{% for tag in tag_list %}
|
||||
<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">
|
||||
{% else %}
|
||||
class="tagBtn2 label label-default">
|
||||
|
@ -47,7 +48,6 @@
|
|||
|
||||
{% 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"><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" >
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -80,11 +80,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_import_modal.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
window.onload=function (){
|
||||
window.onload = function (){
|
||||
var tag_on = document.getElementsByName("tag_on");
|
||||
var oDiv = document.getElementById("ydxbd");
|
||||
if(tag_on.length > 0){
|
||||
|
@ -99,7 +101,7 @@
|
|||
}else{
|
||||
oDiv.style.display = "none";
|
||||
}
|
||||
}; //onload;
|
||||
} //onload;
|
||||
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
|
@ -131,11 +133,52 @@
|
|||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "type_display" }, {data: "env"}, {data: "hardware"}, {data: "is_active" },
|
||||
{data: "is_active"}, {data: "id" }],
|
||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
|
||||
{data: "is_active" }, {data: "is_active"}, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
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});
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,43 +1,60 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5">
|
||||
<a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="system_user_list_table" >
|
||||
<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 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% 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 %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=username">{% trans 'Username' %}</a></th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||
<th class="text-center">{% trans 'Asset group num' %}</th>
|
||||
<th class="text-center">{% trans 'Unreachable' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center"></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>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ urlpatterns = [
|
|||
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
|
||||
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
|
||||
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]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.writer.excel import save_virtual_workbook
|
||||
from openpyxl import load_workbook
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.views.generic import TemplateView, ListView
|
||||
from django.db import IntegrityError
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.shortcuts import get_object_or_404, reverse, redirect
|
||||
from common.utils import int_seq
|
||||
from .utils import CreateAssetTagsMiXin,UpdateAssetTagsMiXin
|
||||
from .models import Asset, AssetGroup, IDC, AssetExtend, AdminUser, SystemUser, Tag
|
||||
from .forms import *
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.views.decorators.csrf import csrf_protect, csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none
|
||||
from .utils import CreateAssetTagsMiXin, UpdateAssetTagsMiXin
|
||||
from . import forms
|
||||
from .models import Asset, AssetGroup, AdminUser, IDC, SystemUser, Tag
|
||||
from .hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
|
@ -33,7 +47,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
|
|||
class AssetCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, CreateView):
|
||||
model = Asset
|
||||
tag_type = 'asset'
|
||||
form_class = AssetCreateForm
|
||||
form_class = forms.AssetCreateForm
|
||||
template_name = 'assets/asset_create.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
|
@ -59,7 +73,7 @@ class AssetCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, CreateView):
|
|||
|
||||
class AssetModalCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, ListView):
|
||||
model = Asset
|
||||
form_class = AssetCreateForm
|
||||
form_class = forms.AssetCreateForm
|
||||
template_name = 'assets/asset_modal_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
|
@ -87,7 +101,7 @@ class AssetModalCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, ListVie
|
|||
|
||||
class AssetUpdateView(AdminUserRequiredMixin, UpdateAssetTagsMiXin, UpdateView):
|
||||
model = Asset
|
||||
form_class = AssetCreateForm
|
||||
form_class = forms.AssetCreateForm
|
||||
template_name = 'assets/asset_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
new_form = ''
|
||||
|
@ -214,7 +228,7 @@ class AssetModalListView(AdminUserRequiredMixin, ListView):
|
|||
|
||||
class AssetGroupCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = AssetGroup
|
||||
form_class = AssetGroupForm
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
#ordering = '-id'
|
||||
|
@ -275,7 +289,7 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
|
|||
|
||||
class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = AssetGroup
|
||||
form_class = AssetGroupForm
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
|
||||
|
@ -317,7 +331,7 @@ class IDCListView(AdminUserRequiredMixin, TemplateView):
|
|||
|
||||
class IDCCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = IDC
|
||||
form_class = IDCForm
|
||||
form_class = forms.IDCForm
|
||||
template_name = 'assets/idc_create_update.html'
|
||||
success_url = reverse_lazy('assets:idc-list')
|
||||
|
||||
|
@ -339,7 +353,7 @@ class IDCCreateView(AdminUserRequiredMixin, CreateView):
|
|||
|
||||
class IDCUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = IDC
|
||||
form_class = IDCForm
|
||||
form_class = forms.IDCForm
|
||||
template_name = 'assets/idc_create_update.html'
|
||||
context_object_name = 'idc'
|
||||
success_url = reverse_lazy('assets:idc-list')
|
||||
|
@ -408,7 +422,7 @@ class AdminUserListView(AdminUserRequiredMixin, TemplateView):
|
|||
|
||||
class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = AdminUser
|
||||
form_class = AdminUserForm
|
||||
form_class = forms.AdminUserForm
|
||||
template_name = 'assets/admin_user_create_update.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
|
||||
|
@ -434,7 +448,7 @@ class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
|
|||
|
||||
class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = AdminUser
|
||||
form_class = AdminUserForm
|
||||
form_class = forms.AdminUserForm
|
||||
template_name = 'assets/admin_user_create_update.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -478,39 +492,21 @@ class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
|||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
|
||||
|
||||
class SystemUserListView(AdminUserRequiredMixin, ListView):
|
||||
model = SystemUser
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
context_object_name = 'system_user_list'
|
||||
class SystemUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/system_user_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('System user list'),
|
||||
'keyword': self.request.GET.get('keyword', '')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(SystemUserListView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
# Todo: Default order by lose asset connection num
|
||||
self.queryset = super(SystemUserListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.sort = sort = self.request.GET.get('sort', '-date_created')
|
||||
|
||||
if keyword:
|
||||
self.queryset = self.queryset.filter(Q(name__icontains=keyword) |
|
||||
Q(comment__icontains=keyword))
|
||||
|
||||
if sort:
|
||||
self.queryset = self.queryset.order_by(sort)
|
||||
return self.queryset
|
||||
|
||||
|
||||
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = SystemUser
|
||||
form_class = SystemUserForm
|
||||
form_class = forms.SystemUserForm
|
||||
template_name = 'assets/system_user_create_update.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
|
||||
|
@ -534,7 +530,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
|
|||
|
||||
class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = SystemUser
|
||||
form_class = SystemUserForm
|
||||
form_class = forms.SystemUserForm
|
||||
template_name = 'assets/system_user_create_update.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -636,7 +632,7 @@ class TagsListView(AdminUserRequiredMixin, ListView):
|
|||
|
||||
class AssetTagCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = Tag
|
||||
form_class = AssetTagForm
|
||||
form_class = forms.AssetTagForm
|
||||
template_name = 'assets/asset_tag_create.html'
|
||||
success_url = reverse_lazy('assets:asset-tag-list')
|
||||
#ordering = '-id'
|
||||
|
@ -685,7 +681,7 @@ class AssetTagDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView):
|
|||
|
||||
class AssetTagUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = Tag
|
||||
form_class = AssetTagForm
|
||||
form_class = forms.AssetTagForm
|
||||
template_name = 'assets/asset_tag_create.html'
|
||||
success_url = reverse_lazy('assets:asset-tag-list')
|
||||
|
||||
|
@ -711,3 +707,127 @@ class AssetTagDeleteView(AdminUserRequiredMixin, DeleteView):
|
|||
model = Tag
|
||||
success_url = reverse_lazy('assets:asset-tag-list')
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(View):
|
||||
@staticmethod
|
||||
def get_asset_attr(asset, attr):
|
||||
if attr in ['admin_user', 'idc']:
|
||||
return getattr(asset, attr).name
|
||||
elif attr in ['status', 'type', 'env']:
|
||||
return getattr(asset, 'get_{}_display'.format(attr))()
|
||||
else:
|
||||
return getattr(asset, attr)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id = cache.get(spm)
|
||||
if not assets_id and not isinstance(assets_id, list):
|
||||
return HttpResponse('May be expired', status=404)
|
||||
|
||||
assets = Asset.objects.filter(id__in=assets_id)
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'Asset'
|
||||
header = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'cpu', 'memory', 'disk',
|
||||
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no',
|
||||
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment']
|
||||
ws.append(header)
|
||||
|
||||
for asset in assets:
|
||||
ws.append([self.get_asset_attr(asset, attr) for attr in header])
|
||||
|
||||
filename = 'assets-{}.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
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
assets_id = json.loads(request.body).get('assets_id', [])
|
||||
print(assets_id)
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
spm = uuid.uuid4().get_hex()
|
||||
cache.set(spm, assets_id, 300)
|
||||
url = reverse('assets:asset-export') + '?spm=%s' % spm
|
||||
return JsonResponse({'redirect': url})
|
||||
|
||||
|
||||
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
form_class = forms.FileForm
|
||||
|
||||
def form_valid(self, form):
|
||||
try:
|
||||
wb = load_workbook(form.cleaned_data['file'])
|
||||
ws = wb.get_active_sheet()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
data = {'valid': False, 'msg': 'Not a valid Excel file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
rows = ws.rows
|
||||
header_all = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'cpu', 'memory', 'disk',
|
||||
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no',
|
||||
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment']
|
||||
header_min = ['hostname', 'ip', 'port', 'admin_user', 'comment']
|
||||
header = [col.value for col in next(rows)]
|
||||
if not set(header).issubset(set(header_all)) and not set(header).issuperset(set(header_min)):
|
||||
data = {'valid': False, 'msg': 'Must be same format as template or export file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
created = []
|
||||
updated = []
|
||||
failed = []
|
||||
for row in rows:
|
||||
asset_dict = dict(zip(header, [col.value for col in row]))
|
||||
if asset_dict.get('admin_user', None):
|
||||
admin_user = get_object_or_none(AdminUser, name=asset_dict['admin_user'])
|
||||
asset_dict['admin_user'] = admin_user
|
||||
|
||||
if asset_dict.get('idc'):
|
||||
idc = get_object_or_none(IDC, name=asset_dict['idc'])
|
||||
asset_dict['idc'] = idc
|
||||
|
||||
if asset_dict.get('type'):
|
||||
asset_display_type_map = dict(zip(dict(Asset.TYPE_CHOICES).values(), dict(Asset.TYPE_CHOICES).keys()))
|
||||
asset_type = asset_display_type_map.get(asset_dict['type'], 'Server')
|
||||
asset_dict['type'] = asset_type
|
||||
|
||||
if asset_dict.get('status'):
|
||||
asset_display_status_map = dict(zip(dict(Asset.STATUS_CHOICES).values(),
|
||||
dict(Asset.STATUS_CHOICES).keys()))
|
||||
asset_status = asset_display_status_map.get(asset_dict['status'], 'In use')
|
||||
asset_dict['status'] = asset_status
|
||||
|
||||
if asset_dict.get('env'):
|
||||
asset_display_env_map = dict(zip(dict(Asset.ENV_CHOICES).values(),
|
||||
dict(Asset.ENV_CHOICES).keys()))
|
||||
asset_env = asset_display_env_map.get(asset_dict['env'], 'Prod')
|
||||
asset_dict['env'] = asset_env
|
||||
|
||||
try:
|
||||
Asset.objects.create(**asset_dict)
|
||||
created.append(asset_dict['ip'])
|
||||
except IntegrityError as e:
|
||||
asset = Asset.objects.filter(ip=asset_dict['ip'], port=asset_dict['port'])
|
||||
if not asset:
|
||||
failed.append(asset_dict['ip'])
|
||||
continue
|
||||
asset.update(**asset_dict)
|
||||
updated.append(asset_dict['ip'])
|
||||
except TypeError as e:
|
||||
print(e)
|
||||
failed.append(asset_dict['ip'])
|
||||
|
||||
data = {
|
||||
'created': created,
|
||||
'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)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
|
@ -91,6 +92,7 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.footable').footable();
|
||||
|
|
|
@ -40,14 +40,13 @@ class NoDeleteModelMixin(models.Model):
|
|||
|
||||
|
||||
class JSONResponseMixin(object):
|
||||
|
||||
"""JSON mixin"""
|
||||
|
||||
def render_json_response(self, context):
|
||||
@staticmethod
|
||||
def render_json_response(context):
|
||||
return JsonResponse(context)
|
||||
|
||||
|
||||
class BulkDeleteApiMixin(object):
|
||||
class IDInFilterMixin(object):
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
id_list = self.request.query_params.get('id__in')
|
||||
|
|
|
@ -44,6 +44,7 @@ def join_attr(seq, attr=None, sep=None):
|
|||
print(seq)
|
||||
return sep.join(seq)
|
||||
|
||||
|
||||
@register.filter
|
||||
def IntToStr(value):
|
||||
def int_to_str(value):
|
||||
return str(value)
|
File diff suppressed because one or more lines are too long
|
@ -1 +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-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]}}]
|
|
@ -98,6 +98,7 @@ TEMPLATES = [
|
|||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.request',
|
||||
'django.template.context_processors.media',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -262,7 +263,6 @@ REST_FRAMEWORK = {
|
|||
# Use Django's standard `django.contrib.auth` permissions,
|
||||
# or allow read-only access for unauthenticated users.
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
# 'rest_framework.permissions.IsAuthenticated',
|
||||
'users.backends.IsValidUser',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
|
@ -272,6 +272,7 @@ REST_FRAMEWORK = {
|
|||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
),
|
||||
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
|
||||
}
|
||||
|
||||
# Custom User Auth model
|
||||
|
|
Binary file not shown.
|
@ -214,9 +214,7 @@ function APIUpdateAttr(props) {
|
|||
|
||||
// Sweet Alert for Delete
|
||||
function objectDelete(obj, name, url) {
|
||||
var $this = $(this);
|
||||
function doDelete() {
|
||||
var uid = $this.data('uid');
|
||||
var body = {};
|
||||
var success = function() {
|
||||
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
|
@ -292,7 +290,7 @@ jumpserver.initDataTable = function (options) {
|
|||
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
|
||||
var table = ele.DataTable({
|
||||
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: {
|
||||
url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json"
|
||||
},
|
||||
|
|
|
@ -24,10 +24,10 @@ class TerminalSerializer(serializers.ModelSerializer):
|
|||
@staticmethod
|
||||
def get_is_alive(obj):
|
||||
log = obj.terminalheatbeat_set.last()
|
||||
if timezone.now() - log.date_created > timezone.timedelta(seconds=600):
|
||||
return False
|
||||
else:
|
||||
if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class TerminalHeatbeatSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
import base64
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.core.cache import cache
|
||||
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.views import APIView
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView, BulkModelViewSet
|
||||
from rest_framework import authentication
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
||||
from common.mixins import BulkDeleteApiMixin
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from .utils import check_user_valid, token_gen
|
||||
from .models import User, UserGroup
|
||||
|
@ -24,10 +22,12 @@ from . import serializers
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class UserViewSet(BulkModelViewSet):
|
||||
class UserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = serializers.UserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filter_fields = ('username', 'email', 'name', 'id')
|
||||
|
||||
|
||||
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
|
||||
|
@ -73,7 +73,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
|
|||
user.save()
|
||||
|
||||
|
||||
class UserGroupViewSet(viewsets.ModelViewSet):
|
||||
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
queryset = UserGroup.objects.all()
|
||||
serializer_class = serializers.UserGroupSerializer
|
||||
|
||||
|
@ -83,52 +83,7 @@ class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
|
|||
serializer_class = serializers.UserGroupUpdateMemeberSerializer
|
||||
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):
|
||||
permission_classes = ()
|
||||
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
|
||||
|
|
|
@ -141,4 +141,4 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
|
|||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
excel = forms.FileField()
|
||||
file = forms.FileField()
|
||||
|
|
|
@ -79,7 +79,7 @@ class User(AbstractUser):
|
|||
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'))
|
||||
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'))
|
||||
secret_key_otp = models.CharField(max_length=16, blank=True)
|
||||
_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
|
|||
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):
|
||||
groups_display = serializers.SerializerMethodField()
|
||||
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
|
||||
|
@ -33,10 +27,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
def get_groups_display(obj):
|
||||
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 Meta:
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}user_import_modal{% endblock %}
|
||||
{% block modal_title%}{% trans "Import User" %}{% endblock %}
|
||||
{% block modal_title%}{% trans "Import user" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<p class="text-success text-center">{% trans "Hint: your excel should organized in the following format." %}</p>
|
||||
<p class="text-success text-center">{% trans "* You should have a very worksheet named `users`." %}</p>
|
||||
<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">
|
||||
<p class="text-success">{% trans "Download template or use export excel format" %}</p>
|
||||
<form method="post" action="{% url 'users:user-import' %}" id="fm_user_import" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2 col-lg-2 " for="id_excel">{% trans "Excel" %}</label>
|
||||
<div class=" col-sm-9 col-lg-9 ">
|
||||
<input id="id_excel" type="file" name="excel" />
|
||||
</div>
|
||||
<label class="control-label" for="id_users">{% trans "Template" %}</label>
|
||||
<a href="{{ MEDIA_URL }}files/user_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
|
||||
</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>
|
||||
</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_user_import{% endblock %}
|
||||
|
|
|
@ -169,7 +169,7 @@
|
|||
id: $this.attr('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 () {
|
||||
$this.closest('tr').remove();
|
||||
};
|
||||
|
|
|
@ -165,7 +165,7 @@
|
|||
id: $this.attr('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 () {
|
||||
$this.closest('tr').remove();
|
||||
};
|
||||
|
|
|
@ -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"},
|
||||
{data: "system_users_join"}, {data: "is_active"}]
|
||||
};
|
||||
|
|
|
@ -47,8 +47,11 @@ $(document).ready(function() {
|
|||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{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 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);
|
||||
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 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) {
|
||||
$(td).html(update_btn)
|
||||
} else {
|
||||
|
@ -63,38 +66,10 @@ $(document).ready(function() {
|
|||
jumpserver.initDataTable(options);
|
||||
}).on('click', '.btn_delete_user_group', function(){
|
||||
var $this = $(this);
|
||||
function doDelete() {
|
||||
var group_id = $this.data('gid');
|
||||
var the_url = "{% url 'api-users:user-group-detail' pk=99991937 %}".replace('99991937', group_id);
|
||||
var body = {};
|
||||
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();
|
||||
});
|
||||
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);
|
||||
objectDelete($this, name, the_url)
|
||||
}).on('click', '#btn_bulk_update', function(){
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#group_list_table').DataTable()
|
||||
|
|
|
@ -3,17 +3,18 @@
|
|||
{% block table_search %}
|
||||
<div class="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default buttons-pdf" tabindex="0" href="#">
|
||||
<span>PDF</span></a>
|
||||
<a class="btn btn-default buttons-excel" tabindex="0" href="#">
|
||||
<span>Excel</span>
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#user_import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% 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"><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" >
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -38,6 +39,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 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">
|
||||
|
@ -74,7 +76,9 @@ $(document).ready(function(){
|
|||
}},
|
||||
{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 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") {
|
||||
$(td).html(update_btn)
|
||||
} else {
|
||||
|
@ -82,20 +86,52 @@ $(document).ready(function(){
|
|||
}
|
||||
}}],
|
||||
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" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
var table = jumpserver.initDataTable(options);
|
||||
|
||||
$('.buttons-pdf').click(function () {
|
||||
$('.btn_export').click(function () {
|
||||
var users = [];
|
||||
var rows = table.rows('.selected').data();
|
||||
$.each(rows, function (index, obj) {
|
||||
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');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$('#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 $data_table = $('#user_list_table').DataTable();
|
||||
|
@ -117,6 +153,14 @@ $(document).ready(function(){
|
|||
$data_table.ajax.reload();
|
||||
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() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
|
@ -154,43 +198,18 @@ $(document).ready(function(){
|
|||
case 'update':
|
||||
doUpdate();
|
||||
break;
|
||||
case 'active':
|
||||
doActive();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}).on('click', '.btn_user_delete', function(){
|
||||
var $this = $(this);
|
||||
function doDelete() {
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
var body = {};
|
||||
var success = function() {
|
||||
var msg = "{% trans 'User Deleted.' %}";
|
||||
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();
|
||||
});
|
||||
var name = $this.data('name');
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
}).on('click', '#btn_user_bulk_update', function(){
|
||||
var json_data = $('#fm_user_bulk_update').serializeObject();
|
||||
var body = {};
|
||||
|
@ -204,10 +223,10 @@ $(document).ready(function(){
|
|||
if (typeof body.groups === 'string') {
|
||||
body.groups = [parseInt(body.groups)]
|
||||
} else if(typeof body.groups === 'array') {
|
||||
new_groups = body.groups.map(Number);
|
||||
var new_groups = body.groups.map(Number);
|
||||
body.groups = new_groups;
|
||||
}
|
||||
var $data_table = $('#user_list_table').DataTable()
|
||||
var $data_table = $('#user_list_table').DataTable();
|
||||
var post_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
var content = Object.assign({id: this.data().id}, body);
|
||||
|
@ -225,22 +244,7 @@ $(document).ready(function(){
|
|||
};
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
|
||||
$('#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>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -23,8 +23,9 @@ urlpatterns = [
|
|||
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]+)/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'^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/create$', views.UserCreateView.as_view(), name='user-create'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/update$', views.UserUpdateView.as_view(), name='user-update'),
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
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.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.mixins import LoginRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
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.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import View
|
||||
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.generic.base import TemplateView
|
||||
from django.views.generic.list import ListView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView, SingleObjectMixin, \
|
||||
FormMixin
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView, SingleObjectMixin, FormMixin
|
||||
from django.views.generic.detail import DetailView
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
|
||||
|
@ -32,7 +38,6 @@ from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_pas
|
|||
from .hands import write_login_log_async
|
||||
from . import forms
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
|
@ -90,7 +95,11 @@ class UserListView(AdminUserRequiredMixin, TemplateView):
|
|||
|
||||
def get_context_data(self, **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
|
||||
|
||||
|
||||
|
@ -231,10 +240,6 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super(UserGroupDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserGroupDeleteView(DeleteView):
|
||||
pass
|
||||
|
||||
|
||||
class UserForgotPasswordView(TemplateView):
|
||||
template_name = 'users/forgot_password.html'
|
||||
|
||||
|
@ -490,55 +495,99 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
return self.render_json_response(data)
|
||||
|
||||
def form_valid(self, form):
|
||||
from openpyxl import load_workbook
|
||||
try:
|
||||
wb = load_workbook(form.cleaned_data['excel'])
|
||||
ws = wb['users']
|
||||
wb = load_workbook(form.cleaned_data['file'])
|
||||
ws = wb.get_active_sheet()
|
||||
except Exception as e:
|
||||
print e
|
||||
error = _('Not a valid Excel file.')
|
||||
data = {
|
||||
'success': False,
|
||||
'msg': error
|
||||
}
|
||||
print(e)
|
||||
data = {'valid': False, 'msg': 'Not a valid Excel file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
errors = []
|
||||
for index, row in enumerate(ws.rows):
|
||||
user_data = [cell.value for cell in row]
|
||||
if len(user_data) != 4:
|
||||
errors.append("Row {}: invalid user data format.".format(index))
|
||||
continue
|
||||
username, email, enable_otp, role = user_data
|
||||
data = {
|
||||
'username': username,
|
||||
'email': email,
|
||||
'enable_otp': True if enable_otp in ['T', '1', 1, True] else False,
|
||||
'role': role
|
||||
}
|
||||
form = forms.UserBulkImportForm(data, auto_id=False)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
rows = ws.rows
|
||||
header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
|
||||
header = [col.value for col in next(rows)]
|
||||
print(header)
|
||||
if header != header_need:
|
||||
data = {'valid': False, 'msg': 'Must be same format as template or export file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
created = []
|
||||
updated = []
|
||||
failed = []
|
||||
for row in rows:
|
||||
user_dict = dict(zip(header, [col.value for col in row]))
|
||||
groups_name = user_dict.pop('groups')
|
||||
if groups_name:
|
||||
groups_name = groups_name.split(',')
|
||||
groups = UserGroup.objects.filter(name__in=groups_name)
|
||||
else:
|
||||
form_errors = form.errors.as_data()
|
||||
for key, err_list in form_errors.iteritems():
|
||||
error_line = "{} :".format(key)
|
||||
for errs in err_list:
|
||||
error_line = "{}{}".format(error_line, ";".join([err for err in errs.messages]))
|
||||
errors.append("Row {}: {}".format(index, error_line))
|
||||
groups = None
|
||||
try:
|
||||
user = User.objects.create(**user_dict)
|
||||
user_add_success_next(user)
|
||||
created.append(user_dict['username'])
|
||||
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 = {
|
||||
'success': True if not errors else False,
|
||||
'msg': 'ok' if not errors else '<br />'.join(errors)
|
||||
'created': created,
|
||||
'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)
|
||||
|
||||
|
||||
def down_csv(request, xx):
|
||||
print(xx)
|
||||
response = HttpResponse(content_type='application/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="somefile.csv"'
|
||||
writer = csv.writer(response)
|
||||
writer.writerow(['First row', 'Foo', 'Bar', 'Baz'])
|
||||
writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"])
|
||||
return response
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class UserExportView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
spm = request.GET.get('spm', '')
|
||||
users_id = cache.get(spm)
|
||||
if not users_id and not isinstance(users_id, list):
|
||||
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
|
||||
|
||||
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})
|
||||
|
||||
|
|
|
@ -14,4 +14,6 @@ djangorestframework-bulk==0.2.1
|
|||
paramiko==2.0.2
|
||||
django-redis-cache==1.7.1
|
||||
requests==2.11.1
|
||||
itsdangerous==0.24
|
||||
itsdangerous==0.24
|
||||
unicodecsv==0.14.1
|
||||
django-filter==1.0.0
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
for app in users assets perms audits teminal ops;do
|
||||
rm -f ../apps/$app/migrations/000*
|
||||
done
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
python ../apps/manage.py loaddata init
|
|
@ -0,0 +1,17 @@
|
|||
#!/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
|
|
@ -0,0 +1,18 @@
|
|||
#!/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
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
python ../apps/manage.py makemigrations
|
||||
|
||||
python ../apps/manage.py migrate
|
Loading…
Reference in New Issue