Commit f450accb authored by BaiJiangjie's avatar BaiJiangjie

[Merge] with dev

parents 0bbfc743 48e87857
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
__version__ = "1.0.0" __version__ = "1.2.0"
...@@ -32,6 +32,7 @@ __all__ = [ ...@@ -32,6 +32,7 @@ __all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeViewSet', 'NodeChildrenApi',
'NodeAssetsApi', 'NodeWithAssetsApi', 'NodeAssetsApi', 'NodeWithAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi' 'TestNodeConnectiveApi'
] ]
...@@ -191,6 +192,19 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView): ...@@ -191,6 +192,19 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
instance.assets.remove(*tuple(assets)) instance.assets.remove(*tuple(assets))
class NodeReplaceAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
for asset in assets:
asset.nodes.set([instance])
class RefreshNodeHardwareInfoApi(APIView): class RefreshNodeHardwareInfoApi(APIView):
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
model = Node model = Node
......
...@@ -49,6 +49,7 @@ class Asset(models.Model): ...@@ -49,6 +49,7 @@ class Asset(models.Model):
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
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'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
...@@ -72,7 +73,6 @@ class Asset(models.Model): ...@@ -72,7 +73,6 @@ class Asset(models.Model):
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
...@@ -84,7 +84,7 @@ class Asset(models.Model): ...@@ -84,7 +84,7 @@ class Asset(models.Model):
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
def __str__(self): def __str__(self):
return self.hostname return '{0.hostname}({0.ip})'.format(self)
@property @property
def is_valid(self): def is_valid(self):
......
...@@ -19,7 +19,7 @@ class Node(models.Model): ...@@ -19,7 +19,7 @@ class Node(models.Model):
is_asset = False is_asset = False
def __str__(self): def __str__(self):
return self.value return self.full_value
@property @property
def name(self): def name(self):
...@@ -30,7 +30,7 @@ class Node(models.Model): ...@@ -30,7 +30,7 @@ class Node(models.Model):
if self == self.__class__.root(): if self == self.__class__.root():
return self.value return self.value
else: else:
return '{}/{}'.format(self.value, self.parent.full_value) return '{} / {}'.format(self.parent.full_value, self.value)
@property @property
def level(self): def level(self):
...@@ -72,7 +72,7 @@ class Node(models.Model): ...@@ -72,7 +72,7 @@ class Node(models.Model):
assets = Asset.objects.all() assets = Asset.objects.all()
else: else:
nodes = self.get_family() nodes = self.get_family()
assets = Asset.objects.filter(nodes__in=nodes) assets = Asset.objects.filter(nodes__in=nodes).distinct()
return assets return assets
def has_assets(self): def has_assets(self):
......
...@@ -109,7 +109,7 @@ class SystemUser(AssetUser): ...@@ -109,7 +109,7 @@ class SystemUser(AssetUser):
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
def __str__(self): def __str__(self):
return self.name return '{0.name}({0.username})'.format(self)
def to_json(self): def to_json(self):
return { return {
......
...@@ -96,6 +96,9 @@ def update_assets_hardware_info_util(assets, task_name=None): ...@@ -96,6 +96,9 @@ def update_assets_hardware_info_util(assets, task_name=None):
task_name = _("更新资产硬件信息") task_name = _("更新资产硬件信息")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()] hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
if not hostname_list:
logger.info("Not hosts get, may be asset is not active or not unixlike platform")
return {}
task, created = update_or_create_ansible_task( task, created = update_or_create_ansible_task(
task_name, hosts=hostname_list, tasks=tasks, pattern='all', task_name, hosts=hostname_list, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
......
{% extends '_modal.html' %} {% extends '_modal.html' %}
{% load i18n %} {% load i18n %}
{% load static %}
{% block modal_class %}modal-lg{% endblock %} {% block modal_class %}modal-lg{% endblock %}
{% block modal_id %}asset_list_modal{% endblock %} {% block modal_id %}asset_list_modal{% endblock %}
{#{% block modal_title%}{% trans "Please select assets" %}{% endblock %}#} {% block modal_title%}{% trans "Asset list" %}{% endblock %}
{% block modal_body %} {% block modal_body %}
{#<div class="btn-group" style="float: right">#} <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
{# <button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>#} <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
{# <ul class="dropdown-menu labels">#} <script src="{% static 'js/jquery.form.min.js' %}"></script>
{# {% for label in labels %}#} <style>
{# <li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>#} .inmodal .modal-header {
{# {% endfor %}#} padding: 10px 10px;
{# </ul>#} text-align: center;
{#</div>#} }
<table class="table table-striped table-bordered table-hover " id="asset_modal_table" width="100%">
#assetTree2.ztree * {
background-color: #f8fafb;
}
#assetTree2.ztree {
background-color: #f8fafb;
}
</style>
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree2" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
<thead> <thead>
<tr> <tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th> <th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th> <th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th> <th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>
</table> </table>
<div id="actions" class="hide"> </div>
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div> </div>
</div> </div>
</div> </div>
<script>
var modal_table; <script>
var zTree2, asset_table2 = 0;
function initModalTable() { function initTable2() {
var options = { var options = {
ele: $('#asset_modal_table'), ele: $('#asset_list_modal_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
$(td).html(rowData.hardware_info)
}},
{targets: 4, 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: 5, 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>')
}
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}', ajax_url: '{% url "api-assets:asset-list" %}',
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "id"}, {data: "hostname" }, {data: "ip" }
{data: "cpu_cores"}, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
], ],
op_html: $('#actions').html() pageLength: 10
}; };
modal_table = jumpserver.initServerSideDataTable(options); asset_table2 = jumpserver.initServerSideDataTable(options);
return modal_table; return asset_table2
} }
$(document).ready(function(){ function onSelected2(event, treeNode) {
initModalTable(); var url = asset_table2.ajax.url();
}).on('click', '#btn_select_assets', function () { url = setUrlParam(url, "node_id", treeNode.id);
var data_table = $('#asset_modal_table').DataTable(); setCookie('node_selected', treeNode.id);
var id_list = []; asset_table2.ajax.url(url);
data_table.rows({selected: true}).every(function(){ asset_table2.ajax.reload();
id_list.push(this.data().id); }
});
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
return
}
var data = {
'assets': id_list
};
var success = function () { function initTree2() {
modal_table.ajax.reload() var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected2
}
}; };
APIUpdateAttr({ var zNodes = [];
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/add/', $.get("{% url 'api-assets:node-list' %}", function(data, status){
'method': 'PUT', $.each(data, function (index, value) {
'body': JSON.stringify(data), value["pId"] = value["parent"];
'success': success value["open"] = true;
}) value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value'];
});
zNodes = data;
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
});
}
$(document).ready(function(){
initTable2();
initTree2();
}) })
</script> </script>
{% endblock %}
{% block modal_button %}
{{ block.super }}
{% endblock %} {% endblock %}
{% block modal_confirm_id %}btn_select_assets{% endblock %} {% block modal_confirm_id %}btn_asset_modal_confirm{% endblock %}
...@@ -117,15 +117,16 @@ ...@@ -117,15 +117,16 @@
<div id="rMenu"> <div id="rMenu">
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li id="menu_asset_create" class="btn-create-asset" tabindex="-1"><a>{% trans 'Create asset' %}</a></li>
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a>{% trans 'Add asset' %}</a></li>
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a>{% trans 'Refresh node hardware info' %}</a></li>
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a>{% trans 'Test node connective' %}</a></li>
<li class="divider"></li> <li class="divider"></li>
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a>{% trans 'Add node' %}</a></li> <li id="m_create" tabindex="-1" onclick="addTreeNode();"><a><i class="fa fa-plus-square-o"></i> {% trans 'Add node' %}</a></li>
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a>{% trans 'Rename node' %}</a></li> <li id="m_del" tabindex="-1" onclick="editTreeNode();"><a><i class="fa fa-pencil-square-o"></i> {% trans 'Rename node' %}</a></li>
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a><i class="fa fa-minus-square"></i> {% trans 'Delete node' %}</a></li>
<li class="divider"></li> <li class="divider"></li>
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a>{% trans 'Delete node' %}</a></li> <li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-copy"></i> {% trans 'Add assets to node' %}</a></li>
<li id="menu_asset_move" class="btn-move-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-cut"></i> {% trans 'Move assets to node' %}</a></li>
<li class="divider"></li>
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
</ul> </ul>
</div> </div>
...@@ -136,6 +137,7 @@ ...@@ -136,6 +137,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var zTree, rMenu, asset_table, show = 0; var zTree, rMenu, asset_table, show = 0;
var update_node_action = "";
function initTable() { function initTable() {
var options = { var options = {
ele: $('#asset_list_table'), ele: $('#asset_list_table'),
...@@ -210,9 +212,10 @@ function removeTreeNode() { ...@@ -210,9 +212,10 @@ function removeTreeNode() {
if (!current_node){ if (!current_node){
return return
} }
if (current_node.children && current_node.children.length > 0) { if (current_node.children && current_node.children.length > 0) {
alert("{% trans 'Have child node, cancel' %}") toastr.error("{% trans 'Have child node, cancel' %}");
} else if (current_node.assets_amount !== 0) {
toastr.error("{% trans 'Have assets, cancel' %}");
} else { } else {
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id ); var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id );
$.ajax({ $.ajax({
...@@ -249,13 +252,6 @@ function OnRightClick(event, treeId, treeNode) { ...@@ -249,13 +252,6 @@ function OnRightClick(event, treeId, treeNode) {
function showRMenu(type, x, y) { function showRMenu(type, x, y) {
$("#rMenu ul").show(); $("#rMenu ul").show();
{#if (type === "root") {#}
{# return#}
{# } else {#}
{# $("#m_del").show();#}
{# $("#m_check").show();#}
{# $("#m_unCheck").show();#}
{# }#}
x -= 220; x -= 220;
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"}); rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
...@@ -459,7 +455,8 @@ $(document).ready(function(){ ...@@ -459,7 +455,8 @@ $(document).ready(function(){
var current_node; var current_node;
if (nodes && nodes.length ===1 ){ if (nodes && nodes.length ===1 ){
current_node = nodes[0]; current_node = nodes[0];
action += "?node_id=" + current_node.id; action = setUrlParam(action, 'node_id', current_node.id);
{#action += "?node_id=" + current_node.id;#}
$form.attr("action", action) $form.attr("action", action)
} }
$form.find('.help-block').remove(); $form.find('.help-block').remove();
...@@ -673,7 +670,45 @@ $(document).ready(function(){ ...@@ -673,7 +670,45 @@ $(document).ready(function(){
break; break;
} }
$(".ipt_check_all").prop("checked", false) $(".ipt_check_all").prop("checked", false)
}); })
.on('click', '#btn_asset_modal_confirm', function () {
var assets_selected = asset_table2.selected;
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
return
}
var data = {
'assets': assets_selected
};
var success = function () {
asset_table2.selected = [];
asset_table2.ajax.reload()
};
var url = '';
if (update_node_action === "move") {
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
} else {
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
}
APIUpdateAttr({
'url': url,
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
}).on('hidden.bs.modal', '#asset_list_modal', function () {
window.location.reload();
}).on('click', '#menu_asset_add', function () {
update_node_action = "add"
}).on('click', '#menu_asset_move', function () {
update_node_action = "move"
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -18,14 +18,25 @@ ...@@ -18,14 +18,25 @@
</div> </div>
</div> </div>
</form> </form>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2({ console.log($.fn.select2.defaults);
closeOnSelect: false $('.select2').select2().off("select2:open");
}); }).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault();
$("#asset_list_modal").modal();
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
$.each(assets, function (id, data) {
$('.select2').val(assets).trigger('change');
}); });
$("#asset_list_modal").modal('hide');
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -81,11 +81,6 @@ function initTable() { ...@@ -81,11 +81,6 @@ function initTable() {
var options = { var options = {
ele: $('#domain_list_table'), ele: $('#domain_list_table'),
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:domain-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 7, createdCell: function (td, cellData, rowData) { {targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
{% block form %} {% block form %}
<form id="groupForm" method="post" class="form-horizontal"> <form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
...@@ -18,14 +20,28 @@ ...@@ -18,14 +20,28 @@
</div> </div>
</div> </div>
</form> </form>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
closeOnSelect: false closeOnSelect: false
})
}).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault();
$("#asset_list_modal").modal();
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
$('.select2 option:selected').each(function (i, data) {
assets.push($(data).attr('value'))
}); });
$.each(assets, function (id, data) {
$('.select2').val(assets).trigger('change');
}); });
$("#asset_list_modal").modal('hide');
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -173,7 +173,7 @@ ...@@ -173,7 +173,7 @@
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %} {% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option> <option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
...@@ -187,7 +187,7 @@ ...@@ -187,7 +187,7 @@
{% for node in system_user.nodes.all %} {% for node in system_user.nodes.all %}
<tr> <tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td> <td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
<td> <td>
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button> <button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
</td> </td>
......
...@@ -40,6 +40,7 @@ urlpatterns = [ ...@@ -40,6 +40,7 @@ urlpatterns = [
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.NodeAssetsApi.as_view(), name='node-assets'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.NodeAssetsApi.as_view(), name='node-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/replace/$', api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', api.TestNodeConnectiveApi.as_view(), name='node-test-connective'), url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
......
...@@ -49,6 +49,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView): ...@@ -49,6 +49,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
'app': _('Assets'), 'app': _('Assets'),
'action': _('Asset list'), 'action': _('Asset list'),
'labels': Label.objects.all().order_by('name'), 'labels': Label.objects.all().order_by('name'),
'nodes': Node.objects.all().order_by('-key'),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -284,24 +285,26 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView): ...@@ -284,24 +285,26 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
if set(row) == {''}: if set(row) == {''}:
continue continue
asset_dict = dict(zip(attr, row)) asset_dict_raw = dict(zip(attr, row))
id_ = asset_dict.pop('id', 0) asset_dict = dict()
for k, v in asset_dict.items(): for k, v in asset_dict_raw.items():
v = v.strip() v = v.strip()
if k == 'is_active': if k == 'is_active':
v = True if v in ['TRUE', 1, 'true'] else False v = False if v in ['False', 0, 'false'] else True
elif k == 'admin_user': elif k == 'admin_user':
v = get_object_or_none(AdminUser, name=v) v = get_object_or_none(AdminUser, name=v)
elif k in ['port', 'cpu_count', 'cpu_cores']: elif k in ['port', 'cpu_count', 'cpu_cores']:
try: try:
v = int(v) v = int(v)
except ValueError: except ValueError:
v = 0 v = ''
elif k == 'domain': elif k == 'domain':
v = get_object_or_none(Domain, name=v) v = get_object_or_none(Domain, name=v)
if v != '':
asset_dict[k] = v asset_dict[k] = v
asset = get_object_or_none(Asset, id=id_) if is_uuid(id_) else None asset = get_object_or_none(Asset, id=asset_dict.pop('id', 0))
if not asset: if not asset:
try: try:
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))): if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
...@@ -316,7 +319,7 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView): ...@@ -316,7 +319,7 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
failed.append('%s: %s' % (asset_dict['hostname'], str(e))) failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
else: else:
for k, v in asset_dict.items(): for k, v in asset_dict.items():
if v: if v != '':
setattr(asset, k, v) setattr(asset, k, v)
try: try:
asset.save() asset.save()
......
...@@ -2,11 +2,15 @@ ...@@ -2,11 +2,15 @@
# #
import json import json
from django.db import models
from django import forms from django import forms
from django.utils import six from django.utils import six
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import serializers from rest_framework import serializers
from .utils import get_signer
signer = get_signer()
class DictField(forms.Field): class DictField(forms.Field):
...@@ -47,3 +51,26 @@ class StringIDField(serializers.Field): ...@@ -47,3 +51,26 @@ class StringIDField(serializers.Field):
class StringManyToManyField(serializers.RelatedField): class StringManyToManyField(serializers.RelatedField):
def to_representation(self, value): def to_representation(self, value):
return value.__str__() return value.__str__()
class EncryptMixin:
def from_db_value(self, value, expression, connection, context):
if value is not None:
return signer.unsign(value)
return super().from_db_value(self, value, expression, connection, context)
def get_prep_value(self, value):
if value is None:
return value
return signer.sign(value).decode('utf-8')
class EncryptTextField(EncryptMixin, models.TextField):
description = _("Encrypt field using Secret Key")
class EncryptCharField(EncryptMixin, models.CharField):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 2048
super().__init__(*args, **kwargs)
This diff is collapsed.
...@@ -36,9 +36,10 @@ urlpatterns = [ ...@@ -36,9 +36,10 @@ urlpatterns = [
url(r'^captcha/', include('captcha.urls')), url(r'^captcha/', include('captcha.urls')),
] ]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [ urlpatterns += [
url(r'^docs/', schema_view, name="docs"), url(r'^docs/', schema_view, name="docs"),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ ]
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework.generics import ListAPIView, get_object_or_404 from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
from rest_framework import viewsets from rest_framework import viewsets
from common.utils import set_or_append_attr_bulk from common.utils import set_or_append_attr_bulk
...@@ -98,6 +98,11 @@ class UserGrantedNodesApi(ListAPIView): ...@@ -98,6 +98,11 @@ class UserGrantedNodesApi(ListAPIView):
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user) nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
return nodes.keys() return nodes.keys()
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
class UserGrantedNodesWithAssetsApi(ListAPIView): class UserGrantedNodesWithAssetsApi(ListAPIView):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
...@@ -246,3 +251,77 @@ class ValidateUserAssetPermissionView(APIView): ...@@ -246,3 +251,77 @@ class ValidateUserAssetPermissionView(APIView):
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
else: else:
return Response({'msg': False}, status=403) return Response({'msg': False}, status=403)
class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
users = serializer.validated_data.get('users')
if users:
perm.users.remove(*tuple(users))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
users = serializer.validated_data.get('users')
if users:
perm.users.add(*tuple(users))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
"""
将用户从授权中移除,Detail页面会调用
"""
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
assets = serializer.validated_data.get('assets')
if assets:
perm.assets.remove(*tuple(assets))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs):
perm = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
assets = serializer.validated_data.get('assets')
if assets:
perm.assets.add(*tuple(assets))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
...@@ -4,10 +4,23 @@ from __future__ import absolute_import, unicode_literals ...@@ -4,10 +4,23 @@ from __future__ import absolute_import, unicode_literals
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .hands import User
from .models import AssetPermission from .models import AssetPermission
class AssetPermissionForm(forms.ModelForm): class AssetPermissionForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP),
label=_("User"),
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Select users')
}
),
required=False,
)
class Meta: class Meta:
model = AssetPermission model = AssetPermission
exclude = ( exclude = (
......
...@@ -4,7 +4,7 @@ from django.db import models ...@@ -4,7 +4,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from common.utils import date_expired_default from common.utils import date_expired_default, set_or_append_attr_bulk
class ValidManager(models.Manager): class ValidManager(models.Manager):
...@@ -45,6 +45,22 @@ class AssetPermission(models.Model): ...@@ -45,6 +45,22 @@ class AssetPermission(models.Model):
return True return True
return False return False
def get_all_users(self):
users = set(self.users.all())
for group in self.user_groups.all():
_users = group.users.all()
set_or_append_attr_bulk(_users, 'inherit', group.name)
users.update(set(_users))
return users
def get_all_assets(self):
assets = set(self.assets.all())
for node in self.nodes.all():
_assets = node.get_all_assets()
set_or_append_attr_bulk(_assets, 'inherit', node.value)
assets.update(set(_assets))
return assets
class NodePermission(models.Model): class NodePermission(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
......
...@@ -57,12 +57,12 @@ ...@@ -57,12 +57,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for asset in page_obj %} {% for asset in object_list %}
<tr> <tr>
<td>{{ asset.hostname }}</td> <td>{{ asset.hostname }}</td>
<td>{{ asset.ip }}</td> <td>{{ asset.ip }}</td>
<td> <td>
<button title="{{ asset.inherit_from_asset_groups }}" data-gid="{{ asset.id }}" class="btn btn-danger btn-xs btn-remove-asset {% if asset.is_inherit_from_asset_groups %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button title="{{ asset.inherit }}" data-gid="{{ asset.id }}" class="btn btn-danger btn-xs btn-remove-asset {% if asset.inherit %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -85,9 +85,9 @@ ...@@ -85,9 +85,9 @@
<form> <form>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td colspan="2"> <td colspan="2">
<select data-placeholder="{% trans 'Select assets' %}" class="select2 asset" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select assets' %}" class="select2" id="asset_select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %} {% for asset in assets_remain %}
<option value="{{ asset.id }}">{{ asset.hostname }}</option> <option value="{{ asset.id }}">{{ asset }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
<div class="panel panel-info"> <div class="panel panel-info">
<div class="panel-heading"> <div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Add asset group to this permission' %} <i class="fa fa-info-circle"></i> {% trans 'Add node to this permission' %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<table class="table group_edit"> <table class="table group_edit">
...@@ -113,25 +113,25 @@ ...@@ -113,25 +113,25 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select asset groups' %}" class="select2 group" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select nodes' %}" class="select2" id="node_select2" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups_remain %} {% for node in nodes_remain %}
<option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}">{{ asset_group.name }}</option> <option value="{{ node.id }}" id="opt_{{ node.id }}">{{ node }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-add-group">{% trans 'Join' %}</button> <button type="button" class="btn btn-info btn-sm" id="btn-add-node">{% trans 'Join' %}</button>
</td> </td>
</tr> </tr>
</form> </form>
{% for asset_group in asset_groups %} {% for node in asset_permission.nodes.all %}
<tr> <tr>
<td ><b class="bdg_user_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td> <td ><b class="bdg_group" data-gid={{ node.id }}>{{ node }}</b></td>
<td> <td>
<button class="btn btn-danger btn-xs btn-remove-group" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger btn-xs btn-remove-node" type="button" data-gid="{{ node.id }}" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -179,49 +179,30 @@ function removeAssets(assets) { ...@@ -179,49 +179,30 @@ function removeAssets(assets) {
}); });
} }
function updateGroup(groups) { function updateNodes(nodes, success) {
var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}"; var the_url = "{% url 'api-perms:asset-permission-detail' pk=asset_permission.id %}";
var body = { var body = {
asset_groups: groups nodes: nodes
}; };
APIUpdateAttr({ APIUpdateAttr({
url: the_url, url: the_url,
body: JSON.stringify(body) body: JSON.stringify(body),
success: success
}); });
} }
jumpserver.assets_selected = {};
jumpserver.nodes_selected = {};
$(document).ready(function () { $(document).ready(function () {
$('.select2.asset').select2() $('.select2').select2();
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id]
});
$('.select2.group').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id]
})
}) })
.on('click', '.btn-add-assets', function () { .on('click', '.btn-add-assets', function () {
if (Object.keys(jumpserver.assets_selected).length === 0) { var assets_selected = $("#asset_select2 option:selected").map(function () {
return $(this).attr('value');
}).get();
if (assets_selected.length === 0) {
return false; return false;
} }
var assets = []; addAssets(assets_selected);
$.map(jumpserver.assets_selected, function(value, index) {
assets.push(index);
});
addAssets(assets);
}) })
.on('click', '.btn-remove-asset', function () { .on('click', '.btn-remove-asset', function () {
var asset_id = $(this).data("gid"); var asset_id = $(this).data("gid");
...@@ -231,38 +212,44 @@ $(document).ready(function () { ...@@ -231,38 +212,44 @@ $(document).ready(function () {
var assets = [asset_id]; var assets = [asset_id];
removeAssets(assets) removeAssets(assets)
}) })
.on('click', '#btn-add-group', function () { .on('click', '#btn-add-node', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) { var nodes_selected = {};
$("#node_select2 option:selected").each(function (i, data) {
nodes_selected[$(data).attr('value')] = $(data).text();
});
if (Object.keys(nodes_selected).length === 0) {
return false; return false;
} }
var nodes_origin = $('.bdg_group').map(function() {
var groups = $('.bdg_group').map(function() {
return $(this).data('gid'); return $(this).data('gid');
}).get(); }).get();
$.map(jumpserver.nodes_selected, function(group_name, index) { var nodes = nodes_origin.concat(Object.keys(nodes_selected));
groups.push(index); var success = function () {
$('#opt_' + index).remove(); $.map(nodes_selected, function(name, id) {
$('#opt_' + id).remove();
$('.group_edit tbody').append( $('.group_edit tbody').append(
'<tr>' + '<tr>' +
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' + '<td><b class="bdg_group" data-gid="' + id + '">' + name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-group" type="button"><i class="fa fa-minus"></i></button></td>' + '<td><button class="btn btn-danger btn-xs pull-right btn-leave-group" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>' '</tr>'
) )
}); });
};
updateGroup(groups); updateNodes(nodes, success);
}) })
.on('click', '.btn-remove-group', function () { .on('click', '.btn-remove-node', function () {
var $this = $(this); var $this = $(this);
var $tr = $this.closest('tr'); var $tr = $this.closest('tr');
var groups = $('.bdg_group').map(function() { var nodes = $('.bdg_group').map(function() {
if ($(this).data('gid') !== $this.data('gid')){ if ($(this).data('gid') !== $this.data('gid')){
return $(this).data('gid'); return $(this).data('gid');
} }
}).get(); }).get();
updateGroup(groups); var success = function () {
$tr.remove() $tr.remove()
};
updateNodes(nodes, success);
}) })
</script> </script>
{% endblock %} {% endblock %}
...@@ -76,12 +76,15 @@ ...@@ -76,12 +76,15 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script> <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2({
closeOnSelect: false
});
$('#datepicker').datepicker({ $('#datepicker').datepicker({
format: "yyyy-mm-dd", format: "yyyy-mm-dd",
todayBtn: "linked", todayBtn: "linked",
...@@ -90,6 +93,20 @@ ...@@ -90,6 +93,20 @@
calendarWeeks: true, calendarWeeks: true,
autoclose: true autoclose: true
}); });
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
e.preventDefault();
$("#asset_list_modal").modal();
}) })
</script> })
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
$('.select2 option:selected').each(function (i, data) {
assets.push($(data).attr('value'))
});
$.each(assets, function (id, data) {
$('.select2').val(assets).trigger('change');
});
$("#asset_list_modal").modal('hide');
})
</script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -57,12 +57,12 @@ ...@@ -57,12 +57,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in page_obj %} {% for user in object_list %}
<tr> <tr>
<td>{{ user.name }}</td> <td>{{ user.name }}</td>
<td>{{ user.username }}</td> <td>{{ user.username }}</td>
<td> <td>
<button class="btn btn-danger btn-xs btn-remove-user {% if user.is_inherit_from_user_groups %} disabled {% endif %}" data-gid="{{ user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger btn-xs btn-remove-user {% if user.inherit %} disabled {% endif %}" data-gid="{{ user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<td colspan="2"> <td colspan="2">
<select data-placeholder="{% trans 'Select user' %}" class="select2 user" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select user' %}" class="select2 user" style="width: 100%" multiple="" tabindex="4">
{% for user in users_remain %} {% for user in users_remain %}
<option value="{{ user.id }}">{{ user.name }}</option> <option value="{{ user.id }}">{{ user }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select user groups' %}" class="select2 user-group" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select user groups' %}" class="select2 user-group" style="width: 100%" multiple="" tabindex="4">
{% for user_group in user_groups_remain %} {% for user_group in user_groups_remain %}
<option value="{{ user_group.id }}" id="opt_{{ user_group.id }}">{{ user_group.name }}</option> <option value="{{ user_group.id }}" id="opt_{{ user_group.id }}">{{ user_group }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
...@@ -127,9 +127,9 @@ ...@@ -127,9 +127,9 @@
</tr> </tr>
</form> </form>
{% for user_group in user_groups %} {% for user_group in asset_permission.user_groups.all %}
<tr> <tr>
<td ><b class="bdg_group" data-gid={{ user_group.id }}>{{ user_group.name }}</b></td> <td ><b class="bdg_group" data-gid={{ user_group.id }}>{{ user_group }}</b></td>
<td> <td>
<button class="btn btn-danger btn-xs btn-remove-group" type="button" data-gid="{{ user_group.id }}" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger btn-xs btn-remove-group" type="button" data-gid="{{ user_group.id }}" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
......
...@@ -11,20 +11,50 @@ router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permi ...@@ -11,20 +11,50 @@ router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permi
urlpatterns = [ urlpatterns = [
# 查询某个用户授权的资产和资产组 # 查询某个用户授权的资产和资产组
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'), url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
url(r'^v1/user/assets/$', api.UserGrantedAssetsApi.as_view(), name='my-assets'), api.UserGrantedAssetsApi.as_view(), name='user-assets'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$', api.UserGrantedNodesApi.as_view(), name='user-nodes'), url(r'^v1/user/assets/$', api.UserGrantedAssetsApi.as_view(),
url(r'^v1/user/nodes/$', api.UserGrantedNodesApi.as_view(), name='my-nodes'), name='my-assets'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
url(r'^v1/user/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'), api.UserGrantedNodesApi.as_view(), name='user-nodes'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), url(r'^v1/user/nodes/$', api.UserGrantedNodesApi.as_view(),
url(r'^v1/user/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), name='my-nodes'),
url(
r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
url(r'^v1/user/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
url(r'^v1/user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$',
api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'),
url(r'^v1/user/nodes-assets/$', api.UserGrantedNodesWithAssetsApi.as_view(),
name='my-nodes-assets'),
# 查询某个用户组授权的资产和资产组 # 查询某个用户组授权的资产和资产组
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'), url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
url(r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes-assets/$',
api.UserGroupGrantedNodesWithAssetsApi.as_view(),
name='user-group-nodes-assets'),
url(
r'^v1/user-group/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/(?P<node_id>[0-9a-zA-Z\-]{36})/assets/$',
api.UserGroupGrantedNodeAssetsApi.as_view(),
name='user-group-node-assets'),
# 用户和资产授权变更
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/remove/$',
api.AssetPermissionRemoveUserApi.as_view(),
name='asset-permission-remove-user'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/user/add/$',
api.AssetPermissionAddUserApi.as_view(),
name='asset-permission-add-user'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/remove/$',
api.AssetPermissionRemoveAssetApi.as_view(),
name='asset-permission-remove-asset'),
url(r'^v1/asset-permissions/(?P<pk>[0-9a-zA-Z\-]{36})/asset/add/$',
api.AssetPermissionAddAssetApi.as_view(),
name='asset-permission-add-asset'),
# 验证用户是否有某个资产和系统用户的权限 # 验证用户是否有某个资产和系统用户的权限
url(r'v1/asset-permission/user/validate/$', api.ValidateUserAssetPermissionView.as_view(), name='validate-user-asset-permission'), url(r'v1/asset-permission/user/validate/$', api.ValidateUserAssetPermissionView.as_view(), name='validate-user-asset-permission'),
......
...@@ -11,8 +11,8 @@ urlpatterns = [ ...@@ -11,8 +11,8 @@ urlpatterns = [
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/update$', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})$', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'),
url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/delete$', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'),
# url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/user$', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'),
# url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), url(r'^asset-permission/(?P<pk>[0-9a-zA-Z\-]{36})/asset$', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'),
] ]
...@@ -4,12 +4,12 @@ from __future__ import unicode_literals, absolute_import ...@@ -4,12 +4,12 @@ from __future__ import unicode_literals, absolute_import
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import ListView, CreateView, UpdateView, DetailView from django.views.generic import ListView, CreateView, UpdateView, DetailView
from django.views.generic.edit import DeleteView from django.views.generic.edit import DeleteView, SingleObjectMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings from django.conf import settings
from common.utils import is_uuid from common.mixins import AdminUserRequiredMixin
from .hands import AdminUserRequiredMixin, Node, Asset from .hands import Node, Asset, SystemUser, User, UserGroup
from .models import AssetPermission from .models import AssetPermission
from .forms import AssetPermissionForm from .forms import AssetPermissionForm
...@@ -83,7 +83,11 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): ...@@ -83,7 +83,11 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'action': _('Update asset permission') 'action': _('Update asset permission'),
'system_users_remain': SystemUser.objects.exclude(
granted_by_permissions=self.object
),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -95,3 +99,59 @@ class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView): ...@@ -95,3 +99,59 @@ class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView):
success_url = reverse_lazy('perms:asset-permission-list') success_url = reverse_lazy('perms:asset-permission-list')
class AssetPermissionUserView(AdminUserRequiredMixin,
SingleObjectMixin,
ListView):
template_name = 'perms/asset_permission_user.html'
context_object_name = 'asset_permission'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetPermission.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
queryset = self.object.get_all_users()
return queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
'action': _('Asset permission user list'),
'users_remain': User.objects.exclude(asset_permissions=self.object)
.exclude(role=User.ROLE_APP),
'user_groups_remain': UserGroup.objects.exclude(
asset_permissions=self.object
)
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AssetPermissionAssetView(AdminUserRequiredMixin,
SingleObjectMixin,
ListView):
template_name = 'perms/asset_permission_asset.html'
context_object_name = 'asset_permission'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetPermission.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
queryset = self.object.get_all_assets()
return queryset
def get_context_data(self, **kwargs):
assets_granted = self.get_queryset()
context = {
'app': _('Perms'),
'action': _('Asset permission asset list'),
'assets_remain': Asset.objects.exclude(id__in=[a.id for a in assets_granted]),
'nodes_remain': Node.objects.exclude(granted_by_permissions=self.object),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
\ No newline at end of file
...@@ -307,7 +307,7 @@ jumpserver.initDataTable = function (options) { ...@@ -307,7 +307,7 @@ jumpserver.initDataTable = function (options) {
last: "»" last: "»"
} }
}, },
lengthMenu: [[15, 25, 50, -1], [15, 25, 50, "All"]] lengthMenu: [[10, 15, 25, 50, -1], [10, 15, 25, 50, "All"]]
}); });
table.on('select', function(e, dt, type, indexes) { table.on('select', function(e, dt, type, indexes) {
var $node = table[ type ]( indexes ).nodes().to$(); var $node = table[ type ]( indexes ).nodes().to$();
...@@ -446,22 +446,56 @@ jumpserver.initServerSideDataTable = function (options) { ...@@ -446,22 +446,56 @@ jumpserver.initServerSideDataTable = function (options) {
last: "»" last: "»"
} }
}, },
lengthMenu: [[15, 25, 50], [15, 25, 50]] lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]]
}); });
table.selected = [];
table.on('select', function(e, dt, type, indexes) { table.on('select', function(e, dt, type, indexes) {
var $node = table[ type ]( indexes ).nodes().to$(); var $node = table[ type ]( indexes ).nodes().to$();
$node.find('input.ipt_check').prop('checked', true); $node.find('input.ipt_check').prop('checked', true);
jumpserver.selected[$node.find('input.ipt_check').prop('id')] = true jumpserver.selected[$node.find('input.ipt_check').prop('id')] = true;
if (type === 'row') {
var rows = table.rows(indexes).data();
$.each(rows, function (id, row) {
if (row.id){
table.selected.push(row.id)
}
})
}
}).on('deselect', function(e, dt, type, indexes) { }).on('deselect', function(e, dt, type, indexes) {
var $node = table[ type ]( indexes ).nodes().to$(); var $node = table[ type ]( indexes ).nodes().to$();
$node.find('input.ipt_check').prop('checked', false); $node.find('input.ipt_check').prop('checked', false);
jumpserver.selected[$node.find('input.ipt_check').prop('id')] = false jumpserver.selected[$node.find('input.ipt_check').prop('id')] = false;
if (type === 'row') {
var rows = table.rows(indexes).data();
$.each(rows, function (id, row) {
if (row.id){
var index = table.selected.indexOf(row.id);
if (index > -1){
table.selected.splice(index, 1)
}
}
})
}
}). }).
on('draw', function(){ on('draw', function(){
$('#op').html(options.op_html || ''); $('#op').html(options.op_html || '');
$('#uc').html(options.uc_html || ''); $('#uc').html(options.uc_html || '');
var table_data = [];
$.each(table.rows().data(), function (id, row) {
if (row.id) {
table_data.push(row.id)
}
}); });
$('.ipt_check_all').on('click', function() {
$.each(table.selected, function (id, data) {
var index = table_data.indexOf(data);
if (index > -1){
table.rows(index).select()
}
});
});
var table_id = table.settings()[0].sTableId;
$('#' + table_id + ' .ipt_check_all').on('click', function() {
if ($(this).prop("checked")) { if ($(this).prop("checked")) {
$(this).closest('table').find('.ipt_check').prop('checked', true); $(this).closest('table').find('.ipt_check').prop('checked', true);
table.rows({search:'applied', page:'current'}).select(); table.rows({search:'applied', page:'current'}).select();
......
<div class="footer fixed"> <div class="footer fixed">
<div class="pull-right"> <div class="pull-right">
Version <strong>1.0.0-{% include '_build.html' %}</strong> GPLv2. Version <strong>1.2.0-{% include '_build.html' %}</strong> GPLv2.
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg"> <img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
</div> </div>
<div> <div>
......
{% load i18n %} {% load i18n %}
<style>
.modal-body {
padding: 0px 20px 0px 20px;
}
</style>
<div aria-hidden="true" role="dialog" id="{% block modal_id %}{% endblock %}" class="modal inmodal"> <div aria-hidden="true" role="dialog" id="{% block modal_id %}{% endblock %}" class="modal inmodal">
<div class="modal-dialog {% block modal_class %}{% endblock %}"> <div class="modal-dialog {% block modal_class %}{% endblock %}">
<div class="modal-content animated fadeIn"> <div class="modal-content animated fadeIn">
...@@ -12,8 +17,10 @@ ...@@ -12,8 +17,10 @@
{% endblock %} {% endblock %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button> <button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
<button class="btn btn-primary" type="button" id="{% block modal_confirm_id %}{% endblock %}">{% trans 'Confirm' %}</button> <button class="btn btn-primary" type="button" id="{% block modal_confirm_id %}{% endblock %}">{% trans 'Confirm' %}</button>
{% endblock %}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
</li> </li>
<li id="audits"> <li id="audits">
<a> <a>
<i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span> <i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span>
</a> </a>
<ul class="nav nav-second-level"> <ul class="nav nav-second-level">
<li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li> <li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li>
......
...@@ -248,7 +248,7 @@ class CommandViewSet(viewsets.ViewSet): ...@@ -248,7 +248,7 @@ class CommandViewSet(viewsets.ViewSet):
class SessionReplayViewSet(viewsets.ViewSet): class SessionReplayViewSet(viewsets.ViewSet):
serializer_class = ReplaySerializer serializer_class = ReplaySerializer
permission_classes = () permission_classes = (IsSuperUserOrAppUser,)
session = None session = None
def gen_session_path(self): def gen_session_path(self):
......
...@@ -45,7 +45,7 @@ class User(AbstractUser): ...@@ -45,7 +45,7 @@ class User(AbstractUser):
wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat')) wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat'))
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone')) phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
otp_level = models.SmallIntegerField(default=0, choices=OTP_LEVEL_CHOICES, verbose_name=_('Enable OTP')) otp_level = models.SmallIntegerField(default=0, choices=OTP_LEVEL_CHOICES, verbose_name=_('Enable OTP'))
otp_secret_key = models.CharField(max_length=16, blank=True, null=True) _otp_secret_key = models.CharField(max_length=128, blank=True, null=True)
# Todo: Auto generate key, let user download # Todo: Auto generate key, let user download
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Private key')) _private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Private key'))
_public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Public key')) _public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Public key'))
...@@ -55,7 +55,7 @@ class User(AbstractUser): ...@@ -55,7 +55,7 @@ class User(AbstractUser):
created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by')) created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by'))
def __str__(self): def __str__(self):
return self.username return '{0.name}({0.username})'.format(self)
@property @property
def password_raw(self): def password_raw(self):
...@@ -70,6 +70,14 @@ class User(AbstractUser): ...@@ -70,6 +70,14 @@ class User(AbstractUser):
def password_raw(self, password_raw_): def password_raw(self, password_raw_):
self.set_password(password_raw_) self.set_password(password_raw_)
@property
def otp_secret_key(self):
return signer.unsign(self._otp_secret_key)
@otp_secret_key.setter
def otp_secret_key(self, item):
self._otp_secret_key = signer.sign(item).decode('utf-8')
def get_absolute_url(self): def get_absolute_url(self):
return reverse('users:user-detail', args=(self.id,)) return reverse('users:user-detail', args=(self.id,))
......
...@@ -13,7 +13,7 @@ Docker 安装见: `Docker官方安装文档 <https://docs.docker.com/install/>`_ ...@@ -13,7 +13,7 @@ Docker 安装见: `Docker官方安装文档 <https://docs.docker.com/install/>`_
``````````````` ```````````````
使用 root 命令行输入:: 使用 root 命令行输入::
$ docker run -d -p 8080:80 -p 2222:2222 registry.jumpserver.org/public/jumpserver:latest $ docker run -d -p 8080:80 -p 2222:2222 registry.jumpserver.org/public/jumpserver:1.0.0
访问 访问
``````````````` ```````````````
...@@ -42,10 +42,13 @@ XShell等工具请添加connection连接 ...@@ -42,10 +42,13 @@ XShell等工具请添加connection连接
:: ::
docker run -d -p 8080:80 -p 2222:2222 -e DB_ENGINE=mysql -e DB_HOST=192.168.1.1 -e DB_PORT=3306 -e DB_USER=root -e DB_PASSWORD=xxx -e DB_NAME=jumpserver registry.jumpserver.org/public/jumpserver:latest docker run -d -p 8080:80 -p 2222:2222 -e DB_ENGINE=mysql -e DB_HOST=192.168.1.1 -e DB_PORT=3306 -e DB_USER=root -e DB_PASSWORD=xxx -e DB_NAME=jumpserver registry.jumpserver.org/public/jumpserver:1.0.0
仓库地址 仓库地址
``````````````` ```````````````
https://github.com/jumpserver/Dockerfile https://github.com/jumpserver/Dockerfile
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
# 看到下面的提示符代表成功,以后运行 Jumpserver 都要先运行以上 source 命令,以下所有命令均在该虚拟环境中运行 # 看到下面的提示符代表成功,以后运行 Jumpserver 都要先运行以上 source 命令,以下所有命令均在该虚拟环境中运行
(py3) [root@localhost py3] (py3) [root@localhost py3]
二. 安装 Jumpserver 0.5.0 二. 安装 Jumpserver 1.0.0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**2.1 下载或 Clone 项目** **2.1 下载或 Clone 项目**
...@@ -201,7 +201,7 @@ ...@@ -201,7 +201,7 @@
Luna 已改为纯前端,需要 Nginx 来运行访问 Luna 已改为纯前端,需要 Nginx 来运行访问
访问(https://github.com/jumpserver/luna/releases)下载对应 release 包,直接解压,不需要编译 访问(https://github.com/jumpserver/luna/releases)下载对应版本的 release 包,直接解压,不需要编译
4.1 解压 Luna 4.1 解压 Luna
...@@ -228,7 +228,7 @@ Luna 已改为纯前端,需要 Nginx 来运行访问 ...@@ -228,7 +228,7 @@ Luna 已改为纯前端,需要 Nginx 来运行访问
-p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \ -p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \
-e JUMPSERVER_KEY_DIR=/config/guacamole/key \ -e JUMPSERVER_KEY_DIR=/config/guacamole/key \
-e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \ -e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \
registry.jumpserver.org/public/guacamole:latest registry.jumpserver.org/public/guacamole:1.0.0
这里所需要注意的是 guacamole 暴露出来的端口是 8081,若与主机上其他端口冲突请自定义一下。 这里所需要注意的是 guacamole 暴露出来的端口是 8081,若与主机上其他端口冲突请自定义一下。
......
...@@ -3,9 +3,10 @@ ...@@ -3,9 +3,10 @@
python ../apps/manage.py shell << EOF python ../apps/manage.py shell << EOF
from perms.models import * from perms.models import *
from assets.models import SystemUser
for old in NodePermission.objects.all(): for old in NodePermission.objects.all():
perm = asset_perm_model.objects.using(db_alias).create( perm = AssetPermission.objects.create(
name="{}-{}-{}".format( name="{}-{}-{}".format(
old.node.value, old.node.value,
old.user_group.name, old.user_group.name,
...@@ -20,5 +21,10 @@ for old in NodePermission.objects.all(): ...@@ -20,5 +21,10 @@ for old in NodePermission.objects.all():
perm.user_groups.add(old.user_group) perm.user_groups.add(old.user_group)
perm.nodes.add(old.node) perm.nodes.add(old.node)
perm.system_users.add(old.system_user) perm.system_users.add(old.system_user)
for s in SystemUser.objects.all():
nodes = list(s.nodes.all())
s.nodes.set([])
s.nodes.set(nodes)
EOF EOF
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment