Add asset system user

pull/530/head
ibuler 2016-09-09 00:09:49 +08:00
parent 5259dd8054
commit 4fc9274e00
8 changed files with 343 additions and 18 deletions

View File

@ -90,6 +90,7 @@ 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)
@ -120,15 +121,15 @@ class AdminUserForm(forms.ModelForm):
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:
print(private_key_file)
admin_user.private_key = private_key_file.read()
admin_user.save()
return self.instance
class Meta:
model = AdminUser
fields = ['name', 'username', 'password', 'private_key_file', 'as_default', 'comment']
fields = ['name', 'username', 'auto_generate_key', 'password', 'private_key_file', 'as_default', 'comment']
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
@ -138,3 +139,76 @@ class AdminUserForm(forms.ModelForm):
'username': '* required',
}
class SystemUserForm(forms.ModelForm):
# Admin user assets define, let user select, save it in form not in view
assets = forms.ModelMultipleChoiceField(queryset=Asset.objects.all(),
label=_('Asset'),
required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
)
asset_groups = forms.ModelMultipleChoiceField(queryset=AssetGroup.objects.all(),
label=_('Asset group'),
required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')})
)
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)
# Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False)
def __init__(self, *args, **kwargs):
# When update a admin user instance, initial it
if kwargs.get('instance'):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
initial['asset_groups'] = kwargs['instance'].asset_groups.all()
super(SystemUserForm, self).__init__(*args, **kwargs)
def _save_m2m(self):
# Save assets relation with admin user
super(SystemUserForm, self)._save_m2m()
assets = self.cleaned_data['assets']
asset_groups = self.cleaned_data['asset_groups']
self.instance.assets.clear()
self.instance.assets.add(*tuple(assets))
self.instance.asset_groups.clear()
self.instance.asset_groups.add(*tuple(asset_groups))
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super(SystemUserForm, self).save(commit=commit)
password = self.cleaned_data['password']
private_key_file = self.cleaned_data['private_key_file']
if password:
system_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:
system_user.private_key = private_key_file.read()
system_user.save()
return self.instance
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'as_default',
'auto_push', 'auto_update', 'sudo', 'comment', 'shell', 'home', 'uid',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
'auth_push': 'Auto push system user to asset',
'auth_update': 'Auto update system user ssh key',
}

View File

@ -135,28 +135,72 @@ class SystemUser(models.Model):
('telnet', 'telnet'),
)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=16, blank=True, verbose_name=_('Username'))
password = models.CharField(max_length=256, blank=True, verbose_name=_('Password'))
protocol = models.CharField(max_length=16, default='ssh', verbose_name=_('Protocol'))
private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key'))
public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
is_default = models.BooleanField(default=True, verbose_name=_('As default'))
username = models.CharField(max_length=16, verbose_name=_('Username'))
_password = models.CharField(max_length=256, blank=True, verbose_name=_('Password'))
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
_private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key'))
_public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
as_default = models.BooleanField(default=False, verbose_name=_('As default'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
auto_update = models.BooleanField(default=True, verbose_name=_('Auto update pass/key'))
sudo = models.TextField(max_length=4096, blank=True, verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, blank=True, verbose_name=_('Shell'))
sudo = models.TextField(max_length=4096, default='/user/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
home = models.CharField(max_length=64, blank=True, verbose_name=_('Home'))
uid = models.IntegerField(blank=True, verbose_name=_('Uid'))
date_created = models.DateTimeField(auto_now=True, null=True)
uid = models.IntegerField(null=True, blank=True, verbose_name=_('Uid'))
date_created = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.CharField(max_length=128, blank=True, verbose_name=_('Comment'))
comment = models.TextField(max_length=128, blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return self.name
@property
def password(self):
return decrypt(self._password)
@password.setter
def password(self, password_raw):
self._password = encrypt(password_raw)
@property
def private_key(self):
return decrypt(self._private_key)
@private_key.setter
def private_key(self, private_key_raw):
self._private_key = encrypt(private_key_raw)
@property
def public_key(self):
return decrypt(self._public_key)
@public_key.setter
def public_key(self, public_key_raw):
self._public_key = encrypt(public_key_raw)
class Meta:
db_table = 'system_user'
@classmethod
def generate_fake(cls, count=100):
from random import seed
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
obj = cls(name=forgery_py.name.full_name(),
username=forgery_py.internet.user_name(),
password=forgery_py.lorem_ipsum.word(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
obj.save()
logger.debug('Generate fake asset group: %s' % obj.name)
except IntegrityError:
print('Error continue')
continue
class AssetGroup(models.Model):
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
@ -206,7 +250,7 @@ class Asset(models.Model):
password = models.CharField(max_length=256, null=True, blank=True, verbose_name=_("Admin password"))
admin_user = models.ForeignKey(AdminUser, null=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
system_user = models.ManyToManyField(SystemUser, blank=True, verbose_name=_("System User"))
system_user = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User"))
idc = models.ForeignKey(IDC, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('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'))
@ -227,7 +271,7 @@ class Asset(models.Model):
comment = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return '%(ip)s:%(port)d' % {'ip': self.ip, 'port': self.port}
return '%(ip)s:%(port)s' % {'ip': self.ip, 'port': self.port}
def initial(self):
pass

View File

@ -13,7 +13,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{% trans 'Create asset group' %}</h5>
<h5>{% trans 'Create admin user' %}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
@ -31,6 +31,12 @@
{% 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">

View File

@ -0,0 +1,88 @@
{% extends 'base.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>
{% 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="ibox-title">
<h5>{% trans 'Create system user' %}</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 enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %}
{{ form.name|bootstrap_horizontal }}
{{ form.username|bootstrap_horizontal }}
{{ form.protocol|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>
<div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8">
{{ form.auto_push}}
</div>
</div>
<div class="form-group">
<label for="{{ form.as_update.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto update' %}</label>
<div class="col-sm-8">
{{ form.auto_update}}
</div>
</div>
{{ form.assets|bootstrap_horizontal }}
{{ form.asset_groups|bootstrap_horizontal }}
{{ form.sudo|bootstrap_horizontal }}
{{ form.comment|bootstrap_horizontal }}
{{ form.home|bootstrap_horizontal }}
{{ form.shell|bootstrap_horizontal }}
{{ form.uid|bootstrap_horizontal }}
<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>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
})
</script>
{% endblock %}

View File

@ -0,0 +1,43 @@
{% extends '_list_base.html' %}
{% load i18n %}
{% load common_tags %}
{% block content_left_head %}
<a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
{% endblock %}
{% block table_head %}
<th class="text-center">{% trans 'ID' %}</th>
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=name">{% trans 'Name' %}</a></th>
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=username">{% trans 'Username' %}</a></th>
<th class="text-center">{% trans 'Asset num' %}</th>
<th class="text-center">{% trans 'Asset group num' %}</th>
<th class="text-center">{% trans 'Lost connection' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center"></th>
{% endblock %}
{% block table_body %}
{% for system_user in system_user_list %}
<tr class="gradeX">
<td class="text-center">{{ system_user.id }}</td>
<td>
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}">
{{ system_user.name }}
</a>
</td>
<td class="text-center">{{ system_user.username }}</td>
<td class="text-center">{{ system_user.assets.count }}</td>
<td class="text-center">{{ system_user.asset_groups.count }}</td>
<td class="text-center">{{ system_user.assets.count }}</td>
<td class="text-center">{{ system_user.comment|truncatewords:8 }}</td>
<td class="text-center">
<!-- Todo: Click script button will paste a url to clipboard like: curl http://url/system_user_create.sh | bash -->
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-primary">{% trans 'Script' %}</a>
<!-- Todo: Click refresh button will run a task to test admin user could connect asset or not immediately -->
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-warning">{% trans 'Refresh' %}</a>
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-info">{% trans 'Update' %}</a>
<a href="{% url 'assets:system-user-delete' pk=system_user.id %}" class="btn btn-xs btn-danger del">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
{% endblock %}

View File

@ -33,5 +33,10 @@ urlpatterns = [
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'),
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'^api/v1.0/', include(router.urls)),
]

View File

@ -11,7 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView, SingleObjectMixin
from .models import Asset, AssetGroup, IDC, AssetExtend, AdminUser, SystemUser
from .forms import AssetForm, AssetGroupForm, IDCForm, AdminUserForm
from .forms import AssetForm, AssetGroupForm, IDCForm, AdminUserForm, SystemUserForm
from .hands import AdminUserRequiredMixin
@ -294,3 +294,68 @@ class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
model = AdminUser
template_name = 'assets/delete_confirm.html'
success_url = 'assets:admin-user-list'
class SystemUserListView(AdminUserRequiredMixin, ListView):
model = SystemUser
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
context_object_name = 'system_user_list'
template_name = 'assets/system_user_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Admin user list'),
'keyword': self.request.GET.get('keyword', '')
}
kwargs.update(context)
return super(SystemUserListView, self).get_context_data(**kwargs)
def get_queryset(self):
# Todo: Default order by lose asset connection num
self.queryset = super(SystemUserListView, self).get_queryset()
self.keyword = keyword = self.request.GET.get('keyword', '')
self.sort = sort = self.request.GET.get('sort', '-date_created')
if keyword:
self.queryset = self.queryset.filter(Q(name__icontains=keyword) |
Q(comment__icontains=keyword))
if sort:
self.queryset = self.queryset.order_by(sort)
return self.queryset
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = SystemUser
form_class = SystemUserForm
template_name = 'assets/system_user_create_update.html'
success_url = reverse_lazy('assets:system-user-list')
success_message = _('Create system user <a href="%s">%s</a> successfully.')
def get_context_data(self, **kwargs):
context = {
'app': 'assets',
'action': 'Create system user'
}
kwargs.update(context)
return super(SystemUserCreateView, self).get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
return self.success_message % (
reverse_lazy('assets:system-user-detail', kwargs={'pk': self.object.pk}),
self.object.name,
)
class SystemUserUpdateView(UpdateView):
pass
class SystemUserDetailView(DetailView):
pass
class SystemUserDeleteView(DeleteView):
pass

View File

@ -22,7 +22,7 @@
<li id="asset-group"><a href="{% url 'assets:asset-group-list' %}">{% trans 'Asset group' %}</a></li>
<li id="idc"><a href="{% url 'assets:idc-list' %}">{% trans 'IDC' %}</a></li>
<li id="admin-user"><a href="{% url 'assets:admin-user-list' %}">{% trans 'Admin user' %}</a></li>
<li id="system-user"><a href="">{% trans 'System user' %}</a></li>
<li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li>
<li id=""><a href="">{% trans 'Label' %}</a></li>
</ul>
</li>