Unverified Commit 5f9f970a authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Perf (#2929)

* [Update] 优化性能

* [Update] 修改assets

* [Update] 优化处理

* [Update] Youhua

* [Update] 修改ungroup

* [Update] 修改perms的api地址,去掉sysuser adminuser的可连接性

* [Update] 修改perms urls兼容

* [Update] 修改分类

* [Update] 修改信号

* [Update] 优化获取授权资产

* [Update] 添加注释 (#2928)

* [update] 去掉nodes 的部分字段

* [Update] 删除不用的代码

* [Update] 拆分users

* [Update] 修改用户的属性
parent ff85e2ef
...@@ -131,7 +131,7 @@ class NodeChildrenAsTreeApi(generics.ListAPIView): ...@@ -131,7 +131,7 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
if not include_assets: if not include_assets:
return queryset return queryset
assets = self.node.get_assets().only( assets = self.node.get_assets().only(
"id", "hostname", "ip", 'platform', "os", "org_id", "id", "hostname", "ip", 'platform', "os", "org_id", "protocols",
) )
for asset in assets: for asset in assets:
queryset.append(asset.as_tree_node(self.node)) queryset.append(asset.as_tree_node(self.node))
......
...@@ -6,7 +6,8 @@ import uuid ...@@ -6,7 +6,8 @@ import uuid
import logging import logging
import random import random
from functools import reduce from functools import reduce
from collections import OrderedDict from collections import OrderedDict, defaultdict
from django.core.cache import cache
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -96,7 +97,53 @@ class ProtocolsMixin: ...@@ -96,7 +97,53 @@ class ProtocolsMixin:
return self.protocols_as_dict.get("ssh", 22) return self.protocols_as_dict.get("ssh", 22)
class Asset(ProtocolsMixin, OrgModelMixin): class NodesRelationMixin:
NODES_CACHE_KEY = 'ASSET_NODES_{}'
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
CACHE_TIME = 3600 * 24 * 7
id = ""
_all_nodes_keys = None
@classmethod
def get_all_nodes_keys(cls):
"""
:return: {asset.id: [node.key, ]}
"""
from .node import Node
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cached = cache.get(cache_key)
if cached:
return cached
assets = Asset.objects.all().only('id').prefetch_related(
models.Prefetch('nodes', queryset=Node.objects.all().only('key'))
)
assets_nodes_keys = {}
for asset in assets:
assets_nodes_keys[asset.id] = [n.key for n in asset.nodes.all()]
cache.set(cache_key, assets_nodes_keys, cls.CACHE_TIME)
return assets_nodes_keys
@classmethod
def expire_all_nodes_keys_cache(cls):
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cache.delete(cache_key)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Important # Important
PLATFORM_CHOICES = ( PLATFORM_CHOICES = (
('Linux', 'Linux'), ('Linux', 'Linux'),
...@@ -182,20 +229,6 @@ class Asset(ProtocolsMixin, OrgModelMixin): ...@@ -182,20 +229,6 @@ class Asset(ProtocolsMixin, OrgModelMixin):
def is_support_ansible(self): def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",) return self.has_protocol('ssh') and self.platform not in ("Other",)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
@property @property
def cpu_info(self): def cpu_info(self):
info = "" info = ""
......
...@@ -212,14 +212,12 @@ class AssetsAmountMixin: ...@@ -212,14 +212,12 @@ class AssetsAmountMixin:
if cached is not None: if cached is not None:
return cached return cached
assets_amount = self.get_all_assets().count() assets_amount = self.get_all_assets().count()
self.assets_amount = assets_amount cache.set(cache_key, assets_amount, self.cache_time)
return assets_amount return assets_amount
@assets_amount.setter @assets_amount.setter
def assets_amount(self, value): def assets_amount(self, value):
self._assets_amount = value self._assets_amount = value
cache_key = self._assets_amount_cache_key.format(self.key)
cache.set(cache_key, value, self.cache_time)
def expire_assets_amount(self): def expire_assets_amount(self):
ancestor_keys = self.get_ancestor_keys(with_self=True) ancestor_keys = self.get_ancestor_keys(with_self=True)
......
...@@ -117,16 +117,6 @@ class SystemUser(AssetUser): ...@@ -117,16 +117,6 @@ class SystemUser(AssetUser):
def __str__(self): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
def to_json(self):
return {
'id': self.id,
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'priority': self.priority,
'auto_push': self.auto_push,
}
@property @property
def login_mode_display(self): def login_mode_display(self):
return self.get_login_mode_display() return self.get_login_mode_display()
......
...@@ -21,7 +21,7 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -21,7 +21,7 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
model = AdminUser model = AdminUser
fields = [ fields = [
'id', 'name', 'username', 'password', 'private_key', 'public_key', 'id', 'name', 'username', 'password', 'private_key', 'public_key',
'comment', 'connectivity_amount', 'assets_amount', 'comment', 'assets_amount',
'date_created', 'date_updated', 'created_by', 'date_created', 'date_updated', 'created_by',
] ]
...@@ -33,7 +33,6 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -33,7 +33,6 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'date_updated': {'read_only': True}, 'date_updated': {'read_only': True},
'created_by': {'read_only': True}, 'created_by': {'read_only': True},
'assets_amount': {'label': _('Asset')}, 'assets_amount': {'label': _('Asset')},
'connectivity_amount': {'label': _('Connectivity')},
} }
......
...@@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer): ...@@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = Node model = Node
fields = [ only_fields = ['id', 'key', 'value', 'org_id']
'id', 'key', 'value', 'assets_amount', 'org_id', fields = only_fields + ['assets_amount']
]
read_only_fields = [ read_only_fields = [
'key', 'assets_amount', 'org_id', 'key', 'assets_amount', 'org_id',
] ]
......
...@@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'id', 'name', 'username', 'password', 'public_key', 'private_key', 'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol', 'login_mode', 'login_mode_display', 'priority', 'protocol',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
'assets_amount', 'connectivity_amount', 'auto_generate_key' 'assets_amount', 'auto_generate_key'
] ]
extra_kwargs = { extra_kwargs = {
'password': {"write_only": True}, 'password': {"write_only": True},
'public_key': {"write_only": True}, 'public_key': {"write_only": True},
'private_key': {"write_only": True}, 'private_key': {"write_only": True},
'assets_amount': {'label': _('Asset')}, 'assets_amount': {'label': _('Asset')},
'connectivity_amount': {'label': _('Connectivity')},
'login_mode_display': {'label': _('Login mode display')}, 'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True}, 'created_by': {'read_only': True},
} }
......
...@@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs): ...@@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through) @receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs): def on_asset_node_changed(sender, instance=None, **kwargs):
logger.debug("Asset nodes change signal received") logger.debug("Asset nodes change signal received")
Asset.expire_all_nodes_keys_cache()
if isinstance(instance, Asset): if isinstance(instance, Asset):
if kwargs['action'] == 'pre_remove': if kwargs['action'] == 'pre_remove':
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
......
...@@ -291,42 +291,6 @@ function defaultCallback(action) { ...@@ -291,42 +291,6 @@ function defaultCallback(action) {
$(document).ready(function () { $(document).ready(function () {
}) })
.on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
if (!current_node_id) {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-show-current-asset', function(){ .on('click', '.btn-show-current-asset', function(){
hideRMenu(); hideRMenu();
$(this).css('display', 'none'); $(this).css('display', 'none');
...@@ -341,17 +305,5 @@ $(document).ready(function () { ...@@ -341,17 +305,5 @@ $(document).ready(function () {
setCookie('show_current_asset', ''); setCookie('show_current_asset', '');
location.reload(); location.reload();
}) })
.on('click', '.btn-test-connective', function () {
hideRMenu();
})
.on('click', '#menu_refresh_assets_amount', function () {
hideRMenu();
var url = "{% url 'api-assets:refresh-assets-amount' %}";
APIUpdateAttr({
'url': url,
'method': 'GET'
});
window.location.reload();
})
</script> </script>
\ No newline at end of file
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
{% load i18n static %} {% load i18n static %}
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
{# Windows或其它硬件可以随意设置一个#}
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%} {% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%} {% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
{% trans 'You can set any one for Windows or other hardware.' %} {% trans 'You can set any one for Windows or other hardware.' %}
...@@ -47,9 +45,9 @@ ...@@ -47,9 +45,9 @@
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th> <th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th> {# <th class="text-center">{% trans 'Reachable' %}</th>#}
<th class="text-center">{% trans 'Unreachable' %}</th> {# <th class="text-center">{% trans 'Unreachable' %}</th>#}
<th class="text-center">{% trans 'Ratio' %}</th> {# <th class="text-center">{% trans 'Ratio' %}</th>#}
<th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -73,44 +71,44 @@ function initTable() { ...@@ -73,44 +71,44 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id); return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}}, }},
{targets: 4, createdCell: function (td, cellData) { {#{targets: 4, createdCell: function (td, cellData) {#}
var innerHtml = ""; {# var innerHtml = "";#}
var data = cellData.reachable; {# var data = cellData.reachable;#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-navy'>" + data + "</span>"; {# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html(innerHtml) {# $(td).html(innerHtml)#}
}}, {#}},#}
{targets: 5, createdCell: function (td, cellData) { {#{targets: 5, createdCell: function (td, cellData) {#}
var data = cellData.unreachable; {# var data = cellData.unreachable;#}
var innerHtml = ""; {# var innerHtml = "";#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-danger'>" + data + "</span>"; {# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 6, createdCell: function (td, cellData, rowData) { {#{targets: 6, createdCell: function (td, cellData, rowData) {#}
var val = 0; {# var val = 0;#}
var innerHtml = ""; {# var innerHtml = "";#}
var total = rowData.assets_amount; {# var total = rowData.assets_amount;#}
var reachable = cellData.reachable; {# var reachable = cellData.reachable;#}
if (total !== 0) { {# if (total !== 0) {#}
val = reachable/total * 100; {# val = reachable/total * 100;#}
} {# }#}
{##}
if (val === 100) { {# if (val === 100) {#}
innerHtml = "<span class='text-navy'>" + val + "% </span>"; {# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
} else { {# } else {#}
var num = new Number(val); {# var num = new Number(val);#}
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; {# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 8, createdCell: function (td, cellData, rowData) { {targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_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_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
...@@ -118,7 +116,7 @@ function initTable() { ...@@ -118,7 +116,7 @@ function initTable() {
ajax_url: '{% url "api-assets:admin-user-list" %}', ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [ columns: [
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, {data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#}
{data: "comment"}, {data: "id"} {data: "comment"}, {data: "id"}
] ]
}; };
......
...@@ -167,7 +167,7 @@ function initTable() { ...@@ -167,7 +167,7 @@ function initTable() {
}}, }},
{targets: 5, createdCell: function (td, cellData, rowData) { {targets: 5, 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 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); 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) $(td).html(update_btn + del_btn)
}} }}
], ],
...@@ -325,8 +325,7 @@ $(document).ready(function(){ ...@@ -325,8 +325,7 @@ $(document).ready(function(){
} }
window.open(url, '_self'); window.open(url, '_self');
}) })
.on('click', '.btn-asset-delete', function () {
.on('click', '.btn_asset_delete', function () {
var $this = $(this); var $this = $(this);
var $data_table = $("#asset_list_table").DataTable(); var $data_table = $("#asset_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html(); var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
...@@ -513,6 +512,40 @@ $(document).ready(function(){ ...@@ -513,6 +512,40 @@ $(document).ready(function(){
update_node_action = "add" update_node_action = "add"
}).on('click', '#menu_asset_move', function () { }).on('click', '#menu_asset_move', function () {
update_node_action = "move" update_node_action = "move"
}).on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
if (!current_node_id) {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
}).on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
}) })
</script> </script>
......
...@@ -53,9 +53,9 @@ ...@@ -53,9 +53,9 @@
<th class="text-center">{% trans 'Protocol' %}</th> <th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Login mode' %}</th> <th class="text-center">{% trans 'Login mode' %}</th>
<th class="text-center">{% trans 'Asset' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th> {# <th class="text-center">{% trans 'Reachable' %}</th>#}
<th class="text-center">{% trans 'Unreachable' %}</th> {# <th class="text-center">{% trans 'Unreachable' %}</th>#}
<th class="text-center">{% trans 'Ratio' %}</th> {# <th class="text-center">{% trans 'Ratio' %}</th>#}
<th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -78,44 +78,44 @@ function initTable() { ...@@ -78,44 +78,44 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
{targets: 6, createdCell: function (td, cellData) { {#{targets: 6, createdCell: function (td, cellData) {#}
var innerHtml = ""; {# var innerHtml = "";#}
var data = cellData.reachable; {# var data = cellData.reachable;#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-navy'>" + data + "</span>"; {# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html(innerHtml) {# $(td).html(innerHtml)#}
}}, {#}},#}
{targets: 7, createdCell: function (td, cellData) { {#{targets: 7, createdCell: function (td, cellData) {#}
var data = cellData.unreachable; {# var data = cellData.unreachable;#}
var innerHtml = ""; {# var innerHtml = "";#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-danger'>" + data + "</span>"; {# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 8, createdCell: function (td, cellData, rowData) { {#{targets: 8, createdCell: function (td, cellData, rowData) {#}
var val = 0; {# var val = 0;#}
var innerHtml = ""; {# var innerHtml = "";#}
var total = rowData.assets_amount; {# var total = rowData.assets_amount;#}
var reachable = cellData.reachable; {# var reachable = cellData.reachable;#}
if (total && total !== 0) { {# if (total && total !== 0) {#}
val = reachable/total * 100; {# val = reachable/total * 100;#}
} {# }#}
{##}
if (val === 100) { {# if (val === 100) {#}
innerHtml = "<span class='text-navy'>" + val + "% </span>"; {# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
} else { {# } else {#}
var num = new Number(val); {# var num = new Number(val);#}
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; {# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 10, createdCell: function (td, cellData, rowData) { {targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_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_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
...@@ -124,7 +124,7 @@ function initTable() { ...@@ -124,7 +124,7 @@ function initTable() {
ajax_url: '{% url "api-assets:system-user-list" %}', ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [ columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" }, {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" },
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "comment" }, {data: "id" } {data: "comment" }, {data: "id" }
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -43,7 +43,6 @@ ...@@ -43,7 +43,6 @@
<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 'Active' %}</th>
<th class="text-center">{% trans 'System users' %}</th> <th class="text-center">{% trans 'System users' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -62,7 +61,7 @@ ...@@ -62,7 +61,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var treeUrl = "{% url 'api-perms:my-nodes-assets-as-tree' %}?show_assets=0&cache_policy=1"; var treeUrl = "{% url 'api-perms:my-nodes-as-tree' %}?&cache_policy=1";
var zTree, asset_table, show=0; var zTree, asset_table, show=0;
var inited = false; var inited = false;
var url; var url;
...@@ -83,20 +82,13 @@ function initTable() { ...@@ -83,20 +82,13 @@ function initTable() {
$(td).html(detail_btn.replace("rowData_id", rowData.id)); $(td).html(detail_btn.replace("rowData_id", rowData.id));
}}, }},
{targets: 3, createdCell: function (td, cellData) { {targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData) {
var users = []; var users = [];
$.each(cellData, function (id, data) { $.each(cellData, function (id, data) {
users.push(data.name); users.push(data.name);
}); });
$(td).html(users.join(', ')) $(td).html(users.join(', '))
}}, }},
{targets: 5, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData); var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(conn_btn) $(td).html(conn_btn)
}} }}
...@@ -104,7 +96,6 @@ function initTable() { ...@@ -104,7 +96,6 @@ function initTable() {
ajax_url: url, ajax_url: url,
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false}, {data: "system_users_granted", orderable: false},
{data: "id", orderable: false} {data: "id", orderable: false}
] ]
......
...@@ -104,7 +104,7 @@ class NodeUtil: ...@@ -104,7 +104,7 @@ class NodeUtil:
_node._assets_amount = len(_node._assets) _node._assets_amount = len(_node._assets)
delattr(_node, '_assets') delattr(_node, '_assets')
self.stack.top._children.append(_node) self.stack.top._children.append(_node)
self.stack.top._all_children.extend([_node] + _node._children) self.stack.top._all_children.extend([_node] + _node._all_children)
def init(self): def init(self):
all_nodes = self.get_all_nodes() all_nodes = self.get_all_nodes()
...@@ -145,29 +145,69 @@ class NodeUtil: ...@@ -145,29 +145,69 @@ class NodeUtil:
def nodes(self): def nodes(self):
return list(self._nodes.values()) return list(self._nodes.values())
def get_family_by_key(self, key):
tree_nodes = set()
node = self.get_node_by_key(key)
if not node:
return []
tree_nodes.update(node._parents)
tree_nodes.add(node)
tree_nodes.update(node._all_children)
return list(tree_nodes)
# 使用给定节点生成一颗树 # 使用给定节点生成一颗树
# 找到他们的祖先节点 # 找到他们的祖先节点
# 可选找到他们的子孙节点 # 可选找到他们的子孙节点
def get_family(self, nodes, with_children=False): def get_family(self, node):
tree_nodes = set() return self.get_family_by_key(node.key)
for n in nodes:
node = self.get_node_by_key(n.key) def get_family_keys_by_key(self, key):
if not node: nodes = self.get_family_by_key(key)
continue return [n.key for n in nodes]
tree_nodes.update(node._parents)
tree_nodes.add(node)
if with_children:
tree_nodes.update(node._children)
return list(tree_nodes)
def get_nodes_parents(self, nodes, with_self=True): def get_some_nodes_family_by_keys(self, keys):
family = set()
for key in keys:
family.update(self.get_family_by_key(key))
return family
def get_some_nodes_family_keys_by_keys(self, keys):
family = self.get_some_nodes_family_by_keys(keys)
return [n.key for n in family]
def get_nodes_parents_by_key(self, key, with_self=True):
parents = set() parents = set()
for n in nodes: node = self.get_node_by_key(key)
node = self.get_node_by_key(n.key) if not node:
parents.update(set(node._parents)) return []
if with_self: parents.update(set(node._parents))
parents.add(node) if with_self:
return parents parents.add(node)
return list(parents)
def get_node_parents(self, node, with_self=True):
return self.get_nodes_parents_by_key(node.key, with_self=with_self)
def get_nodes_parents_keys_by_key(self, key, with_self=True):
nodes = self.get_nodes_parents_by_key(key, with_self=with_self)
return [n.key for n in nodes]
def get_all_children_by_key(self, key, with_self=True):
children = set()
node = self.get_node_by_key(key)
if not node:
return []
children.update(set(node._all_children))
if with_self:
children.add(node)
return list(children)
def get_children(self, node, with_self=True):
return self.get_all_children_by_key(node.key, with_self=with_self)
def get_children_keys_by_key(self, key, with_self=True):
nodes = self.get_all_children_by_key(key, with_self=with_self)
return [n.key for n in nodes]
def test_node_tree(): def test_node_tree():
......
...@@ -4,11 +4,13 @@ import os ...@@ -4,11 +4,13 @@ import os
import uuid import uuid
from django.core.cache import cache from django.core.cache import cache
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import generics, serializers from rest_framework import generics, serializers
from .http import HttpResponseTemporaryRedirect
from .const import KEY_CACHE_RESOURCES_ID from .const import KEY_CACHE_RESOURCES_ID
__all__ = [ __all__ = [
...@@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView): ...@@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView):
cache_key = KEY_CACHE_RESOURCES_ID.format(spm) cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
cache.set(cache_key, resources_id, 300) cache.set(cache_key, resources_id, 300)
return Response({'spm': spm}) return Response({'spm': spm})
@csrf_exempt
def redirect_plural_name_api(request, *args, **kwargs):
resource = kwargs.get("resource", "")
full_path = request.get_full_path()
full_path = full_path.replace(resource, resource+"s", 1)
return HttpResponseTemporaryRedirect(full_path)
# -*- coding: utf-8 -*-
#
from django.http import HttpResponse
from django.utils.encoding import iri_to_uri
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
...@@ -46,12 +46,17 @@ class TreeNode: ...@@ -46,12 +46,17 @@ class TreeNode:
def __gt__(self, other): def __gt__(self, other):
if self.isParent and not other.isParent: if self.isParent and not other.isParent:
return False result = False
elif not self.isParent and other.isParent: elif not self.isParent and other.isParent:
return True result = True
if self.pId != other.pId: elif self.pId != other.pId:
return self.pId > other.pId result = self.pId > other.pId
return self.name > other.name else:
result = self.name > other.name
return result
def __le__(self, other):
return not self.__gt__(other)
def __eq__(self, other): def __eq__(self, other):
return self.id == other.id return self.id == other.id
...@@ -74,7 +79,7 @@ class Tree: ...@@ -74,7 +79,7 @@ class Tree:
raise ValueError("Parent must not be node parent") raise ValueError("Parent must not be node parent")
node.pId = parent.id node.pId = parent.id
parent.isParent = True parent.isParent = True
self.nodes[node.id] = node self.nodes[node.key] = node
def get_nodes(self): def get_nodes(self):
return sorted(self.nodes.values()) return sorted(self.nodes.values())
......
...@@ -7,6 +7,7 @@ import logging ...@@ -7,6 +7,7 @@ import logging
import datetime import datetime
import uuid import uuid
from functools import wraps from functools import wraps
import time
import copy import copy
import ipaddress import ipaddress
...@@ -179,3 +180,18 @@ def random_string(length): ...@@ -179,3 +180,18 @@ def random_string(length):
charset = string.ascii_letters + string.digits charset = string.ascii_letters + string.digits
s = [random.choice(charset) for i in range(length)] s = [random.choice(charset) for i in range(length)]
return ''.join(s) return ''.join(s)
logger = get_logger(__name__)
def timeit(func):
def wrapper(*args, **kwargs):
logger.debug("Start call: {}".format(func.__name__))
now = time.time()
result = func(*args, **kwargs)
using = (time.time() - now) * 1000
msg = "Call {} end, using: {:.1f}ms".format(func.__name__, using)
logger.debug(msg)
return result
return wrapper
...@@ -2,7 +2,7 @@ import datetime ...@@ -2,7 +2,7 @@ import datetime
import re import re
import time import time
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponseRedirect
from django.conf import settings from django.conf import settings
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from django.utils import timezone from django.utils import timezone
...@@ -13,13 +13,14 @@ from rest_framework.response import Response ...@@ -13,13 +13,14 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.encoding import iri_to_uri
from users.models import User from users.models import User
from assets.models import Asset from assets.models import Asset
from terminal.models import Session from terminal.models import Session
from orgs.utils import current_org from orgs.utils import current_org
from common.permissions import PermissionsMixin, IsValidUser from common.permissions import PermissionsMixin, IsValidUser
from common.http import HttpResponseTemporaryRedirect
class IndexView(PermissionsMixin, TemplateView): class IndexView(PermissionsMixin, TemplateView):
...@@ -203,14 +204,6 @@ class I18NView(View): ...@@ -203,14 +204,6 @@ class I18NView(View):
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$') api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
@csrf_exempt @csrf_exempt
def redirect_format_api(request, *args, **kwargs): def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode() _path, query = request.path, request.GET.urlencode()
......
This diff is collapsed.
...@@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): ...@@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
else: else:
return Response({"error": serializer.errors}) return Response({"error": serializer.errors})
...@@ -7,148 +7,57 @@ from rest_framework.generics import ( ...@@ -7,148 +7,57 @@ from rest_framework.generics import (
) )
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer from ..hands import UserGroup
from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
RemoteAppPermissionUtil,
)
from ..hands import (
UserGroup, Node, NodeSerializer, RemoteAppSerializer,
)
from .. import serializers, const from .. import serializers, const
from .user_permission import (
UserGrantedAssetsApi, UserGrantedNodesApi, UserGrantedNodesWithAssetsApi,
UserGrantedNodesWithAssetsAsTreeApi, UserGrantedNodeAssetsApi,
)
__all__ = [ __all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
'UserGroupGrantedNodesWithAssetsAsTreeApi', 'UserGroupGrantedNodesWithAssetsAsTreeApi',
'UserGroupGrantedRemoteAppsApi',
] ]
class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedAssetsApi(UserGrantedAssetsApi):
permission_classes = (IsOrgAdmin,) def get_object(self):
serializer_class = serializers.AssetGrantedSerializer
def get_queryset(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
queryset = []
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id) user_group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(user_group) return user_group
assets = util.get_assets()
for k, v in assets.items():
k.system_users_granted = v
queryset.append(k)
return queryset
class UserGroupGrantedNodesApi(ListAPIView): class UserGroupGrantedNodesApi(UserGrantedNodesApi):
permission_classes = (IsOrgAdmin,) def get_object(self):
serializer_class = NodeSerializer user_group_id = self.kwargs.get('pk', '')
user_group = get_object_or_404(UserGroup, id=user_group_id)
def get_queryset(self): return user_group
group_id = self.kwargs.get('pk', '')
queryset = []
if group_id:
group = get_object_or_404(UserGroup, id=group_id)
util = AssetPermissionUtil(group)
nodes = util.get_nodes_with_assets()
return nodes.keys()
return queryset
class UserGroupGrantedNodesWithAssetsApi(ListAPIView): class UserGroupGrantedNodesWithAssetsApi(UserGrantedNodesWithAssetsApi):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
def get_queryset(self): def get_object(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
queryset = [] user_group = get_object_or_404(UserGroup, id=user_group_id)
return user_group
if not user_group_id:
return queryset
class UserGroupGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsAsTreeApi):
def get_object(self):
user_group_id = self.kwargs.get('pk', '')
user_group = get_object_or_404(UserGroup, id=user_group_id) user_group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(user_group) return user_group
nodes = util.get_nodes_with_assets()
for node, _assets in nodes.items():
assets = _assets.keys()
for asset, system_users in _assets.items():
asset.system_users_granted = system_users
node.assets_granted = assets
queryset.append(node)
return queryset
class UserGroupGrantedNodesWithAssetsAsTreeApi(ListAPIView):
serializer_class = TreeNodeSerializer
permission_classes = (IsOrgAdminOrAppUser,)
show_assets = True
system_user_id = None
def get(self, request, *args, **kwargs):
self.show_assets = request.query_params.get('show_assets', '1') == '1'
self.system_user_id = request.query_params.get('system_user')
return super().get(request, *args, **kwargs)
def get_queryset(self): class UserGroupGrantedNodeAssetsApi(UserGrantedNodeAssetsApi):
user_group_id = self.kwargs.get('pk', '')
queryset = []
group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(group)
if self.system_user_id:
util.filter_permissions(system_users=self.system_user_id)
nodes = util.get_nodes_with_assets()
for node, assets in nodes.items():
data = parse_node_to_tree_node(node)
queryset.append(data)
if not self.show_assets:
continue
for asset, system_users in assets.items():
data = parse_asset_to_tree_node(node, asset, system_users)
queryset.append(data)
queryset = sorted(queryset)
return queryset
class UserGroupGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
def get_queryset(self): def get_object(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
node_id = self.kwargs.get('node_id')
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(user_group)
if str(node_id) == const.UNGROUPED_NODE_ID:
node = util.tree.ungrouped_node
else:
node = get_object_or_404(Node, id=node_id)
nodes = util.get_nodes_with_assets()
assets = nodes.get(node, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
return assets
# RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView):
permission_classes = (IsOrgAdmin, )
serializer_class = RemoteAppSerializer
def get_queryset(self):
queryset = []
user_group_id = self.kwargs.get('pk')
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id) user_group = get_object_or_404(UserGroup, id=user_group_id)
util = RemoteAppPermissionUtil(user_group) return user_group
queryset = util.get_remote_apps()
return queryset
This diff is collapsed.
...@@ -13,13 +13,13 @@ from ..utils import ( ...@@ -13,13 +13,13 @@ from ..utils import (
RemoteAppPermissionUtil, construct_remote_apps_tree_root, RemoteAppPermissionUtil, construct_remote_apps_tree_root,
parse_remote_app_to_tree_node, parse_remote_app_to_tree_node,
) )
from ..hands import User, RemoteApp, RemoteAppSerializer from ..hands import User, RemoteApp, RemoteAppSerializer, UserGroup
from ..mixins import RemoteAppFilterMixin from ..mixins import RemoteAppFilterMixin
__all__ = [ __all__ = [
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi', 'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
'UserGrantedRemoteAppsAsTreeApi', 'UserGrantedRemoteAppsAsTreeApi', 'UserGroupGrantedRemoteAppsApi',
] ]
...@@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView): ...@@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView):
if remote_app not in remote_apps: if remote_app not in remote_apps:
return Response({'msg': False}, status=403) return Response({'msg': False}, status=403)
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
# RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser, )
serializer_class = RemoteAppSerializer
def get_queryset(self):
queryset = []
user_group_id = self.kwargs.get('pk')
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = RemoteAppPermissionUtil(user_group)
queryset = util.get_remote_apps()
return queryset
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002" UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002"
EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003" EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003"
EMPTY_NODE_KEY = "1:-2"
...@@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import date_expired_default, set_or_append_attr_bulk from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin from orgs.mixins import OrgModelMixin
from assets.models import Asset, SystemUser, Node
from .base import BasePermission from .base import BasePermission
...@@ -85,7 +86,11 @@ class AssetPermission(BasePermission): ...@@ -85,7 +86,11 @@ class AssetPermission(BasePermission):
@classmethod @classmethod
def get_queryset_with_prefetch(cls): def get_queryset_with_prefetch(cls):
return cls.objects.all().valid().prefetch_related('nodes', 'assets', 'system_users') return cls.objects.all().valid().prefetch_related(
models.Prefetch('nodes', queryset=Node.objects.all().only('key')),
models.Prefetch('assets', queryset=Asset.objects.all().only('id')),
models.Prefetch('system_users', queryset=SystemUser.objects.all().only('id'))
)
def get_all_assets(self): def get_all_assets(self):
assets = set(self.assets.all()) assets = set(self.assets.all())
......
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
# #
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from assets.models import Node, SystemUser from assets.models import Node, SystemUser, Asset
from assets.serializers import AssetSerializer from assets.serializers import ProtocolsField
from .asset_permission import ActionsField from .asset_permission import ActionsField
__all__ = [ __all__ = [
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'GrantedNodeSerializer',
'NodeGrantedSerializer', 'AssetGrantedSerializer', 'NodeGrantedSerializer', 'AssetGrantedSerializer',
'ActionsSerializer', 'ActionsSerializer', 'AssetSystemUserSerializer',
] ]
...@@ -23,58 +23,36 @@ class AssetSystemUserSerializer(serializers.ModelSerializer): ...@@ -23,58 +23,36 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = SystemUser model = SystemUser
fields = ( only_fields = (
'id', 'name', 'username', 'priority', "actions", 'id', 'name', 'username', 'priority',
'protocol', 'login_mode', 'protocol', 'login_mode',
) )
fields = list(only_fields) + ["actions"]
read_only_fields = fields
class AssetGrantedSerializer(AssetSerializer): class AssetGrantedSerializer(serializers.ModelSerializer):
""" """
被授权资产的数据结构 被授权资产的数据结构
""" """
protocols = ProtocolsField(label=_('Protocols'), required=False, read_only=True)
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True) system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField() system_users_join = serializers.SerializerMethodField()
system_users_only_fields = AssetSystemUserSerializer.Meta.only_fields
@staticmethod
def get_system_users_join(obj):
system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
def get_field_names(self, declared_fields, info):
fields = (
"id", "hostname", "ip", "protocols",
"system_users_granted", "is_active", "system_users_join", "os",
'domain', "platform", "comment", "org_id", "org_name",
)
return fields
class AssetPermissionNodeSerializer(serializers.ModelSerializer):
asset = AssetGrantedSerializer(required=False)
assets_amount = serializers.SerializerMethodField()
tree_id = serializers.SerializerMethodField()
tree_parent = serializers.SerializerMethodField()
class Meta: class Meta:
model = Node model = Asset
fields = [ only_fields = [
'id', 'key', 'value', 'asset', 'is_node', 'org_id', "id", "hostname", "ip", "protocols", "os", 'domain',
'tree_id', 'tree_parent', 'assets_amount', "platform", "org_id",
] ]
fields = only_fields + ['system_users_granted', 'system_users_join', "org_name"]
read_only_fields = fields
@staticmethod @staticmethod
def get_assets_amount(obj): def get_system_users_join(obj):
return obj.assets_amount system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
@staticmethod
def get_tree_id(obj):
return obj.key
@staticmethod
def get_tree_parent(obj):
return obj.parent_key
class NodeGrantedSerializer(serializers.ModelSerializer): class NodeGrantedSerializer(serializers.ModelSerializer):
...@@ -82,28 +60,19 @@ class NodeGrantedSerializer(serializers.ModelSerializer): ...@@ -82,28 +60,19 @@ class NodeGrantedSerializer(serializers.ModelSerializer):
授权资产组 授权资产组
""" """
assets_granted = AssetGrantedSerializer(many=True, read_only=True) assets_granted = AssetGrantedSerializer(many=True, read_only=True)
assets_amount = serializers.SerializerMethodField() assets_amount = serializers.ReadOnlyField()
parent = serializers.SerializerMethodField() name = serializers.ReadOnlyField(source='value')
name = serializers.SerializerMethodField()
assets_only_fields = AssetGrantedSerializer.Meta.only_fields
system_users_only_fields = AssetGrantedSerializer.system_users_only_fields
class Meta: class Meta:
model = Node model = Node
fields = [ only_fields = ['id', 'key', 'value', "org_id"]
'id', 'key', 'name', 'value', 'parent', fields = only_fields + [
'assets_granted', 'assets_amount', 'org_id', 'name', 'assets_granted', 'assets_amount',
] ]
read_only_fields = fields
@staticmethod
def get_assets_amount(obj):
return len(obj.assets_granted)
@staticmethod
def get_name(obj):
return obj.name
@staticmethod
def get_parent(obj):
return obj.parent.id
class GrantedNodeSerializer(serializers.ModelSerializer): class GrantedNodeSerializer(serializers.ModelSerializer):
...@@ -112,6 +81,7 @@ class GrantedNodeSerializer(serializers.ModelSerializer): ...@@ -112,6 +81,7 @@ class GrantedNodeSerializer(serializers.ModelSerializer):
fields = [ fields = [
'id', 'name', 'key', 'value', 'id', 'name', 'key', 'value',
] ]
read_only_fields = fields
class ActionsSerializer(serializers.Serializer): class ActionsSerializer(serializers.Serializer):
......
# coding:utf-8 # coding:utf-8
from django.urls import path from django.urls import path, re_path
from rest_framework import routers from rest_framework import routers
from common import api as capi
from .. import api from .. import api
app_name = 'perms' app_name = 'perms'
...@@ -12,26 +13,36 @@ router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remot ...@@ -12,26 +13,36 @@ router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remot
asset_permission_urlpatterns = [ asset_permission_urlpatterns = [
# 查询某个用户授权的资产和资产组 # Assets
path('user/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view()),
path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'), path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
path('user/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'), path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
path('user/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
path('user/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'), # Node as tree
path('user/nodes/children/', api.UserGrantedNodeChildrenApi.as_view(), name='my-node-children'), path('users/<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
path('user/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), path('users/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
path('user/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
path('user/<uuid:pk>/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), # Nodes
path('user/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
path('user/<uuid:pk>/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-assets-as-tree'), path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-assets-as-tree'),
# Node assets
path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
path('users/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
# Node with assets
path('users/<uuid:pk>/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'),
path('users/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'),
# Node assets as tree
path('users/<uuid:pk>/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-assets-as-tree'),
path('users/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-assets-as-tree'),
# 查询某个用户组授权的资产和资产组 # 查询某个用户组授权的资产和资产组
path('user-group/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), path('user-groups/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
path('user-group/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
path('user-group/<uuid:pk>/nodes-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'), path('user-groups/<uuid:pk>/nodes-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-assets'),
path('user-group/<uuid:pk>/nodes-assets/tree/', api.UserGroupGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-group-nodes-assets-as-tree'), path('user-groups/<uuid:pk>/nodes-assets/tree/', api.UserGroupGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-group-nodes-assets-as-tree'),
path('user-group/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), path('user-groups/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
# 用户和资产授权变更 # 用户和资产授权变更
path('asset-permissions/<uuid:pk>/user/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'), path('asset-permissions/<uuid:pk>/user/remove/', api.AssetPermissionRemoveUserApi.as_view(), name='asset-permission-remove-user'),
...@@ -40,25 +51,25 @@ asset_permission_urlpatterns = [ ...@@ -40,25 +51,25 @@ asset_permission_urlpatterns = [
path('asset-permissions/<uuid:pk>/asset/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'), path('asset-permissions/<uuid:pk>/asset/add/', api.AssetPermissionAddAssetApi.as_view(), name='asset-permission-add-asset'),
# 验证用户是否有某个资产和系统用户的权限 # 验证用户是否有某个资产和系统用户的权限
path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), path('asset-permissions/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
path('asset-permission/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
] ]
remote_app_permission_urlpatterns = [ remote_app_permission_urlpatterns = [
# 查询用户授权的RemoteApp # 查询用户授权的RemoteApp
path('user/<uuid:pk>/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'), path('users/<uuid:pk>/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'),
path('user/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'), path('users/remote-apps/', api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'),
# 获取用户授权的RemoteApp树 # 获取用户授权的RemoteApp树
path('user/<uuid:pk>/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='user-remote-apps-as-tree'), path('users/<uuid:pk>/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='user-remote-apps-as-tree'),
path('user/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='my-remote-apps-as-tree'), path('users/remote-apps/tree/', api.UserGrantedRemoteAppsAsTreeApi.as_view(), name='my-remote-apps-as-tree'),
# 查询用户组授权的RemoteApp # 查询用户组授权的RemoteApp
path('user-group/<uuid:pk>/remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'), path('user-groups/<uuid:pk>/remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'),
# 校验用户对RemoteApp的权限 # 校验用户对RemoteApp的权限
path('remote-app-permission/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'), path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'),
# 用户和RemoteApp变更 # 用户和RemoteApp变更
path('remote-app-permissions/<uuid:pk>/user/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'), path('remote-app-permissions/<uuid:pk>/user/add/', api.RemoteAppPermissionAddUserApi.as_view(), name='remote-app-permission-add-user'),
...@@ -67,7 +78,11 @@ remote_app_permission_urlpatterns = [ ...@@ -67,7 +78,11 @@ remote_app_permission_urlpatterns = [
path('remote-app-permissions/<uuid:pk>/remote-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'), path('remote-app-permissions/<uuid:pk>/remote-app/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'),
] ]
urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns old_version_urlpatterns = [
re_path('(?P<resource>user|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api)
]
urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns + old_version_urlpatterns
urlpatterns += router.urls urlpatterns += router.urls
This diff is collapsed.
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from common.struct import Stack
from common.utils import timeit
from assets.utils import NodeUtil
class PermStackUtilMixin:
def __init__(self, debug=False):
self.stack = None
self._nodes = {}
self._debug = debug
@staticmethod
def sorted_by(node_dict):
return [int(i) for i in node_dict['key'].split(':')]
@staticmethod
def is_children(item1, item2):
key1 = item1["key"]
key2 = item2["key"]
return key2.startswith(key1 + ':') and (
len(key2.split(':')) - len(key1.split(':'))
) == 1
def debug(self, msg):
self._debug and print(msg)
class PermSystemUserNodeUtil(PermStackUtilMixin):
"""
self._nodes: {node.key: {system_user.id: actions,}}
"""
@timeit
def get_nodes_family_and_system_users(self, nodes_with_system_users):
"""
返回所有nodes_with_system_users中的node的家族节点的信息,
并子会继承祖先的系统用户和actions信息
:param nodes_with_system_users:
{node.key: {system_user.id: actions,}, }
:return:
{node.key: {system_user.id: actions,}, }
"""
node_util = NodeUtil()
_nodes_keys = nodes_with_system_users.keys()
family_keys = node_util.get_some_nodes_family_keys_by_keys(_nodes_keys)
nodes_items = []
for i in family_keys:
system_users = nodes_with_system_users.get(i, defaultdict(int))
item = {"key": i, "system_users": system_users}
nodes_items.append(item)
# 按照父子关系排序
nodes_items.sort(key=self.sorted_by)
nodes_items.append({"key": "", "system_users": defaultdict(int)})
self.stack = Stack()
for item in nodes_items:
self.debug("准备: {} 栈顶: {}".format(
item['key'], self.stack.top["key"] if self.stack.top else None)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while self.stack.top and not self.is_children(self.stack.top, item):
# 出栈
self.pop_from_stack_system_users()
# 入栈
self.push_to_stack_system_users(item)
# 出栈最后一个
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
return self._nodes
def push_to_stack_system_users(self, item):
"""
:param item:
{"key": node.key, "system_users": {system_user.id: actions,},}
"""
if not self.stack.is_empty():
item_system_users = item["system_users"]
for system_user, action in self.stack.top["system_users"].items():
# 更新栈顶的系统用户和action到将要入栈的item中
item_system_users[system_user] |= action
item["system_users"] = item_system_users
self.debug("入栈: {}".format(item['key']))
self.stack.push(item)
# 出栈
def pop_from_stack_system_users(self):
_node = self.stack.pop()
self._nodes[_node["key"]] = _node["system_users"]
self.debug("出栈: {} 栈顶: {}".format(_node['key'], self.stack.top['key'] if self.stack.top else None))
class PermAssetsAmountUtil(PermStackUtilMixin):
def push_to_stack_nodes_amount(self, item):
self.debug("入栈: {}".format(item['key']))
self.stack.push(item)
def pop_from_stack_nodes_amount(self):
_node = self.stack.pop()
self.debug("出栈: {} 栈顶: {}".format(
_node['key'], self.stack.top['key'] if self.stack.top else None)
)
_node["assets_amount"] = len(_node["all_assets"] | _node["assets"])
self._nodes[_node.pop("key")] = _node
if not self.stack.top:
return
self.stack.top["all_assets"]\
.update(_node["all_assets"] | _node["assets"])
def compute_nodes_assets_amount(self, nodes_with_assets):
self.stack = Stack()
nodes_items = []
for key, values in nodes_with_assets.items():
nodes_items.append({
"key": key, "assets": values["assets"],
"all_assets": values["all_assets"], "assets_amount": 0
})
nodes_items.sort(key=self.sorted_by)
nodes_items.append({"key": "", "assets": set(), "all_assets": set(), "assets_amount": 0})
self.stack = Stack()
for item in nodes_items:
self.debug("准备: {} 栈顶: {}".format(
item['key'], self.stack.top["key"] if self.stack.top else None)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while self.stack.top and not self.is_children(self.stack.top, item):
self.pop_from_stack_nodes_amount()
self.push_to_stack_nodes_amount(item)
# 出栈最后一个
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
return self._nodes
\ No newline at end of file
This diff is collapsed.
...@@ -29,7 +29,6 @@ ...@@ -29,7 +29,6 @@
<div class="file-manager "> <div class="file-manager ">
<div id="assetTree" class="ztree"> <div id="assetTree" class="ztree">
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
</div> </div>
...@@ -43,7 +42,6 @@ ...@@ -43,7 +42,6 @@
<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 'Active' %}</th>
<th class="text-center">{% trans 'System users' %}</th> <th class="text-center">{% trans 'System users' %}</th>
</tr> </tr>
</thead> </thead>
...@@ -64,7 +62,7 @@ var zTree; ...@@ -64,7 +62,7 @@ var zTree;
var inited = false; var inited = false;
var url; var url;
var asset_table; var asset_table;
var treeUrl = "{% url 'api-perms:user-nodes-assets-as-tree' pk=object.id %}?show_assets=0&cache_policy=1"; var treeUrl = "{% url 'api-perms:user-nodes-as-tree' pk=object.id %}?&cache_policy=1";
function initTable() { function initTable() {
if (inited){ if (inited){
...@@ -83,13 +81,6 @@ function initTable() { ...@@ -83,13 +81,6 @@ function initTable() {
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
{targets: 3, createdCell: function (td, cellData) { {targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData) {
var users = []; var users = [];
$.each(cellData, function (id, data) { $.each(cellData, function (id, data) {
var name = htmlEscape(data.name); var name = htmlEscape(data.name);
...@@ -101,7 +92,6 @@ function initTable() { ...@@ -101,7 +92,6 @@ function initTable() {
ajax_url: url, ajax_url: url,
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false} {data: "system_users_granted", orderable: false}
] ]
}; };
......
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