mirror of https://github.com/jumpserver/jumpserver
[Bugfix] 修复一些明显的bug
parent
5a92972120
commit
76df1de634
|
@ -44,9 +44,11 @@ class AssetViewSet(IDInFilterMixin, BulkModelViewSet):
|
|||
else:
|
||||
assets_granted = get_user_granted_assets(self.request.user)
|
||||
queryset = self.queryset.filter(id__in=[asset.id for asset in assets_granted])
|
||||
|
||||
cluster_id = self.request.query_params.get('cluster_id')
|
||||
asset_group_id = self.request.query_params.get('asset_group_id')
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
|
||||
if cluster_id:
|
||||
queryset = queryset.filter(cluster__id=cluster_id)
|
||||
if asset_group_id:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# coding:utf-8
|
||||
import uuid
|
||||
from django import forms
|
||||
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__)
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
|
||||
|
@ -57,11 +55,11 @@ class AssetUpdateForm(forms.ModelForm):
|
|||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
assets = forms.MultipleChoiceField(
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
required=True,
|
||||
help_text='* required',
|
||||
label=_('Select assets'),
|
||||
choices=[(asset.id, asset.hostname) for asset in Asset.objects.all()],
|
||||
queryset=Asset.objects.all(),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
|
@ -94,10 +92,9 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
|||
|
||||
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
||||
if k in changed_fields}
|
||||
print(cleaned_data)
|
||||
assets_id = cleaned_data.pop('assets')
|
||||
assets = cleaned_data.pop('assets')
|
||||
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)
|
||||
if groups:
|
||||
for asset in assets:
|
||||
|
@ -175,16 +172,18 @@ class AdminUserForm(forms.ModelForm):
|
|||
password = None
|
||||
|
||||
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)
|
||||
return admin_user
|
||||
|
||||
def clean_private_key_file(self):
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
password = self.cleaned_data['password']
|
||||
|
||||
if private_key_file:
|
||||
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'))
|
||||
return private_key
|
||||
return private_key_file
|
||||
|
|
|
@ -18,10 +18,6 @@ __all__ = ['Asset']
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_default_cluster():
|
||||
return Cluster.initial()
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
# Todo: Move them to settings
|
||||
STATUS_CHOICES = (
|
||||
|
@ -48,7 +44,7 @@ class Asset(models.Model):
|
|||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
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'))
|
||||
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'),)
|
||||
|
|
|
@ -10,7 +10,6 @@ from django.db import models
|
|||
import logging
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .user import SystemUser
|
||||
|
||||
__all__ = ['AssetGroup']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
@ -66,16 +66,15 @@ class AssetUser(models.Model):
|
|||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key:
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
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)
|
||||
if not os.path.exists(key_path):
|
||||
with open(key_path, 'w') as f:
|
||||
f.write(key_str)
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
|
@ -105,7 +104,6 @@ class AssetUser(models.Model):
|
|||
update_fields.append('_public_key')
|
||||
|
||||
if update_fields:
|
||||
print(update_fields)
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def auto_gen_auth(self):
|
||||
|
@ -149,7 +147,11 @@ class AdminUser(AssetUser):
|
|||
|
||||
@property
|
||||
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
|
||||
def become_pass(self, password):
|
||||
|
@ -199,7 +201,7 @@ class SystemUser(AssetUser):
|
|||
('K', 'Public key'),
|
||||
)
|
||||
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'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
{
|
||||
'update_assets_hardware_period': {
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Public IP' %}:</td>
|
||||
<td><b>{{ asset.public_ip }}</b></td>
|
||||
<td><b>{{ asset.public_ip|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Port' %}:</td>
|
||||
|
@ -74,12 +74,12 @@
|
|||
{% if asset.admin_user_avail %}
|
||||
<td><b>{{ asset.admin_user_avail.name }}</b></td>
|
||||
{% else %}
|
||||
<td><b>None</b></td>
|
||||
<td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<td>{% trans 'Cluster' %}:</td>
|
||||
|
@ -87,39 +87,39 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Cabinet number' %}:</td>
|
||||
<td><b>{{ asset.cabinet_no }}</b></td>
|
||||
<td><b>{{ asset.cabinet_no|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Cabinet position' %}:</td>
|
||||
<td><b>{{ asset.cabinet_pos }}</b></td>
|
||||
<td><b>{{ asset.cabinet_pos|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Vendor' %}:</td>
|
||||
<td><b>{{ asset.vendor }}</b></td>
|
||||
<td><b>{{ asset.vendor|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Model' %}:</td>
|
||||
<td><b>{{ asset.model }}</b></td>
|
||||
<td><b>{{ asset.model|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<td>{% trans 'Memory' %}:</td>
|
||||
<td><b>{{ asset.memory }}</b></td>
|
||||
<td><b>{{ asset.memory|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Disk' %}:</td>
|
||||
<td><b>{{ asset.disk_total }}</b></td>
|
||||
<td><b>{{ asset.disk_total|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Platform' %}:</td>
|
||||
<td><b>{{ asset.platform }}</b></td>
|
||||
<td><b>{{ asset.platform|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<td>{% trans 'Asset status' %}:</td>
|
||||
|
@ -127,7 +127,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Is active' %}:</td>
|
||||
<td><b>{{ asset.is_active }}</b></td>
|
||||
<td><b>{{ asset.is_active|yesno:"Yes,No" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset type' %}:</td>
|
||||
|
@ -139,11 +139,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Serial number' %}:</td>
|
||||
<td><b>{{ asset.sn }}</b></td>
|
||||
<td><b>{{ asset.sn|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset number' %}:</td>
|
||||
<td><b>{{ asset.number }}</b></td>
|
||||
<td><b>{{ asset.number|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
|
|
|
@ -64,8 +64,7 @@
|
|||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
columnDefs: [
|
||||
|
@ -103,48 +102,51 @@ $(document).ready(function(){
|
|||
{data: "is_active" }, {data: "is_connective"}, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
var table = jumpserver.initDataTable(options);
|
||||
return 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');
|
||||
}
|
||||
})
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '#btn_export', function () {
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var rows = $data_table.rows('.selected').data();
|
||||
var assets = [];
|
||||
$.each(rows, function (index, obj) {
|
||||
assets.push(obj.id)
|
||||
});
|
||||
$('#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();
|
||||
}
|
||||
$.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');
|
||||
}
|
||||
$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 () {
|
||||
var $this = $(this);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
系统用户是 用户登录资产(服务器)时使用的用户,如 web, sa, dba等具有特殊功能的用户。系统用户创建时,如果选择了自动推送
|
||||
Jumpserver会使用ansible自动推送到系统用户所在集群的资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -5,26 +5,22 @@
|
|||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
|
||||
<style>
|
||||
.custom{
|
||||
margin-right:5px;
|
||||
}
|
||||
#modal .modal-body { max-height: 200px; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content_left_head %}{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
{# <div class="html5buttons">#}
|
||||
{# <div class="dt-buttons btn-group">#}
|
||||
{# <a class="btn btn-default btn_export" tabindex="0">#}
|
||||
{# <span>{% trans "Export" %}</span>#}
|
||||
{# </a>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
<div class="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_container %}
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
||||
<thead>
|
||||
|
@ -36,8 +32,8 @@
|
|||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Env' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Valid' %}</th>
|
||||
<th class="text-center">{% trans 'Alive' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Connective' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -48,216 +44,45 @@
|
|||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<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 () {
|
||||
var $this = $(this);
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:asset-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (plain_id_list.length == 0) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
||||
function doDeactive() {
|
||||
var body = $.each(id_list, function(index, asset_object) {
|
||||
asset_object['is_active'] = false;
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
}
|
||||
function doActive() {
|
||||
var body = $.each(id_list, function(index, asset_object) {
|
||||
asset_object['is_active'] = true;
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
}
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
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;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||
console.log('{{ the_url }}');
|
||||
var detail_btn = '<a href="{{ the_url }}">' + 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 == 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else 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_info"},
|
||||
{data: "is_active" }, {data: "is_connective"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
console.log(JSON.stringify(post_list));
|
||||
console.log(the_url);
|
||||
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
|
||||
$('#asset_bulk_update_modal').modal('hide');
|
||||
return jumpserver.initDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
from collections import defaultdict
|
||||
from common.utils import get_object_or_none
|
||||
from .models import Asset, SystemUser
|
||||
|
||||
|
@ -17,3 +18,12 @@ def get_system_user_by_name(name):
|
|||
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
|
||||
|
||||
# Create your models here.
|
||||
|
|
|
@ -195,11 +195,11 @@ def ssh_key_string_to_obj(text, password=None):
|
|||
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):
|
||||
private_key = private_key.decode("utf-8")
|
||||
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)):
|
||||
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.')
|
||||
|
||||
|
||||
def validate_ssh_private_key(text):
|
||||
def validate_ssh_private_key(text, password=None):
|
||||
if isinstance(text, bytes):
|
||||
try:
|
||||
text = text.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
|
||||
key = ssh_key_string_to_obj(text)
|
||||
key = ssh_key_string_to_obj(text, password=password)
|
||||
if key is None:
|
||||
return False
|
||||
else:
|
||||
|
|
|
@ -59,7 +59,6 @@ INSTALLED_APPS = [
|
|||
'assets.apps.AssetsConfig',
|
||||
'perms.apps.PermsConfig',
|
||||
'ops.apps.OpsConfig',
|
||||
# 'audits.apps.AuditsConfig',
|
||||
'common.apps.CommonConfig',
|
||||
'terminal.apps.TerminalConfig',
|
||||
'rest_framework',
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.plugins.callback.default import CallbackModule
|
||||
|
||||
|
@ -21,9 +19,8 @@ class AdHocResultCallback(CallbackModule):
|
|||
# "contacted": {"hostname",...},
|
||||
# "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
|
||||
# }
|
||||
self.results_raw = dict(ok=defaultdict(dict), failed=defaultdict(dict),
|
||||
unreachable=defaultdict(dict), skipped=defaultdict(dict))
|
||||
self.results_summary = dict(contacted=[], dark=defaultdict(dict))
|
||||
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
|
||||
self.results_summary = dict(contacted=[], dark={})
|
||||
super().__init__()
|
||||
|
||||
def gather_result(self, t, res):
|
||||
|
@ -34,8 +31,8 @@ class AdHocResultCallback(CallbackModule):
|
|||
|
||||
if self.results_raw[t].get(host):
|
||||
self.results_raw[t][host][task_name] = task_result
|
||||
# else:
|
||||
# self.results_raw[t][host] = {task_name: task_result}
|
||||
else:
|
||||
self.results_raw[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):
|
||||
|
@ -45,10 +42,10 @@ class AdHocResultCallback(CallbackModule):
|
|||
if host not in contacted:
|
||||
contacted.append(host)
|
||||
else:
|
||||
# if dark.get(host):
|
||||
dark[host][task_name] = task_result.values
|
||||
# else:
|
||||
# dark[host] = {task_name: task_result}
|
||||
if dark.get(host):
|
||||
dark[host][task_name] = task_result.values
|
||||
else:
|
||||
dark[host] = {task_name: task_result}
|
||||
if host in contacted:
|
||||
contacted.remove(host)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ class BaseHost(Host):
|
|||
}
|
||||
"""
|
||||
self.host_data = host_data
|
||||
print(host_data)
|
||||
hostname = host_data.get('hostname') or host_data.get('ip')
|
||||
port = host_data.get('port') or 22
|
||||
super().__init__(hostname, port)
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
{% trans "Search" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -37,7 +37,7 @@
|
|||
{% block table_head %}
|
||||
<th class="text-center"></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 'Hosts' %}</th>
|
||||
<th class="text-center">{% trans 'Success' %}</th>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
import re
|
||||
|
||||
import time
|
||||
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 .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):
|
||||
inventory = get_inventory(
|
||||
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
|
||||
|
||||
|
||||
@record_adhoc
|
||||
def run_adhoc_object(adhoc, **options):
|
||||
"""
|
||||
:param adhoc: Instance of AdHoc
|
||||
|
@ -109,6 +109,8 @@ def create_or_update_task(
|
|||
run_as_admin=False, run_as="", become_info=None,
|
||||
created_by=None
|
||||
):
|
||||
print(options)
|
||||
print(task_name)
|
||||
task = get_object_or_none(Task, name=task_name)
|
||||
if task is None:
|
||||
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:
|
||||
new_adhoc.save()
|
||||
task.latest_adhoc = new_adhoc
|
||||
print("Return task")
|
||||
return task
|
||||
|
||||
|
||||
|
|
|
@ -38,3 +38,34 @@ class AssetPermissionForm(forms.ModelForm):
|
|||
'user_groups': _('User or user 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="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' }}">
|
||||
<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>
|
||||
<span class="help-block ">{{ form.date_expired.errors }}</span>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
{% block table_search %}
|
||||
{% endblock %}
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
提前规划好集群中的系统用户,授权时选择的资产(组内资产)必须存在该系统用户,否则可能无法成功登录
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'perms:asset-permission-create' %}" class="btn btn-sm btn-primary ">
|
||||
|
|
|
@ -66,9 +66,7 @@ class MessageMixin:
|
|||
return success_message
|
||||
|
||||
|
||||
class AssetPermissionCreateView(AdminUserRequiredMixin,
|
||||
MessageMixin,
|
||||
CreateView):
|
||||
class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = AssetPermission
|
||||
form_class = AssetPermissionForm
|
||||
template_name = 'perms/asset_permission_create_update.html'
|
||||
|
@ -83,8 +81,19 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
|
|||
kwargs.update(context)
|
||||
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
|
||||
form_class = AssetPermissionForm
|
||||
template_name = 'perms/asset_permission_create_update.html'
|
||||
|
@ -98,6 +107,17 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, MessageMixin, UpdateView
|
|||
kwargs.update(context)
|
||||
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):
|
||||
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) {
|
||||
// props = {url: .., body: , success: , error: , method: ,}
|
||||
props = props || {};
|
||||
var success_message = props.success_message || 'Update successfully!';
|
||||
var fail_message = props.fail_message || 'Error occurred while updating.';
|
||||
var success_message = props.success_message || '更新成功!';
|
||||
var fail_message = props.fail_message || '更新时发生未知错误.';
|
||||
$.ajax({
|
||||
url: props.url,
|
||||
type: props.method || "PATCH",
|
||||
|
@ -183,7 +183,7 @@ function objectDelete(obj, name, url, redirectTo) {
|
|||
function doDelete() {
|
||||
var body = {};
|
||||
var success = function() {
|
||||
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
// swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
if (!redirectTo) {
|
||||
$(obj).parent().parent().remove();
|
||||
} else {
|
||||
|
@ -191,7 +191,7 @@ function objectDelete(obj, name, url, redirectTo) {
|
|||
}
|
||||
};
|
||||
var fail = function() {
|
||||
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
|
||||
swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
|
@ -202,14 +202,14 @@ function objectDelete(obj, name, url, redirectTo) {
|
|||
});
|
||||
}
|
||||
swal({
|
||||
title: 'Are you sure delete ?',
|
||||
title: '你确定删除吗 ?',
|
||||
text: " [" + name + "] ",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: 'Confirm',
|
||||
closeOnConfirm: false
|
||||
cancelButtonText: '取消',
|
||||
confirmButtonColor: "#ed5565",
|
||||
confirmButtonText: '确认',
|
||||
closeOnConfirm: true,
|
||||
}, function () {
|
||||
doDelete()
|
||||
});
|
||||
|
@ -334,3 +334,9 @@ String.prototype.format = function(args) {
|
|||
}
|
||||
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">
|
||||
<ul class="nav" id="side-menu">
|
||||
{% include '_user_profile.html' %}
|
||||
{% if request.user.is_superuser %}
|
||||
{% if request.user.is_superuser and request.COOKIES.admin == "Yes" %}
|
||||
{% include '_nav.html' %}
|
||||
{% else %}
|
||||
{% include '_nav_user.html' %}
|
||||
|
|
|
@ -34,16 +34,14 @@
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li id="ops">
|
||||
<a>
|
||||
<i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
|
||||
</a>
|
||||
<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>
|
||||
</li>
|
||||
|
||||
<li id="terminal">
|
||||
<a>
|
||||
<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>#}
|
||||
{# </ul>#}
|
||||
{#</li>#}
|
||||
<li id="">
|
||||
<a href="">
|
||||
<i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
|
||||
</a>
|
||||
</li>
|
||||
{#<li id="">#}
|
||||
{# <a href="">#}
|
||||
{# <i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>#}
|
||||
{# </a>#}
|
||||
{#</li>#}
|
||||
<li class="special_link">
|
||||
<a href="http://www.jumpserver.org" target="_blank"><i class="fa fa-database"></i>
|
||||
<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-update' %}">{% trans 'Profile settings' %}</a></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>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -26,3 +33,15 @@
|
|||
JMS
|
||||
</div>
|
||||
</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 -*-
|
||||
#
|
||||
import time
|
||||
|
||||
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
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content_left_head %}
|
||||
123
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
|
@ -51,12 +50,12 @@
|
|||
</select>
|
||||
</div>
|
||||
<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 class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
{% trans 'Search' %}
|
||||
</button>
|
||||
</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>'
|
||||
.replace('{{ DEFAULT_PK }}', cellData)
|
||||
.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 user.is_superuser %}
|
||||
$(td).html(connect_btn + update_btn + delete_btn);
|
||||
{% else %}
|
||||
$(td).html(connect_btn);
|
||||
{% endif %}
|
||||
$(td).html(update_btn + delete_btn);
|
||||
} else {
|
||||
{% if user.is_superuser %}
|
||||
$(td).html(accept_btn + reject_btn);
|
||||
|
|
|
@ -67,15 +67,23 @@ class UserProfileForm(forms.ModelForm):
|
|||
|
||||
class UserPasswordForm(forms.Form):
|
||||
old_password = forms.CharField(
|
||||
max_length=128, widget=forms.PasswordInput)
|
||||
max_length=128, widget=forms.PasswordInput,
|
||||
label=_("Old password")
|
||||
)
|
||||
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(
|
||||
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):
|
||||
self.instance = kwargs.pop('instance')
|
||||
super(UserPasswordForm, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_old_password(self):
|
||||
old_password = self.cleaned_data['old_password']
|
||||
|
@ -102,20 +110,21 @@ class UserPublicKeyForm(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_rsa.pub here.'))
|
||||
help_text=_('Paste your id_rsa.pub here.')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'instance' in kwargs:
|
||||
self.instance = kwargs.pop('instance')
|
||||
else:
|
||||
self.instance = None
|
||||
super(UserPublicKeyForm, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_public_key(self):
|
||||
public_key = self.cleaned_data['public_key']
|
||||
if self.instance.public_key and public_key == self.instance.public_key:
|
||||
raise forms.ValidationError(_('Public key should not be the '
|
||||
'same as your old one.'))
|
||||
msg = _('Public key should not be the same as your old one.')
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
if not validate_ssh_public_key(public_key):
|
||||
raise forms.ValidationError(_('Not a valid ssh public key'))
|
||||
|
@ -129,11 +138,11 @@ class UserPublicKeyForm(forms.Form):
|
|||
|
||||
|
||||
class UserBulkUpdateForm(forms.ModelForm):
|
||||
users = forms.MultipleChoiceField(
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
required=True,
|
||||
help_text='* required',
|
||||
label=_('Select users'),
|
||||
choices=[(user.id, user.name) for user in User.objects.all()],
|
||||
queryset=User.objects.all(),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
|
@ -162,9 +171,9 @@ class UserBulkUpdateForm(forms.ModelForm):
|
|||
|
||||
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
||||
if k in changed_fields}
|
||||
users_id = cleaned_data.pop('users', '')
|
||||
users = cleaned_data.pop('users', '')
|
||||
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)
|
||||
if groups:
|
||||
for user in users:
|
||||
|
|
|
@ -146,7 +146,7 @@ class User(AbstractUser):
|
|||
if not self.name:
|
||||
self.name = self.username
|
||||
|
||||
super(User, self).save(*args, **kwargs)
|
||||
super().save(*args, **kwargs)
|
||||
# Add the current user to the default group.
|
||||
if not self.groups.count():
|
||||
group = UserGroup.initial()
|
||||
|
@ -180,13 +180,14 @@ class User(AbstractUser):
|
|||
return False
|
||||
|
||||
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:
|
||||
return self.avatar.url
|
||||
if self.is_superuser:
|
||||
return admin_default
|
||||
else:
|
||||
avatar_dir = os.path.join(settings.MEDIA_ROOT, 'avatar')
|
||||
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'
|
||||
return user_default
|
||||
|
||||
def generate_reset_token(self):
|
||||
return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600)
|
||||
|
|
|
@ -31,12 +31,12 @@
|
|||
</select>
|
||||
</div>
|
||||
<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 class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
{% trans "Search" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -85,7 +85,9 @@
|
|||
forceParse: false,
|
||||
autoclose: true
|
||||
});
|
||||
$('.select2').select2();
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -73,7 +73,7 @@ function initTable() {
|
|||
}
|
||||
}},
|
||||
{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 = "";
|
||||
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ user.username }}") {
|
||||
|
|
Loading…
Reference in New Issue