mirror of https://github.com/jumpserver/jumpserver
[Bugfix] 修复一些明显的bug
parent
5a92972120
commit
76df1de634
|
@ -44,9 +44,11 @@ class AssetViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||||
else:
|
else:
|
||||||
assets_granted = get_user_granted_assets(self.request.user)
|
assets_granted = get_user_granted_assets(self.request.user)
|
||||||
queryset = self.queryset.filter(id__in=[asset.id for asset in assets_granted])
|
queryset = self.queryset.filter(id__in=[asset.id for asset in assets_granted])
|
||||||
|
|
||||||
cluster_id = self.request.query_params.get('cluster_id')
|
cluster_id = self.request.query_params.get('cluster_id')
|
||||||
asset_group_id = self.request.query_params.get('asset_group_id')
|
asset_group_id = self.request.query_params.get('asset_group_id')
|
||||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||||
|
|
||||||
if cluster_id:
|
if cluster_id:
|
||||||
queryset = queryset.filter(cluster__id=cluster_id)
|
queryset = queryset.filter(cluster__id=cluster_id)
|
||||||
if asset_group_id:
|
if asset_group_id:
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
import uuid
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
@ -9,7 +8,6 @@ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen,
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
class AssetCreateForm(forms.ModelForm):
|
class AssetCreateForm(forms.ModelForm):
|
||||||
|
|
||||||
|
@ -57,11 +55,11 @@ class AssetUpdateForm(forms.ModelForm):
|
||||||
|
|
||||||
|
|
||||||
class AssetBulkUpdateForm(forms.ModelForm):
|
class AssetBulkUpdateForm(forms.ModelForm):
|
||||||
assets = forms.MultipleChoiceField(
|
assets = forms.ModelMultipleChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
help_text='* required',
|
help_text='* required',
|
||||||
label=_('Select assets'),
|
label=_('Select assets'),
|
||||||
choices=[(asset.id, asset.hostname) for asset in Asset.objects.all()],
|
queryset=Asset.objects.all(),
|
||||||
widget=forms.SelectMultiple(
|
widget=forms.SelectMultiple(
|
||||||
attrs={
|
attrs={
|
||||||
'class': 'select2',
|
'class': 'select2',
|
||||||
|
@ -94,10 +92,9 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||||
|
|
||||||
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
||||||
if k in changed_fields}
|
if k in changed_fields}
|
||||||
print(cleaned_data)
|
assets = cleaned_data.pop('assets')
|
||||||
assets_id = cleaned_data.pop('assets')
|
|
||||||
groups = cleaned_data.pop('groups', [])
|
groups = cleaned_data.pop('groups', [])
|
||||||
assets = Asset.objects.filter(id__in=assets_id)
|
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
|
||||||
assets.update(**cleaned_data)
|
assets.update(**cleaned_data)
|
||||||
if groups:
|
if groups:
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
|
@ -175,16 +172,18 @@ class AdminUserForm(forms.ModelForm):
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
if private_key:
|
if private_key:
|
||||||
public_key = ssh_pubkey_gen(private_key)
|
public_key = ssh_pubkey_gen(private_key, password=password)
|
||||||
|
|
||||||
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
|
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
|
||||||
return admin_user
|
return admin_user
|
||||||
|
|
||||||
def clean_private_key_file(self):
|
def clean_private_key_file(self):
|
||||||
private_key_file = self.cleaned_data['private_key_file']
|
private_key_file = self.cleaned_data['private_key_file']
|
||||||
|
password = self.cleaned_data['password']
|
||||||
|
|
||||||
if private_key_file:
|
if private_key_file:
|
||||||
private_key = private_key_file.read()
|
private_key = private_key_file.read()
|
||||||
if not validate_ssh_private_key(private_key):
|
if not validate_ssh_private_key(private_key, password):
|
||||||
raise forms.ValidationError(_('Invalid private key'))
|
raise forms.ValidationError(_('Invalid private key'))
|
||||||
return private_key
|
return private_key
|
||||||
return private_key_file
|
return private_key_file
|
||||||
|
|
|
@ -18,10 +18,6 @@ __all__ = ['Asset']
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_default_cluster():
|
|
||||||
return Cluster.initial()
|
|
||||||
|
|
||||||
|
|
||||||
class Asset(models.Model):
|
class Asset(models.Model):
|
||||||
# Todo: Move them to settings
|
# Todo: Move them to settings
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
|
@ -48,7 +44,7 @@ class Asset(models.Model):
|
||||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
|
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
|
||||||
cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster'),)
|
cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster'))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||||
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),)
|
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),)
|
||||||
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),)
|
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),)
|
||||||
|
|
|
@ -10,7 +10,6 @@ from django.db import models
|
||||||
import logging
|
import logging
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .user import SystemUser
|
|
||||||
|
|
||||||
__all__ = ['AssetGroup']
|
__all__ = ['AssetGroup']
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -66,16 +66,15 @@ class AssetUser(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_key_file(self):
|
def private_key_file(self):
|
||||||
if not self.private_key:
|
if not self.private_key_obj:
|
||||||
return None
|
return None
|
||||||
project_dir = settings.PROJECT_DIR
|
project_dir = settings.PROJECT_DIR
|
||||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||||
key_str = signer.unsign(self._private_key)
|
key_str = signer.unsign(self._private_key)
|
||||||
key_name = md5(key_str.encode('utf-8')).hexdigest()
|
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
|
||||||
key_path = os.path.join(tmp_dir, key_name)
|
key_path = os.path.join(tmp_dir, key_name)
|
||||||
if not os.path.exists(key_path):
|
if not os.path.exists(key_path):
|
||||||
with open(key_path, 'w') as f:
|
self.private_key_obj.write_private_key_file(key_path)
|
||||||
f.write(key_str)
|
|
||||||
os.chmod(key_path, 0o400)
|
os.chmod(key_path, 0o400)
|
||||||
return key_path
|
return key_path
|
||||||
|
|
||||||
|
@ -105,7 +104,6 @@ class AssetUser(models.Model):
|
||||||
update_fields.append('_public_key')
|
update_fields.append('_public_key')
|
||||||
|
|
||||||
if update_fields:
|
if update_fields:
|
||||||
print(update_fields)
|
|
||||||
self.save(update_fields=update_fields)
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
def auto_gen_auth(self):
|
def auto_gen_auth(self):
|
||||||
|
@ -149,7 +147,11 @@ class AdminUser(AssetUser):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def become_pass(self):
|
def become_pass(self):
|
||||||
return signer.unsign(self._become_pass)
|
password = signer.unsign(self._become_pass)
|
||||||
|
if password:
|
||||||
|
return password
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
@become_pass.setter
|
@become_pass.setter
|
||||||
def become_pass(self, password):
|
def become_pass(self, password):
|
||||||
|
@ -199,7 +201,7 @@ class SystemUser(AssetUser):
|
||||||
('K', 'Public key'),
|
('K', 'Public key'),
|
||||||
)
|
)
|
||||||
cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster"))
|
cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster"))
|
||||||
priority = models.IntegerField(default=10, verbose_name=_("Priority")) # Todo: If user granted more priority user, default will be login as the hign
|
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
|
||||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||||
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
|
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
|
||||||
|
|
|
@ -402,12 +402,6 @@ def push_system_user_on_auth_change(sender, instance=None, update_fields=None, *
|
||||||
push_system_user_to_cluster_assets.delay(instance, task_name)
|
push_system_user_to_cluster_assets.delay(instance, task_name)
|
||||||
|
|
||||||
|
|
||||||
@receiver(on_app_ready, dispatch_uid="my_unique_identifier")
|
|
||||||
def test_admin_user_on_app_ready(sender, **kwargs):
|
|
||||||
logger.debug("Receive app ready signal, test admin connectability")
|
|
||||||
test_admin_user_connectability_period.delay()
|
|
||||||
|
|
||||||
|
|
||||||
celery_app.conf['CELERYBEAT_SCHEDULE'].update(
|
celery_app.conf['CELERYBEAT_SCHEDULE'].update(
|
||||||
{
|
{
|
||||||
'update_assets_hardware_period': {
|
'update_assets_hardware_period': {
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Public IP' %}:</td>
|
<td>{% trans 'Public IP' %}:</td>
|
||||||
<td><b>{{ asset.public_ip }}</b></td>
|
<td><b>{{ asset.public_ip|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Port' %}:</td>
|
<td>{% trans 'Port' %}:</td>
|
||||||
|
@ -74,12 +74,12 @@
|
||||||
{% if asset.admin_user_avail %}
|
{% if asset.admin_user_avail %}
|
||||||
<td><b>{{ asset.admin_user_avail.name }}</b></td>
|
<td><b>{{ asset.admin_user_avail.name }}</b></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td><b>None</b></td>
|
<td></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Remote card IP' %}:</td>
|
<td>{% trans 'Remote card IP' %}:</td>
|
||||||
<td><b>{{ asset.remote_card_ip }}</b></td>
|
<td><b>{{ asset.remote_card_ip|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Cluster' %}:</td>
|
<td>{% trans 'Cluster' %}:</td>
|
||||||
|
@ -87,39 +87,39 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Cabinet number' %}:</td>
|
<td>{% trans 'Cabinet number' %}:</td>
|
||||||
<td><b>{{ asset.cabinet_no }}</b></td>
|
<td><b>{{ asset.cabinet_no|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Cabinet position' %}:</td>
|
<td>{% trans 'Cabinet position' %}:</td>
|
||||||
<td><b>{{ asset.cabinet_pos }}</b></td>
|
<td><b>{{ asset.cabinet_pos|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Vendor' %}:</td>
|
<td>{% trans 'Vendor' %}:</td>
|
||||||
<td><b>{{ asset.vendor }}</b></td>
|
<td><b>{{ asset.vendor|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Model' %}:</td>
|
<td>{% trans 'Model' %}:</td>
|
||||||
<td><b>{{ asset.model }}</b></td>
|
<td><b>{{ asset.model|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'CPU' %}:</td>
|
<td>{% trans 'CPU' %}:</td>
|
||||||
<td><b>{{ asset.cpu_model }} {{ asset.cpu_count }}*{{ asset.cpu_cores }}</b></td>
|
<td><b>{{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Memory' %}:</td>
|
<td>{% trans 'Memory' %}:</td>
|
||||||
<td><b>{{ asset.memory }}</b></td>
|
<td><b>{{ asset.memory|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Disk' %}:</td>
|
<td>{% trans 'Disk' %}:</td>
|
||||||
<td><b>{{ asset.disk_total }}</b></td>
|
<td><b>{{ asset.disk_total|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Platform' %}:</td>
|
<td>{% trans 'Platform' %}:</td>
|
||||||
<td><b>{{ asset.platform }}</b></td>
|
<td><b>{{ asset.platform|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'OS' %}:</td>
|
<td>{% trans 'OS' %}:</td>
|
||||||
<td><b>{{ asset.os }} {{ asset.os_version }} {{ asset.os_arch }}</b></td>
|
<td><b>{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Asset status' %}:</td>
|
<td>{% trans 'Asset status' %}:</td>
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Is active' %}:</td>
|
<td>{% trans 'Is active' %}:</td>
|
||||||
<td><b>{{ asset.is_active }}</b></td>
|
<td><b>{{ asset.is_active|yesno:"Yes,No" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Asset type' %}:</td>
|
<td>{% trans 'Asset type' %}:</td>
|
||||||
|
@ -139,11 +139,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Serial number' %}:</td>
|
<td>{% trans 'Serial number' %}:</td>
|
||||||
<td><b>{{ asset.sn }}</b></td>
|
<td><b>{{ asset.sn|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Asset number' %}:</td>
|
<td>{% trans 'Asset number' %}:</td>
|
||||||
<td><b>{{ asset.number }}</b></td>
|
<td><b>{{ asset.number|default:"" }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Created by' %}:</td>
|
<td>{% trans 'Created by' %}:</td>
|
||||||
|
|
|
@ -64,8 +64,7 @@
|
||||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function initTable() {
|
||||||
$(document).ready(function(){
|
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#asset_list_table'),
|
ele: $('#asset_list_table'),
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
|
@ -103,48 +102,51 @@ $(document).ready(function(){
|
||||||
{data: "is_active" }, {data: "is_connective"}, {data: "id" }],
|
{data: "is_active" }, {data: "is_connective"}, {data: "id" }],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
var table = jumpserver.initDataTable(options);
|
return jumpserver.initDataTable(options);
|
||||||
|
}
|
||||||
|
|
||||||
$('.btn_export').click(function () {
|
$(document).ready(function(){
|
||||||
var assets = [];
|
initTable();
|
||||||
var rows = table.rows('.selected').data();
|
})
|
||||||
$.each(rows, function (index, obj) {
|
.on('click', '#btn_export', function () {
|
||||||
assets.push(obj.id)
|
var $data_table = $('#asset_list_table').DataTable();
|
||||||
});
|
var rows = $data_table.rows('.selected').data();
|
||||||
console.log(assets);
|
var assets = [];
|
||||||
$.ajax({
|
$.each(rows, function (index, obj) {
|
||||||
url: "{% url "assets:asset-export" %}",
|
assets.push(obj.id)
|
||||||
method: 'POST',
|
|
||||||
data: JSON.stringify({assets_id: assets}),
|
|
||||||
dataType: "json",
|
|
||||||
success: function (data, textStatus) {
|
|
||||||
window.open(data.redirect)
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
toastr.error('Export failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
$('#btn_asset_import').click(function() {
|
$.ajax({
|
||||||
var $form = $('#fm_asset_import');
|
url: "{% url "assets:asset-export" %}",
|
||||||
$form.find('.help-block').remove();
|
method: 'POST',
|
||||||
function success (data) {
|
data: JSON.stringify({assets_id: assets}),
|
||||||
if (data.valid === false) {
|
dataType: "json",
|
||||||
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
|
success: function (data, textStatus) {
|
||||||
} else {
|
window.open(data.redirect)
|
||||||
$('#id_created').html(data.created_info);
|
},
|
||||||
$('#id_created_detail').html(data.created.join(', '));
|
error: function () {
|
||||||
$('#id_updated').html(data.updated_info);
|
toastr.error('Export failed');
|
||||||
$('#id_updated_detail').html(data.updated.join(', '));
|
|
||||||
$('#id_failed').html(data.failed_info);
|
|
||||||
$('#id_failed_detail').html(data.failed.join(', '));
|
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
|
||||||
$data_table.ajax.reload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$form.ajaxSubmit({success: success});
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.on('click', '#btn_import', function () {
|
||||||
|
var $form = $('#fm_asset_import');
|
||||||
|
$form.find('.help-block').remove();
|
||||||
|
function success (data) {
|
||||||
|
if (data.valid === false) {
|
||||||
|
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
|
||||||
|
} else {
|
||||||
|
$('#id_created').html(data.created_info);
|
||||||
|
$('#id_created_detail').html(data.created.join(', '));
|
||||||
|
$('#id_updated').html(data.updated_info);
|
||||||
|
$('#id_updated_detail').html(data.updated.join(', '));
|
||||||
|
$('#id_failed').html(data.failed_info);
|
||||||
|
$('#id_failed_detail').html(data.failed.join(', '));
|
||||||
|
var $data_table = $('#asset_list_table').DataTable();
|
||||||
|
$data_table.ajax.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$form.ajaxSubmit({success: success});
|
||||||
|
})
|
||||||
|
|
||||||
.on('click', '.btn_asset_delete', function () {
|
.on('click', '.btn_asset_delete', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}{% endblock %}
|
{% block table_search %}{% endblock %}
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<div class="uc pull-left m-r-5">
|
<div class="uc pull-left m-r-5">
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
{% extends '_base_list.html' %}
|
{% extends '_base_list.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block help_message %}
|
||||||
|
<div class="alert alert-info help-message">
|
||||||
|
系统用户是 用户登录资产(服务器)时使用的用户,如 web, sa, dba等具有特殊功能的用户。系统用户创建时,如果选择了自动推送
|
||||||
|
Jumpserver会使用ansible自动推送到系统用户所在集群的资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -5,26 +5,22 @@
|
||||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.custom{
|
|
||||||
margin-right:5px;
|
|
||||||
}
|
|
||||||
#modal .modal-body { max-height: 200px; }
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_left_head %}{% endblock %}
|
{% block content_left_head %}{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
{# <div class="html5buttons">#}
|
<div class="html5buttons">
|
||||||
{# <div class="dt-buttons btn-group">#}
|
<div class="dt-buttons btn-group">
|
||||||
{# <a class="btn btn-default btn_export" tabindex="0">#}
|
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||||
{# <span>{% trans "Export" %}</span>#}
|
<span>{% trans "Import" %}</span>
|
||||||
{# </a>#}
|
</a>
|
||||||
{# </div>#}
|
<a class="btn btn-default btn_export" tabindex="0">
|
||||||
{# </div>#}
|
<span>{% trans "Export" %}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -36,8 +32,8 @@
|
||||||
<th class="text-center">{% trans 'Type' %}</th>
|
<th class="text-center">{% trans 'Type' %}</th>
|
||||||
<th class="text-center">{% trans 'Env' %}</th>
|
<th class="text-center">{% trans 'Env' %}</th>
|
||||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||||
<th class="text-center">{% trans 'Valid' %}</th>
|
<th class="text-center">{% trans 'Active' %}</th>
|
||||||
<th class="text-center">{% trans 'Alive' %}</th>
|
<th class="text-center">{% trans 'Connective' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -48,216 +44,45 @@
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function(){
|
|
||||||
var options = {
|
|
||||||
ele: $('#asset_list_table'),
|
|
||||||
columnDefs: [
|
|
||||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
|
||||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
|
||||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
|
||||||
}},
|
|
||||||
{targets: 7, 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: 8, 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>')
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
],
|
|
||||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
|
||||||
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
|
||||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
|
|
||||||
{data: "is_active" }, {data: "is_active"}],
|
|
||||||
};
|
|
||||||
var table = jumpserver.initDataTable(options);
|
|
||||||
$('.btn_export').click(function () {
|
|
||||||
var assets = [];
|
|
||||||
var rows = table.rows('.selected').data();
|
|
||||||
$.each(rows, function (index, obj) {
|
|
||||||
assets.push(obj.id)
|
|
||||||
});
|
|
||||||
console.log(assets);
|
|
||||||
$.ajax({
|
|
||||||
url: "{% url "assets:asset-export" %}",
|
|
||||||
method: 'POST',
|
|
||||||
data: JSON.stringify({assets_id: assets}),
|
|
||||||
dataType: "json",
|
|
||||||
success: function (data, textStatus) {
|
|
||||||
window.open(data.redirect)
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
toastr.error('Export failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
$('#btn_asset_import').click(function() {
|
|
||||||
var $form = $('#fm_asset_import');
|
|
||||||
$form.find('.help-block').remove();
|
|
||||||
function success (data) {
|
|
||||||
if (data.valid === false) {
|
|
||||||
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
|
|
||||||
} else {
|
|
||||||
$('#id_created').html(data.created_info);
|
|
||||||
$('#id_created_detail').html(data.created.join(', '));
|
|
||||||
$('#id_updated').html(data.updated_info);
|
|
||||||
$('#id_updated_detail').html(data.updated.join(', '));
|
|
||||||
$('#id_failed').html(data.failed_info);
|
|
||||||
$('#id_failed_detail').html(data.failed.join(', '));
|
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
|
||||||
$data_table.ajax.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$form.ajaxSubmit({success: success});
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
.on('click', '.btn_asset_delete', function () {
|
function initTable() {
|
||||||
var $this = $(this);
|
var options = {
|
||||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
ele: $('#asset_list_table'),
|
||||||
var uid = $this.data('uid');
|
columnDefs: [
|
||||||
var the_url = '{% url "api-assets:asset-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||||
objectDelete($this, name, the_url);
|
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||||
})
|
console.log('{{ the_url }}');
|
||||||
|
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||||
.on('click', '#btn_bulk_update', function () {
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
var action = $('#slct_bulk_update').val();
|
}},
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
{targets: 7, createdCell: function (td, cellData) {
|
||||||
var id_list = [];
|
if (!cellData) {
|
||||||
var plain_id_list = [];
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
$data_table.rows({selected: true}).every(function(){
|
} else {
|
||||||
id_list.push({id: this.data().id});
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
plain_id_list.push(this.data().id);
|
}
|
||||||
});
|
}},
|
||||||
if (plain_id_list.length == 0) {
|
{targets: 8, createdCell: function (td, cellData) {
|
||||||
return false;
|
if (cellData == 'Unknown'){
|
||||||
}
|
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
} else if (!cellData) {
|
||||||
function doDeactive() {
|
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||||
var body = $.each(id_list, function(index, asset_object) {
|
} else {
|
||||||
asset_object['is_active'] = false;
|
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||||
});
|
}
|
||||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
}}
|
||||||
$data_table.ajax.reload();
|
],
|
||||||
jumpserver.checked = false;
|
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||||
}
|
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||||
function doActive() {
|
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"},
|
||||||
var body = $.each(id_list, function(index, asset_object) {
|
{data: "is_active" }, {data: "is_connective"}],
|
||||||
asset_object['is_active'] = true;
|
op_html: $('#actions').html()
|
||||||
});
|
|
||||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
|
||||||
$data_table.ajax.reload();
|
|
||||||
jumpserver.checked = false;
|
|
||||||
}
|
|
||||||
function doDelete() {
|
|
||||||
swal({
|
|
||||||
title: "{% trans 'Are you sure?' %}",
|
|
||||||
text: "{% trans 'This will delete the selected assets !!!' %}",
|
|
||||||
type: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#DD6B55",
|
|
||||||
confirmButtonText: "{% trans 'Confirm' %}",
|
|
||||||
closeOnConfirm: false
|
|
||||||
}, function() {
|
|
||||||
var success = function() {
|
|
||||||
var msg = "{% trans 'Asset Deleted.' %}";
|
|
||||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
|
||||||
$('#asset_list_table').DataTable().ajax.reload();
|
|
||||||
};
|
|
||||||
var fail = function() {
|
|
||||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
|
||||||
swal("{% trans 'Asset Delete' %}", msg, "error");
|
|
||||||
};
|
|
||||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
|
||||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
|
||||||
$data_table.ajax.reload();
|
|
||||||
jumpserver.checked = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function doUpdate() {
|
|
||||||
$('#asset_bulk_update_modal').modal('show');
|
|
||||||
}
|
|
||||||
switch(action) {
|
|
||||||
case 'deactive':
|
|
||||||
doDeactive();
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
doDelete();
|
|
||||||
break;
|
|
||||||
case 'update':
|
|
||||||
doUpdate();
|
|
||||||
break;
|
|
||||||
case 'active':
|
|
||||||
doActive();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.on('click', '#btn_asset_bulk_update', function () {
|
|
||||||
var json_data = $("#fm_asset_bulk_update").serializeObject();
|
|
||||||
var body = {};
|
|
||||||
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
|
|
||||||
if (json_data.type != '') {
|
|
||||||
body.type = json_data.type;
|
|
||||||
}
|
|
||||||
if (json_data.groups != undefined) {
|
|
||||||
body.groups = json_data.groups;
|
|
||||||
}
|
|
||||||
if (typeof body.groups === 'string') {
|
|
||||||
body.groups = [parseInt(body.groups)]
|
|
||||||
} else if(typeof body.groups === 'array') {
|
|
||||||
var new_groups = body.groups.map(Number);
|
|
||||||
body.groups = new_groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json_data.system_users != undefined) {
|
|
||||||
body.system_users = json_data.system_users;
|
|
||||||
}
|
|
||||||
if (typeof body.system_users === 'string') {
|
|
||||||
body.system_users = [parseInt(body.system_users)]
|
|
||||||
} else if(typeof body.system_users === 'array') {
|
|
||||||
var new_users = body.system_users.map(Number);
|
|
||||||
body.system_users = new_users;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json_data.tags != undefined) {
|
|
||||||
body.tags = json_data.tags;
|
|
||||||
}
|
|
||||||
if (typeof body.tags == 'string') {
|
|
||||||
body.tags = [parseInt(body.tags)];
|
|
||||||
} else if (typeof body.tags === 'array') {
|
|
||||||
var new_tags = body.tags.map(Number);
|
|
||||||
body.tags = new_tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $data_table = $('#asset_list_table').DataTable();
|
|
||||||
var post_list = [];
|
|
||||||
$data_table.rows({selected: true}).every(function(){
|
|
||||||
var content = Object.assign({id: this.data().id}, body);
|
|
||||||
post_list.push(content);
|
|
||||||
});
|
|
||||||
if (post_list === []) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
|
||||||
var success = function() {
|
|
||||||
var msg = "{% trans 'The selected assets has been updated successfully.' %}";
|
|
||||||
swal("{% trans 'Asset Updated' %}", msg, "success");
|
|
||||||
$('#asset_list_table').DataTable().ajax.reload();
|
|
||||||
jumpserver.checked = false;
|
|
||||||
};
|
};
|
||||||
console.log(JSON.stringify(post_list));
|
return jumpserver.initDataTable(options);
|
||||||
console.log(the_url);
|
}
|
||||||
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
|
|
||||||
$('#asset_bulk_update_modal').modal('hide');
|
$(document).ready(function(){
|
||||||
|
initTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
#
|
#
|
||||||
|
from collections import defaultdict
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from .models import Asset, SystemUser
|
from .models import Asset, SystemUser
|
||||||
|
|
||||||
|
@ -17,3 +18,12 @@ def get_system_user_by_name(name):
|
||||||
return system_user
|
return system_user
|
||||||
|
|
||||||
|
|
||||||
|
def check_assets_have_system_user(assets, system_users):
|
||||||
|
errors = defaultdict(list)
|
||||||
|
|
||||||
|
for system_user in system_users:
|
||||||
|
clusters = system_user.cluster.all()
|
||||||
|
for asset in assets:
|
||||||
|
if asset.cluster not in clusters:
|
||||||
|
errors[asset].append(system_user)
|
||||||
|
return errors
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
|
|
@ -195,11 +195,11 @@ def ssh_key_string_to_obj(text, password=None):
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost'):
|
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
|
||||||
if isinstance(private_key, bytes):
|
if isinstance(private_key, bytes):
|
||||||
private_key = private_key.decode("utf-8")
|
private_key = private_key.decode("utf-8")
|
||||||
if isinstance(private_key, string_types):
|
if isinstance(private_key, string_types):
|
||||||
private_key = ssh_key_string_to_obj(private_key)
|
private_key = ssh_key_string_to_obj(private_key, password=password)
|
||||||
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
||||||
raise IOError('Invalid private key')
|
raise IOError('Invalid private key')
|
||||||
|
|
||||||
|
@ -238,14 +238,14 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h
|
||||||
raise IOError('These is error when generate ssh key.')
|
raise IOError('These is error when generate ssh key.')
|
||||||
|
|
||||||
|
|
||||||
def validate_ssh_private_key(text):
|
def validate_ssh_private_key(text, password=None):
|
||||||
if isinstance(text, bytes):
|
if isinstance(text, bytes):
|
||||||
try:
|
try:
|
||||||
text = text.decode("utf-8")
|
text = text.decode("utf-8")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
key = ssh_key_string_to_obj(text)
|
key = ssh_key_string_to_obj(text, password=password)
|
||||||
if key is None:
|
if key is None:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -59,7 +59,6 @@ INSTALLED_APPS = [
|
||||||
'assets.apps.AssetsConfig',
|
'assets.apps.AssetsConfig',
|
||||||
'perms.apps.PermsConfig',
|
'perms.apps.PermsConfig',
|
||||||
'ops.apps.OpsConfig',
|
'ops.apps.OpsConfig',
|
||||||
# 'audits.apps.AuditsConfig',
|
|
||||||
'common.apps.CommonConfig',
|
'common.apps.CommonConfig',
|
||||||
'terminal.apps.TerminalConfig',
|
'terminal.apps.TerminalConfig',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from ansible.plugins.callback import CallbackBase
|
from ansible.plugins.callback import CallbackBase
|
||||||
from ansible.plugins.callback.default import CallbackModule
|
from ansible.plugins.callback.default import CallbackModule
|
||||||
|
|
||||||
|
@ -21,9 +19,8 @@ class AdHocResultCallback(CallbackModule):
|
||||||
# "contacted": {"hostname",...},
|
# "contacted": {"hostname",...},
|
||||||
# "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
|
# "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
|
||||||
# }
|
# }
|
||||||
self.results_raw = dict(ok=defaultdict(dict), failed=defaultdict(dict),
|
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
|
||||||
unreachable=defaultdict(dict), skipped=defaultdict(dict))
|
self.results_summary = dict(contacted=[], dark={})
|
||||||
self.results_summary = dict(contacted=[], dark=defaultdict(dict))
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def gather_result(self, t, res):
|
def gather_result(self, t, res):
|
||||||
|
@ -34,8 +31,8 @@ class AdHocResultCallback(CallbackModule):
|
||||||
|
|
||||||
if self.results_raw[t].get(host):
|
if self.results_raw[t].get(host):
|
||||||
self.results_raw[t][host][task_name] = task_result
|
self.results_raw[t][host][task_name] = task_result
|
||||||
# else:
|
else:
|
||||||
# self.results_raw[t][host] = {task_name: task_result}
|
self.results_raw[t][host] = {task_name: task_result}
|
||||||
self.clean_result(t, host, task_name, task_result)
|
self.clean_result(t, host, task_name, task_result)
|
||||||
|
|
||||||
def clean_result(self, t, host, task_name, task_result):
|
def clean_result(self, t, host, task_name, task_result):
|
||||||
|
@ -45,10 +42,10 @@ class AdHocResultCallback(CallbackModule):
|
||||||
if host not in contacted:
|
if host not in contacted:
|
||||||
contacted.append(host)
|
contacted.append(host)
|
||||||
else:
|
else:
|
||||||
# if dark.get(host):
|
if dark.get(host):
|
||||||
dark[host][task_name] = task_result.values
|
dark[host][task_name] = task_result.values
|
||||||
# else:
|
else:
|
||||||
# dark[host] = {task_name: task_result}
|
dark[host] = {task_name: task_result}
|
||||||
if host in contacted:
|
if host in contacted:
|
||||||
contacted.remove(host)
|
contacted.remove(host)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class BaseHost(Host):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
self.host_data = host_data
|
self.host_data = host_data
|
||||||
|
print(host_data)
|
||||||
hostname = host_data.get('hostname') or host_data.get('ip')
|
hostname = host_data.get('hostname') or host_data.get('ip')
|
||||||
port = host_data.get('port') or 22
|
port = host_data.get('port') or 22
|
||||||
super().__init__(hostname, port)
|
super().__init__(hostname, port)
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-btn">
|
<div class="input-group-btn">
|
||||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||||
搜索
|
{% trans "Search" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
{% block table_head %}
|
{% block table_head %}
|
||||||
<th class="text-center"></th>
|
<th class="text-center"></th>
|
||||||
<th class="text-center">{% trans 'Name' %}</th>
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
<th class="text-center">{% trans 'F/S/T' %}</th>
|
<th class="text-center">{% trans 'Run times' %}</th>
|
||||||
<th class="text-center">{% trans 'Versions' %}</th>
|
<th class="text-center">{% trans 'Versions' %}</th>
|
||||||
<th class="text-center">{% trans 'Hosts' %}</th>
|
<th class="text-center">{% trans 'Hosts' %}</th>
|
||||||
<th class="text-center">{% trans 'Success' %}</th>
|
<th class="text-center">{% trans 'Success' %}</th>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
import re
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none, get_short_uuid_str
|
from common.utils import get_logger, get_object_or_none, get_short_uuid_str
|
||||||
from .ansible import AdHocRunner, CommandResultCallback
|
from .ansible import AdHocRunner, CommandResultCallback
|
||||||
|
@ -58,6 +58,7 @@ def get_inventory(hostname_list, run_as_admin=False, run_as=None, become_info=No
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@record_adhoc
|
||||||
def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info=None):
|
def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info=None):
|
||||||
inventory = get_inventory(
|
inventory = get_inventory(
|
||||||
hostname_list, run_as_admin=run_as_admin,
|
hostname_list, run_as_admin=run_as_admin,
|
||||||
|
@ -67,7 +68,6 @@ def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info
|
||||||
return runner
|
return runner
|
||||||
|
|
||||||
|
|
||||||
@record_adhoc
|
|
||||||
def run_adhoc_object(adhoc, **options):
|
def run_adhoc_object(adhoc, **options):
|
||||||
"""
|
"""
|
||||||
:param adhoc: Instance of AdHoc
|
:param adhoc: Instance of AdHoc
|
||||||
|
@ -109,6 +109,8 @@ def create_or_update_task(
|
||||||
run_as_admin=False, run_as="", become_info=None,
|
run_as_admin=False, run_as="", become_info=None,
|
||||||
created_by=None
|
created_by=None
|
||||||
):
|
):
|
||||||
|
print(options)
|
||||||
|
print(task_name)
|
||||||
task = get_object_or_none(Task, name=task_name)
|
task = get_object_or_none(Task, name=task_name)
|
||||||
if task is None:
|
if task is None:
|
||||||
task = Task(name=task_name, created_by=created_by)
|
task = Task(name=task_name, created_by=created_by)
|
||||||
|
@ -125,6 +127,7 @@ def create_or_update_task(
|
||||||
if not adhoc or adhoc != new_adhoc:
|
if not adhoc or adhoc != new_adhoc:
|
||||||
new_adhoc.save()
|
new_adhoc.save()
|
||||||
task.latest_adhoc = new_adhoc
|
task.latest_adhoc = new_adhoc
|
||||||
|
print("Return task")
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,3 +38,34 @@ class AssetPermissionForm(forms.ModelForm):
|
||||||
'user_groups': _('User or user group at least one required'),
|
'user_groups': _('User or user group at least one required'),
|
||||||
'asset_groups': _('Asset or Asset group at least one required'),
|
'asset_groups': _('Asset or Asset group at least one required'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def clean_system_users(self):
|
||||||
|
from assets.utils import check_assets_have_system_user
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
assets = self.cleaned_data['assets']
|
||||||
|
asset_groups = self.cleaned_data['asset_groups']
|
||||||
|
system_users = self.cleaned_data['system_users']
|
||||||
|
|
||||||
|
error_data = check_assets_have_system_user(assets, system_users)
|
||||||
|
if error_data:
|
||||||
|
for asset, system_users in error_data.items():
|
||||||
|
msg = _("Asset {} not have [{}] system users, please check \n")
|
||||||
|
error = forms.ValidationError(msg.format(
|
||||||
|
asset.hostname,
|
||||||
|
", ".join(system_user.name for system_user in system_users)
|
||||||
|
))
|
||||||
|
errors.append(error)
|
||||||
|
|
||||||
|
for group in asset_groups:
|
||||||
|
msg = _("Asset {}: {} not have [{}] system users, please check")
|
||||||
|
assets = group.assets.all()
|
||||||
|
error_data = check_assets_have_system_user(assets, system_users)
|
||||||
|
for asset, system_users in error_data.items():
|
||||||
|
errors.append(msg.format(
|
||||||
|
group.name, asset.hostname,
|
||||||
|
", ".join(system_user.name for system_user in system_users)
|
||||||
|
))
|
||||||
|
if errors:
|
||||||
|
raise forms.ValidationError(errors)
|
||||||
|
return self.cleaned_data['system_users']
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group date">
|
<div class="input-group date">
|
||||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
<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' }}">
|
<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'|default:form.date_expired.value }}">
|
||||||
</div>
|
</div>
|
||||||
<span class="help-block ">{{ form.date_expired.errors }}</span>
|
<span class="help-block ">{{ form.date_expired.errors }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,12 @@
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block help_message %}
|
||||||
|
<div class="alert alert-info help-message">
|
||||||
|
提前规划好集群中的系统用户,授权时选择的资产(组内资产)必须存在该系统用户,否则可能无法成功登录
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<div class="uc pull-left m-r-5">
|
<div class="uc pull-left m-r-5">
|
||||||
<a href="{% url 'perms:asset-permission-create' %}" class="btn btn-sm btn-primary ">
|
<a href="{% url 'perms:asset-permission-create' %}" class="btn btn-sm btn-primary ">
|
||||||
|
|
|
@ -66,9 +66,7 @@ class MessageMixin:
|
||||||
return success_message
|
return success_message
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionCreateView(AdminUserRequiredMixin,
|
class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||||
MessageMixin,
|
|
||||||
CreateView):
|
|
||||||
model = AssetPermission
|
model = AssetPermission
|
||||||
form_class = AssetPermissionForm
|
form_class = AssetPermissionForm
|
||||||
template_name = 'perms/asset_permission_create_update.html'
|
template_name = 'perms/asset_permission_create_update.html'
|
||||||
|
@ -83,8 +81,19 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def get_success_message(self, cleaned_data):
|
||||||
|
url = reverse_lazy(
|
||||||
|
'perms:asset-permission-detail',
|
||||||
|
kwargs={'pk': self.object.pk}
|
||||||
|
)
|
||||||
|
success_message = _(
|
||||||
|
'Create asset permission <a href="{url}"> {name} </a> '
|
||||||
|
'success.'.format(url=url, name=self.object.name)
|
||||||
|
)
|
||||||
|
return success_message
|
||||||
|
|
||||||
class AssetPermissionUpdateView(AdminUserRequiredMixin, MessageMixin, UpdateView):
|
|
||||||
|
class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
model = AssetPermission
|
model = AssetPermission
|
||||||
form_class = AssetPermissionForm
|
form_class = AssetPermissionForm
|
||||||
template_name = 'perms/asset_permission_create_update.html'
|
template_name = 'perms/asset_permission_create_update.html'
|
||||||
|
@ -98,6 +107,17 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, MessageMixin, UpdateView
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def get_success_message(self, cleaned_data):
|
||||||
|
url = reverse_lazy(
|
||||||
|
'perms:asset-permission-detail',
|
||||||
|
kwargs={'pk': self.object.pk}
|
||||||
|
)
|
||||||
|
success_message = _(
|
||||||
|
'Update asset permission <a href="{url}"> {name} </a> '
|
||||||
|
'success.'.format(url=url, name=self.object.name)
|
||||||
|
)
|
||||||
|
return success_message
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
|
class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
template_name = 'perms/asset_permission_detail.html'
|
template_name = 'perms/asset_permission_detail.html'
|
||||||
|
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
|
@ -156,8 +156,8 @@ function activeNav() {
|
||||||
function APIUpdateAttr(props) {
|
function APIUpdateAttr(props) {
|
||||||
// props = {url: .., body: , success: , error: , method: ,}
|
// props = {url: .., body: , success: , error: , method: ,}
|
||||||
props = props || {};
|
props = props || {};
|
||||||
var success_message = props.success_message || 'Update successfully!';
|
var success_message = props.success_message || '更新成功!';
|
||||||
var fail_message = props.fail_message || 'Error occurred while updating.';
|
var fail_message = props.fail_message || '更新时发生未知错误.';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: props.url,
|
url: props.url,
|
||||||
type: props.method || "PATCH",
|
type: props.method || "PATCH",
|
||||||
|
@ -183,7 +183,7 @@ function objectDelete(obj, name, url, redirectTo) {
|
||||||
function doDelete() {
|
function doDelete() {
|
||||||
var body = {};
|
var body = {};
|
||||||
var success = function() {
|
var success = function() {
|
||||||
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
// swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||||
if (!redirectTo) {
|
if (!redirectTo) {
|
||||||
$(obj).parent().parent().remove();
|
$(obj).parent().parent().remove();
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,7 +191,7 @@ function objectDelete(obj, name, url, redirectTo) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var fail = function() {
|
var fail = function() {
|
||||||
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
|
swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: url,
|
url: url,
|
||||||
|
@ -202,14 +202,14 @@ function objectDelete(obj, name, url, redirectTo) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
swal({
|
swal({
|
||||||
title: 'Are you sure delete ?',
|
title: '你确定删除吗 ?',
|
||||||
text: " [" + name + "] ",
|
text: " [" + name + "] ",
|
||||||
type: "warning",
|
type: "warning",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
cancelButtonText: 'Cancel',
|
cancelButtonText: '取消',
|
||||||
confirmButtonColor: "#DD6B55",
|
confirmButtonColor: "#ed5565",
|
||||||
confirmButtonText: 'Confirm',
|
confirmButtonText: '确认',
|
||||||
closeOnConfirm: false
|
closeOnConfirm: true,
|
||||||
}, function () {
|
}, function () {
|
||||||
doDelete()
|
doDelete()
|
||||||
});
|
});
|
||||||
|
@ -334,3 +334,9 @@ String.prototype.format = function(args) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setCookie(key, value) {
|
||||||
|
var expires = new Date();
|
||||||
|
expires.setTime(expires.getTime() + (24 * 60 * 60 * 1000));
|
||||||
|
document.cookie = key + '=' + value + ';expires=' + expires.toUTCString();
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="sidebar-collapse">
|
<div class="sidebar-collapse">
|
||||||
<ul class="nav" id="side-menu">
|
<ul class="nav" id="side-menu">
|
||||||
{% include '_user_profile.html' %}
|
{% include '_user_profile.html' %}
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser and request.COOKIES.admin == "Yes" %}
|
||||||
{% include '_nav.html' %}
|
{% include '_nav.html' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include '_nav_user.html' %}
|
{% include '_nav_user.html' %}
|
||||||
|
|
|
@ -34,16 +34,14 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li id="ops">
|
<li id="ops">
|
||||||
<a>
|
<a>
|
||||||
<i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
|
<i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="nav nav-second-level">
|
<ul class="nav nav-second-level">
|
||||||
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Tasks' %}</a></li>
|
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li id="terminal">
|
<li id="terminal">
|
||||||
<a>
|
<a>
|
||||||
<i class="fa fa-rocket"></i> <span class="nav-label">{% trans 'Terminal' %}</span><span class="fa arrow"></span>
|
<i class="fa fa-rocket"></i> <span class="nav-label">{% trans 'Terminal' %}</span><span class="fa arrow"></span>
|
||||||
|
@ -65,11 +63,11 @@
|
||||||
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
|
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
|
||||||
{# </ul>#}
|
{# </ul>#}
|
||||||
{#</li>#}
|
{#</li>#}
|
||||||
<li id="">
|
{#<li id="">#}
|
||||||
<a href="">
|
{# <a href="">#}
|
||||||
<i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
|
{# <i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>#}
|
||||||
</a>
|
{# </a>#}
|
||||||
</li>
|
{#</li>#}
|
||||||
<li class="special_link">
|
<li class="special_link">
|
||||||
<a href="http://www.jumpserver.org" target="_blank"><i class="fa fa-database"></i>
|
<a href="http://www.jumpserver.org" target="_blank"><i class="fa fa-database"></i>
|
||||||
<span class="nav-label">{% trans 'Visit us' %}</span>
|
<span class="nav-label">{% trans 'Visit us' %}</span>
|
||||||
|
|
|
@ -19,6 +19,13 @@
|
||||||
<li><a href="{% url 'users:user-profile' %}">{% trans 'Profile' %}</a></li>
|
<li><a href="{% url 'users:user-profile' %}">{% trans 'Profile' %}</a></li>
|
||||||
<li><a href="{% url 'users:user-profile-update' %}">{% trans 'Profile settings' %}</a></li>
|
<li><a href="{% url 'users:user-profile-update' %}">{% trans 'Profile settings' %}</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
{% if request.COOKIES.admin == 'No' %}
|
||||||
|
<li><a id="switch_admin">{% trans 'Admin page' %}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a id="switch_user">{% trans 'User page' %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<li><a href="{% url 'users:logout' %}">{% trans 'Logout' %}</a></li>
|
<li><a href="{% url 'users:logout' %}">{% trans 'Logout' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,3 +33,15 @@
|
||||||
JMS
|
JMS
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
})
|
||||||
|
.on('click', '#switch_admin', function () {
|
||||||
|
setCookie("admin", "Yes");
|
||||||
|
window.location = "/"
|
||||||
|
})
|
||||||
|
.on('click', '#switch_user', function () {
|
||||||
|
setCookie("admin", "No");
|
||||||
|
window.location = "/"
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import time
|
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.core.cache import cache
|
|
||||||
from django.db.utils import ProgrammingError, OperationalError
|
|
||||||
|
|
||||||
from .models import Session
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CACHE_REFRESH_INTERVAL = 10
|
CACHE_REFRESH_INTERVAL = 10
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_left_head %}
|
{% block content_left_head %}
|
||||||
123
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
|
@ -51,12 +50,12 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control input-sm" name="command" placeholder="Command" value="{{ command }}">
|
<input type="text" class="form-control input-sm" name="command" placeholder="{% trans 'Command' %}" value="{{ command }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-btn">
|
<div class="input-group-btn">
|
||||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||||
搜索
|
{% trans 'Search' %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -78,14 +78,8 @@ function initTable() {
|
||||||
var reject_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="{{ DEFAULT_PK }}" data-name="99991938">{% trans "Reject" %}</a>'
|
var reject_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="{{ DEFAULT_PK }}" data-name="99991938">{% trans "Reject" %}</a>'
|
||||||
.replace('{{ DEFAULT_PK }}', cellData)
|
.replace('{{ DEFAULT_PK }}', cellData)
|
||||||
.replace('99991938', rowData.name);
|
.replace('99991938', rowData.name);
|
||||||
var connect_btn = '<a href="" class="btn btn-xs btn-warning btn-connect" >{% trans "Connect" %}</a> '
|
|
||||||
.replace('{{ DEFAULT_PK }}', cellData);
|
|
||||||
if (rowData.is_accepted) {
|
if (rowData.is_accepted) {
|
||||||
{% if user.is_superuser %}
|
$(td).html(update_btn + delete_btn);
|
||||||
$(td).html(connect_btn + update_btn + delete_btn);
|
|
||||||
{% else %}
|
|
||||||
$(td).html(connect_btn);
|
|
||||||
{% endif %}
|
|
||||||
} else {
|
} else {
|
||||||
{% if user.is_superuser %}
|
{% if user.is_superuser %}
|
||||||
$(td).html(accept_btn + reject_btn);
|
$(td).html(accept_btn + reject_btn);
|
||||||
|
|
|
@ -67,15 +67,23 @@ class UserProfileForm(forms.ModelForm):
|
||||||
|
|
||||||
class UserPasswordForm(forms.Form):
|
class UserPasswordForm(forms.Form):
|
||||||
old_password = forms.CharField(
|
old_password = forms.CharField(
|
||||||
max_length=128, widget=forms.PasswordInput)
|
max_length=128, widget=forms.PasswordInput,
|
||||||
|
label=_("Old password")
|
||||||
|
)
|
||||||
new_password = forms.CharField(
|
new_password = forms.CharField(
|
||||||
min_length=5, max_length=128, widget=forms.PasswordInput)
|
min_length=5, max_length=128,
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
label=_("New password")
|
||||||
|
)
|
||||||
confirm_password = forms.CharField(
|
confirm_password = forms.CharField(
|
||||||
min_length=5, max_length=128, widget=forms.PasswordInput)
|
min_length=5, max_length=128,
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
label=_("Confirm password")
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.instance = kwargs.pop('instance')
|
self.instance = kwargs.pop('instance')
|
||||||
super(UserPasswordForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def clean_old_password(self):
|
def clean_old_password(self):
|
||||||
old_password = self.cleaned_data['old_password']
|
old_password = self.cleaned_data['old_password']
|
||||||
|
@ -102,20 +110,21 @@ class UserPublicKeyForm(forms.Form):
|
||||||
public_key = forms.CharField(
|
public_key = forms.CharField(
|
||||||
label=_('ssh public key'), max_length=5000,
|
label=_('ssh public key'), max_length=5000,
|
||||||
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
|
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
|
||||||
help_text=_('Paste your id_rsa.pub here.'))
|
help_text=_('Paste your id_rsa.pub here.')
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'instance' in kwargs:
|
if 'instance' in kwargs:
|
||||||
self.instance = kwargs.pop('instance')
|
self.instance = kwargs.pop('instance')
|
||||||
else:
|
else:
|
||||||
self.instance = None
|
self.instance = None
|
||||||
super(UserPublicKeyForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def clean_public_key(self):
|
def clean_public_key(self):
|
||||||
public_key = self.cleaned_data['public_key']
|
public_key = self.cleaned_data['public_key']
|
||||||
if self.instance.public_key and public_key == self.instance.public_key:
|
if self.instance.public_key and public_key == self.instance.public_key:
|
||||||
raise forms.ValidationError(_('Public key should not be the '
|
msg = _('Public key should not be the same as your old one.')
|
||||||
'same as your old one.'))
|
raise forms.ValidationError(msg)
|
||||||
|
|
||||||
if not validate_ssh_public_key(public_key):
|
if not validate_ssh_public_key(public_key):
|
||||||
raise forms.ValidationError(_('Not a valid ssh public key'))
|
raise forms.ValidationError(_('Not a valid ssh public key'))
|
||||||
|
@ -129,11 +138,11 @@ class UserPublicKeyForm(forms.Form):
|
||||||
|
|
||||||
|
|
||||||
class UserBulkUpdateForm(forms.ModelForm):
|
class UserBulkUpdateForm(forms.ModelForm):
|
||||||
users = forms.MultipleChoiceField(
|
users = forms.ModelMultipleChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
help_text='* required',
|
help_text='* required',
|
||||||
label=_('Select users'),
|
label=_('Select users'),
|
||||||
choices=[(user.id, user.name) for user in User.objects.all()],
|
queryset=User.objects.all(),
|
||||||
widget=forms.SelectMultiple(
|
widget=forms.SelectMultiple(
|
||||||
attrs={
|
attrs={
|
||||||
'class': 'select2',
|
'class': 'select2',
|
||||||
|
@ -162,9 +171,9 @@ class UserBulkUpdateForm(forms.ModelForm):
|
||||||
|
|
||||||
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
||||||
if k in changed_fields}
|
if k in changed_fields}
|
||||||
users_id = cleaned_data.pop('users', '')
|
users = cleaned_data.pop('users', '')
|
||||||
groups = cleaned_data.pop('groups', [])
|
groups = cleaned_data.pop('groups', [])
|
||||||
users = User.objects.filter(id__in=users_id)
|
users = User.objects.filter(id__in=[user.id for user in users])
|
||||||
users.update(**cleaned_data)
|
users.update(**cleaned_data)
|
||||||
if groups:
|
if groups:
|
||||||
for user in users:
|
for user in users:
|
||||||
|
|
|
@ -146,7 +146,7 @@ class User(AbstractUser):
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = self.username
|
self.name = self.username
|
||||||
|
|
||||||
super(User, self).save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
# Add the current user to the default group.
|
# Add the current user to the default group.
|
||||||
if not self.groups.count():
|
if not self.groups.count():
|
||||||
group = UserGroup.initial()
|
group = UserGroup.initial()
|
||||||
|
@ -180,13 +180,14 @@ class User(AbstractUser):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def avatar_url(self):
|
def avatar_url(self):
|
||||||
|
admin_default = settings.STATIC_URL + "img/avatar/admin.png"
|
||||||
|
user_default = settings.STATIC_URL + "img/avatar/user.png"
|
||||||
if self.avatar:
|
if self.avatar:
|
||||||
return self.avatar.url
|
return self.avatar.url
|
||||||
|
if self.is_superuser:
|
||||||
|
return admin_default
|
||||||
else:
|
else:
|
||||||
avatar_dir = os.path.join(settings.MEDIA_ROOT, 'avatar')
|
return user_default
|
||||||
if os.path.isdir(avatar_dir):
|
|
||||||
return os.path.join(settings.MEDIA_URL, 'avatar', 'default.png')
|
|
||||||
return 'https://www.gravatar.com/avatar/c6812ab450230979465d7bf288eadce2a?s=120&d=identicon'
|
|
||||||
|
|
||||||
def generate_reset_token(self):
|
def generate_reset_token(self):
|
||||||
return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600)
|
return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600)
|
||||||
|
|
|
@ -31,12 +31,12 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
<input type="text" class="form-control input-sm" name="keyword" placeholder="{% trans "Search" %}" value="{{ keyword }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-btn">
|
<div class="input-group-btn">
|
||||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||||
搜索
|
{% trans "Search" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,7 +85,9 @@
|
||||||
forceParse: false,
|
forceParse: false,
|
||||||
autoclose: true
|
autoclose: true
|
||||||
});
|
});
|
||||||
$('.select2').select2();
|
$('.select2').select2({
|
||||||
|
dropdownAutoWidth: true
|
||||||
|
});
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -73,7 +73,7 @@ function initTable() {
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||||
var update_btn = '<a href="{% url "users:user-update" pk='00000000-0000-0000-0000-000000000000' %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
|
var update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
|
||||||
|
|
||||||
var del_btn = "";
|
var del_btn = "";
|
||||||
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ user.username }}") {
|
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ user.username }}") {
|
||||||
|
|
Loading…
Reference in New Issue