mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-24 14:02:01 +00:00
Merge branch 'audits'
This commit is contained in:
commit
6e69c018b4
@ -1,33 +1,29 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework import viewsets, serializers, generics
|
||||
from rest_framework import viewsets, generics, mixins
|
||||
from rest_framework.response import Response
|
||||
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.utils import get_object_or_none, signer
|
||||
from .hands import IsSuperUserOrTerminalUser, IsSuperUser
|
||||
from .models import AssetGroup, Asset, IDC, SystemUser
|
||||
from .serializers import AssetBulkUpdateSerializer
|
||||
from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser
|
||||
from . import serializers
|
||||
|
||||
|
||||
class AssetGroupSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
class AssetViewSet(viewsets.ModelViewSet):
|
||||
"""API endpoint that allows Asset to be viewed or edited."""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
|
||||
|
||||
class AssetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
# fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
|
||||
|
||||
|
||||
class IDCSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = IDC
|
||||
# fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
|
||||
def get_queryset(self):
|
||||
queryset = super(AssetViewSet, self).get_queryset()
|
||||
idc = self.request.query_params.get('idc', '')
|
||||
if idc:
|
||||
queryset = queryset.filter(idc__id=idc)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetGroupViewSet(viewsets.ModelViewSet):
|
||||
@ -35,26 +31,44 @@ class AssetGroupViewSet(viewsets.ModelViewSet):
|
||||
some other comment
|
||||
"""
|
||||
queryset = AssetGroup.objects.all()
|
||||
serializer_class = AssetGroupSerializer
|
||||
serializer_class = serializers.AssetGroupSerializer
|
||||
|
||||
|
||||
class AssetViewSet(viewsets.ModelViewSet):
|
||||
"""API endpoint that allows Asset to be viewed or edited."""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = AssetSerializer
|
||||
|
||||
|
||||
class IDCViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class IDCViewSet(viewsets.ModelViewSet):
|
||||
"""API endpoint that allows IDC to be viewed or edited."""
|
||||
queryset = IDC.objects.all()
|
||||
serializer_class = IDCSerializer
|
||||
serializer_class = serializers.IDCSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AdminUserViewSet(viewsets.ModelViewSet):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class SystemUserViewSet(viewsets.ModelViewSet):
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
# class IDCAssetsApi(generics.ListAPIView):
|
||||
# model = IDC
|
||||
# serializer_class = serializers.AssetSerializer
|
||||
#
|
||||
# def get(self, request, *args, **kwargs):
|
||||
# filter_kwargs = {self.lookup_field: self.kwargs[self.lookup_field]}
|
||||
# self.object = get_object_or_404(self.model, **filter_kwargs)
|
||||
# return super(IDCAssetsApi, self).get(request, *args, **kwargs)
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# return self.object.assets.all()
|
||||
|
||||
|
||||
class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = AssetBulkUpdateSerializer
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
# coding:utf-8
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser, Tag
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen
|
||||
|
||||
|
||||
# class AssetForm(forms.ModelForm):
|
||||
@ -23,12 +24,10 @@ from django.utils.translation import gettext_lazy as _
|
||||
#
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs.get('instance', None)
|
||||
if instance:
|
||||
initial = kwargs.get('initial', {})
|
||||
#tags = instance.tags.all()
|
||||
initial['tags'] = [t.pk for t in kwargs['instance'].tags.all()]
|
||||
super(AssetCreateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
@ -141,7 +140,6 @@ class AdminUserForm(forms.ModelForm):
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
|
||||
)
|
||||
auto_generate_key = forms.BooleanField(required=True, initial=True)
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(widget=forms.PasswordInput, max_length=100, min_length=8, strip=True,
|
||||
help_text=_('If also set private key, use that first'), required=False)
|
||||
@ -166,21 +164,36 @@ class AdminUserForm(forms.ModelForm):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
admin_user = super(AdminUserForm, self).save(commit=commit)
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
private_key = self.cleaned_data['private_key_file']
|
||||
public_key = ssh_pubkey_gen(private_key)
|
||||
|
||||
if password:
|
||||
admin_user.password = password
|
||||
print(password)
|
||||
# Todo: Validate private key file, and generate public key
|
||||
# Todo: Auto generate private key and public key
|
||||
if private_key_file:
|
||||
admin_user.private_key = private_key_file.read()
|
||||
if private_key:
|
||||
admin_user.private_key = private_key
|
||||
admin_user.public_key = public_key
|
||||
admin_user.save()
|
||||
return self.instance
|
||||
return admin_user
|
||||
|
||||
def clean_private_key_file(self):
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read()
|
||||
if not validate_ssh_private_key(private_key):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return private_key
|
||||
return private_key_file
|
||||
|
||||
def clean(self):
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data.get('private_key_file', '')
|
||||
|
||||
if not (password or private_key_file):
|
||||
raise forms.ValidationError(_('Password and private key file must be input one'))
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = ['name', 'username', 'auto_generate_key', 'password', 'private_key_file', 'as_default', 'comment']
|
||||
fields = ['name', 'username', 'password', 'private_key_file', 'comment']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
|
@ -6,8 +6,9 @@ from django.db import models
|
||||
from django.core import serializers
|
||||
import logging
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from common.utils import signer
|
||||
from common.utils import signer, validate_ssh_private_key
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -91,13 +92,21 @@ class AssetExtend(models.Model):
|
||||
unique_together = ('key', 'value')
|
||||
|
||||
|
||||
def private_key_validator(value):
|
||||
if not validate_ssh_private_key(value):
|
||||
raise ValidationError(
|
||||
_('%(value)s is not an even number'),
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
|
||||
class AdminUser(models.Model):
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=16, verbose_name=_('Username'))
|
||||
_password = models.CharField(max_length=256, blank=True, verbose_name=_('Password'))
|
||||
_private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key'))
|
||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
_private_key = models.CharField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'),
|
||||
validators=[private_key_validator,])
|
||||
_public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
as_default = models.BooleanField(default=False, verbose_name=_('As default'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True)
|
||||
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
|
||||
@ -107,7 +116,7 @@ class AdminUser(models.Model):
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return decrypt(self._password)
|
||||
return signer.unsign(self._password)
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
@ -129,6 +138,10 @@ class AdminUser(models.Model):
|
||||
def public_key(self, public_key_raw):
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return self.assets.count()
|
||||
|
||||
class Meta:
|
||||
db_table = 'admin_user'
|
||||
|
||||
@ -216,6 +229,10 @@ class SystemUser(models.Model):
|
||||
assets = set(self.assets.all()) | self.get_assets_inherit_from_asset_groups()
|
||||
return list(assets)
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return self.assets.count()
|
||||
|
||||
class Meta:
|
||||
db_table = 'system_user'
|
||||
|
||||
@ -298,9 +315,8 @@ class Asset(models.Model):
|
||||
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
|
||||
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
|
||||
system_users = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User"))
|
||||
idc = models.ForeignKey(IDC, null=True, related_name='assets',
|
||||
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets',
|
||||
on_delete=models.SET_NULL, verbose_name=_('IDC'),)
|
||||
# default=get_default_idc)
|
||||
mac_address = models.CharField(max_length=20, null=True, blank=True, verbose_name=_("Mac address"))
|
||||
brand = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Brand'))
|
||||
cpu = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU'))
|
||||
@ -329,6 +345,7 @@ class Asset(models.Model):
|
||||
def __unicode__(self):
|
||||
return '%(ip)s:%(port)s' % {'ip': self.ip, 'port': self.port}
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
warning = ''
|
||||
if not self.is_active:
|
||||
|
@ -1,24 +1,86 @@
|
||||
# -*- 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
|
||||
from .models import AssetGroup, Asset, IDC, AssetExtend, AdminUser, SystemUser
|
||||
from common.mixins import BulkDeleteApiMixin
|
||||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
|
||||
|
||||
|
||||
class AssetBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
# group_display = serializers.SerializerMethodField()
|
||||
# active_display = serializers.SerializerMethodField()
|
||||
#groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all())
|
||||
class AssetGroupSerializer(serializers.ModelSerializer):
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
# assets = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
|
||||
class AdminUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.append('assets_amount')
|
||||
return fields
|
||||
|
||||
|
||||
class SystemUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
exclude = ('_password', '_private_key', '_public_key')
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.append('assets_amount')
|
||||
return fields
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
system_users = SystemUserSerializer(many=True, read_only=True)
|
||||
admin_user = AdminUserSerializer(many=False, read_only=True)
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = ['id', 'port', 'idc']
|
||||
|
||||
# def get_group_display(self, obj):
|
||||
# return " ".join([group.name for group in obj.groups.all()])
|
||||
#
|
||||
# def get_active_display(self, obj):
|
||||
# # TODO: user ative state
|
||||
# return not (obj.is_expired and obj.is_active)
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
system_users = SystemUserSerializer(many=True, read_only=True)
|
||||
is_inherited = serializers.SerializerMethodField()
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
fields = ("id", "hostname", "ip", "port", "system_users", "is_inherited",
|
||||
"is_active", "system_users_join", "comment")
|
||||
|
||||
@staticmethod
|
||||
def get_is_inherited(obj):
|
||||
if getattr(obj, 'inherited', ''):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_system_users_join(obj):
|
||||
return ', '.join([system_user.username for system_user in obj.system_users.all()])
|
||||
|
||||
|
||||
class IDCSerializer(serializers.ModelSerializer):
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = IDC
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(IDCSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.append('assets_amount')
|
||||
return fields
|
||||
|
@ -31,20 +31,8 @@
|
||||
{% csrf_token %}
|
||||
{{ form.name|bootstrap_horizontal }}
|
||||
{{ form.username|bootstrap_horizontal }}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.auto_generate_key}}
|
||||
</div>
|
||||
</div>
|
||||
{{ form.password|bootstrap_horizontal }}
|
||||
{{ form.private_key_file|bootstrap_horizontal }}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.as_default.id_for_label }}" class="col-sm-2 control-label">{% trans 'As default' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.as_default}}
|
||||
</div>
|
||||
</div>
|
||||
{{ form.assets|bootstrap_horizontal }}
|
||||
{{ form.comment|bootstrap_horizontal }}
|
||||
|
||||
|
@ -1,41 +1,66 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
<a href="{% url 'assets:admin-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create admin user" %} </a>
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5">
|
||||
<a href="{% url "assets:admin-user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create admin user" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="admin_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 num' %}</th>
|
||||
<th class="text-center">{% trans 'Lost connection' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#admin_user_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, 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>');
|
||||
}},
|
||||
{# {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);
|
||||
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: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);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center"><a href="{% url 'assets:admin-user-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center"><a href="{% url 'assets:admin-user-list' %}?sort=username">{% trans 'Username' %}</a></th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||
<th class="text-center">{% trans 'Lost connection' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center"></th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for admin_user in admin_user_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">{{ admin_user.id }}</td>
|
||||
<td>
|
||||
<a href="{% url 'assets:admin-user-detail' pk=admin_user.id %}">
|
||||
{{ admin_user.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ admin_user.username }}</td>
|
||||
<td class="text-center">{{ admin_user.assets.count }}</td>
|
||||
<td class="text-center">{{ admin_user.assets.count }}</td>
|
||||
<td class="text-center">{{ admin_user.comment|truncatewords:8 }}</td>
|
||||
<td class="text-center">
|
||||
<!-- Todo: Click script button will paste a url to clipboard like: curl http://url/admin_user_create.sh | bash -->
|
||||
<a href="{% url 'assets:admin-user-update' pk=admin_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:admin-user-update' pk=admin_user.id %}" class="btn btn-xs btn-warning">{% trans 'Refresh' %}</a>
|
||||
<a href="{% url 'assets:admin-user-update' pk=admin_user.id %}" class="btn btn-xs btn-info">{% trans 'Update' %}</a>
|
||||
<a onclick="obj_del(this,'{{ admin_user.name }}','{% url 'assets:admin-user-delete' admin_user.id %}')" class="btn btn-xs btn-danger del">{% trans 'Delete' %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -30,12 +30,11 @@
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
@ -44,10 +43,9 @@
|
||||
$('.select2').select2();
|
||||
$("#id_tags").select2({
|
||||
tags: true,
|
||||
maximumSelectionLength: 8, //最多能够选择的个数
|
||||
maximumSelectionLength: 8 //最多能够选择的个数
|
||||
//closeOnSelect: false
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@ -5,13 +5,6 @@
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: left;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
@ -94,7 +87,7 @@ div.dataTables_wrapper div.dataTables_filter,
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
$('.select2-system-user').select2();
|
||||
})
|
||||
});
|
||||
|
||||
$('#add_asset').on('click',function(){
|
||||
$('#modal').modal('show');
|
||||
@ -104,7 +97,7 @@ div.dataTables_wrapper div.dataTables_filter,
|
||||
show: false,
|
||||
backdrop: 'static',
|
||||
keyboard: 'false',
|
||||
remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}",
|
||||
remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}"
|
||||
});
|
||||
|
||||
$('#modal').on('show.bs.modal',function(){
|
||||
|
@ -7,19 +7,11 @@
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
margin-left: 15px;
|
||||
float: right !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: right !important;
|
||||
}
|
||||
.custom{
|
||||
/*float:left;*/
|
||||
margin-right:5px;
|
||||
}
|
||||
#modal .modal-body { max-height: 200px; }
|
||||
.custom{
|
||||
/*float:left;*/
|
||||
margin-right:5px;
|
||||
}
|
||||
#modal .modal-body { max-height: 200px; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content_left_head %}{% endblock %}
|
||||
@ -80,7 +72,8 @@ div.dataTables_wrapper div.dataTables_filter,
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'assets:asset-update' pk=asset.id %}" class="btn btn-xs btn-info">{% trans 'Update' %}</a>
|
||||
<a onclick="objectDelete(this,'{{ asset.hostname }}','{% url 'assets:asset-delete' pk=asset.id %}')" class="btn btn-xs btn-danger del">{% trans 'Delete' %}</a>
|
||||
<a onclick="objectDelete(this,'{{ asset.hostname }}','{% url 'api-assets:asset-detail' pk=asset.id %}')" class="btn btn-xs btn-danger del">
|
||||
{% trans 'Delete' %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -190,18 +183,18 @@ div.dataTables_wrapper div.dataTables_filter,
|
||||
}else{
|
||||
$(this).addClass('selected');
|
||||
this.children[0].children[0].checked=1;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn_bulk_update').on('click',function(){
|
||||
var column2 = table.rows('.selected').data();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
var the_url = "{% url 'assets:asset-bulk-update-api' %}";
|
||||
var the_url = "{% url 'api-assets:asset-bulk-update' %}";
|
||||
for(var i=0;i<column2.length;i++){
|
||||
id_list.push({id: column2[i].id,hostname:column2[i].ip});
|
||||
plain_id_list.push(parseInt(column2[i].id));
|
||||
};
|
||||
}
|
||||
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
//APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
@ -227,18 +220,18 @@ div.dataTables_wrapper div.dataTables_filter,
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function doUpdate() {
|
||||
// alert(plain_id_list);
|
||||
// $('#asset_bulk_update_modal').modal('show');
|
||||
window.location.href="{% url 'assets:asset-modal-update' %}?plain_id_lists="+plain_id_list
|
||||
};
|
||||
}
|
||||
|
||||
var action = $('#slct_bulk_update option:selected').val();
|
||||
if (id_list.length === 0) {
|
||||
action = 'default';
|
||||
};
|
||||
}
|
||||
switch(action) {
|
||||
case 'deactive':
|
||||
alert(action+"未完成");
|
||||
|
@ -46,9 +46,7 @@
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
var table = $('#editable').DataTable({
|
||||
"aLengthMenu": [[10, 25, 50, -1], ["10", "25", "50", "all"]],
|
||||
"iDisplayLength":25,
|
||||
|
@ -8,7 +8,7 @@
|
||||
<body>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>Are you sure you want to delete "{{ object.name }}"?</p>
|
||||
<p>{% trans 'Are you sure delete' %} <b>{{ object.name }} </b> ?</p>
|
||||
<input type="submit" value="Confirm" />
|
||||
</form>
|
||||
</body>
|
||||
|
128
apps/assets/templates/assets/idc_assets.html
Normal file
128
apps/assets/templates/assets/idc_assets.html
Normal file
@ -0,0 +1,128 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load common_tags %}
|
||||
{% load users_tags %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:idc-detail' pk=idc.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="active"><a href="{% url 'assets:idc-assets' pk=idc.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'IDC assets' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'IDC assets' %} <b>{{ idc.name }} </b><span class="badge"></span></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-striped table-bordered table-hover " id="idc_assets_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Valid' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Attach to assets ' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset in assets_remain %}
|
||||
<option value="{{ asset.id }}">{{ asset.ip}}:{{ asset.port }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-primary btn-sm">{% trans 'Attach' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
var options = {
|
||||
ele: $('#idc_assets_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 5, 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>')
|
||||
}
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?idc={{ idc.id }}',
|
||||
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "type" }, {data: "is_active" }]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
138
apps/assets/templates/assets/idc_detail.html
Normal file
138
apps/assets/templates/assets/idc_detail.html
Normal file
@ -0,0 +1,138 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load common_tags %}
|
||||
{% load users_tags %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:idc-detail' pk=idc.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'assets:idc-assets' pk=idc.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'IDC assets' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:idc-update' pk=idc.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger" href="{% url 'assets:idc-delete' pk=idc.id %}">
|
||||
<i class="fa fa-edit"></i>Delete
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-9" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ idc.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td>{% trans 'Name' %}:</td>
|
||||
<td><b>{{ idc.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Bandwidth' %}:</td>
|
||||
<td><b>{{ idc.bandwidth }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Contact' %}:</td>
|
||||
<td><b>{{ idc.contact }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Phone' %}:</td>
|
||||
<td><b>{{ idc.phone }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Address' %}:</td>
|
||||
<td><b>{{ idc.address }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Intranet' %}:</td>
|
||||
<td><b>{{ idc.Intranet }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Extranet' %}:</td>
|
||||
<td><b>{{ idc.extranet }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Operator' %}:</td>
|
||||
<td><b>{{ idc.operator }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ system_user.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset_group.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ system_user.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
{# function switch_user_status(obj) {#}
|
||||
{# var status = $(obj).prop('checked');#}
|
||||
{##}
|
||||
{# $.ajax({#}
|
||||
{# url: "{% url 'users:user-active-api' pk=user.id %}",#}
|
||||
{# type: "PUT",#}
|
||||
{# data: {#}
|
||||
{# 'is_active': status#}
|
||||
{# },#}
|
||||
{# success: function (data, status) {#}
|
||||
{# console.log(data)#}
|
||||
{# },#}
|
||||
{# error: function () {#}
|
||||
{# console.log('error')#}
|
||||
{# }#}
|
||||
{# })#}
|
||||
{# }#}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,43 +1,64 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
<a href="{% url 'assets:idc-create' %}" class="btn btn-sm btn-primary "> {% trans "Create IDC" %} </a>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
|
||||
</th>
|
||||
<th class="text-center"><a href="{% url 'assets:idc-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||
<th class="text-center">{% trans 'Contact' %}</th>
|
||||
<th class="text-center">{% trans 'Phone' %}</th>
|
||||
<th class="text-center">{% trans 'operation' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for idc in idc_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">
|
||||
<input type="checkbox" name="checked" value="{{ idc.id }}">
|
||||
</td>
|
||||
<td class="text-center">{{ idc.name }}</td>
|
||||
<td class="text-center">{{ idc.assets.count }}</td>
|
||||
{# <td class="text-center">{{ idc.bandwidth }}</td>#}
|
||||
<td class="text-center">{{ idc.contact }}</td>
|
||||
<td class="text-center">{{ idc.phone }}</td>
|
||||
{# <td class="text-center">{{ idc.address }}</td>#}
|
||||
<td class="text-center">
|
||||
<a href="{% url 'assets:idc-update' pk=idc.id %}" class="btn btn-xs btn-info">{% trans 'Update' %}</a>
|
||||
<a onclick="objectDelete(this, '{{ idc.name }}', '{% url 'assets:idc-delete' idc.id %}')" class="btn btn-xs btn-danger del">{% trans 'Delete' %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5">
|
||||
<a href="{% url "assets:idc-create" %}" class="btn btn-sm btn-primary"> {% trans "Create IDC" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="idc_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center"><a href="{% url 'assets:idc-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||
<th class="text-center">{% trans 'Contact' %}</th>
|
||||
<th class="text-center">{% trans 'Phone' %}</th>
|
||||
<th class="text-center">{% trans 'Operator' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#idc_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:idc-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{# {targets: 4, createdCell: function (td, cellData) {#}
|
||||
{# var innerHtml = cellData.length > 8 ? cellData.substring(0, 8) + '...': 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 update_btn = '<a href="{% url "assets:idc-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);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:idc-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "assets_amount" }, {data: "contact" }, {data: "phone" },
|
||||
{data: "operator" }, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
@ -1,73 +0,0 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url, include
|
||||
import views
|
||||
import api
|
||||
# from .api import (
|
||||
# AssetGroupViewSet, AssetViewSet, IDCViewSet
|
||||
# )
|
||||
# from rest_framework import routers
|
||||
# router = routers.DefaultRouter()
|
||||
# router.register(r'assetgroup', AssetGroupViewSet)
|
||||
# router.register(r'asset', AssetViewSet)
|
||||
# router.register(r'idc', IDCViewSet)
|
||||
app_name = 'assets'
|
||||
|
||||
urlpatterns = [
|
||||
# Resource asset url
|
||||
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/(?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'),
|
||||
url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'),
|
||||
url(r'^asset-modal-update$', views.AssetModalCreateView.as_view(), name='asset-modal-update'),
|
||||
|
||||
# Resource asset group url
|
||||
url(r'^asset-group$', views.AssetGroupListView.as_view(), name='asset-group-list'),
|
||||
url(r'^asset-group/create$', views.AssetGroupCreateView.as_view(), name='asset-group-create'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/update$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/delete$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'),
|
||||
|
||||
url(r'^tags$', views.TagsListView.as_view(), name='asset-tag-list'),
|
||||
url(r'^asset-by-tag/(?P<tag_id>[0-9]+)$', views.TagView.as_view(), name='asset-tags'),
|
||||
url(r'^tags/create$', views.AssetTagCreateView.as_view(), name='asset-tag-create'),
|
||||
url(r'^asset-tag/(?P<pk>[0-9]+)$', views.AssetTagDetailView.as_view(), name='asset-tag-detail'),
|
||||
url(r'^asset-tag/(?P<pk>[0-9]+)/update$', views.AssetTagUpdateView.as_view(), name='asset-tag-update'),
|
||||
url(r'^asset-tag/(?P<pk>[0-9]+)/delete$', views.AssetTagDeleteView.as_view(), name='asset-tag-delete'),
|
||||
|
||||
# Resource idc url
|
||||
url(r'^idc$', views.IDCListView.as_view(), name='idc-list'),
|
||||
url(r'^idc/create$', views.IDCCreateView.as_view(), name='idc-create'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)$', views.IDCDetailView.as_view(), name='idc-detail'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/update', views.IDCUpdateView.as_view(), name='idc-update'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/delete$', views.IDCDeleteView.as_view(), name='idc-delete'),
|
||||
|
||||
# Resource admin user url
|
||||
url(r'^admin-user$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
url(r'^admin-user/create$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/update', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/delete$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
|
||||
# Resource system user url
|
||||
url(r'^system-user$', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
url(r'^system-user/create$', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/update', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/delete$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/asset$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
# url(r'^system-user/(?P<pk>[0-9]+)/asset-group$', views.SystemUserAssetGroupView.as_view(),
|
||||
# name='system-user-asset-group'),
|
||||
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^v1/assets/$', api.AssetViewSet.as_view({'get':'list'}), name='assets-list-api'),
|
||||
url(r'^v1/assets_bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update-api'),
|
||||
url(r'^v1/idc/$', api.IDCViewSet.as_view({'get':'list'}), name='idc-list-json'),
|
||||
url(r'^v1/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'),
|
||||
]
|
||||
|
||||
|
1
apps/assets/urls/__init__.py
Normal file
1
apps/assets/urls/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
23
apps/assets/urls/api_urls.py
Normal file
23
apps/assets/urls/api_urls.py
Normal file
@ -0,0 +1,23 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from .. import api
|
||||
from rest_framework import routers
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'v1/asset-groups', api.AssetGroupViewSet, 'asset-group')
|
||||
router.register(r'v1/assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'v1/idc', api.IDCViewSet, 'idc')
|
||||
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||
|
||||
urlpatterns = [
|
||||
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/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
58
apps/assets/urls/views_urls.py
Normal file
58
apps/assets/urls/views_urls.py
Normal file
@ -0,0 +1,58 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from .. import views
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
urlpatterns = [
|
||||
# Resource asset url
|
||||
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/(?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'),
|
||||
url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'),
|
||||
url(r'^asset-modal-update$', views.AssetModalCreateView.as_view(), name='asset-modal-update'),
|
||||
|
||||
# Resource asset group url
|
||||
url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'),
|
||||
url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'),
|
||||
|
||||
url(r'^tags/$', views.TagsListView.as_view(), name='asset-tag-list'),
|
||||
url(r'^asset-by-tag/(?P<tag_id>[0-9]+)/$', views.TagView.as_view(), name='asset-tags'),
|
||||
url(r'^tags/create/$', views.AssetTagCreateView.as_view(), name='asset-tag-create'),
|
||||
url(r'^asset-tag/(?P<pk>[0-9]+)/$', views.AssetTagDetailView.as_view(), name='asset-tag-detail'),
|
||||
url(r'^asset-tag/(?P<pk>[0-9]+)/update/$', views.AssetTagUpdateView.as_view(), name='asset-tag-update'),
|
||||
url(r'^asset-tag/(?P<pk>[0-9]+)/delete/$', views.AssetTagDeleteView.as_view(), name='asset-tag-delete'),
|
||||
|
||||
# Resource idc url
|
||||
url(r'^idc/$', views.IDCListView.as_view(), name='idc-list'),
|
||||
url(r'^idc/create/$', views.IDCCreateView.as_view(), name='idc-create'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/$', views.IDCDetailView.as_view(), name='idc-detail'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/update/', views.IDCUpdateView.as_view(), name='idc-update'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/delete/$', views.IDCDeleteView.as_view(), name='idc-delete'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/assets/$', views.IDCAssetsView.as_view(), name='idc-assets'),
|
||||
|
||||
# Resource admin user url
|
||||
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
|
||||
# Resource system user url
|
||||
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
# url(r'^system-user/(?P<pk>[0-9]+)/asset-group$', views.SystemUserAssetGroupView.as_view(),
|
||||
# name='system-user-asset-group'),
|
||||
|
||||
]
|
||||
|
@ -30,21 +30,21 @@ class AssetListView(AdminUserRequiredMixin, ListView):
|
||||
@staticmethod
|
||||
def sorted_by_valid_and_ip(asset):
|
||||
ip_list = int_seq(asset.ip.split('.'))
|
||||
ip_list.insert(0, asset.is_valid()[0])
|
||||
ip_list.insert(0, asset.is_valid[0])
|
||||
return ip_list
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'asset list',
|
||||
'tag_list': [(i.id,i.name,i.asset_set.all().count())for i in Tag.objects.all().order_by('name')]
|
||||
'tag_list': [(i.id, i.name, i.asset_set.all().count())for i in Tag.objects.all().order_by('name')]
|
||||
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView):
|
||||
class AssetCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, CreateView):
|
||||
model = Asset
|
||||
tag_type = 'asset'
|
||||
form_class = AssetCreateForm
|
||||
@ -58,7 +58,8 @@ class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView):
|
||||
return super(AssetCreateView, self).form_valid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
print(form.errors)
|
||||
if form.errors.get('__all__'):
|
||||
form.errors['all'] = form.errors.get('__all__')
|
||||
return super(AssetCreateView, self).form_invalid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -207,7 +208,7 @@ class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
plain_id_lists = self.request.GET.get('plain_id_lists')
|
||||
self.s = self.request.GET.get('plain_id_lists')
|
||||
if "," in str(self.s):
|
||||
self.plain_id_lists = [int(x) for x in self.s.split(',')]
|
||||
self.plain_id_lists = [int(x) for x in self.s.split(',')]
|
||||
else:
|
||||
self.plain_id_lists = [self.s]
|
||||
|
||||
@ -217,19 +218,19 @@ class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
else:
|
||||
plain_id_lists = [int(self.s)]
|
||||
context = {
|
||||
'all_assets':plain_id_lists
|
||||
'all_assets' :plain_id_lists
|
||||
}
|
||||
kwargs.update(context)
|
||||
if group_id:
|
||||
group = AssetGroup.objects.get(id=group_id)
|
||||
context = {
|
||||
'all_assets':[x.id for x in group.assets.all()]
|
||||
'all_assets': [x.id for x in group.assets.all()]
|
||||
}
|
||||
kwargs.update(context)
|
||||
if tag_id:
|
||||
tag = Tag.objects.get(id=tag_id)
|
||||
context = {
|
||||
'all_assets':[x.id for x in tag.asset_set.all()]
|
||||
'all_assets': [x.id for x in tag.asset_set.all()]
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetModalListView, self).get_context_data(**kwargs)
|
||||
@ -341,33 +342,33 @@ class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
|
||||
|
||||
class IDCListView(AdminUserRequiredMixin, ListView):
|
||||
model = IDC
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
context_object_name = 'idc_list'
|
||||
class IDCListView(AdminUserRequiredMixin, TemplateView):
|
||||
# model = IDC
|
||||
# paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
# context_object_name = 'idc_list'
|
||||
template_name = 'assets/idc_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('IDC list'),
|
||||
'keyword': self.request.GET.get('keyword', '')
|
||||
# 'keyword': self.request.GET.get('keyword', '')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(IDCListView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super(IDCListView, 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
|
||||
# def get_queryset(self):
|
||||
# self.queryset = super(IDCListView, 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 IDCCreateView(AdminUserRequiredMixin, CreateView):
|
||||
@ -414,7 +415,15 @@ class IDCUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
|
||||
|
||||
class IDCDetailView(AdminUserRequiredMixin, DetailView):
|
||||
pass
|
||||
model = IDC
|
||||
template_name = 'assets/idc_detail.html'
|
||||
context_object_name = 'idc'
|
||||
|
||||
|
||||
class IDCAssetsView(AdminUserRequiredMixin, DetailView):
|
||||
model = IDC
|
||||
template_name = 'assets/idc_assets.html'
|
||||
context_object_name = 'idc'
|
||||
|
||||
|
||||
class IDCDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
@ -423,34 +432,34 @@ class IDCDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
success_url = reverse_lazy('assets:idc-list')
|
||||
|
||||
|
||||
class AdminUserListView(AdminUserRequiredMixin, ListView):
|
||||
class AdminUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
model = AdminUser
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
context_object_name = 'admin_user_list'
|
||||
# paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
# context_object_name = 'admin_user_list'
|
||||
template_name = 'assets/admin_user_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Admin user list'),
|
||||
'keyword': self.request.GET.get('keyword', '')
|
||||
# 'keyword': self.request.GET.get('keyword', '')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AdminUserListView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
# Todo: Default order by lose asset connection num
|
||||
self.queryset = super(AdminUserListView, 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
|
||||
# def get_queryset(self):
|
||||
# Todo: Default order by lose asset connection num
|
||||
# self.queryset = super(AdminUserListView, 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 AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
@ -475,6 +484,9 @@ class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
|
||||
))
|
||||
return success_message
|
||||
|
||||
def form_invalid(self, form):
|
||||
return super(AdminUserCreateView, self).form_invalid(form)
|
||||
|
||||
|
||||
class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = AdminUser
|
||||
|
@ -3,14 +3,14 @@
|
||||
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from rest_framework import generics
|
||||
from rest_framework import generics, viewsets
|
||||
from rest_framework.views import APIView, Response
|
||||
|
||||
from . import models, serializers
|
||||
from .hands import IsSuperUserOrTerminalUser, Terminal
|
||||
|
||||
|
||||
class ProxyLogListCreateApi(generics.ListCreateAPIView):
|
||||
class ProxyLogViewSet(viewsets.ModelViewSet):
|
||||
"""User proxy to backend server need call this api.
|
||||
|
||||
params: {
|
||||
@ -34,18 +34,8 @@ class ProxyLogListCreateApi(generics.ListCreateAPIView):
|
||||
serializer_class = serializers.ProxyLogSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
# Todo: May be save log_file
|
||||
super(ProxyLogListCreateApi, self).perform_create(serializer)
|
||||
|
||||
|
||||
class ProxyLogDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = models.ProxyLog.objects.all()
|
||||
serializer_class = serializers.ProxyLogSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
|
||||
|
||||
class CommandLogListCreateApi(generics.ListCreateAPIView):
|
||||
class CommandLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = models.CommandLog.objects.all()
|
||||
serializer_class = serializers.CommandLogSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.models import User
|
||||
from assets.models import Asset, SystemUser
|
||||
from users.backends import IsSuperUserOrTerminalUser
|
||||
|
@ -22,9 +22,7 @@ class LoginLog(models.Model):
|
||||
login_ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
||||
login_city = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Login city'))
|
||||
user_agent = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('User agent'))
|
||||
from_terminal = models.ForeignKey
|
||||
date_login = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
||||
date_logout = models.DateTimeField(null=True, verbose_name=_('Date logout'))
|
||||
|
||||
class Meta:
|
||||
db_table = 'login_log'
|
||||
|
12
apps/audits/tasks.py
Normal file
12
apps/audits/tasks.py
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from celery import shared_task
|
||||
from .utils import write_login_log
|
||||
|
||||
|
||||
@shared_task
|
||||
def write_login_log_async(*args, **kwargs):
|
||||
write_login_log(*args, **kwargs)
|
||||
|
100
apps/audits/templates/audits/login_log_list.html
Normal file
100
apps/audits/templates/audits/login_log_list.html
Normal file
@ -0,0 +1,100 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="username">
|
||||
<option value="">{% trans 'Select user' %}</option>
|
||||
{% for user in user_list %}
|
||||
<option value="{{ user.username }}" {% if user.username == username %} selected {% endif %}>{{ user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'UA' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'City' %}</th>
|
||||
<th class="text-center">{% trans 'Date' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for login_log in login_log_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">
|
||||
{{ login_log.id }}
|
||||
{# <a href="{% url 'audits:proxy-log-detail' pk=login_log.id %}">{{ login_log.id }}</a>#}
|
||||
</td>
|
||||
<td class="text-center">{{ login_log.username }}</td>
|
||||
<td class="text-center">{{ login_log.name }}</td>
|
||||
<td class="text-center">{{ login_log.get_login_type_display }}</td>
|
||||
{% if login_log.login_type == 'W' %}
|
||||
<td class="text-center">
|
||||
<span href="javascript:void(0);" data-toggle="tooltips" title="{{ login_log.user_agent }}">{{ login_log.user_agent | truncatechars:20 }}</span>
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ login_log.terminal }}</td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ login_log.login_ip }}</td>
|
||||
<td class="text-center">{{ login_log.login_city }}</td>
|
||||
<td class="text-center">{{ login_log.date_login }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"order": []
|
||||
});
|
||||
$('#date .input-daterange').datepicker({
|
||||
dateFormat: 'mm/dd/yy',
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
autoclose: true
|
||||
});
|
||||
$('.select2').select2();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
1
apps/audits/urls/__init__.py
Normal file
1
apps/audits/urls/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
14
apps/audits/urls/api_urls.py
Normal file
14
apps/audits/urls/api_urls.py
Normal file
@ -0,0 +1,14 @@
|
||||
from django.conf.urls import url
|
||||
from rest_framework import routers
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'audits'
|
||||
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'v1/proxy-log', api.ProxyLogViewSet, 'proxy-log')
|
||||
router.register(r'v1/command-log', api.CommandLogViewSet, 'command-log')
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
@ -1,8 +1,5 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
|
||||
import api
|
||||
import views
|
||||
from .. import views
|
||||
|
||||
app_name = 'audits'
|
||||
|
||||
@ -11,11 +8,7 @@ urlpatterns = [
|
||||
url(r'^proxy-log/(?P<pk>\d+)$', views.ProxyLogDetailView.as_view(), name='proxy-log-detail'),
|
||||
url(r'^proxy-log/(?P<pk>\d+)/commands$', views.ProxyLogCommandsListView.as_view(), name='proxy-log-commands-list'),
|
||||
url(r'^command-log$', views.CommandLogListView.as_view(), name='command-log-list'),
|
||||
url(r'^login-log$', views.LoginLogListView.as_view(), name='login-log-list'),
|
||||
]
|
||||
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^v1/proxy-log/$', api.ProxyLogListCreateApi.as_view(), name='proxy-log-list-create-api'),
|
||||
url(r'^v1/proxy-log/(?P<pk>\d+)/$', api.ProxyLogDetailApi.as_view(), name='proxy-log-detail-api'),
|
||||
url(r'^v1/command-log/$', api.CommandLogListCreateApi.as_view(), name='command-log-create-list-api'),
|
||||
]
|
@ -1,5 +1,47 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from __future__ import unicode_literals
|
||||
import requests
|
||||
import ipaddress
|
||||
|
||||
from .models import LoginLog
|
||||
|
||||
|
||||
def validate_ip(ip):
|
||||
try:
|
||||
ipaddress.ip_address(ip.decode('utf-8'))
|
||||
return True
|
||||
except ValueError:
|
||||
print('valid error')
|
||||
return False
|
||||
|
||||
|
||||
def write_login_log(username, name='', login_type='W',
|
||||
terminal='', login_ip='', user_agent=''):
|
||||
if not (login_ip and validate_ip(login_ip)):
|
||||
login_ip = '0.0.0.0'
|
||||
if not name:
|
||||
name = username
|
||||
login_city = get_ip_city(login_ip)
|
||||
LoginLog.objects.create(username=username, name=name, login_type=login_type, login_ip=login_ip,
|
||||
terminal=terminal, login_city=login_city, user_agent=user_agent)
|
||||
|
||||
|
||||
def get_ip_city(ip, timeout=10):
|
||||
# Taobao ip api: http://ip.taobao.com//service/getIpInfo.php?ip=8.8.8.8
|
||||
# Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=js
|
||||
|
||||
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=' + ip
|
||||
r = requests.get(url, timeout=timeout)
|
||||
city = 'Unknown'
|
||||
if r.status_code == 200:
|
||||
try:
|
||||
data = r.json()
|
||||
if data['code'] == 0:
|
||||
city = data['data']['country'] + data['data']['city']
|
||||
except ValueError:
|
||||
pass
|
||||
return city
|
||||
|
||||
|
||||
|
@ -10,13 +10,13 @@ from django.urls import reverse_lazy
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import ProxyLog, CommandLog
|
||||
from .utils import AdminUserRequiredMixin
|
||||
from .hands import User, Asset, SystemUser
|
||||
from .models import ProxyLog, CommandLog, LoginLog
|
||||
from .hands import User, Asset, SystemUser, AdminUserRequiredMixin
|
||||
|
||||
|
||||
seven_days_ago_s = (datetime.datetime.now()-datetime.timedelta(7)).strftime('%m/%d/%Y')
|
||||
now_s = datetime.datetime.now().strftime('%m/%d/%Y')
|
||||
date_now = timezone.localtime(timezone.now())
|
||||
now_s = date_now.strftime('%m/%d/%Y')
|
||||
seven_days_ago_s = (date_now-timezone.timedelta(7)).strftime('%m/%d/%Y')
|
||||
|
||||
|
||||
class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
||||
@ -54,6 +54,7 @@ class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
print(self.date_to_s)
|
||||
context = {
|
||||
'app': _('Audits'),
|
||||
'action': _('Proxy log list'),
|
||||
@ -152,3 +153,43 @@ class CommandLogListView(AdminUserRequiredMixin, ListView):
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(CommandLogListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LoginLogListView(AdminUserRequiredMixin, ListView):
|
||||
model = LoginLog
|
||||
template_name = 'audits/login_log_list.html'
|
||||
context_object_name = 'login_log_list'
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super(LoginLogListView, self).get_queryset()
|
||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
||||
self.username = username = self.request.GET.get('username', '')
|
||||
self.date_from_s = date_from_s = self.request.GET.get('date_from', '%s' % seven_days_ago_s)
|
||||
self.date_to_s = date_to_s = self.request.GET.get('date_to', '%s' % now_s)
|
||||
|
||||
if date_from_s:
|
||||
date_from = timezone.datetime.strptime(date_from_s, '%m/%d/%Y')
|
||||
self.queryset = self.queryset.filter(date_login__gt=date_from)
|
||||
if date_to_s:
|
||||
date_to = timezone.datetime.strptime(date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S')
|
||||
self.queryset = self.queryset.filter(date_login__lt=date_to)
|
||||
if username:
|
||||
self.queryset = self.queryset.filter(username=username)
|
||||
if keyword:
|
||||
self.queryset = self.queryset.filter(Q(username__contains=keyword) |
|
||||
Q(name__icontains=keyword) |
|
||||
Q(login_ip=keyword)).distinct()
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Audits'),
|
||||
'action': _('Proxy log list'),
|
||||
'user_list': User.objects.all().order_by('username'),
|
||||
'keyword': self.keyword,
|
||||
'date_from': self.date_from_s,
|
||||
'date_to': self.date_to_s,
|
||||
'username': self.username,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(LoginLogListView, self).get_context_data(**kwargs)
|
||||
|
@ -3,4 +3,3 @@ from __future__ import absolute_import
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
from .celery import app as celery_app
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from celery import Celery
|
||||
|
||||
@ -16,6 +16,5 @@ app = Celery('jumpserver')
|
||||
# Using a string here means the worker will not have to
|
||||
# pickle the object when using Windows.
|
||||
app.config_from_object('django.conf:settings')
|
||||
|
||||
app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from celery import shared_task
|
||||
# from celery import shared_task
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from common import celery_app as app
|
||||
|
||||
|
||||
@shared_task(name='send_mail_async')
|
||||
@app.task
|
||||
def send_mail_async(*args, **kwargs):
|
||||
""" Using celery to send email async
|
||||
|
||||
@ -26,9 +27,3 @@ def send_mail_async(*args, **kwargs):
|
||||
args = tuple(args)
|
||||
|
||||
send_mail(*args, **kwargs)
|
||||
|
||||
|
||||
# def send_mail_async(subject, message, from_mail, recipient_list, fail_silently=False, html_message=None):
|
||||
# if settings.CONFIG.MAIL_SUBJECT_PREFIX:
|
||||
# subject += settings.CONFIG.MAIL_SUBJECT_PREFIX
|
||||
# send_mail(subject, message, from_mail, recipient_list, fail_silently=fail_silently, html_message=html_message)
|
||||
|
@ -3,11 +3,15 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from six import string_types
|
||||
import os
|
||||
from itertools import chain
|
||||
import string
|
||||
import logging
|
||||
import datetime
|
||||
import paramiko
|
||||
|
||||
import paramiko
|
||||
import sshpubkeys
|
||||
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
|
||||
BadSignature, SignatureExpired
|
||||
from django.shortcuts import reverse as dj_reverse
|
||||
@ -15,6 +19,11 @@ from django.conf import settings
|
||||
from django.core import signing
|
||||
from django.utils import timezone
|
||||
|
||||
try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
|
||||
SECRET_KEY = settings.SECRET_KEY
|
||||
|
||||
|
||||
@ -162,4 +171,88 @@ def timesince(dt, since='', default="just now"):
|
||||
return default
|
||||
|
||||
|
||||
def ssh_key_string_to_obj(text):
|
||||
key_f = StringIO.StringIO(text)
|
||||
key = None
|
||||
try:
|
||||
key = paramiko.RSAKey.from_private_key(key_f)
|
||||
except paramiko.SSHException:
|
||||
pass
|
||||
|
||||
try:
|
||||
key = paramiko.DSSKey.from_private_key(key_f)
|
||||
except paramiko.SSHException:
|
||||
pass
|
||||
return key
|
||||
|
||||
|
||||
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost'):
|
||||
if isinstance(private_key, string_types):
|
||||
private_key = ssh_key_string_to_obj(private_key)
|
||||
|
||||
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
||||
raise IOError('Invalid private key')
|
||||
|
||||
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
|
||||
'key_type': private_key.get_name(),
|
||||
'key_content': private_key.get_base64(),
|
||||
'username': username,
|
||||
'hostname': hostname,
|
||||
}
|
||||
return public_key
|
||||
|
||||
|
||||
def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
|
||||
"""Generate user ssh private and public key
|
||||
|
||||
Use paramiko RSAKey generate it.
|
||||
:return private key str and public key str
|
||||
"""
|
||||
|
||||
if hostname is None:
|
||||
hostname = os.uname()[1]
|
||||
|
||||
f = StringIO.StringIO()
|
||||
|
||||
try:
|
||||
if type == 'rsa':
|
||||
private_key_obj = paramiko.RSAKey.generate(length)
|
||||
elif type == 'dsa':
|
||||
private_key_obj = paramiko.DSSKey.generate(length)
|
||||
else:
|
||||
raise IOError('SSH private key must be `rsa` or `dsa`')
|
||||
private_key_obj.write_private_key(f, password=password)
|
||||
private_key = f.getvalue()
|
||||
public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
|
||||
return private_key, public_key
|
||||
except IOError:
|
||||
raise IOError('These is error when generate ssh key.')
|
||||
|
||||
|
||||
def validate_ssh_private_key(text):
|
||||
key = ssh_key_string_to_obj(text)
|
||||
if key is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def validate_ssh_public_key(text):
|
||||
ssh = sshpubkeys.SSHKey(text)
|
||||
try:
|
||||
ssh.parse()
|
||||
except sshpubkeys.InvalidKeyException:
|
||||
return False
|
||||
except NotImplementedError as e:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def setattr_bulk(seq, key, value):
|
||||
def set_attr(obj):
|
||||
setattr(obj, key, value)
|
||||
return obj
|
||||
return map(set_attr, seq)
|
||||
|
||||
|
||||
signer = Signer()
|
@ -2,6 +2,3 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@ import os
|
||||
import sys
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
from datetime import timedelta
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@ -284,6 +285,31 @@ BROKER_URL = 'redis://%(password)s%(host)s:%(port)s/3' % {
|
||||
}
|
||||
CELERY_RESULT_BACKEND = BROKER_URL
|
||||
|
||||
# TERMINAL_HEATBEAT_INTERVAL = CONFIG.TERMINAL_HEATBEAT_INTERVAL or 30
|
||||
|
||||
# crontab job
|
||||
# CELERYBEAT_SCHEDULE = {
|
||||
# Check terminal is alive every 10m
|
||||
# 'check_terminal_alive': {
|
||||
# 'task': 'terminal.tasks.check_terminal_alive',
|
||||
# 'schedule': timedelta(seconds=TERMINAL_HEATBEAT_INTERVAL),
|
||||
# 'args': (),
|
||||
# },
|
||||
# }
|
||||
|
||||
|
||||
# Cache use redis
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'redis_cache.RedisCache',
|
||||
'LOCATION': 'redis://%(password)s%(host)s:%(port)s/4' % {
|
||||
'password': CONFIG.REDIS_PASSWORD + '@' if CONFIG.REDIS_PASSWORD else '',
|
||||
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
||||
'port': CONFIG.REDIS_PORT or 6379,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Captcha settings, more see https://django-simple-captcha.readthedocs.io/en/latest/advanced.html
|
||||
CAPTCHA_IMAGE_SIZE = (75, 33)
|
||||
CAPTCHA_FOREGROUND_COLOR = '#001100'
|
||||
|
@ -22,11 +22,16 @@ from django.views.generic.base import TemplateView
|
||||
urlpatterns = [
|
||||
url(r'^captcha/', include('captcha.urls')),
|
||||
url(r'^$', TemplateView.as_view(template_name='base.html'), name='index'),
|
||||
url(r'^(api/)?users/', include('users.urls')),
|
||||
url(r'^(api/)?assets/', include('assets.urls')),
|
||||
url(r'^(api/)?perms/', include('perms.urls')),
|
||||
url(r'^(api/)?audits/', include('audits.urls')),
|
||||
url(r'^(api/)?terminal/', include('terminal.urls')),
|
||||
url(r'^users/', include('users.urls.views_urls', namespace='users')),
|
||||
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
||||
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
||||
url(r'^audits/', include('audits.urls.views_urls', namespace='audits')),
|
||||
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')),
|
||||
url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')),
|
||||
url(r'^api/assets/', include('assets.urls.api_urls', namespace='api-assets')),
|
||||
url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||
url(r'^api/audits/', include('audits.urls.api_urls', namespace='api-audits')),
|
||||
url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
||||
]
|
||||
|
||||
|
||||
|
@ -1610,7 +1610,7 @@ msgid ""
|
||||
"here reset password</a>\n"
|
||||
" </br>\n"
|
||||
" This link is valid for 1 hour. After it expires, <a href=\"%"
|
||||
"(forget_password_url)s?email=%(email)s\">request new one<</a>\n"
|
||||
"(forget_password_url)s?email=%(email)s\">request new one</a>\n"
|
||||
"\n"
|
||||
" </br>\n"
|
||||
" ---\n"
|
||||
|
1
apps/ops/urls/__init__.py
Normal file
1
apps/ops/urls/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
7
apps/ops/urls/api_urls.py
Normal file
7
apps/ops/urls/api_urls.py
Normal file
@ -0,0 +1,7 @@
|
||||
# coding:utf-8
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework import routers
|
||||
|
||||
|
||||
|
8
apps/ops/urls/views_urls.py
Normal file
8
apps/ops/urls/views_urls.py
Normal file
@ -0,0 +1,8 @@
|
||||
# coding:utf-8
|
||||
|
||||
from django.conf.urls import url
|
||||
from .. import views
|
||||
|
||||
app_name = 'ops'
|
||||
|
||||
|
@ -2,49 +2,120 @@
|
||||
#
|
||||
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import ListCreateAPIView
|
||||
from rest_framework.generics import ListAPIView, get_object_or_404
|
||||
from rest_framework import viewsets
|
||||
from users.backends import IsValidUser, IsSuperUser
|
||||
from .utils import get_user_granted_assets, get_user_granted_asset_groups
|
||||
from common.utils import get_object_or_none
|
||||
from .utils import get_user_granted_assets, get_user_granted_asset_groups, get_user_asset_permissions, \
|
||||
get_user_group_asset_permissions, get_user_group_granted_assets, get_user_group_granted_asset_groups
|
||||
from .models import AssetPermission
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, AssetGroup, AssetGroupSerializer
|
||||
from . import serializers
|
||||
|
||||
|
||||
class AssetPermissionListCreateApi(ListCreateAPIView):
|
||||
class AssetPermissionViewSet(viewsets.ModelViewSet):
|
||||
queryset = AssetPermission.objects.all()
|
||||
serializer_class = serializers.AssetPermissionSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(AssetPermissionViewSet, self).get_queryset()
|
||||
user_id = self.request.query_params.get('user', '')
|
||||
user_group_id = self.request.query_params.get('user_group', '')
|
||||
|
||||
class UserAssetsApi(APIView):
|
||||
if user_id and user_id.isdigit():
|
||||
user = get_object_or_404(User, id=int(user_id))
|
||||
queryset = get_user_asset_permissions(user)
|
||||
|
||||
if user_group_id:
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
queryset = get_user_group_asset_permissions(user_group)
|
||||
return queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
if getattr(self, 'user_id', ''):
|
||||
return serializers.UserAssetPermissionSerializer
|
||||
return serializers.AssetPermissionSerializer
|
||||
|
||||
|
||||
class RevokeUserAssetPermission(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
permission_id = str(request.data.get('id', ''))
|
||||
user_id = str(request.data.get('user_id', ''))
|
||||
|
||||
if permission_id and user_id and permission_id.isdigit() and user_id.isdigit():
|
||||
asset_permission = get_object_or_404(AssetPermission, id=int(permission_id))
|
||||
user = get_object_or_404(User, id=int(user_id))
|
||||
|
||||
if asset_permission and user:
|
||||
asset_permission.users.remove(user)
|
||||
return Response({'msg': 'success'})
|
||||
return Response({'msg': 'failed'}, status=404)
|
||||
|
||||
|
||||
class RevokeUserGroupAssetPermission(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
permission_id = str(request.data.get('id', ''))
|
||||
user_group_id = str(request.data.get('user_group_id', ''))
|
||||
|
||||
if permission_id and user_group_id and permission_id.isdigit() and user_group_id.isdigit():
|
||||
asset_permission = get_object_or_404(AssetPermission, id=int(permission_id))
|
||||
user_group = get_object_or_404(UserGroup, id=int(user_group_id))
|
||||
|
||||
if asset_permission and user_group:
|
||||
asset_permission.user_groups.remove(user_group)
|
||||
return Response({'msg': 'success'})
|
||||
return Response({'msg': 'failed'}, status=404)
|
||||
|
||||
|
||||
class UserGrantedAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
|
||||
if user_id:
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
queryset = get_user_granted_assets(user)
|
||||
else:
|
||||
queryset = []
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedAssetGroupsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = AssetGroupSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_id = self.kwargs.get('pk', '')
|
||||
|
||||
if user_id:
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
queryset = get_user_granted_asset_groups(user)
|
||||
else:
|
||||
queryset = []
|
||||
return queryset
|
||||
|
||||
|
||||
class MyGrantedAssetsApi(ListAPIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
assets_json = []
|
||||
user = request.user
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if user:
|
||||
assets = get_user_granted_assets(user)
|
||||
|
||||
for asset, system_users in assets.items():
|
||||
assets_json.append({
|
||||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'port': asset.port,
|
||||
'system_users': [
|
||||
{
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
} for system_user in system_users
|
||||
],
|
||||
'comment': asset.comment
|
||||
})
|
||||
|
||||
return Response(assets_json, status=200)
|
||||
queryset = get_user_granted_assets(user)
|
||||
else:
|
||||
queryset = []
|
||||
return queryset
|
||||
|
||||
|
||||
class UserAssetsGroupsApi(APIView):
|
||||
class MyGrantedAssetsGroupsApi(APIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@ -56,46 +127,61 @@ class UserAssetsGroupsApi(APIView):
|
||||
for asset in assets:
|
||||
for asset_group in asset.groups.all():
|
||||
if asset_group.id in asset_groups:
|
||||
asset_groups[asset_group.id]['asset_num'] += 1
|
||||
asset_groups[asset_group.id]['asset_amount'] += 1
|
||||
else:
|
||||
asset_groups[asset_group.id] = {
|
||||
'id': asset_group.id,
|
||||
'name': asset_group.name,
|
||||
'comment': asset_group.comment,
|
||||
'asset_num': 1
|
||||
'assets_amount': 1
|
||||
}
|
||||
|
||||
asset_groups_json = asset_groups.values()
|
||||
return Response(asset_groups_json, status=200)
|
||||
|
||||
|
||||
class UserAssetsGroupAssetsApi(APIView):
|
||||
class MyAssetGroupAssetsApi(ListAPIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# asset_group_id = request.query_params.get('asset_group_id', -1)
|
||||
asset_group_id = kwargs.get('pk', -1)
|
||||
# asset_group_name = request.query_params.get('asset_group_name', '')
|
||||
user = request.user
|
||||
assets_json = []
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
asset_group_id = self.kwargs.get('pk', -1)
|
||||
user = self.request.user
|
||||
asset_group = get_object_or_none(AssetGroup, id=asset_group_id)
|
||||
|
||||
if user:
|
||||
if user and asset_group:
|
||||
assets = get_user_granted_assets(user)
|
||||
for asset, system_users in assets.items():
|
||||
for asset_group in asset.groups.all():
|
||||
if str(asset_group.id) == asset_group_id: # and asset_group.name == asset_group_name:
|
||||
assets_json.append({
|
||||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'port': asset.port,
|
||||
'system_users': [
|
||||
{
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
} for system_user in system_users
|
||||
],
|
||||
'comment': asset.comment
|
||||
})
|
||||
return Response(assets_json, status=200)
|
||||
for asset in assets:
|
||||
if asset_group in asset.groups.all():
|
||||
queryset.append(asset)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGroupGrantedAssetsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = AssetGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_group_id = self.kwargs.get('pk', '')
|
||||
|
||||
if user_group_id:
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
queryset = get_user_group_granted_assets(user_group)
|
||||
else:
|
||||
queryset = []
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGroupGrantedAssetGroupsApi(ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = AssetGroupSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_group_id = self.kwargs.get('pk', '')
|
||||
|
||||
if user_group_id:
|
||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||
queryset = get_user_group_granted_asset_groups(user_group)
|
||||
else:
|
||||
queryset = []
|
||||
return queryset
|
@ -4,6 +4,7 @@
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, AssetGroup, SystemUser
|
||||
from assets.serializers import AssetGrantedSerializer, AssetGroupSerializer
|
||||
|
||||
|
||||
def associate_system_users_with_assets(system_users, assets, asset_groups):
|
||||
|
@ -1,34 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from common.utils import get_object_or_none
|
||||
from .models import AssetPermission
|
||||
from .hands import User
|
||||
|
||||
|
||||
class AssetPermissionSerializer(serializers.ModelSerializer):
|
||||
# users_amount = serializers.SerializerMethodField()
|
||||
# user_groups_amount = serializers.SerializerMethodField()
|
||||
# assets_amount = serializers.SerializerMethodField()
|
||||
# asset_groups_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
fields = ['id', 'name', 'users', 'user_groups', 'assets', 'asset_groups',
|
||||
'system_users', 'is_active', 'comment', 'date_expired']
|
||||
|
||||
# @staticmethod
|
||||
# def get_users_amount(obj):
|
||||
# return obj.users.count()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_user_groups_amount(obj):
|
||||
# return obj.user_groups.count()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_assets_amount(obj):
|
||||
# return obj.assets.count()
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_asset_groups_amount(obj):
|
||||
# return obj.asset_groups.count()
|
||||
|
||||
class UserAssetPermissionSerializer(AssetPermissionSerializer):
|
||||
is_inherited = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def get_is_inherited(obj):
|
||||
if getattr(obj, 'inherited', ''):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
1
apps/perms/urls/__init__.py
Normal file
1
apps/perms/urls/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
37
apps/perms/urls/api_urls.py
Normal file
37
apps/perms/urls/api_urls.py
Normal file
@ -0,0 +1,37 @@
|
||||
# coding:utf-8
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework import routers
|
||||
from .. import api
|
||||
|
||||
app_name = 'perms'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/user/my/assets/$', api.MyGrantedAssetsApi.as_view(), name='my-assets'),
|
||||
url(r'^v1/user/my/asset-groups/$', api.MyGrantedAssetsGroupsApi.as_view(), name='my-asset-groups'),
|
||||
url(r'^v1/user/my/asset-group/(?P<pk>[0-9]+)/assets/$', api.MyAssetGroupAssetsApi.as_view(),
|
||||
name='user-my-asset-group-assets'),
|
||||
|
||||
# Select user permission of asset and asset group
|
||||
url(r'^v1/user/(?P<pk>[0-9]+)/assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
url(r'^v1/user/(?P<pk>[0-9]+)/asset-groups/$', api.UserGrantedAssetGroupsApi.as_view(),
|
||||
name='user-asset-groups'),
|
||||
|
||||
# Select user group permission of asset and asset group
|
||||
url(r'^v1/user-group/(?P<pk>[0-9]+)/assets/$', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||
url(r'^v1/user-group/(?P<pk>[0-9]+)/asset-groups/$', api.UserGroupGrantedAssetGroupsApi.as_view(),
|
||||
name='user-group-asset-groups'),
|
||||
|
||||
|
||||
# Revoke permission api
|
||||
url(r'^v1/asset-permissions/user/revoke/', api.RevokeUserAssetPermission.as_view(),
|
||||
name='revoke-user-asset-permission'),
|
||||
url(r'^v1/asset-permissions/user-group/revoke/', api.RevokeUserGroupAssetPermission.as_view(),
|
||||
name='revoke-user-group-asset-permission'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
@ -1,8 +1,7 @@
|
||||
# coding:utf-8
|
||||
|
||||
from django.conf.urls import url
|
||||
import views
|
||||
import api
|
||||
from .. import views
|
||||
|
||||
app_name = 'perms'
|
||||
|
||||
@ -21,14 +20,4 @@ urlpatterns = [
|
||||
name='asset-permission-asset-list'),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^v1/asset-permission/$', api.AssetPermissionListCreateApi.as_view(),
|
||||
name='asset-permission-list-create-api'),
|
||||
url(r'^v1/user/assets/$', api.UserAssetsApi.as_view(),
|
||||
name='user-assets'),
|
||||
url(r'^v1/user/asset-groups/$', api.UserAssetsGroupsApi.as_view(),
|
||||
name='user-asset-groups'),
|
||||
url(r'^v1/user/asset-groups/(?P<pk>[0-9]+)/assets/$', api.UserAssetsGroupAssetsApi.as_view(),
|
||||
name='user-asset-groups-assets'),
|
||||
]
|
||||
|
@ -1,5 +1,6 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from common.utils import setattr_bulk
|
||||
from .hands import User, UserGroup, Asset, AssetGroup, SystemUser
|
||||
|
||||
|
||||
@ -7,7 +8,7 @@ def get_user_group_granted_asset_groups(user_group):
|
||||
"""Return asset groups granted of the user group
|
||||
|
||||
:param user_group: Instance of :class: ``UserGroup``
|
||||
:return: {asset1: {system_user1, }, asset1: {system_user1, system_user2]}
|
||||
:return: {asset_group1: {system_user1, }, asset_group2: {system_user1, system_user2}}
|
||||
"""
|
||||
asset_groups = {}
|
||||
asset_permissions = user_group.asset_permissions.all()
|
||||
@ -20,7 +21,6 @@ def get_user_group_granted_asset_groups(user_group):
|
||||
asset_groups[asset_group] |= set(asset_permission.system_users.all())
|
||||
else:
|
||||
asset_groups[asset_group] = set(asset_permission.system_users.all())
|
||||
|
||||
return asset_groups
|
||||
|
||||
|
||||
@ -41,7 +41,6 @@ def get_user_group_granted_assets(user_group):
|
||||
assets[asset] |= set(asset_permission.system_users.all())
|
||||
else:
|
||||
assets[asset] = set(asset_permission.system_users.all())
|
||||
|
||||
return assets
|
||||
|
||||
|
||||
@ -61,7 +60,7 @@ def get_user_granted_asset_groups_direct(user):
|
||||
if asset_group in asset_groups:
|
||||
asset_groups[asset_group] |= set(asset_permission.system_users.all())
|
||||
else:
|
||||
setattr(asset_group, 'is_inherit_from_user_group', False)
|
||||
setattr(asset_group, 'inherited', False)
|
||||
asset_groups[asset_group] = set(asset_permission.system_users.all())
|
||||
|
||||
return asset_groups
|
||||
@ -89,7 +88,7 @@ def get_user_granted_asset_groups_inherit_from_user_groups(user):
|
||||
if asset_group in asset_groups:
|
||||
asset_groups[asset_group] |= set(asset_permission.system_users.all())
|
||||
else:
|
||||
setattr(asset_group, 'is_inherit_from_user_group', True)
|
||||
setattr(asset_group, 'inherited', True)
|
||||
asset_groups[asset_group] = set(asset_permission.system_users.all())
|
||||
|
||||
return asset_groups
|
||||
@ -112,7 +111,6 @@ def get_user_granted_asset_groups(user):
|
||||
asset_groups[asset_group] |= asset_groups_direct[asset_group]
|
||||
else:
|
||||
asset_groups[asset_group] = asset_groups_direct[asset_group]
|
||||
|
||||
return asset_groups
|
||||
|
||||
|
||||
@ -132,10 +130,8 @@ def get_user_granted_assets_direct(user):
|
||||
if asset in assets:
|
||||
assets[asset] |= set(asset_permission.system_users.all())
|
||||
else:
|
||||
setattr(asset, 'is_inherit_from_user_groups', False)
|
||||
setattr(asset, 'is_inherit_from_user_groups', False)
|
||||
setattr(asset, 'inherited', False)
|
||||
assets[asset] = set(asset_permission.system_users.all())
|
||||
|
||||
return assets
|
||||
|
||||
|
||||
@ -154,7 +150,7 @@ def get_user_granted_assets_inherit_from_user_groups(user):
|
||||
if asset in assets:
|
||||
assets[asset] |= assets_inherited[asset]
|
||||
else:
|
||||
setattr(asset, 'is_inherit_from_user_groups', True)
|
||||
setattr(asset, 'inherited', True)
|
||||
assets[asset] = assets_inherited[asset]
|
||||
|
||||
return assets
|
||||
@ -175,10 +171,25 @@ def get_user_granted_assets(user):
|
||||
assets[asset] |= assets_direct[asset]
|
||||
else:
|
||||
assets[asset] = assets_direct[asset]
|
||||
|
||||
return assets
|
||||
|
||||
|
||||
def get_user_group_asset_permissions(user_group):
|
||||
permissions = user_group.asset_permissions.all()
|
||||
return permissions
|
||||
|
||||
|
||||
def get_user_asset_permissions(user):
|
||||
user_group_permissions = set()
|
||||
direct_permissions = set(setattr_bulk(user.asset_permissions.all(), 'inherited', 0))
|
||||
|
||||
for user_group in user.groups.all():
|
||||
permissions = get_user_group_asset_permissions(user_group)
|
||||
user_group_permissions |= set(permissions)
|
||||
user_group_permissions = set(setattr_bulk(user_group_permissions, 'inherited', 1))
|
||||
return direct_permissions | user_group_permissions
|
||||
|
||||
|
||||
def get_user_groups_granted_in_asset(asset):
|
||||
pass
|
||||
|
||||
|
@ -255,3 +255,12 @@ table.dataTable tbody td.selected td i.text-navy
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: right !important;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
margin-left: 15px;
|
||||
}
|
@ -1585,9 +1585,9 @@ table.dataTable thead .sorting_desc_disabled {
|
||||
.dataTables_wrapper {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.dataTables_length {
|
||||
float: left;
|
||||
}
|
||||
/*.dataTables_length {*/
|
||||
/*float: left;*/
|
||||
/*}*/
|
||||
.dataTables_filter label {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
@ -189,9 +189,8 @@ function activeNav() {
|
||||
function APIUpdateAttr(props) {
|
||||
// props = {url: .., body: , success: , error: , method: ,}
|
||||
props = props || {};
|
||||
success_message = props.success_message || 'Update Successfully!';
|
||||
fail_message = props.fail_message || 'Error occurred while updating.';
|
||||
|
||||
var success_message = props.success_message || 'Update Successfully!';
|
||||
var fail_message = props.fail_message || 'Error occurred while updating.';
|
||||
$.ajax({
|
||||
url: props.url,
|
||||
type: props.method || "PATCH",
|
||||
@ -199,19 +198,18 @@ function APIUpdateAttr(props) {
|
||||
contentType: props.content_type || "application/json; charset=utf-8",
|
||||
dataType: props.data_type || "json"
|
||||
}).done(function(data, textStatue, jqXHR) {
|
||||
toastr.success(success_message);
|
||||
if (typeof props.success === 'function') {
|
||||
return props.success(data);
|
||||
} else {
|
||||
toastr.success(success_message);
|
||||
}
|
||||
}
|
||||
|
||||
}).fail(function(jqXHR, textStatue, errorThrown) {
|
||||
toastr.error(fail_message);
|
||||
if (typeof props.error === 'function') {
|
||||
return props.error(errorThrown);
|
||||
} else {
|
||||
toastr.error(fail_message);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
// return true;
|
||||
}
|
||||
|
||||
// Sweet Alert for Delete
|
||||
@ -267,6 +265,7 @@ $.fn.serializeObject = function()
|
||||
};
|
||||
var jumpserver = {};
|
||||
jumpserver.checked = false;
|
||||
jumpserver.selected = {};
|
||||
jumpserver.initDataTable = function (options) {
|
||||
// options = {
|
||||
// ele *: $('#dataTable_id'),
|
||||
@ -285,59 +284,37 @@ jumpserver.initDataTable = function (options) {
|
||||
{
|
||||
targets: 0,
|
||||
orderable: false,
|
||||
createdCell: function(td) {
|
||||
$(td).html('<input type="checkbox" class="ipt_check">');
|
||||
}
|
||||
},
|
||||
createdCell: function(td, cellData) {
|
||||
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
|
||||
}},
|
||||
{className: 'text-center', targets: '_all'}
|
||||
];
|
||||
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
|
||||
var table = ele.DataTable({
|
||||
pageLength: options.pageLength || 25,
|
||||
pageLength: options.pageLength || 15,
|
||||
dom: options.dom || '<"#uc.pull-left"><"html5buttons"B>flti<"row m-t"<"#op.col-md-6"><"col-md-6"p>>',
|
||||
language: {
|
||||
url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json"
|
||||
},
|
||||
order: options.order || [[ 1, 'asc' ]],
|
||||
buttons: options.buttons || [
|
||||
{extend: 'excel',
|
||||
exportOptions: {
|
||||
modifier: {
|
||||
selected: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{extend: 'pdf',
|
||||
exportOptions: {
|
||||
modifier: {
|
||||
selected: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{extend: 'print',
|
||||
customize: function (win){
|
||||
$(win.document.body).addClass('white-bg');
|
||||
$(win.document.body).css('font-size', '10px');
|
||||
$(win.document.body).find('table')
|
||||
.addClass('compact')
|
||||
.css('font-size', 'inherit');
|
||||
}
|
||||
}
|
||||
],
|
||||
select: options.select || 'multi',
|
||||
buttons: [],
|
||||
columnDefs: columnDefs,
|
||||
select: options.select || {style: 'multi'},
|
||||
ajax: {
|
||||
url: options.ajax_url ,
|
||||
dataSrc: ""
|
||||
},
|
||||
columns: options.columns || []
|
||||
columns: options.columns || [],
|
||||
lengthMenu: [[15, 25, 50, -1], [15, 25, 50, "All"]]
|
||||
});
|
||||
table.on('select', function(e, dt, type, indexes) {
|
||||
var $node = table[ type ]( indexes ).nodes().to$();
|
||||
$node.find('input.ipt_check').prop('checked', true);
|
||||
jumpserver.selected[$node.find('input.ipt_check').prop('id')] = true
|
||||
}).on('deselect', function(e, dt, type, indexes) {
|
||||
var $node = table[ type ]( indexes ).nodes().to$();
|
||||
$node.find('input.ipt_check').prop('checked', false);
|
||||
jumpserver.selected[$node.find('input.ipt_check').prop('id')] = false
|
||||
}).on('draw', function(){
|
||||
$('#op').html(options.op_html || '');
|
||||
$('#uc').html(options.uc_html || '');
|
||||
@ -353,5 +330,6 @@ jumpserver.initDataTable = function (options) {
|
||||
table.rows().deselect();
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
};
|
||||
|
@ -28,10 +28,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
{% block form %} {% endblock %}
|
||||
{% if form.errors.all %}
|
||||
<div class="alert alert-danger" style="margin: 20px auto 0px">
|
||||
{{ form.errors.all }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block form %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="footer fixed">
|
||||
<div class="pull-right">
|
||||
Version <strong>0.3.3</strong> GPL.
|
||||
Version <strong>0.4.0</strong> GPL.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Copyright</strong> Jumpserver.org Team © 2014-2016
|
||||
|
@ -48,10 +48,10 @@
|
||||
<li id="command-log">
|
||||
<a href="{% url 'audits:command-log-list' %}">{% trans 'Command log' %}</a>
|
||||
</li>
|
||||
<li id="login">
|
||||
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Login log' %}</a>
|
||||
<li id="login-log">
|
||||
<a href="{% url 'audits:login-log-list' %}">{% trans 'Login log' %}</a>
|
||||
</li>
|
||||
<li id="admin">
|
||||
<li id="admin-log">
|
||||
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Admin log' %}</a>
|
||||
</li>
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
@ -11,42 +14,51 @@ from .serializers import TerminalSerializer, TerminalHeatbeatSerializer
|
||||
from .hands import IsSuperUserOrTerminalUser
|
||||
|
||||
|
||||
class TerminalCreateListApi(ListCreateAPIView):
|
||||
class TerminalViewSet(viewsets.ModelViewSet):
|
||||
queryset = Terminal.objects.all()
|
||||
serializer_class = TerminalSerializer
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
def create(self, request, *args, **kwargs):
|
||||
name = signer.unsign(request.data.get('name', ''))
|
||||
if name:
|
||||
terminal = get_object_or_none(Terminal, name=name)
|
||||
if terminal:
|
||||
data = {
|
||||
'data': {'name': name, 'id': terminal.id},
|
||||
}
|
||||
if terminal.is_active:
|
||||
return Response(data={'data': {'name': name, 'id': terminal.id},
|
||||
'msg': 'Success'},
|
||||
status=200)
|
||||
data['msg'] = 'Success'
|
||||
return Response(data=data, status=200)
|
||||
else:
|
||||
return Response(data={'data': {'name': name, 'ip': terminal.ip},
|
||||
'msg': 'Need admin active it'},
|
||||
status=203)
|
||||
data['msg'] = 'Need admin active this terminal'
|
||||
return Response(data=data, status=203)
|
||||
|
||||
else:
|
||||
ip = request.META.get('X-Real-IP') or request.META.get('REMOTE_ADDR')
|
||||
terminal = Terminal.objects.create(name=name, ip=ip)
|
||||
return Response(data={'data': {'name': name, 'ip': terminal.ip},
|
||||
'msg': 'Need admin active it'},
|
||||
status=201)
|
||||
data = {
|
||||
'data': {'name': name, 'id': terminal.id},
|
||||
'msg': 'Need admin active this terminal',
|
||||
}
|
||||
return Response(data=data, status=201)
|
||||
else:
|
||||
return Response(data={'msg': 'Secrete key invalid'}, status=401)
|
||||
|
||||
|
||||
class TerminalHeatbeatApi(ListCreateAPIView):
|
||||
model = TerminalHeatbeat
|
||||
queryset = TerminalHeatbeat.objects.all()
|
||||
serializer_class = TerminalHeatbeatSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
|
||||
|
||||
class TerminalApiDetailUpdateDetailApi(RetrieveUpdateDestroyAPIView):
|
||||
queryset = Terminal.objects.all()
|
||||
serializer_class = TerminalSerializer
|
||||
class TerminalHeatbeatViewSet(viewsets.ModelViewSet):
|
||||
queryset = TerminalHeatbeat.objects.all()
|
||||
serializer_class = TerminalHeatbeatSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
terminal = request.user
|
||||
TerminalHeatbeat.objects.create(terminal=terminal)
|
||||
return Response({'msg': 'Success'})
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Terminal, TerminalHeatbeat
|
||||
@ -8,22 +9,30 @@ from .hands import ProxyLog
|
||||
|
||||
|
||||
class TerminalSerializer(serializers.ModelSerializer):
|
||||
proxy_amount = serializers.SerializerMethodField()
|
||||
proxy_online = serializers.SerializerMethodField()
|
||||
is_alive = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = ['id', 'name', 'ip', 'type', 'url', 'comment', 'is_active',
|
||||
'get_type_display', 'proxy_amount']
|
||||
fields = ['id', 'name', 'ip', 'type', 'url', 'comment',
|
||||
'is_active', 'get_type_display', 'proxy_online', 'is_alive']
|
||||
|
||||
@staticmethod
|
||||
def get_proxy_amount(obj):
|
||||
def get_proxy_online(obj):
|
||||
return ProxyLog.objects.filter(terminal=obj.name, is_finished=False).count()
|
||||
|
||||
@staticmethod
|
||||
def get_is_alive(obj):
|
||||
log = obj.terminalheatbeat_set.last()
|
||||
if timezone.now() - log.date_created > timezone.timedelta(seconds=600):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class TerminalHeatbeatSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TerminalHeatbeat
|
||||
fields = ['terminal']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
5
apps/terminal/tasks.py
Normal file
5
apps/terminal/tasks.py
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
@ -27,8 +27,9 @@
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'proxy_amount' %}</th>
|
||||
<th class="text-center">{% trans 'proxy online' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Alive' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -42,6 +43,7 @@
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#terminal_list_table'),
|
||||
buttons: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "users:user-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
@ -54,7 +56,14 @@ $(document).ready(function(){
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
console.log(rowData.name);
|
||||
var update_btn = '<a href="{% url "terminal:terminal-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
|
||||
.replace('99991937', cellData);
|
||||
@ -64,9 +73,9 @@ $(document).ready(function(){
|
||||
$(td).html(update_btn + delete_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "terminal:terminal-list-create-api" %}',
|
||||
ajax_url: '{% url "api-terminal:terminal-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "ip" }, {data: "get_type_display" },
|
||||
{data: "proxy_amount"}, {data: "is_active" }, {data: "id"}],
|
||||
{data: "proxy_online"}, {data: "is_active" }, {data: 'is_active'}, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
@ -74,7 +83,7 @@ $(document).ready(function(){
|
||||
var $this = $(this);
|
||||
var uid = $this.data('uid');
|
||||
var name = $(this).data('name');
|
||||
var the_url = '{% url "terminal:terminal-detail-update-delete-api" pk=99991937 %}'.replace('99991937', uid);
|
||||
var the_url = '{% url "api-terminal:terminal-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
objectDelete($this, name, the_url)
|
||||
})
|
||||
</script>
|
||||
|
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework import routers
|
||||
|
||||
import views
|
||||
import api
|
||||
@ -14,9 +15,11 @@ urlpatterns = [
|
||||
url(r'^terminal/(?P<pk>\d+)/update$', views.TerminalUpdateView.as_view(), name='terminal-update'),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^v1/terminal/$', api.TerminalCreateListApi.as_view(), name='terminal-list-create-api'),
|
||||
url(r'^v1/terminal/(?P<pk>\d+)/$', api.TerminalApiDetailUpdateDetailApi.as_view(),
|
||||
name='terminal-detail-update-delete-api'),
|
||||
url(r'^v1/terminal-heatbeat/$', api.TerminalHeatbeatApi.as_view(), name='terminal-heatbeat-api'),
|
||||
]
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'v1/terminal/heatbeat', api.TerminalHeatbeatViewSet, 'terminal-heatbeat')
|
||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||
# urlpatterns += [
|
||||
# url(r'v1/terminal/heatbeat/', api.TerminalHeatbeatApi.as_view(), name='api-terminal-heatbeat')
|
||||
# ]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
1
apps/terminal/urls/__init__.py
Normal file
1
apps/terminal/urls/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
16
apps/terminal/urls/api_urls.py
Normal file
16
apps/terminal/urls/api_urls.py
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import routers
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'terminal'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'v1/terminal/heatbeat', api.TerminalHeatbeatViewSet, 'terminal-heatbeat')
|
||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
14
apps/terminal/urls/views_urls.py
Normal file
14
apps/terminal/urls/views_urls.py
Normal file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .. import views
|
||||
|
||||
app_name = 'terminal'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^terminal$', views.TerminalListView.as_view(), name='terminal-list'),
|
||||
url(r'^terminal/(?P<pk>\d+)/update$', views.TerminalUpdateView.as_view(), name='terminal-update'),
|
||||
]
|
@ -6,120 +6,130 @@ 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
|
||||
from rest_framework import generics, status, viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView, BulkModelViewSet
|
||||
from rest_framework import authentication
|
||||
|
||||
from common.mixins import BulkDeleteApiMixin
|
||||
from common.utils import get_logger
|
||||
from .utils import check_user_valid, token_gen
|
||||
from .models import User, UserGroup
|
||||
from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
|
||||
GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
|
||||
from .hands import write_login_log_async
|
||||
from .backends import IsSuperUser, IsTerminalUser, IsValidUser, IsSuperUserOrTerminalUser
|
||||
from . import serializers
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class UserDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
class UserViewSet(BulkModelViewSet):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserDetailSerializer
|
||||
serializer_class = serializers.UserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class UserAndGroupEditApi(generics.RetrieveUpdateAPIView):
|
||||
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserAndGroupSerializer
|
||||
serializer_class = serializers.UserUpdateGroupSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class UserResetPasswordApi(generics.UpdateAPIView):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserDetailSerializer
|
||||
serializer_class = serializers.UserSerializer
|
||||
|
||||
def perform_update(self, serializer):
|
||||
# Note: we are not updating the user object here.
|
||||
# We just do the reset-password staff.
|
||||
user = self.get_object()
|
||||
import uuid
|
||||
from .utils import send_reset_password_mail
|
||||
user = self.get_object()
|
||||
user.password_raw = str(uuid.uuid4())
|
||||
user.save()
|
||||
from .utils import send_reset_password_mail
|
||||
send_reset_password_mail(user)
|
||||
|
||||
|
||||
class UserResetPKApi(generics.UpdateAPIView):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserDetailSerializer
|
||||
serializer_class = serializers.UserSerializer
|
||||
|
||||
def perform_update(self, serializer):
|
||||
from .utils import send_reset_ssh_key_mail
|
||||
user = self.get_object()
|
||||
user.is_public_key_valid = False
|
||||
user.save()
|
||||
from .utils import send_reset_ssh_key_mail
|
||||
send_reset_ssh_key_mail(user)
|
||||
|
||||
|
||||
class UserUpdatePKApi(generics.UpdateAPIView):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserPKUpdateSerializer
|
||||
serializer_class = serializers.UserPKUpdateSerializer
|
||||
|
||||
def perform_update(self, serializer):
|
||||
user = self.get_object()
|
||||
user.private_key = serializer.validated_data['_public_key']
|
||||
user.public_key = serializer.validated_data['_public_key']
|
||||
user.save()
|
||||
|
||||
|
||||
class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
class UserGroupViewSet(viewsets.ModelViewSet):
|
||||
queryset = UserGroup.objects.all()
|
||||
serializer_class = 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()
|
||||
serializer_class = serializers.UserGroupSerializer
|
||||
|
||||
|
||||
class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserBulkUpdateSerializer
|
||||
permission_classes = (IsSuperUserOrTerminalUser,)
|
||||
|
||||
# def get(self, request, *args, **kwargs):
|
||||
# return super(UserListUpdateApi, self).get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
|
||||
queryset = UserGroup.objects.all()
|
||||
serializer_class = GroupBulkUpdateSerializer
|
||||
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 DeleteUserFromGroupApi(generics.DestroyAPIView):
|
||||
queryset = UserGroup.objects.all()
|
||||
serializer_class = GroupDetailSerializer
|
||||
# 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)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
group = self.get_object()
|
||||
self.perform_destroy(group, **kwargs)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
#
|
||||
# class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
# queryset = UserGroup.objects.all()
|
||||
# serializer_class = serializers.GroupBulkUpdateSerializer
|
||||
#
|
||||
|
||||
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 UserTokenApi(APIView):
|
||||
# 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
|
||||
|
||||
@ -127,10 +137,11 @@ class UserTokenApi(APIView):
|
||||
username = request.data.get('username', '')
|
||||
password = request.data.get('password', '')
|
||||
public_key = request.data.get('public_key', '')
|
||||
remote_addr = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
remote_addr = base64.b64encode(remote_addr).replace('=', '')
|
||||
remote_addr = request.data.get('remote_addr', '')
|
||||
terminal = request.data.get('terminal', '')
|
||||
login_type = request.data.get('login_type', 'T')
|
||||
user = check_user_valid(username=username, password=password, public_key=public_key)
|
||||
|
||||
if user:
|
||||
token = cache.get('%s_%s' % (user.id, remote_addr))
|
||||
if not token:
|
||||
@ -138,6 +149,8 @@ class UserTokenApi(APIView):
|
||||
|
||||
cache.set(token, user.id, self.expiration)
|
||||
cache.set('%s_%s' % (user.id, remote_addr), token, self.expiration)
|
||||
write_login_log_async.delay(user.username, name=user.name, terminal=terminal,
|
||||
login_ip=remote_addr, login_type=login_type)
|
||||
return Response({'token': token, 'id': user.id, 'username': user.username, 'name': user.name})
|
||||
else:
|
||||
return Response({'msg': 'Invalid password or public key or user is not active or expired'})
|
||||
return Response({'msg': 'Invalid password or public key or user is not active or expired'}, status=401)
|
||||
|
@ -78,6 +78,7 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||
|
||||
def authenticate_credentials(self, token, request):
|
||||
user_id = cache.get(token)
|
||||
print('Auth id: %s' % user_id)
|
||||
user = get_object_or_none(User, id=user_id)
|
||||
|
||||
if not user:
|
||||
@ -87,7 +88,6 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||
remote_addr = base64.b16encode(remote_addr).replace('=', '')
|
||||
cache.set(token, user_id, self.expiration)
|
||||
cache.set('%s_%s' % (user.id, remote_addr), token, self.expiration)
|
||||
|
||||
return user, None
|
||||
|
||||
|
||||
|
@ -5,8 +5,9 @@ from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from captcha.fields import CaptchaField
|
||||
|
||||
from common.utils import validate_ssh_public_key
|
||||
from perms.models import AssetPermission
|
||||
from .models import User, UserGroup
|
||||
from .hands import AssetPermission
|
||||
|
||||
|
||||
class UserLoginForm(AuthenticationForm):
|
||||
@ -17,8 +18,7 @@ class UserLoginForm(AuthenticationForm):
|
||||
captcha = CaptchaField()
|
||||
|
||||
|
||||
class UserCreateForm(forms.ModelForm):
|
||||
|
||||
class UserCreateUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
@ -36,32 +36,30 @@ class UserCreateForm(forms.ModelForm):
|
||||
|
||||
|
||||
class UserBulkImportForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'email', 'enable_otp', 'role']
|
||||
|
||||
|
||||
class UserUpdateForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'name', 'email', 'groups', 'wechat',
|
||||
'phone', 'enable_otp', 'role', 'date_expired', 'comment',
|
||||
]
|
||||
help_texts = {
|
||||
'username': '* required',
|
||||
'email': '* required',
|
||||
'groups': '* required'
|
||||
}
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Join user groups')}),
|
||||
}
|
||||
# class UserUpdateForm(forms.ModelForm):
|
||||
#
|
||||
# class Meta:
|
||||
# model = User
|
||||
# fields = [
|
||||
# 'name', 'email', 'groups', 'wechat',
|
||||
# 'phone', 'enable_otp', 'role', 'date_expired', 'comment',
|
||||
# ]
|
||||
# help_texts = {
|
||||
# 'username': '* required',
|
||||
# 'email': '* required',
|
||||
# 'groups': '* required'
|
||||
# }
|
||||
# widgets = {
|
||||
# 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Join user groups')}),
|
||||
# }
|
||||
|
||||
|
||||
class UserGroupForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
fields = [
|
||||
@ -84,22 +82,14 @@ class UserKeyForm(forms.Form):
|
||||
public_key = forms.CharField(
|
||||
label=_('ssh public key'), max_length=5000,
|
||||
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
|
||||
help_text=_('Paste your id_ras.pub here.'))
|
||||
help_text=_('Paste your id_rsa.pub here.'))
|
||||
|
||||
def clean_public_key(self):
|
||||
public_key = self.cleaned_data['public_key']
|
||||
if self.user._public_key and public_key == self.user.public_key:
|
||||
if self.user.public_key and public_key == self.user.public_key:
|
||||
raise forms.ValidationError(_('Public key should not be the same as your old one.'))
|
||||
from sshpubkeys import SSHKey
|
||||
from sshpubkeys.exceptions import InvalidKeyException
|
||||
ssh = SSHKey(public_key)
|
||||
try:
|
||||
ssh.parse()
|
||||
except InvalidKeyException as e:
|
||||
print e
|
||||
raise forms.ValidationError(_('Not a valid ssh public key'))
|
||||
except NotImplementedError as e:
|
||||
print e
|
||||
|
||||
if not validate_ssh_public_key(public_key):
|
||||
raise forms.ValidationError(_('Not a valid ssh public key'))
|
||||
return public_key
|
||||
|
||||
@ -108,7 +98,6 @@ class UserPrivateAssetPermissionForm(forms.ModelForm):
|
||||
|
||||
def save(self, commit=True):
|
||||
self.instance = super(UserPrivateAssetPermissionForm, self).save(commit=commit)
|
||||
# self.instance.private_for = 'U'
|
||||
self.instance.users = [self.user]
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
@ -126,3 +115,30 @@ class UserPrivateAssetPermissionForm(forms.ModelForm):
|
||||
'system_users': forms.SelectMultiple(attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select system users')}),
|
||||
}
|
||||
|
||||
|
||||
class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
|
||||
|
||||
def save(self, commit=True):
|
||||
self.instance = super(UserGroupPrivateAssetPermissionForm, self).save(commit=commit)
|
||||
self.instance.user_groups = [self.user_group]
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
fields = [
|
||||
'assets', 'asset_groups', 'system_users', 'name',
|
||||
]
|
||||
widgets = {
|
||||
'assets': forms.SelectMultiple(attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select assets')}),
|
||||
'asset_groups': forms.SelectMultiple(attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset groups')}),
|
||||
'system_users': forms.SelectMultiple(attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select system users')}),
|
||||
}
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
excel = forms.FileField()
|
||||
|
@ -11,5 +11,6 @@
|
||||
"""
|
||||
|
||||
from terminal.models import Terminal
|
||||
from perms.models import AssetPermission
|
||||
from perms.utils import get_user_granted_assets, get_user_granted_asset_groups
|
||||
from audits.tasks import write_login_log_async
|
||||
# from perms.models import AssetPermission
|
||||
# from perms.utils import get_user_granted_assets, get_user_granted_asset_groups
|
||||
|
@ -3,6 +3,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core import signing
|
||||
@ -234,7 +235,7 @@ class User(AbstractUser):
|
||||
user.groups.add(UserGroup.initial())
|
||||
|
||||
def delete(self):
|
||||
if self.pk == 1:
|
||||
if self.pk == 1 or self.username == 'admin':
|
||||
return
|
||||
return super(User, self).delete()
|
||||
|
||||
|
@ -5,14 +5,37 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
|
||||
|
||||
from common.utils import signer
|
||||
from common.utils import signer, validate_ssh_public_key
|
||||
from .models import User, UserGroup
|
||||
|
||||
|
||||
class UserDetailSerializer(serializers.ModelSerializer):
|
||||
# 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())
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
|
||||
list_serializer_class = BulkListSerializer
|
||||
exclude = ['first_name', 'last_name', 'password', '_private_key', '_public_key']
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend(['groups_display', 'get_role_display', 'is_valid'])
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
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):
|
||||
@ -22,21 +45,14 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
|
||||
|
||||
@staticmethod
|
||||
def validate__public_key(value):
|
||||
from sshpubkeys import SSHKey
|
||||
from sshpubkeys.exceptions import InvalidKeyException
|
||||
ssh = SSHKey(value)
|
||||
try:
|
||||
ssh.parse()
|
||||
except InvalidKeyException as e:
|
||||
print e
|
||||
raise serializers.ValidationError(_('Not a valid ssh public key'))
|
||||
except NotImplementedError as e:
|
||||
print e
|
||||
if not validate_ssh_public_key(value):
|
||||
print('Not a valid key')
|
||||
print(value)
|
||||
raise serializers.ValidationError(_('Not a valid ssh public key'))
|
||||
return value
|
||||
|
||||
|
||||
class UserAndGroupSerializer(serializers.ModelSerializer):
|
||||
class UserUpdateGroupSerializer(serializers.ModelSerializer):
|
||||
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
|
||||
|
||||
class Meta:
|
||||
@ -44,43 +60,63 @@ class UserAndGroupSerializer(serializers.ModelSerializer):
|
||||
fields = ['id', 'groups']
|
||||
|
||||
|
||||
class GroupDetailSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users']
|
||||
|
||||
|
||||
class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
group_display = serializers.SerializerMethodField()
|
||||
active_display = serializers.SerializerMethodField()
|
||||
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
|
||||
|
||||
class Meta(object):
|
||||
model = User
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = ['id', 'is_active', 'username', 'name', 'email', 'role', 'avatar',
|
||||
'enable_otp', 'comment', 'groups', 'get_role_display',
|
||||
'group_display', 'active_display']
|
||||
|
||||
@staticmethod
|
||||
def get_group_display(obj):
|
||||
return " ".join([group.name for group in obj.groups.all()])
|
||||
|
||||
@staticmethod
|
||||
def get_active_display(obj):
|
||||
# TODO: user active state
|
||||
return not (obj.is_expired and obj.is_active)
|
||||
|
||||
|
||||
class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
user_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = ['id', 'name', 'comment', 'user_amount']
|
||||
|
||||
@staticmethod
|
||||
def get_user_amount(obj):
|
||||
return obj.users.count()
|
||||
|
||||
|
||||
class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer):
|
||||
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
fields = ['id', 'users']
|
||||
|
||||
|
||||
# class GroupDetailSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = UserGroup
|
||||
# fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users']
|
||||
|
||||
|
||||
# class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
# group_display = serializers.SerializerMethodField()
|
||||
# active_display = serializers.SerializerMethodField()
|
||||
# groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
|
||||
#
|
||||
# class Meta(object):
|
||||
# model = User
|
||||
# list_serializer_class = BulkListSerializer
|
||||
# fields = ['id', 'is_active', 'username', 'name', 'email', 'role', 'avatar',
|
||||
# 'enable_otp', 'comment', 'groups', 'get_role_display',
|
||||
# 'group_display', 'active_display']
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_group_display(obj):
|
||||
# return " ".join([group.name for group in obj.groups.all()])
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_active_display(obj):
|
||||
# TODO: user active state
|
||||
# return not (obj.is_expired and obj.is_active)
|
||||
#
|
||||
#
|
||||
# class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
# user_amount = serializers.SerializerMethodField()
|
||||
#
|
||||
# class Meta:
|
||||
# model = UserGroup
|
||||
# list_serializer_class = BulkListSerializer
|
||||
# fields = ['id', 'name', 'comment', 'user_amount']
|
||||
#
|
||||
# @staticmethod
|
||||
# def get_user_amount(obj):
|
||||
# return obj.users.count()
|
||||
#
|
||||
|
@ -1,82 +1,52 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
<link href="{% static "css/plugins/datepicker/datepicker3.css" %}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
{% block form %}
|
||||
<form method="post" class="form-horizontal" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Account' %}</h3>
|
||||
{{ form.username|bootstrap_horizontal }}
|
||||
{{ form.name|bootstrap_horizontal }}
|
||||
{{ form.email|bootstrap_horizontal }}
|
||||
{{ form.groups|bootstrap_horizontal }}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% block user_template_title %}{% trans 'Create user' %}{% endblock %}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<form method="post" class="form-horizontal" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Account' %}</h3>
|
||||
{% block username %} {% endblock %}
|
||||
{{ form.name|bootstrap_horizontal }}
|
||||
{{ form.email|bootstrap_horizontal }}
|
||||
{{ form.groups|bootstrap_horizontal }}
|
||||
<div class="hr-line-dashed"></div>
|
||||
{% block password %} {% endblock %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
{% block password %} {% endblock %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Security and Role' %}</h3>
|
||||
{{ form.role|bootstrap_horizontal }}
|
||||
<div class="form-group {% if form.date_expired.errors %} has-error {% endif %}" id="date_5">
|
||||
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group date">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input id="{{ form.date_expired.id_for_label }}" name="{{ form.date_expired.html_name }}" type="text" class="form-control" value="{{ form.date_expired.value|date:'Y-m-d' }}">
|
||||
</div>
|
||||
<span class="help-block ">{{ form.date_expired.errors }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{# {{ form.date_expired|bootstrap_horizontal }}#}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.enable_otp.id_for_label }}" class="col-sm-2 control-label">{% trans 'Enable OTP' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.enable_otp }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Profile' %}</h3>
|
||||
{{ form.phone|bootstrap_horizontal }}
|
||||
{{ form.wechat|bootstrap_horizontal }}
|
||||
{{ form.comment|bootstrap_horizontal }}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Security and Role' %}</h3>
|
||||
{{ form.role|bootstrap_horizontal }}
|
||||
<div class="form-group {% if form.date_expired.errors %} has-error {% endif %}" id="date_5">
|
||||
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group date">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input id="{{ form.date_expired.id_for_label }}" name="{{ form.date_expired.html_name }}" type="text" class="form-control" value="{{ form.date_expired.value|date:'Y-m-d' }}">
|
||||
</div>
|
||||
<span class="help-block ">{{ form.date_expired.errors }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ form.date_expired|bootstrap_horizontal }}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.enable_otp.id_for_label }}" class="col-sm-2 control-label">{% trans 'Enable OTP' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.enable_otp }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Profile' %}</h3>
|
||||
{{ form.phone|bootstrap_horizontal }}
|
||||
{{ form.wechat|bootstrap_horizontal }}
|
||||
{{ form.comment|bootstrap_horizontal }}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
|
@ -17,34 +17,21 @@
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'users:user-detail' pk=user_object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
<a href="{% url 'users:user-detail' pk=user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-asset-permission' pk=user_object.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
<a href="{% url 'users:user-asset-permission' pk=user.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
<a href="{% url 'users:user-granted-asset' pk=user.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-login-history' pk=user_object.id %}" class="text-center"><i class="fa fa-calculator-o"></i> {% trans 'Login history' %}</a>
|
||||
</li>
|
||||
<form id="search_form" method="get" action="" class="pull-right mail-search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||
<div class="input-group-btn">
|
||||
<button id="search_btn" type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset permission of ' %} <b>{{ user_object.name }}</b></span>
|
||||
<span style="float: left">{% trans 'Asset permission of ' %} <b>{{ user.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
@ -60,52 +47,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover">
|
||||
<table class="table table-hover " id="user_permissions_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><a href="{% url 'perms:asset-permission-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center">{% trans 'User ' %}</th>
|
||||
<th class="text-center">{% trans 'User group ' %}</th>
|
||||
<th class="text-center">{% trans 'Asset ' %}</th>
|
||||
<th class="text-center">{% trans 'Asset group ' %}</th>
|
||||
<th class="text-center">{% trans 'System user ' %}</th>
|
||||
<th class="text-center">
|
||||
<a href="#">{% trans 'Is valid' %}</a>
|
||||
</th>
|
||||
<th></th>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Asset' %}</th>
|
||||
<th>{% trans 'Asset group' %}</th>
|
||||
<th>{% trans 'System user' %}</th>
|
||||
<th>{% trans 'Valid' %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset_permission in object_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">
|
||||
<a href="{% url 'perms:asset-permission-detail' pk=asset_permission.id %}">
|
||||
{{ asset_permission.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ asset_permission.users.count}}</td>
|
||||
<td class="text-center">{{ asset_permission.user_groups.count}}</td>
|
||||
<td class="text-center">{{ asset_permission.assets.count }}</td>
|
||||
<td class="text-center">{{ asset_permission.asset_groups.count }}</td>
|
||||
<td class="text-center">{{ asset_permission.system_users.count }}</td>
|
||||
<td class="text-center">
|
||||
{% if asset_permission.is_valid %}
|
||||
<i class="fa fa-check text-navy"></i>
|
||||
{% else %}
|
||||
<i class="fa fa-times text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-xs btn_delete_user_group {% if asset_permission.is_inherit_from_user_groups %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
{% include '_pagination.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -115,7 +73,7 @@
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick create permission for user' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="post" action="{% url 'users:user-asset-permission-create' pk=user_object.id %}">
|
||||
<form method="post" action="{% url 'users:user-asset-permission-create' pk=user.id %}">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% csrf_token %}
|
||||
@ -159,25 +117,69 @@
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
{# function switch_user_status(obj) {#}
|
||||
{# var status = $(obj).prop('checked');#}
|
||||
{##}
|
||||
{# $.ajax({#}
|
||||
{# url: "{% url 'users:user-active-api' pk=user_object.id %}",#}
|
||||
{# type: "PUT",#}
|
||||
{# data: {#}
|
||||
{# 'is_active': status#}
|
||||
{# },#}
|
||||
{# success: function (data, status) {#}
|
||||
{# console.log(data)#}
|
||||
{# },#}
|
||||
{# error: function () {#}
|
||||
{# console.log('error')#}
|
||||
{# }#}
|
||||
{# })#}
|
||||
{# }#}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
});
|
||||
var options = {
|
||||
ele: $('#user_permissions_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
select: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "perms:asset-permission-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 2, createdCell: function (td, cellData, rowData) {
|
||||
var dataLength = cellData.length;
|
||||
$(td).html(dataLength);
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
var dataLength = cellData.length;
|
||||
$(td).html(dataLength);
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var dataLength = cellData.length;
|
||||
$(td).html(dataLength);
|
||||
}},
|
||||
{targets: 5, 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 btn = '<button class="btn btn-danger btn-xs btn_del_permission disabled" id=99991937 type="button" style="float: right;"><i class="fa fa-minus"></i></button>';
|
||||
if (rowData.is_inherited) {
|
||||
$(td).html(btn)
|
||||
} else {
|
||||
btn = btn.replace('99991937', cellData);
|
||||
$(td).html(btn.replace('disabled', ''));
|
||||
}
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-perms:asset-permission-list" %}?user={{ user.id }}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "assets" }, {data: "asset_groups"},
|
||||
{data: "system_users"}, {data: "is_active"}, {data: "id"}]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}).on('click', '.btn_del_permission', function () {
|
||||
var $this = $(this);
|
||||
var body = {
|
||||
id: $this.attr('id'),
|
||||
user_id: {{ user.id }}
|
||||
};
|
||||
var the_url = "{% url 'perms:revoke-user-asset-permission' %}";
|
||||
var success = function () {
|
||||
$this.closest('tr').remove();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PUT',
|
||||
success_message: '{% trans "Revoke Successfully!" %}',
|
||||
success: success
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@ -2,9 +2,9 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap %}
|
||||
{% block user_template_title %}{% trans "Create user" %}{% endblock %}
|
||||
{% block username %}
|
||||
{{ form.username|bootstrap_horizontal }}
|
||||
{% endblock %}
|
||||
{#{% block username %}#}
|
||||
{# {{ form.username|bootstrap_horizontal }}#}
|
||||
{#{% endblock %}#}
|
||||
{% block password %}
|
||||
<h3>{% trans 'Password' %}</h3>
|
||||
<div class="form-group">
|
||||
|
@ -18,20 +18,24 @@
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-detail' pk=user_object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
<a href="{% url 'users:user-detail' pk=user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-asset-permission' pk=user_object.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
<a href="{% url 'users:user-asset-permission' pk=user.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-granted-asset' pk=user.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'users:user-update' pk=user.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
<li><a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a></li>
|
||||
<li><a href="{% url 'users:user-login-history' pk=user_object.id %}" class="text-center"><i class="fa fa-calculator-o"></i> {% trans 'Login history' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ user_object.name }}</b></span>
|
||||
<span class="label"><b>{{ user.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
@ -51,56 +55,56 @@
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<img src="{{ user_object | user_avatar_url }}" class="img-circle" width="64" height="64">
|
||||
<img src="{{ user|user_avatar_url }}" class="img-circle" width="64" height="64">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="20%">{% trans 'Name' %}:</td>
|
||||
<td><b>{{ user_object.name }}</b></td>
|
||||
<td><b>{{ user.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Username' %}:</td>
|
||||
<td><b>{{ user_object.username }}</b></td>
|
||||
<td><b>{{ user.username }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Email' %}:</td>
|
||||
<td><b>{{ user_object.email }}</b></td>
|
||||
<td><b>{{ user.email }}</b></td>
|
||||
</tr>
|
||||
{% if user_object.phone %}
|
||||
{% if user.phone %}
|
||||
<tr>
|
||||
<td>{% trans 'Phone' %}:</td>
|
||||
<td><b>{{ user_object.phone }}</b></td>
|
||||
<td><b>{{ user.phone }}</b></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if user_object.wechat %}
|
||||
{% if user.wechat %}
|
||||
<tr>
|
||||
<td>{% trans 'Wechat' %}:</td>
|
||||
<td><b>{{ user_object.wechat }}</b></td>
|
||||
<td><b>{{ user.wechat }}</b></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{% trans 'Role' %}:</td>
|
||||
<td><b>{{ user_object.get_role_display }}</b></td>
|
||||
<td><b>{{ user.get_role_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date expired' %}:</td>
|
||||
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
|
||||
<td><b>{{ user.date_expired|date:"Y-m-j H:i:s" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ user_object.created_by }}</b></td>
|
||||
<td><b>{{ user.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date joined' %}:</td>
|
||||
<td><b>{{ user_object.date_joined|date:"Y-m-j H:i:s" }}</b></td>
|
||||
<td><b>{{ user.date_joined|date:"Y-m-j H:i:s" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Last login' %}:</td>
|
||||
<td><b>{{ user_object.last_login|date:"Y-m-j H:i:s" }}</b></td>
|
||||
<td><b>{{ user.last_login|date:"Y-m-j H:i:s" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ user_object.comment }}</b></td>
|
||||
<td><b>{{ user.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -120,7 +124,7 @@
|
||||
<td><span class="pull-right">
|
||||
<div class="switch">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" {% if user_object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
|
||||
<input type="checkbox" {% if user.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
|
||||
<label class="onoffswitch-label" for="is_active">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
@ -134,7 +138,7 @@
|
||||
<td><span class="pull-right">
|
||||
<div class="switch">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" class="onoffswitch-checkbox" {% if user_object.enable_otp %} checked {% endif %}
|
||||
<input type="checkbox" class="onoffswitch-checkbox" {% if user.enable_otp %} checked {% endif %}
|
||||
id="enable_otp">
|
||||
<label class="onoffswitch-label" for="enable_otp">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
@ -183,7 +187,7 @@
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Join user groups' %}" id="slct_groups" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
<select data-placeholder="{% trans 'Join user groups' %}" id="groups_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for group in groups %}
|
||||
<option value="{{ group.id }}" id="opt_{{ group.id }}">{{ group.name }}</option>
|
||||
{% endfor %}
|
||||
@ -192,16 +196,18 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-small" id="btn_add_user_group">{% trans 'Join' %}</button>
|
||||
<button type="button" class="btn btn-info btn-small" id="btn_join_group">{% trans 'Join' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for group in user_object.groups.all %}
|
||||
{% for group in user.groups.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_user_group" data-gid={{ group.id }}>{{ group.name }}</b></td>
|
||||
<td >
|
||||
<b class="bdg_group" data-gid={{ group.id }}>{{ group.name }}</b>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn_delete_user_group" type="button"><i class="fa fa-minus"></i></button>
|
||||
<button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -219,51 +225,48 @@
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.selected_groups = {};
|
||||
jumpserver.groups_selected = {};
|
||||
|
||||
function updateUserGroups(user_groups) {
|
||||
var the_url = "{% url 'users:group-user-edit-api' pk=user_object.id %}";
|
||||
function updateUserGroups(groups) {
|
||||
var the_url = "{% url 'api-users:user-update-group' pk=user.id %}";
|
||||
var body = {
|
||||
id: {{ user_object.id }},
|
||||
groups: Object.assign([], user_groups)
|
||||
groups: Object.assign([], groups)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#slct_groups').val('');
|
||||
$.map(jumpserver.selected_groups, function(group_name, index) {
|
||||
$('#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_user_group" data-gid="' + index + '">' + group_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn_delete_user_group" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'<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.selected_groups
|
||||
jumpserver.selected_groups = {};
|
||||
toastr.success('{% trans "UserGroup Update Success!" %}')
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.groups_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success,
|
||||
method: 'PUT'
|
||||
success: success
|
||||
});
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.selected_groups[data.id] = data.text;
|
||||
jumpserver.groups_selected[data.id] = data.text;
|
||||
}).on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.selected_groups[data.id]
|
||||
delete jumpserver.groups_selected[data.id]
|
||||
})
|
||||
}).on('click', '#is_active', function() {
|
||||
var the_url = "{% url 'users:user-patch-api' pk=user_object.id %}";
|
||||
var checked = !$(this).prop('checked');
|
||||
var the_url = "{% url 'api-users:user-detail' pk=user.id %}";
|
||||
var checked = $(this).prop('checked');
|
||||
var body = {
|
||||
'is_active': checked
|
||||
};
|
||||
@ -274,8 +277,8 @@ $(document).ready(function() {
|
||||
success_message: success
|
||||
});
|
||||
}).on('click', '#enable_otp', function() {
|
||||
var the_url = "{% url 'users:user-patch-api' pk=user_object.id %}";
|
||||
var checked = !$(this).prop('checked');
|
||||
var the_url = "{% url 'api-users:user-detail' pk=user.id %}";
|
||||
var checked = $(this).prop('checked');
|
||||
var body = {
|
||||
'enable_otp': checked
|
||||
};
|
||||
@ -285,38 +288,38 @@ $(document).ready(function() {
|
||||
body: JSON.stringify(body),
|
||||
success_message: success
|
||||
});
|
||||
}).on('click', '#btn_add_user_group', function() {
|
||||
if (Object.keys(jumpserver.selected_groups).length === 0) {
|
||||
}).on('click', '#btn_join_group', function() {
|
||||
if (Object.keys(jumpserver.groups_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var user_groups = $('.bdg_user_group').map(function() {
|
||||
var groups = $('.bdg_group').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.selected_groups, function(value, index) {
|
||||
user_groups.push(parseInt(index));
|
||||
$.map(jumpserver.groups_selected, function(value, index) {
|
||||
groups.push(parseInt(index));
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
updateUserGroups(user_groups)
|
||||
}).on('click', '.btn_delete_user_group', function() {
|
||||
updateUserGroups(groups)
|
||||
}).on('click', '.btn_leave_group', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_user_group');
|
||||
var $badge = $tr.find('.bdg_group');
|
||||
var gid = $badge.data('gid');
|
||||
var group_name = $badge.html() || $badge.text();
|
||||
$('#slct_groups').append(
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var user_groups = $('.bdg_user_group').map(function() {
|
||||
var groups = $('.bdg_group').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateUserGroups(user_groups)
|
||||
updateUserGroups(groups)
|
||||
}).on('click', '#btn_reset_password', function() {
|
||||
function doReset() {
|
||||
var the_url = '{% url "users:user-reset-password-api" pk=user_object.id %}';
|
||||
var the_url = '{% url "api-users:user-reset-password" pk=user.id %}';
|
||||
var body = {};
|
||||
var success = function() {
|
||||
var msg = "{% trans 'E-mail sent successfully. An e-mail has been sent to the user\'s mailbox.' %}";
|
||||
var msg = "{% trans "An e-mail has been sent to the user\'s mailbox." %}";
|
||||
swal("{% trans 'Password-Reset' %}", msg, "success");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
@ -338,7 +341,7 @@ $(document).ready(function() {
|
||||
});
|
||||
}).on('click', '#btn_reset_pk', function() {
|
||||
function doReset() {
|
||||
var the_url = '{% url "users:user-reset-pk-api" pk=user_object.id %}';
|
||||
var the_url = '{% url "api-users:user-reset-pk" pk=user.id %}';
|
||||
var body = {};
|
||||
var success = function() {
|
||||
var msg = "{% trans 'The reset-ssh-public-key E-mail has been sent successfully. Please inform the user to update his new ssh public key.' %}";
|
||||
@ -346,7 +349,7 @@ $(document).ready(function() {
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
body: body,
|
||||
success: success
|
||||
});
|
||||
}
|
||||
@ -364,7 +367,7 @@ $(document).ready(function() {
|
||||
}).on('click', '#btn_user_update_pk', function(){
|
||||
var $this = $(this);
|
||||
var pk = $('#txt_pk').val();
|
||||
var the_url = '{% url "users:user-update-pk-api" pk=user_object.id %}';
|
||||
var the_url = '{% url "api-users:user-update-pk" pk=user.id %}';
|
||||
var body = {'_public_key': pk};
|
||||
var success = function() {
|
||||
$('#txt_pk').val('');
|
||||
@ -372,8 +375,7 @@ $(document).ready(function() {
|
||||
var msg = "{% trans 'Successfully updated the SSH public key.' %}";
|
||||
swal("{% trans 'User SSH Public Key Update' %}", msg, "success");
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Failed to update the user\'s SSH public key.' %}";
|
||||
var fail = function(msg) {
|
||||
swal({
|
||||
title: "{% trans 'User SSH Public Key Update' %}",
|
||||
text: msg,
|
||||
@ -386,7 +388,7 @@ $(document).ready(function() {
|
||||
$('#txt_pk').focus();
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
|
||||
});
|
||||
</script>
|
||||
|
@ -17,34 +17,21 @@
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'users:user-detail' pk=user_object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
<a href="{% url 'users:user-detail' pk=user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-asset-permission' pk=user_object.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
<a href="{% url 'users:user-asset-permission' pk=user.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
<a href="{% url 'users:user-granted-asset' pk=user.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-login-history' pk=user_object.id %}" class="text-center"><i class="fa fa-calculator-o"></i> {% trans 'Login history' %}</a>
|
||||
</li>
|
||||
<form id="search_form" method="get" action="" class="pull-right mail-search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||
<div class="input-group-btn">
|
||||
<button id="search_btn" type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Granted assets of ' %} <b>{{ user_object.name }}</b></span>
|
||||
<span style="float: left">{% trans 'Assets granted of ' %} <b>{{ user.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
@ -60,54 +47,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover">
|
||||
<table class="table table-hover " id="user_assets_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><a href="{% url 'perms:asset-permission-list' %}?sort=name">{% trans 'Hostname' %}</a></th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Port' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th class="text-center">
|
||||
<a href="#">{% trans 'Is valid' %}</a>
|
||||
</th>
|
||||
<th></th>
|
||||
<th class="text-center"></th>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'System user' %}</th>
|
||||
<th>{% trans 'Valid' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset, system_users in object_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">
|
||||
<a href="{% url 'assets:asset-detail' pk=asset.id %}">
|
||||
{{ asset.hostname }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ asset.ip }}</td>
|
||||
<td class="text-center">{{ asset.port }}</td>
|
||||
<td class="text-center">{{ system_users|join_attr:"name" }}</td>
|
||||
<td class="text-center">
|
||||
{% if asset.is_valid %}
|
||||
<i class="fa fa-check text-navy"></i>
|
||||
{% else %}
|
||||
<i class="fa fa-times text-danger"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-xs btn_delete_user_group {% if asset.is_inherit_from_user_groups or asset.is_inherit_from_asset_groups %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
{% include '_pagination.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset groups granted of ' %} <b>{{ user_object.name }}</b></span>
|
||||
<span style="float: left">{% trans 'Asset groups granted of ' %} <b>{{ user.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
@ -123,30 +83,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover">
|
||||
<table class="table table-hover" id="user_asset_groups_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Asset count' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th></th>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Asset' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset_group, system_users in asset_groups %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">
|
||||
<a href="{% url 'assets:asset-group-detail' pk=asset_group.id %}">
|
||||
{{ asset_group.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ asset_group.assets.count }}</td>
|
||||
<td class="text-center">{{ system_users|join_attr:"name" }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-xs btn_delete_user_group {% if not asset_group.is_inherit_from_user_groups %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -161,25 +106,53 @@
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
{# function switch_user_status(obj) {#}
|
||||
{# var status = $(obj).prop('checked');#}
|
||||
{##}
|
||||
{# $.ajax({#}
|
||||
{# url: "{% url 'users:user-active-api' pk=user_object.id %}",#}
|
||||
{# type: "PUT",#}
|
||||
{# data: {#}
|
||||
{# 'is_active': status#}
|
||||
{# },#}
|
||||
{# success: function (data, status) {#}
|
||||
{# console.log(data)#}
|
||||
{# },#}
|
||||
{# error: function () {#}
|
||||
{# console.log('error')#}
|
||||
{# }#}
|
||||
{# })#}
|
||||
{# }#}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
var options = {
|
||||
ele: $('#user_assets_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
select: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
if (cellData.length > 10){
|
||||
$(td).html(cellData.substring(1, 10) + '..')
|
||||
} else {
|
||||
$(td).html(cellData)
|
||||
}
|
||||
}},
|
||||
{targets: 5, 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>')
|
||||
}
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-perms:user-assets" pk=user.id %}',
|
||||
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port"},
|
||||
{data: "system_users_join"}, {data: "is_active"}]
|
||||
};
|
||||
var options2 = {
|
||||
ele: $('#user_asset_groups_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
select: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-perms:user-asset-groups" pk=user.id %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "assets_amount" }]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initDataTable(options2);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
183
apps/users/templates/users/user_group_asset_permission.html
Normal file
183
apps/users/templates/users/user_group_asset_permission.html
Normal file
@ -0,0 +1,183 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load common_tags %}
|
||||
{% load users_tags %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'users:user-group-detail' pk=user_group.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-group-asset-permission' pk=user_group.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-group-granted-asset' pk=user_group.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset permission of ' %} <b>{{ user_group.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover " id="user_group_permissions_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Asset' %}</th>
|
||||
<th>{% trans 'Asset group' %}</th>
|
||||
<th>{% trans 'System user' %}</th>
|
||||
<th>{% trans 'Valid' %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick create permission for user group' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="post" action="{% url 'users:user-group-asset-permission-create' pk=user_group.id %}">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% csrf_token %}
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="1" style="padding-top: 0">
|
||||
{{ form.name|bootstrap }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="1" style="padding-top: 0">
|
||||
{{ form.assets|bootstrap }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="1" style="padding-top: 0">
|
||||
{{ form.asset_groups|bootstrap }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="1" style="padding-top: 0">
|
||||
{{ form.system_users|bootstrap }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td>
|
||||
<button type="submit" class="btn btn-primary btn-sm">{% trans 'Submit' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
var options = {
|
||||
ele: $('#user_group_permissions_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
select: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "perms:asset-permission-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 2, createdCell: function (td, cellData, rowData) {
|
||||
var dataLength = cellData.length;
|
||||
$(td).html(dataLength);
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
var dataLength = cellData.length;
|
||||
$(td).html(dataLength);
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var dataLength = cellData.length;
|
||||
$(td).html(dataLength);
|
||||
}},
|
||||
{targets: 5, 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 btn = '<button class="btn btn-danger btn-xs btn_del_permission" id=99991937 type="button" style="float: right;"><i class="fa fa-minus"></i></button>';
|
||||
btn = btn.replace('99991937', cellData);
|
||||
$(td).html(btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-perms:asset-permission-list" %}?user_group={{ user_group.id }}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "assets" }, {data: "asset_groups"},
|
||||
{data: "system_users"}, {data: "is_active"}, {data: "id"}]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}).on('click', '.btn_del_permission', function () {
|
||||
var $this = $(this);
|
||||
var body = {
|
||||
id: $this.attr('id'),
|
||||
user_group_id: {{ user_group.id }}
|
||||
};
|
||||
var the_url = "{% url 'perms:revoke-user-group-asset-permission' %}";
|
||||
var success = function () {
|
||||
$this.closest('tr').remove();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PUT',
|
||||
success_message: '{% trans "Revoke Successfully!" %}',
|
||||
success: success
|
||||
});
|
||||
}).on('click', 'buttons-excel', function () {
|
||||
console.log('click excel')
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@ -19,20 +19,21 @@
|
||||
<form method="post" class="form-horizontal" action="" >
|
||||
{% csrf_token %}
|
||||
{{ form.name|bootstrap_horizontal }}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="users" class="col-sm-2 control-label">{% trans 'Users' %}</label>
|
||||
<div class="col-sm-9">
|
||||
<select name="users" id="users" data-placeholder="{% trans 'Select User' %}" class="select2 form-control m-b" multiple tabindex="2">
|
||||
<select name="users" id="id_users" data-placeholder="{% trans 'Select User' %}" class="select2 form-control m-b" multiple tabindex="2">
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}">{{ user.name }}</option>
|
||||
{% if user.id in group_users %}
|
||||
<option value="{{ user.id }}" selected>{{ user.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ user.id }}">{{ user.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form.comment|bootstrap_horizontal }}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Cancel' %}</button>
|
||||
@ -45,11 +46,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "users/_select_user_modal.html" %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2().val([{{ group_users }}]).trigger("change");
|
||||
$('.select2').select2();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@ -10,54 +10,6 @@
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
|
||||
<script src="{% static "js/plugins/dataTables/dataTables.min.js" %}"></script>
|
||||
<style>
|
||||
.user_div {
|
||||
color: #5e5e5e;
|
||||
font-family: "Open Sans";
|
||||
padding: 3px 8px;
|
||||
text-shadow: none;
|
||||
}
|
||||
.user_div .ui_container {
|
||||
background-color: #d1dade;
|
||||
width: 100%;
|
||||
}
|
||||
.user_div a {
|
||||
color: #5e5e5e;
|
||||
}
|
||||
.user_div .remove {
|
||||
color: #fff;
|
||||
}
|
||||
dl {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 10px;
|
||||
border-bottom: 1px solid #e7eaec;
|
||||
}
|
||||
dt {
|
||||
float: left;
|
||||
width: 30%;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
dd {
|
||||
float: left;
|
||||
width: 70%;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
#group_user_row dt {
|
||||
border-bottom: 1px solid #e7eaec;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#group_user_row dd {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
@ -67,7 +19,16 @@ dd {
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-group-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User Group Detail' %} </a>
|
||||
<a href="{% url 'users:user-group-detail' pk=user_group.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User Group Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-group-asset-permission' pk=user_group.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-group-granted-asset' pk=user.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'users:user-group-update' pk=user_group.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -75,7 +36,7 @@ dd {
|
||||
<div class="col-sm-7" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ object.name }}</b></span>
|
||||
<span class="label"><b>{{ user_group.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
@ -86,53 +47,62 @@ dd {
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<dl>
|
||||
<dt width="20%">{% trans 'Name' %}:</dt>
|
||||
<dd><b>{{ object.name }}</b></dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans 'Comment' %}:</dt>
|
||||
<dd><b>{{ object.comment }}</b></dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans 'Created at' %}:</dt>
|
||||
<dd><b>{{ object.date_created }}</b></dd>
|
||||
</dl>
|
||||
<dl id="group_user_row">
|
||||
<dt>{% trans 'Users' %}:</dt>
|
||||
<dd style="line-height: 2" id="group_user_container">
|
||||
{% for user in object.users.all %}
|
||||
<div class="col-sm-4 user_div"><div class="ui_container row"><div class="col-xs-9"><a href="{{ user.get_absolute_url }}" title="{{ user.name }}" data-toggle="tooltip">{{ user.name|truncatechars:15 }}</a></div><div class="col-xs-3"><a data-uid="{{ user.id }}" class="btn_remove m-l-5"><i class="remove fa fa-times-circle"></i></a></div></div></div>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
</dl>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="20%">{% trans 'Name' %}:</td>
|
||||
<td><b>{{ user_group.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Create by' %}:</td>
|
||||
<td><b>{{ user_group.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ user_group.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ user_group.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick modify' %}
|
||||
<i class="fa fa-info-circle"></i> {% trans 'User' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<table class="table user_edit">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td>{% trans 'Add User' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
<button type="button" class="btn btn-primary btn-xs" id="btn_group_add_user" style="width: 54px" data-toggle="modal" data-target="#select_user_modal">{% trans 'Add' %}</button>
|
||||
</span>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add user' %}" id="slct_users" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}" id="opt_{{ user.id }}">{{ user.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Delete' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
<button type="button" class="btn btn-danger btn-xs" id="btn_group_delete" style="width: 54px">{% trans 'Delete' %}</button>
|
||||
</span>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-primary btn-small" id="btn_add_user">{% trans 'Add' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for user in user_group.users.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_user" data-uid={{ user.id }}>{{ user.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn_remove_user" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -143,101 +113,77 @@ dd {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "users/_select_user_modal.html" %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).on('click', '.btn_remove', function(){
|
||||
var $this = $(this);
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "users:delete-user-from-group-api" pk=object.id uid=99991937 %}'.replace('99991937', uid);
|
||||
var success = function(){
|
||||
$this.closest('.user_div').remove();
|
||||
};
|
||||
var error = function(){};
|
||||
APIUpdateAttr({url: the_url, body: "{}", method: "DELETE", success: success, error: error});
|
||||
return false;
|
||||
}).on('click', '#btn_group_delete', function() {
|
||||
function doDelete() {
|
||||
var the_url = '{% url "users:user-group-detail-api" pk=object.id %}';
|
||||
var success = function() {
|
||||
window.location.href = '{% url "users:user-group-list" %}';
|
||||
};
|
||||
APIUpdateAttr({url: the_url, body: "{}", method: "DELETE", success: success});
|
||||
}
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the current group, but will not delete any user of it.' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
doDelete();
|
||||
});
|
||||
}).on('shown.bs.modal', '#select_user_modal', function() {
|
||||
if ($.fn.dataTable.isDataTable('#select_user_table')) {
|
||||
return true;
|
||||
}
|
||||
var options = {
|
||||
ele: $('#select_user_table'),
|
||||
pageLength: 10,
|
||||
buttons: [],
|
||||
columnDefs: [
|
||||
{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: 4, createdCell: function (td, cellData) {
|
||||
var innerHtml = cellData.length > 8 ? cellData.substring(0, 8) + '...': cellData;
|
||||
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "users:user-bulk-update-api" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, {data: "group_display" },
|
||||
{data: function(){return 999}}, {data: "active_display" }],
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}).on('click', '#btn_select_user', function() {
|
||||
var $data_table = $('#select_user_table').DataTable();
|
||||
var plain_id_list = [];
|
||||
var selected_users = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
plain_id_list.push(this.data().id);
|
||||
selected_users.push({id: this.data().id, name: this.data().name});
|
||||
});
|
||||
if (plain_id_list === []) {
|
||||
return false;
|
||||
};
|
||||
jumpserver.users_selected = {};
|
||||
|
||||
function updateGroupMember(users) {
|
||||
var the_url = "{% url 'api-users:user-group-update-user' pk=user_group.id %}";
|
||||
var body = {
|
||||
id: {{ object.id }},
|
||||
users: plain_id_list.map(Number)
|
||||
users: Object.assign([], users)
|
||||
};
|
||||
$('#select_user_modal').modal('hide');
|
||||
var the_url = "{% url 'users:user-group-detail-api' pk=object.id %}";
|
||||
var success = function() {
|
||||
toastr.success('{% trans "The selected users has been added to current group." %}');
|
||||
var html = "";
|
||||
$.each(selected_users, function(index, user) {
|
||||
html += [
|
||||
'<div class="col-sm-4 user_div"><div class="ui_container row"><div class="col-xs-9"><a title="',
|
||||
user.name,
|
||||
'" data-toggle="tooltip" href="',
|
||||
'{% url "users:user-detail" pk=99991937 %}'.replace(99991937, user.id),
|
||||
'">',
|
||||
user.name.length >=13 ? user.name.substring(0, 12) + '...' : user.name,
|
||||
'</a></div><div class="col-xs-3"><a data-uid="',
|
||||
user.id,
|
||||
'" class="btn_remove m-l-5"><i class="remove fa fa-times-circle"></i></a></div></div></div>\n',
|
||||
].join("");
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#slct_users').val('');
|
||||
$.map(jumpserver.users_selected, function(user_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of users
|
||||
$('.user_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_user" data-uid="' + index + '">' + user_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn_remove_user" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
$(html).appendTo($('#group_user_container'));
|
||||
// clear jumpserver.selected_groups
|
||||
jumpserver.users_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.users_selected[data.id] = data.text;
|
||||
}).on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.users_selected[data.id]
|
||||
})
|
||||
}).on('click', '.btn_remove_user', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_user');
|
||||
var uid = $badge.data('uid');
|
||||
var user_name = $badge.html() || $badge.text();
|
||||
$('#slct_users').append(
|
||||
'<option value="' + uid + '" id="opt_' + uid + '">' + user_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var users = $('.bdg_user').map(function() {
|
||||
return $(this).data('uid');
|
||||
}).get();
|
||||
console.log(users);
|
||||
updateGroupMember(users)
|
||||
}).on('click', '#btn_add_user', function() {
|
||||
if (Object.keys(jumpserver.users_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
APIUpdateAttr({url: the_url, body: JSON.stringify(body), success: success});
|
||||
var users = $('.bdg_user').map(function() {
|
||||
return $(this).data('uid');
|
||||
}).get();
|
||||
$.map(jumpserver.users_selected, function(value, index) {
|
||||
users.push(parseInt(index));
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
console.log(users);
|
||||
updateGroupMember(users)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
158
apps/users/templates/users/user_group_granted_asset.html
Normal file
158
apps/users/templates/users/user_group_granted_asset.html
Normal file
@ -0,0 +1,158 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load common_tags %}
|
||||
{% load users_tags %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'users:user-group-detail' pk=user_group.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'users:user-group-asset-permission' pk=user_group.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset permission' %}</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-group-granted-asset' pk=user_group.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Assets granted of ' %} <b>{{ user_group.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover " id="user_assets_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'System user' %}</th>
|
||||
<th>{% trans 'Valid' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset groups granted of ' %} <b>{{ user_group.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover" id="user_asset_groups_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Asset' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
var options = {
|
||||
ele: $('#user_assets_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
select: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
if (cellData.length > 10){
|
||||
$(td).html(cellData.substring(1, 10) + '..')
|
||||
} else {
|
||||
$(td).html(cellData)
|
||||
}
|
||||
}},
|
||||
{targets: 5, 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>')
|
||||
}
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "perms:api-user-group-assets" pk=user_group.id %}',
|
||||
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port"},
|
||||
{data: "system_users_join"}, {data: "is_active"}]
|
||||
};
|
||||
var options2 = {
|
||||
ele: $('#user_asset_groups_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
select: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-perms:user-group-asset-groups" pk=user_group.id %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "assets_amount" }]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initDataTable(options2);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,18 +1,5 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block custom_head_css_js %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: right !important;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
margin-left: 15px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="pull-left m-r-5"><a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary ">{% trans "Add User Group" %}</a></div>
|
||||
@ -20,11 +7,10 @@ div.dataTables_wrapper div.dataTables_filter {
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<div class="checkbox checkbox-default"><input id="" type="checkbox" class="ipt_check_all"><label></label></div>
|
||||
<input id="" type="checkbox" class="ipt_check_all">
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center">{% trans 'User Amount' %}</a></th>
|
||||
<th class="text-center">{% trans 'Asset Amount' %}</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'User Amount' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
@ -56,11 +42,11 @@ $(document).ready(function() {
|
||||
var detail_btn = '<a href="{% url "users:user-group-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var innerHtml = cellData.length > 18 ? cellData.substring(0, 18) + '...': cellData;
|
||||
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
|
||||
{targets: 3, 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: 5, 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 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);
|
||||
if (rowData.id === 1) {
|
||||
@ -69,9 +55,9 @@ $(document).ready(function() {
|
||||
$(td).html(update_btn + del_btn)
|
||||
}
|
||||
}}],
|
||||
ajax_url: '{% url "users:user-group-bulk-update-api" %}',
|
||||
ajax_url: '{% url "api-users:user-group-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "user_amount"},
|
||||
{data: function(){return 999}}, {data: "comment"}, {data: "id" }],
|
||||
{data: "comment"}, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
@ -79,7 +65,7 @@ $(document).ready(function() {
|
||||
var $this = $(this);
|
||||
function doDelete() {
|
||||
var group_id = $this.data('gid');
|
||||
var the_url = "{% url 'users:user-group-detail-api' 99991937 %}".replace('99991937', group_id);
|
||||
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.' %}";
|
||||
@ -89,7 +75,7 @@ $(document).ready(function() {
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Group Deleting failed.' %}";
|
||||
swal("{% trans 'Group Delete' %}", msg, "error");
|
||||
}
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
@ -118,8 +104,8 @@ $(document).ready(function() {
|
||||
});
|
||||
if (plain_id_list === []) {
|
||||
return false;
|
||||
};
|
||||
var the_url = "{% url 'users:user-group-bulk-update-api' %}";
|
||||
}
|
||||
var the_url = "{% url 'api-users:user-group-list' %}";
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
|
@ -1,22 +1,19 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block custom_head_css_js %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: right !important;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
margin-left: 15px;
|
||||
}
|
||||
</style>
|
||||
{% 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>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<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 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>
|
||||
<table class="table table-striped table-bordered table-hover " id="user_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
@ -28,7 +25,6 @@ div.dataTables_wrapper div.dataTables_filter {
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Role' %}</th>
|
||||
<th class="text-center">{% trans 'User group' %}</th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
@ -66,31 +62,39 @@ $(document).ready(function(){
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var innerHtml = cellData.length > 8 ? cellData.substring(0, 8) + '...': cellData;
|
||||
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
|
||||
var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData;
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
{targets: 5, 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: 7, 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 del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
||||
if (rowData.id === 1) {
|
||||
if (rowData.id === 1 || rowData.username == "admin") {
|
||||
$(td).html(update_btn)
|
||||
} else {
|
||||
$(td).html(update_btn + del_btn)
|
||||
}
|
||||
}}],
|
||||
ajax_url: '{% url "users:user-bulk-update-api" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, {data: "group_display" },
|
||||
{data: function(){return 999}}, {data: "active_display" }, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
ajax_url: '{% url "api-users:user-list" %}',
|
||||
columns: [{data: "id"}, {data: "username" }, {data: "name" }, {data: "get_role_display" },
|
||||
{data: "groups_display" }, {data: "is_valid" }, {data: "id" }]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
var table = jumpserver.initDataTable(options);
|
||||
|
||||
$('.buttons-pdf').click(function () {
|
||||
var users = [];
|
||||
var rows = table.rows('.selected').data();
|
||||
$.each(rows, function (index, obj) {
|
||||
users.push(obj.id)
|
||||
})
|
||||
});
|
||||
|
||||
}).on('click', '#btn_bulk_update', function(){
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#user_list_table').DataTable();
|
||||
@ -103,7 +107,7 @@ $(document).ready(function(){
|
||||
if (id_list === []) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'users:user-bulk-update-api' %}";
|
||||
var the_url = "{% url 'api-users:user-list' %}";
|
||||
function doDeactive() {
|
||||
var body = $.each(id_list, function(index, user_object) {
|
||||
user_object['is_active'] = false;
|
||||
@ -156,7 +160,7 @@ $(document).ready(function(){
|
||||
var $this = $(this);
|
||||
function doDelete() {
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "users:user-patch-api" pk=99991937 %}'.replace('99991937', uid);
|
||||
var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
var body = {};
|
||||
var success = function() {
|
||||
var msg = "{% trans 'User Deleted.' %}";
|
||||
@ -211,7 +215,7 @@ $(document).ready(function(){
|
||||
if (post_list === []) {
|
||||
return false
|
||||
}
|
||||
var the_url = "{% url 'users:user-bulk-update-api' %}";
|
||||
var the_url = "{% url 'api-users:user-list' %}";
|
||||
var success = function() {
|
||||
var msg = "{% trans 'The selected users has been updated successfully.' %}";
|
||||
swal("{% trans 'User Updated' %}", msg, "success");
|
||||
@ -230,13 +234,11 @@ $(document).ready(function(){
|
||||
} else {
|
||||
$('#user_import_modal').modal('hide');
|
||||
var $data_table = $('#user_list_table').DataTable();
|
||||
toastr.success("{% trans 'Import User Success.' %}")
|
||||
toastr.success("{% trans 'Import User Success.' %}");
|
||||
$data_table.ajax.reload();
|
||||
}
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
}).on('change', '#id_excel', function() {
|
||||
$(this).siblings('.help-block').remove();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -1,14 +1,6 @@
|
||||
{% extends 'users/_user.html' %}
|
||||
{% load i18n %}
|
||||
{% block user_template_title %}{% trans "Update user" %}{% endblock %}
|
||||
{% block username %}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.username.id_for_label }}" class="col-sm-2 control-label">{% trans 'Username' %}</label>
|
||||
<div class="col-sm-9 controls" >
|
||||
<input id="{{ form.username.id_for_label }}" name="{{ form.username.html_name }}" type="text" value="{{ user_object.username }}" readonly class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block password %}
|
||||
<h3>{% trans 'Password' %}</h3>
|
||||
<div class="form-group">
|
||||
|
1
apps/users/urls/__init__.py
Normal file
1
apps/users/urls/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
34
apps/users/urls/api_urls.py
Normal file
34
apps/users/urls/api_urls.py
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
from .. import api
|
||||
|
||||
app_name = 'users'
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'v1/users', api.UserViewSet, 'user')
|
||||
router.register(r'v1/user-groups', api.UserGroupViewSet, 'user-group')
|
||||
# router.register(r'v1/user-groups', api.AssetViewSet, 'api-groups')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# url(r'^v1/users/$', api.UserListUpdateApi.as_view(), name='user-bulk-update-api'),
|
||||
url(r'^v1/users/token/$', api.UserAuthApi.as_view(), name='user-token'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/update-pk/$', api.UserUpdatePKApi.as_view(), name='user-update-pk'),
|
||||
# url(r'^v1/user-groups/$', api.GroupListUpdateApi.as_view(), name='user-group-bulk-update-api'),
|
||||
# url(r'^v1/user-groups/(?P<pk>\d+)/$', api.GroupDetailApi.as_view(), name='user-group-detail-api'),
|
||||
# url(r'^v1/user-groups/(?P<pk>\d+)/user/(?P<uid>\d+)/$',
|
||||
# api.DeleteUserFromGroupApi.as_view(), name='delete-user-from-group-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/groups/$',
|
||||
api.UserUpdateGroupApi.as_view(), name='user-update-group'),
|
||||
url(r'^v1/user-groups/(?P<pk>\d+)/users/$',
|
||||
api.UserGroupUpdateUserApi.as_view(), name='user-group-update-user'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
@ -1,7 +1,7 @@
|
||||
from django.conf.urls import url
|
||||
from __future__ import absolute_import
|
||||
|
||||
import views
|
||||
import api
|
||||
from django.conf.urls import url
|
||||
from .. import views
|
||||
|
||||
app_name = 'users'
|
||||
|
||||
@ -14,37 +14,30 @@ urlpatterns = [
|
||||
url(r'^password/reset$', views.UserResetPasswordView.as_view(), name='reset-password'),
|
||||
url(r'^password/reset/success$', views.UserResetPasswordSuccessView.as_view(),
|
||||
name='reset-password-success'),
|
||||
# User view
|
||||
url(r'^user$', views.UserListView.as_view(), name='user-list'),
|
||||
url(r'^user/(?P<pk>[0-9]+)$', views.UserDetailView.as_view(), name='user-detail'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/asset-permission$', views.UserAssetPermissionView.as_view(),
|
||||
name='user-asset-permission'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/asset-permission/create$', views.UserAssetPermissionCreateView.as_view(),
|
||||
name='user-asset-permission-create'),
|
||||
url(r'^user/(?P<pk>[0-9]+)/granted-asset', 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'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
|
||||
url(r'^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/(?P<pk>[0-9]+)/update$', views.UserUpdateView.as_view(), name='user-update'),
|
||||
|
||||
# User group view
|
||||
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
|
||||
url(r'^user-group/create$', views.UserGroupCreateView.as_view(), name='user-group-create'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/update$', views.UserGroupUpdateView.as_view(), name='user-group-update'),
|
||||
]
|
||||
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^v1/users/$', api.UserListUpdateApi.as_view(), name='user-bulk-update-api'),
|
||||
url(r'^v1/users/token/$', api.UserTokenApi.as_view(), name='user-token-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/$', api.UserDetailApi.as_view(), name='user-patch-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk-api'),
|
||||
url(r'^v1/users/(?P<pk>\d+)/update-pk/$', api.UserUpdatePKApi.as_view(), name='user-update-pk-api'),
|
||||
url(r'^v1/user-groups/$', api.GroupListUpdateApi.as_view(), name='user-group-bulk-update-api'),
|
||||
url(r'^v1/user-groups/(?P<pk>\d+)/$', api.GroupDetailApi.as_view(), name='user-group-detail-api'),
|
||||
url(r'^v1/user-groups/(?P<pk>\d+)/user/(?P<uid>\d+)/$',
|
||||
api.DeleteUserFromGroupApi.as_view(), name='delete-user-from-group-api'),
|
||||
url(r'^v1/user-groups/(?P<pk>\d+)/users/$',
|
||||
api.UserAndGroupEditApi.as_view(), name='group-user-edit-api'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/asset-permission$', views.UserGroupAssetPermissionView.as_view(),
|
||||
name='user-group-asset-permission'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/asset-permission/create$', views.UserGroupAssetPermissionCreateView.as_view(),
|
||||
name='user-group-asset-permission-create'),
|
||||
url(r'^user-group/(?P<pk>[0-9]+)/assets', views.UserGroupGrantedAssetView.as_view(),
|
||||
name='user-group-granted-asset'),
|
||||
]
|
@ -34,38 +34,6 @@ class AdminUserRequiredMixin(UserPassesTestMixin):
|
||||
return self.request.user.is_staff
|
||||
|
||||
|
||||
def ssh_key_gen(length=2048, password=None, username='root', hostname=None):
|
||||
"""Generate user ssh private and public key
|
||||
|
||||
Use paramiko RSAKey generate it.
|
||||
|
||||
"""
|
||||
|
||||
if hostname is None:
|
||||
hostname = os.uname()[1]
|
||||
|
||||
f = StringIO.StringIO()
|
||||
|
||||
try:
|
||||
logger.debug(_('Begin to generate ssh private key ...'))
|
||||
private_key_obj = RSAKey.generate(length)
|
||||
private_key_obj.write_private_key(f, password=password)
|
||||
private_key = f.getvalue()
|
||||
|
||||
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
|
||||
'key_type': private_key_obj.get_name(),
|
||||
'key_content': private_key_obj.get_base64(),
|
||||
'username': username,
|
||||
'hostname': hostname,
|
||||
}
|
||||
|
||||
logger.debug(_('Finish to generate ssh private key ...'))
|
||||
return private_key, public_key
|
||||
|
||||
except IOError:
|
||||
raise IOError(_('These is error when generate ssh key.'))
|
||||
|
||||
|
||||
def user_add_success_next(user):
|
||||
subject = _('Create account successfully')
|
||||
recipient_list = [user.email]
|
||||
@ -107,7 +75,7 @@ def send_reset_password_mail(user):
|
||||
</br>
|
||||
<a href="%(rest_password_url)s?token=%(rest_password_token)s">Click here reset password</a>
|
||||
</br>
|
||||
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one<</a>
|
||||
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
|
||||
|
||||
</br>
|
||||
---
|
||||
@ -152,60 +120,60 @@ def send_reset_ssh_key_mail(user):
|
||||
send_mail_async.delay(subject, message, recipient_list, html_message=message)
|
||||
|
||||
|
||||
def validate_ssh_pk(text):
|
||||
"""
|
||||
Expects a SSH private key as string.
|
||||
Returns a boolean and a error message.
|
||||
If the text is parsed as private key successfully,
|
||||
(True,'') is returned. Otherwise,
|
||||
(False, <message describing the error>) is returned.
|
||||
|
||||
from https://github.com/githubnemo/SSH-private-key-validator/blob/master/validate.py
|
||||
|
||||
"""
|
||||
|
||||
if not text:
|
||||
return False, 'No text given'
|
||||
|
||||
startPattern = re.compile("^-----BEGIN [A-Z]+ PRIVATE KEY-----")
|
||||
optionPattern = re.compile("^.+: .+")
|
||||
contentPattern = re.compile("^([a-zA-Z0-9+/]{64}|[a-zA-Z0-9+/]{1,64}[=]{0,2})$")
|
||||
endPattern = re.compile("^-----END [A-Z]+ PRIVATE KEY-----")
|
||||
|
||||
def contentState(text):
|
||||
for i in range(0, len(text)):
|
||||
line = text[i]
|
||||
|
||||
if endPattern.match(line):
|
||||
if i == len(text) - 1 or len(text[i + 1]) == 0:
|
||||
return True, ''
|
||||
else:
|
||||
return False, 'At end but content coming'
|
||||
|
||||
elif not contentPattern.match(line):
|
||||
return False, 'Wrong string in content section'
|
||||
|
||||
return False, 'No content or missing end line'
|
||||
|
||||
def optionState(text):
|
||||
for i in range(0, len(text)):
|
||||
line = text[i]
|
||||
|
||||
if line[-1:] == '\\':
|
||||
return optionState(text[i + 2:])
|
||||
|
||||
if not optionPattern.match(line):
|
||||
return contentState(text[i + 1:])
|
||||
|
||||
return False, 'Expected option, found nothing'
|
||||
|
||||
def startState(text):
|
||||
if len(text) == 0 or not startPattern.match(text[0]):
|
||||
return False, 'Header is wrong'
|
||||
return optionState(text[1:])
|
||||
|
||||
return startState([n.strip() for n in text.splitlines()])
|
||||
# def validate_ssh_pk(text):
|
||||
# """
|
||||
# Expects a SSH private key as string.
|
||||
# Returns a boolean and a error message.
|
||||
# If the text is parsed as private key successfully,
|
||||
# (True,'') is returned. Otherwise,
|
||||
# (False, <message describing the error>) is returned.
|
||||
#
|
||||
# from https://github.com/githubnemo/SSH-private-key-validator/blob/master/validate.py
|
||||
#
|
||||
# """
|
||||
#
|
||||
# if not text:
|
||||
# return False, 'No text given'
|
||||
#
|
||||
# startPattern = re.compile("^-----BEGIN [A-Z]+ PRIVATE KEY-----")
|
||||
# optionPattern = re.compile("^.+: .+")
|
||||
# contentPattern = re.compile("^([a-zA-Z0-9+/]{64}|[a-zA-Z0-9+/]{1,64}[=]{0,2})$")
|
||||
# endPattern = re.compile("^-----END [A-Z]+ PRIVATE KEY-----")
|
||||
#
|
||||
# def contentState(text):
|
||||
# for i in range(0, len(text)):
|
||||
# line = text[i]
|
||||
#
|
||||
# if endPattern.match(line):
|
||||
# if i == len(text) - 1 or len(text[i + 1]) == 0:
|
||||
# return True, ''
|
||||
# else:
|
||||
# return False, 'At end but content coming'
|
||||
#
|
||||
# elif not contentPattern.match(line):
|
||||
# return False, 'Wrong string in content section'
|
||||
#
|
||||
# return False, 'No content or missing end line'
|
||||
#
|
||||
# def optionState(text):
|
||||
# for i in range(0, len(text)):
|
||||
# line = text[i]
|
||||
#
|
||||
# if line[-1:] == '\\':
|
||||
# return optionState(text[i + 2:])
|
||||
#
|
||||
# if not optionPattern.match(line):
|
||||
# return contentState(text[i + 1:])
|
||||
#
|
||||
# return False, 'Expected option, found nothing'
|
||||
|
||||
# def startState(text):
|
||||
# if len(text) == 0 or not startPattern.match(text[0]):
|
||||
# return False, 'Header is wrong'
|
||||
# return optionState(text[1:])
|
||||
#
|
||||
# return startState([n.strip() for n in text.splitlines()])
|
||||
#
|
||||
|
||||
def check_user_valid(**kwargs):
|
||||
password = kwargs.pop('password', None)
|
||||
@ -216,8 +184,14 @@ def check_user_valid(**kwargs):
|
||||
return None
|
||||
if password and user.check_password(password):
|
||||
return user
|
||||
if public_key and user.public_key == public_key:
|
||||
return user
|
||||
if public_key:
|
||||
public_key_saved = user.public_key.split()
|
||||
if len(public_key_saved) == 1:
|
||||
if public_key == public_key_saved[0]:
|
||||
return user
|
||||
elif len(public_key_saved) > 1:
|
||||
if public_key == public_key_saved[1]:
|
||||
return user
|
||||
return None
|
||||
|
||||
|
||||
|
@ -2,13 +2,14 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import csv
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
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
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -21,16 +22,15 @@ from django.views.generic.list import ListView
|
||||
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
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from perms.models import AssetPermission
|
||||
from .models import User, UserGroup
|
||||
from .forms import UserCreateForm, UserUpdateForm, UserGroupForm, UserLoginForm, UserInfoForm, UserKeyForm, \
|
||||
UserPrivateAssetPermissionForm, UserBulkImportForm
|
||||
from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_password_mail
|
||||
from .hands import AssetPermission, get_user_granted_asset_groups, get_user_granted_assets
|
||||
from .hands import write_login_log_async
|
||||
from . import forms
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@ -41,7 +41,7 @@ logger = get_logger(__name__)
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLoginView(FormView):
|
||||
template_name = 'users/login.html'
|
||||
form_class = UserLoginForm
|
||||
form_class = forms.UserLoginForm
|
||||
redirect_field_name = 'next'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@ -51,6 +51,10 @@ class UserLoginView(FormView):
|
||||
|
||||
def form_valid(self, form):
|
||||
auth_login(self.request, form.get_user())
|
||||
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
write_login_log_async.delay(self.request.user.username, self.request.user.name,
|
||||
login_type='W', login_ip=login_ip, user_agent=user_agent)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
@ -64,7 +68,7 @@ class UserLoginView(FormView):
|
||||
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
class UserLogoutView(TemplateView):
|
||||
template_name = 'common/flash_message_standalone.html'
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
auth_logout(request)
|
||||
@ -92,7 +96,7 @@ class UserListView(AdminUserRequiredMixin, TemplateView):
|
||||
|
||||
class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = User
|
||||
form_class = UserCreateForm
|
||||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_create.html'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
success_message = _('Create user <a href="%s">%s</a> successfully.')
|
||||
@ -118,7 +122,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
|
||||
class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = User
|
||||
form_class = UserUpdateForm
|
||||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_update.html'
|
||||
context_object_name = 'user_object'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
@ -142,7 +146,7 @@ class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class UserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_detail.html'
|
||||
context_object_name = "user_object"
|
||||
context_object_name = "user"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
groups = UserGroup.objects.exclude(id__in=self.object.groups.all())
|
||||
@ -162,8 +166,8 @@ class UserGroupListView(AdminUserRequiredMixin, TemplateView):
|
||||
|
||||
class UserGroupCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = UserGroup
|
||||
form_class = UserGroupForm
|
||||
template_name = 'users/user_group_create.html'
|
||||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -184,15 +188,15 @@ class UserGroupCreateView(AdminUserRequiredMixin, CreateView):
|
||||
|
||||
class UserGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = UserGroup
|
||||
form_class = UserGroupForm
|
||||
template_name = 'users/user_group_create.html'
|
||||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.object = self.get_object()
|
||||
# self.object = self.get_object()
|
||||
context = super(UserGroupUpdateView, self).get_context_data(**kwargs)
|
||||
users = User.objects.all()
|
||||
group_users = ",".join([str(u.id) for u in self.object.users.all()])
|
||||
group_users = [user.id for user in self.object.users.all()]
|
||||
context.update({
|
||||
'app': _('Users'),
|
||||
'action': _('Update User Group'),
|
||||
@ -213,10 +217,16 @@ class UserGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
|
||||
class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = UserGroup
|
||||
context_object_name = 'user_group'
|
||||
template_name = 'users/user_group_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {'app': _('Users'), 'action': _('User Group Detail')}
|
||||
users = User.objects.exclude(id__in=self.object.users.all())
|
||||
context = {
|
||||
'app': _('Users'),
|
||||
'action': _('User Group Detail'),
|
||||
'users': users,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
@ -239,7 +249,7 @@ class UserForgotPasswordView(TemplateView):
|
||||
|
||||
|
||||
class UserForgotPasswordSendmailSuccessView(TemplateView):
|
||||
template_name = 'common/flash_message_standalone.html'
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
@ -252,7 +262,7 @@ class UserForgotPasswordSendmailSuccessView(TemplateView):
|
||||
|
||||
|
||||
class UserResetPasswordSuccessView(TemplateView):
|
||||
template_name = 'common/flash_message_standalone.html'
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
@ -294,7 +304,7 @@ class UserResetPasswordView(TemplateView):
|
||||
|
||||
class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||
template_name = 'users/first_login.html'
|
||||
form_list = [UserInfoForm, UserKeyForm]
|
||||
form_list = [forms.UserInfoForm, forms.UserKeyForm]
|
||||
file_storage = default_storage
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
@ -343,32 +353,15 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||
|
||||
|
||||
class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
model = User
|
||||
template_name = 'users/user_asset_permission.html'
|
||||
context_object_name = 'user_object'
|
||||
form_class = UserPrivateAssetPermissionForm
|
||||
context_object_name = 'user'
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=User.objects.all())
|
||||
return super(UserAssetPermissionView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_asset_permission_inherit_from_user_group(self):
|
||||
asset_permissions = set()
|
||||
user_groups = self.object.groups.all()
|
||||
|
||||
for user_group in user_groups:
|
||||
for asset_permission in user_group.asset_permissions.all():
|
||||
setattr(asset_permission, 'is_inherit_from_user_groups', True)
|
||||
setattr(asset_permission, 'inherit_from_user_groups',
|
||||
getattr(asset_permission, b'inherit_from_user_groups', set()).add(user_group))
|
||||
asset_permissions.add(asset_permission)
|
||||
return asset_permissions
|
||||
|
||||
def get_queryset(self):
|
||||
asset_permissions = set(self.object.asset_permissions.all()) \
|
||||
| self.get_asset_permission_inherit_from_user_group()
|
||||
return list(asset_permissions)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Users',
|
||||
@ -378,66 +371,111 @@ class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMix
|
||||
return super(UserAssetPermissionView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserGroupAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMixin, ListView):
|
||||
model = UserGroup
|
||||
template_name = 'users/user_group_asset_permission.html'
|
||||
context_object_name = 'user_group'
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupAssetPermissionView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Users',
|
||||
'action': 'User group asset permissions',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupAssetPermissionView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserAssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = UserPrivateAssetPermissionForm
|
||||
form_class = forms.UserPrivateAssetPermissionForm
|
||||
model = AssetPermission
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_object = self.get_object(queryset=User.objects.all())
|
||||
return redirect(reverse('users:user-asset-permission', kwargs={'pk': user_object.id}))
|
||||
user = self.get_object(queryset=User.objects.all())
|
||||
return redirect(reverse('users:user-asset-permission', kwargs={'pk': user.id}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.user_object = self.get_object(queryset=User.objects.all())
|
||||
self.user = self.get_object(queryset=User.objects.all())
|
||||
return super(UserAssetPermissionCreateView, self).post(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super(UserAssetPermissionCreateView, self).get_form(form_class=form_class)
|
||||
form.user = self.user_object
|
||||
form.user = self.user
|
||||
return form
|
||||
|
||||
def form_invalid(self, form):
|
||||
print(form.errors)
|
||||
return redirect(reverse('users:user-asset-permission', kwargs={'pk': self.user_object.id}))
|
||||
return redirect(reverse('users:user-asset-permission', kwargs={'pk': self.user.id}))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('users:user-asset-permission', kwargs={'pk': self.user_object.id})
|
||||
return reverse('users:user-asset-permission', kwargs={'pk': self.user.id})
|
||||
|
||||
|
||||
class UserGrantedAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
class UserGroupAssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = forms.UserGroupPrivateAssetPermissionForm
|
||||
model = AssetPermission
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_group = self.get_object(queryset=UserGroup.objects.all())
|
||||
return redirect(reverse('users:user-group-asset-permission', kwargs={'pk': user_group.id}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.user_group = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupAssetPermissionCreateView, self).post(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super(UserGroupAssetPermissionCreateView, self).get_form(form_class=form_class)
|
||||
form.user_group = self.user_group
|
||||
return form
|
||||
|
||||
def form_invalid(self, form):
|
||||
return redirect(reverse('users:user-group-asset-permission', kwargs={'pk': self.user_group.id}))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('users:user-group-asset-permission', kwargs={'pk': self.user_group.id})
|
||||
|
||||
|
||||
class UserGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_granted_asset.html'
|
||||
context_object_name = 'user_object'
|
||||
context_object_name = 'user'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=User.objects.all())
|
||||
return super(UserGrantedAssetView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
# Convert format from {'asset': ['system_users'], ..} to
|
||||
# [('asset', ['system_users']), ('asset', ['system_users']))
|
||||
assets_granted = [(asset, system_users) for asset, system_users in
|
||||
get_user_granted_assets(self.object).items()]
|
||||
|
||||
return assets_granted
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
asset_groups = [(asset_group, system_users) for asset_group, system_users in
|
||||
get_user_granted_asset_groups(self.object).items()]
|
||||
context = {
|
||||
'app': 'User',
|
||||
'action': 'User granted asset',
|
||||
'asset_groups': asset_groups,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGrantedAssetView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
excel = forms.FileField()
|
||||
class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'users/user_group_granted_asset.html'
|
||||
context_object_name = 'user_group'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=UserGroup.objects.all())
|
||||
return super(UserGroupGrantedAssetView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'User',
|
||||
'action': 'User group granted asset',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserGroupGrantedAssetView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
form_class = FileForm
|
||||
form_class = forms.FileForm
|
||||
|
||||
def form_invalid(self, form):
|
||||
try:
|
||||
@ -478,7 +516,7 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
'enable_otp': True if enable_otp in ['T', '1', 1, True] else False,
|
||||
'role': role
|
||||
}
|
||||
form = UserBulkImportForm(data, auto_id=False)
|
||||
form = forms.UserBulkImportForm(data, auto_id=False)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
else:
|
||||
@ -493,3 +531,14 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
'msg': 'ok' if not errors else '<br />'.join(errors)
|
||||
}
|
||||
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
|
||||
|
||||
|
@ -11,3 +11,7 @@ django-simple-captcha==0.5.2
|
||||
django-formtools==1.0
|
||||
sshpubkeys==2.2.0
|
||||
djangorestframework-bulk==0.2.1
|
||||
paramiko==2.0.2
|
||||
django-redis-cache==1.7.1
|
||||
requests==2.11.1
|
||||
itsdangerous==0.24
|
@ -28,8 +28,9 @@ def start_django():
|
||||
|
||||
def start_celery():
|
||||
os.chdir(apps_dir)
|
||||
os.environ.setdefault('C_FORCE_ROOT', '1')
|
||||
print('start celery')
|
||||
subprocess.call('celery -A common worker -l info', shell=True)
|
||||
subprocess.call('celery -A common worker -B -s /tmp/celerybeat-schedule -l info ', shell=True)
|
||||
|
||||
|
||||
def main():
|
||||
|
Loading…
Reference in New Issue
Block a user