mirror of https://github.com/jumpserver/jumpserver
Merge branch 'dev' of github.com:jumpserver/jumpserver into dev
commit
21ffa8b28a
|
@ -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
|
||||||
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:
|
||||||
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):
|
||||||
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:
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
'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')},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,19 @@ from orgs.mixins import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
||||||
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
|
|
||||||
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CommandFilter
|
model = CommandFilter
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
list_serializer_class = AdaptedBulkListSerializer
|
||||||
fields = '__all__'
|
fields = [
|
||||||
|
'id', 'name', 'org_id', 'org_name', 'is_active', 'comment',
|
||||||
|
'created_by', 'date_created', 'date_updated', 'rules', 'system_users'
|
||||||
|
]
|
||||||
|
|
||||||
|
extra_kwargs = {
|
||||||
|
'rules': {'read_only': True},
|
||||||
|
'system_users': {'read_only': True}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from common.serializers import AdaptedBulkListSerializer
|
||||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
from ..models import Domain, Gateway
|
from ..models import Domain, Gateway
|
||||||
|
from .base import AuthSerializerMixin
|
||||||
|
|
||||||
|
|
||||||
class DomainSerializer(BulkOrgResourceModelSerializer):
|
class DomainSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
@ -26,14 +27,14 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
|
||||||
return obj.gateway_set.all().count()
|
return obj.gateway_set.all().count()
|
||||||
|
|
||||||
|
|
||||||
class GatewaySerializer(BulkOrgResourceModelSerializer):
|
class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Gateway
|
model = Gateway
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
list_serializer_class = AdaptedBulkListSerializer
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'ip', 'port', 'protocol', 'username',
|
'id', 'name', 'ip', 'port', 'protocol', 'username', 'password',
|
||||||
'domain', 'is_active', 'date_created', 'date_updated',
|
'private_key', 'public_key', 'domain', 'is_active', 'date_created',
|
||||||
'created_by', 'comment',
|
'date_updated', 'created_by', 'comment',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
'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):
|
||||||
@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'])
|
||||||
|
|
|
@ -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 @@
|
||||||
<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() {
|
||||||
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() {
|
||||||
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"}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,3 +18,29 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
})
|
||||||
|
.on("submit", "form", function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
var the_url = '{% url 'api-assets:cmd-filter-list' %}';
|
||||||
|
var redirect_to = '{% url "assets:cmd-filter-list" %}';
|
||||||
|
var method = "POST";
|
||||||
|
{% if type == "update" %}
|
||||||
|
the_url = '{% url 'api-assets:cmd-filter-detail' pk=object.id %}';
|
||||||
|
method = "PUT";
|
||||||
|
{% endif %}
|
||||||
|
var form = $("form");
|
||||||
|
var data = form.serializeObject();
|
||||||
|
var props = {
|
||||||
|
url: the_url,
|
||||||
|
data: data,
|
||||||
|
method: method,
|
||||||
|
form: form,
|
||||||
|
redirect_to: redirect_to
|
||||||
|
};
|
||||||
|
formSubmit(props);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -95,6 +95,32 @@ function protocolChange() {
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
protocolChange();
|
protocolChange();
|
||||||
})
|
})
|
||||||
|
.on("submit", "form", function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
var form = $("form");
|
||||||
|
var data = form.serializeObject();
|
||||||
|
data["private_key"] = $("#id_private_key_file").data('file');
|
||||||
|
var method = "POST";
|
||||||
|
var the_url = '{% url "api-assets:gateway-list" %}';
|
||||||
|
var redirect_to = '{% url "assets:domain-gateway-list" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", data.domain);
|
||||||
|
{% if type == "update" %}
|
||||||
|
the_url = '{% url 'api-assets:gateway-detail' pk=object.id %}';
|
||||||
|
method = "PUT";
|
||||||
|
{% endif %}
|
||||||
|
var props = {
|
||||||
|
url:the_url,
|
||||||
|
data:data,
|
||||||
|
method:method,
|
||||||
|
form:form,
|
||||||
|
redirect_to:redirect_to
|
||||||
|
};
|
||||||
|
formSubmit(props);
|
||||||
|
})
|
||||||
|
.on('change', '#id_private_key_file', function () {
|
||||||
|
readFile($(this)).on("onload", function (evt, data) {
|
||||||
|
$(this).attr("data-file", data)
|
||||||
|
})
|
||||||
|
})
|
||||||
.on('change', protocol_id, function(){
|
.on('change', protocol_id, function(){
|
||||||
protocolChange();
|
protocolChange();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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() {
|
||||||
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() {
|
||||||
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 @@
|
||||||
<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 @@
|
||||||
|
|
||||||
{% 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() {
|
||||||
$(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() {
|
||||||
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:
|
||||||
_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:
|
||||||
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)
|
|
||||||
if not node:
|
|
||||||
continue
|
|
||||||
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_family_keys_by_key(self, key):
|
||||||
|
nodes = self.get_family_by_key(key)
|
||||||
|
return [n.key for n in nodes]
|
||||||
|
|
||||||
|
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:
|
||||||
|
return []
|
||||||
parents.update(set(node._parents))
|
parents.update(set(node._parents))
|
||||||
if with_self:
|
if with_self:
|
||||||
parents.add(node)
|
parents.add(node)
|
||||||
return parents
|
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():
|
||||||
|
|
|
@ -47,6 +47,7 @@ class CommandFilterCreateView(PermissionsMixin, CreateView):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Create command filter'),
|
'action': _('Create command filter'),
|
||||||
|
'type': 'create'
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
@ -64,6 +65,7 @@ class CommandFilterUpdateView(PermissionsMixin, UpdateView):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Update command filter'),
|
'action': _('Update command filter'),
|
||||||
|
'type': 'update'
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -132,6 +132,7 @@ class DomainGatewayCreateView(PermissionsMixin, CreateView):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Create gateway'),
|
'action': _('Create gateway'),
|
||||||
|
'type': 'create'
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
@ -152,6 +153,7 @@ class DomainGatewayUpdateView(PermissionsMixin, UpdateView):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
'action': _('Update gateway'),
|
'action': _('Update gateway'),
|
||||||
|
"type": "update"
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -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):
|
||||||
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)
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# -*- 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:
|
||||||
|
|
||||||
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:
|
||||||
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
|
||||||
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):
|
||||||
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
|
||||||
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
|
||||||
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):
|
||||||
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()
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-07-08 15:32+0800\n"
|
"POT-Creation-Date: 2019-07-11 11:23+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
||||||
|
@ -76,10 +76,10 @@ msgstr "运行参数"
|
||||||
#: applications/templates/applications/remote_app_list.html:22
|
#: applications/templates/applications/remote_app_list.html:22
|
||||||
#: applications/templates/applications/user_remote_app_list.html:18
|
#: applications/templates/applications/user_remote_app_list.html:18
|
||||||
#: assets/forms/domain.py:15 assets/forms/label.py:13
|
#: assets/forms/domain.py:15 assets/forms/label.py:13
|
||||||
#: assets/models/asset.py:286 assets/models/authbook.py:24
|
#: assets/models/asset.py:342 assets/models/authbook.py:24
|
||||||
#: assets/serializers/admin_user.py:35 assets/serializers/asset_user.py:81
|
#: assets/serializers/admin_user.py:35 assets/serializers/asset_user.py:81
|
||||||
#: assets/serializers/system_user.py:30
|
#: assets/serializers/system_user.py:30
|
||||||
#: assets/templates/assets/admin_user_list.html:49
|
#: assets/templates/assets/admin_user_list.html:47
|
||||||
#: assets/templates/assets/domain_detail.html:60
|
#: assets/templates/assets/domain_detail.html:60
|
||||||
#: assets/templates/assets/domain_list.html:26
|
#: assets/templates/assets/domain_list.html:26
|
||||||
#: assets/templates/assets/label_list.html:16
|
#: assets/templates/assets/label_list.html:16
|
||||||
|
@ -95,7 +95,7 @@ msgstr "运行参数"
|
||||||
#: terminal/templates/terminal/command_list.html:66
|
#: terminal/templates/terminal/command_list.html:66
|
||||||
#: terminal/templates/terminal/session_list.html:28
|
#: terminal/templates/terminal/session_list.html:28
|
||||||
#: terminal/templates/terminal/session_list.html:72
|
#: terminal/templates/terminal/session_list.html:72
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:114
|
#: xpack/plugins/change_auth_plan/forms.py:115
|
||||||
#: xpack/plugins/change_auth_plan/models.py:413
|
#: xpack/plugins/change_auth_plan/models.py:413
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
|
||||||
|
@ -139,7 +139,7 @@ msgstr "系统用户"
|
||||||
#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
|
#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
|
||||||
#: assets/models/group.py:20 assets/models/label.py:18
|
#: assets/models/group.py:20 assets/models/label.py:18
|
||||||
#: assets/templates/assets/admin_user_detail.html:56
|
#: assets/templates/assets/admin_user_detail.html:56
|
||||||
#: assets/templates/assets/admin_user_list.html:47
|
#: assets/templates/assets/admin_user_list.html:45
|
||||||
#: assets/templates/assets/cmd_filter_detail.html:61
|
#: assets/templates/assets/cmd_filter_detail.html:61
|
||||||
#: assets/templates/assets/cmd_filter_list.html:24
|
#: assets/templates/assets/cmd_filter_list.html:24
|
||||||
#: assets/templates/assets/domain_detail.html:56
|
#: assets/templates/assets/domain_detail.html:56
|
||||||
|
@ -173,7 +173,7 @@ msgstr "系统用户"
|
||||||
#: users/templates/users/user_list.html:35
|
#: users/templates/users/user_list.html:35
|
||||||
#: users/templates/users/user_profile.html:51
|
#: users/templates/users/user_profile.html:51
|
||||||
#: users/templates/users/user_pubkey_update.html:53
|
#: users/templates/users/user_pubkey_update.html:53
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:97
|
#: xpack/plugins/change_auth_plan/forms.py:98
|
||||||
#: xpack/plugins/change_auth_plan/models.py:61
|
#: xpack/plugins/change_auth_plan/models.py:61
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
|
||||||
|
@ -205,7 +205,7 @@ msgstr "参数"
|
||||||
|
|
||||||
#: applications/models/remote_app.py:43
|
#: applications/models/remote_app.py:43
|
||||||
#: applications/templates/applications/remote_app_detail.html:77
|
#: applications/templates/applications/remote_app_detail.html:77
|
||||||
#: assets/models/asset.py:151 assets/models/base.py:36
|
#: assets/models/asset.py:221 assets/models/base.py:36
|
||||||
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
|
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
|
||||||
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
|
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
|
||||||
#: assets/templates/assets/admin_user_detail.html:68
|
#: assets/templates/assets/admin_user_detail.html:68
|
||||||
|
@ -229,7 +229,7 @@ msgstr "创建者"
|
||||||
# msgstr "创建者"
|
# msgstr "创建者"
|
||||||
#: applications/models/remote_app.py:46
|
#: applications/models/remote_app.py:46
|
||||||
#: applications/templates/applications/remote_app_detail.html:73
|
#: applications/templates/applications/remote_app_detail.html:73
|
||||||
#: assets/models/asset.py:152 assets/models/base.py:34
|
#: assets/models/asset.py:222 assets/models/base.py:34
|
||||||
#: assets/models/cluster.py:26 assets/models/domain.py:23
|
#: assets/models/cluster.py:26 assets/models/domain.py:23
|
||||||
#: assets/models/group.py:22 assets/models/label.py:25
|
#: assets/models/group.py:22 assets/models/label.py:25
|
||||||
#: assets/templates/assets/admin_user_detail.html:64
|
#: assets/templates/assets/admin_user_detail.html:64
|
||||||
|
@ -257,12 +257,12 @@ msgstr "创建日期"
|
||||||
#: applications/templates/applications/remote_app_detail.html:81
|
#: applications/templates/applications/remote_app_detail.html:81
|
||||||
#: applications/templates/applications/remote_app_list.html:24
|
#: applications/templates/applications/remote_app_list.html:24
|
||||||
#: applications/templates/applications/user_remote_app_list.html:20
|
#: applications/templates/applications/user_remote_app_list.html:20
|
||||||
#: assets/models/asset.py:153 assets/models/base.py:33
|
#: assets/models/asset.py:223 assets/models/base.py:33
|
||||||
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
|
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
|
||||||
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
|
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
|
||||||
#: assets/models/domain.py:53 assets/models/group.py:23
|
#: assets/models/domain.py:53 assets/models/group.py:23
|
||||||
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
|
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
|
||||||
#: assets/templates/assets/admin_user_list.html:53
|
#: assets/templates/assets/admin_user_list.html:51
|
||||||
#: assets/templates/assets/asset_detail.html:132
|
#: assets/templates/assets/asset_detail.html:132
|
||||||
#: assets/templates/assets/cmd_filter_detail.html:65
|
#: assets/templates/assets/cmd_filter_detail.html:65
|
||||||
#: assets/templates/assets/cmd_filter_list.html:27
|
#: assets/templates/assets/cmd_filter_list.html:27
|
||||||
|
@ -412,8 +412,8 @@ msgstr "详情"
|
||||||
#: applications/templates/applications/remote_app_list.html:56
|
#: applications/templates/applications/remote_app_list.html:56
|
||||||
#: assets/templates/assets/_asset_user_list.html:70
|
#: assets/templates/assets/_asset_user_list.html:70
|
||||||
#: assets/templates/assets/admin_user_detail.html:24
|
#: assets/templates/assets/admin_user_detail.html:24
|
||||||
#: assets/templates/assets/admin_user_list.html:29
|
#: assets/templates/assets/admin_user_list.html:27
|
||||||
#: assets/templates/assets/admin_user_list.html:114
|
#: assets/templates/assets/admin_user_list.html:112
|
||||||
#: assets/templates/assets/asset_detail.html:27
|
#: assets/templates/assets/asset_detail.html:27
|
||||||
#: assets/templates/assets/asset_list.html:78
|
#: assets/templates/assets/asset_list.html:78
|
||||||
#: assets/templates/assets/asset_list.html:169
|
#: assets/templates/assets/asset_list.html:169
|
||||||
|
@ -456,7 +456,7 @@ msgstr "更新"
|
||||||
#: applications/templates/applications/remote_app_detail.html:25
|
#: applications/templates/applications/remote_app_detail.html:25
|
||||||
#: applications/templates/applications/remote_app_list.html:57
|
#: applications/templates/applications/remote_app_list.html:57
|
||||||
#: assets/templates/assets/admin_user_detail.html:28
|
#: assets/templates/assets/admin_user_detail.html:28
|
||||||
#: assets/templates/assets/admin_user_list.html:115
|
#: assets/templates/assets/admin_user_list.html:113
|
||||||
#: assets/templates/assets/asset_detail.html:31
|
#: assets/templates/assets/asset_detail.html:31
|
||||||
#: assets/templates/assets/asset_list.html:170
|
#: assets/templates/assets/asset_list.html:170
|
||||||
#: assets/templates/assets/cmd_filter_detail.html:33
|
#: assets/templates/assets/cmd_filter_detail.html:33
|
||||||
|
@ -515,7 +515,7 @@ msgstr "创建远程应用"
|
||||||
#: applications/templates/applications/user_remote_app_list.html:21
|
#: applications/templates/applications/user_remote_app_list.html:21
|
||||||
#: assets/models/cmd_filter.py:54
|
#: assets/models/cmd_filter.py:54
|
||||||
#: assets/templates/assets/_asset_user_list.html:20
|
#: assets/templates/assets/_asset_user_list.html:20
|
||||||
#: assets/templates/assets/admin_user_list.html:54
|
#: assets/templates/assets/admin_user_list.html:52
|
||||||
#: assets/templates/assets/asset_list.html:100
|
#: assets/templates/assets/asset_list.html:100
|
||||||
#: assets/templates/assets/cmd_filter_list.html:28
|
#: assets/templates/assets/cmd_filter_list.html:28
|
||||||
#: assets/templates/assets/cmd_filter_rule_list.html:63
|
#: assets/templates/assets/cmd_filter_rule_list.html:63
|
||||||
|
@ -598,25 +598,21 @@ msgid "Test if the assets under the node are connectable: {}"
|
||||||
msgstr "测试节点下资产是否可连接: {}"
|
msgstr "测试节点下资产是否可连接: {}"
|
||||||
|
|
||||||
#: assets/const.py:77 assets/models/utils.py:43
|
#: assets/const.py:77 assets/models/utils.py:43
|
||||||
#: assets/templates/assets/admin_user_list.html:51
|
|
||||||
#: assets/templates/assets/system_user_list.html:57
|
|
||||||
msgid "Unreachable"
|
msgid "Unreachable"
|
||||||
msgstr "不可达"
|
msgstr "不可达"
|
||||||
|
|
||||||
#: assets/const.py:78 assets/models/utils.py:44
|
#: assets/const.py:78 assets/models/utils.py:44
|
||||||
#: assets/templates/assets/admin_user_list.html:50
|
|
||||||
#: assets/templates/assets/asset_list.html:99
|
#: assets/templates/assets/asset_list.html:99
|
||||||
#: assets/templates/assets/system_user_list.html:56
|
|
||||||
#: users/templates/users/user_group_granted_asset.html:47
|
#: users/templates/users/user_group_granted_asset.html:47
|
||||||
msgid "Reachable"
|
msgid "Reachable"
|
||||||
msgstr "可连接"
|
msgstr "可连接"
|
||||||
|
|
||||||
#: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:9
|
#: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:9
|
||||||
#: xpack/plugins/license/models.py:79
|
#: xpack/plugins/license/models.py:78
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr "未知"
|
msgstr "未知"
|
||||||
|
|
||||||
#: assets/forms/asset.py:24 assets/models/asset.py:117
|
#: assets/forms/asset.py:24 assets/models/asset.py:187
|
||||||
#: assets/models/domain.py:50
|
#: assets/models/domain.py:50
|
||||||
#: assets/templates/assets/domain_gateway_list.html:69
|
#: assets/templates/assets/domain_gateway_list.html:69
|
||||||
#: assets/templates/assets/user_asset_list.html:168
|
#: assets/templates/assets/user_asset_list.html:168
|
||||||
|
@ -624,7 +620,7 @@ msgstr "未知"
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "端口"
|
msgstr "端口"
|
||||||
|
|
||||||
#: assets/forms/asset.py:45 assets/models/asset.py:122
|
#: assets/forms/asset.py:45 assets/models/asset.py:192
|
||||||
#: assets/models/user.py:107 assets/templates/assets/asset_detail.html:190
|
#: assets/models/user.py:107 assets/templates/assets/asset_detail.html:190
|
||||||
#: assets/templates/assets/asset_detail.html:198
|
#: assets/templates/assets/asset_detail.html:198
|
||||||
#: assets/templates/assets/system_user_assets.html:83
|
#: assets/templates/assets/system_user_assets.html:83
|
||||||
|
@ -633,7 +629,7 @@ msgstr "端口"
|
||||||
msgid "Nodes"
|
msgid "Nodes"
|
||||||
msgstr "节点"
|
msgstr "节点"
|
||||||
|
|
||||||
#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:126
|
#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:196
|
||||||
#: assets/models/cluster.py:19 assets/models/user.py:65
|
#: assets/models/cluster.py:19 assets/models/user.py:65
|
||||||
#: assets/templates/assets/asset_detail.html:76 templates/_nav.html:24
|
#: assets/templates/assets/asset_detail.html:76 templates/_nav.html:24
|
||||||
#: xpack/plugins/cloud/models.py:124
|
#: xpack/plugins/cloud/models.py:124
|
||||||
|
@ -651,7 +647,7 @@ msgstr "管理用户"
|
||||||
msgid "Label"
|
msgid "Label"
|
||||||
msgstr "标签"
|
msgstr "标签"
|
||||||
|
|
||||||
#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:121
|
#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:191
|
||||||
#: assets/models/domain.py:26 assets/models/domain.py:52
|
#: assets/models/domain.py:26 assets/models/domain.py:52
|
||||||
#: assets/templates/assets/asset_detail.html:80
|
#: assets/templates/assets/asset_detail.html:80
|
||||||
#: assets/templates/assets/user_asset_list.html:173
|
#: assets/templates/assets/user_asset_list.html:173
|
||||||
|
@ -660,14 +656,14 @@ msgid "Domain"
|
||||||
msgstr "网域"
|
msgstr "网域"
|
||||||
|
|
||||||
#: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93
|
#: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93
|
||||||
#: assets/forms/asset.py:128 assets/models/node.py:255
|
#: assets/forms/asset.py:128 assets/models/node.py:254
|
||||||
#: assets/templates/assets/asset_create.html:42
|
#: assets/templates/assets/asset_create.html:42
|
||||||
#: perms/forms/asset_permission.py:71 perms/forms/asset_permission.py:78
|
#: perms/forms/asset_permission.py:71 perms/forms/asset_permission.py:78
|
||||||
#: perms/models/asset_permission.py:101
|
#: perms/models/asset_permission.py:101
|
||||||
#: perms/templates/perms/asset_permission_list.html:49
|
#: perms/templates/perms/asset_permission_list.html:49
|
||||||
#: perms/templates/perms/asset_permission_list.html:70
|
#: perms/templates/perms/asset_permission_list.html:70
|
||||||
#: perms/templates/perms/asset_permission_list.html:120
|
#: perms/templates/perms/asset_permission_list.html:120
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:115
|
#: xpack/plugins/change_auth_plan/forms.py:116
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15
|
||||||
#: xpack/plugins/cloud/models.py:123
|
#: xpack/plugins/cloud/models.py:123
|
||||||
|
@ -696,7 +692,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
|
||||||
#: assets/forms/asset.py:108 assets/forms/asset.py:112
|
#: assets/forms/asset.py:108 assets/forms/asset.py:112
|
||||||
#: assets/forms/domain.py:17 assets/forms/label.py:15
|
#: assets/forms/domain.py:17 assets/forms/label.py:15
|
||||||
#: perms/templates/perms/asset_permission_asset.html:88
|
#: perms/templates/perms/asset_permission_asset.html:88
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:105
|
#: xpack/plugins/change_auth_plan/forms.py:106
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:84
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:84
|
||||||
msgid "Select assets"
|
msgid "Select assets"
|
||||||
msgstr "选择资产"
|
msgstr "选择资产"
|
||||||
|
@ -719,7 +715,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:21
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:21
|
||||||
#: assets/templates/assets/_asset_user_list.html:16
|
#: assets/templates/assets/_asset_user_list.html:16
|
||||||
#: assets/templates/assets/admin_user_detail.html:60
|
#: assets/templates/assets/admin_user_detail.html:60
|
||||||
#: assets/templates/assets/admin_user_list.html:48
|
#: assets/templates/assets/admin_user_list.html:46
|
||||||
#: assets/templates/assets/domain_gateway_list.html:71
|
#: assets/templates/assets/domain_gateway_list.html:71
|
||||||
#: assets/templates/assets/system_user_detail.html:62
|
#: assets/templates/assets/system_user_detail.html:62
|
||||||
#: assets/templates/assets/system_user_list.html:52 audits/models.py:94
|
#: assets/templates/assets/system_user_list.html:52 audits/models.py:94
|
||||||
|
@ -734,7 +730,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
|
||||||
#: users/templates/users/user_detail.html:67
|
#: users/templates/users/user_detail.html:67
|
||||||
#: users/templates/users/user_list.html:36
|
#: users/templates/users/user_list.html:36
|
||||||
#: users/templates/users/user_profile.html:47
|
#: users/templates/users/user_profile.html:47
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:99
|
#: xpack/plugins/change_auth_plan/forms.py:100
|
||||||
#: xpack/plugins/change_auth_plan/models.py:63
|
#: xpack/plugins/change_auth_plan/models.py:63
|
||||||
#: xpack/plugins/change_auth_plan/models.py:409
|
#: xpack/plugins/change_auth_plan/models.py:409
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
|
||||||
|
@ -809,7 +805,7 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写"
|
||||||
msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"
|
msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"
|
||||||
msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
|
msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
|
||||||
|
|
||||||
#: assets/models/asset.py:112 assets/models/domain.py:49
|
#: assets/models/asset.py:182 assets/models/domain.py:49
|
||||||
#: assets/serializers/asset_user.py:28
|
#: assets/serializers/asset_user.py:28
|
||||||
#: assets/templates/assets/_asset_list_modal.html:46
|
#: assets/templates/assets/_asset_list_modal.html:46
|
||||||
#: assets/templates/assets/_asset_user_list.html:15
|
#: assets/templates/assets/_asset_user_list.html:15
|
||||||
|
@ -826,7 +822,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
|
||||||
msgid "IP"
|
msgid "IP"
|
||||||
msgstr "IP"
|
msgstr "IP"
|
||||||
|
|
||||||
#: assets/models/asset.py:113 assets/serializers/asset_user.py:27
|
#: assets/models/asset.py:183 assets/serializers/asset_user.py:27
|
||||||
#: assets/templates/assets/_asset_list_modal.html:45
|
#: assets/templates/assets/_asset_list_modal.html:45
|
||||||
#: assets/templates/assets/_asset_user_auth_update_modal.html:9
|
#: assets/templates/assets/_asset_user_auth_update_modal.html:9
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:15
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:15
|
||||||
|
@ -843,101 +839,100 @@ msgstr "IP"
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
msgstr "主机名"
|
msgstr "主机名"
|
||||||
|
|
||||||
#: assets/models/asset.py:116 assets/models/domain.py:51
|
#: assets/models/asset.py:186 assets/models/domain.py:51
|
||||||
#: assets/models/user.py:110 assets/templates/assets/asset_detail.html:72
|
#: assets/models/user.py:110 assets/templates/assets/asset_detail.html:72
|
||||||
#: assets/templates/assets/domain_gateway_list.html:70
|
#: assets/templates/assets/domain_gateway_list.html:70
|
||||||
#: assets/templates/assets/system_user_detail.html:70
|
#: assets/templates/assets/system_user_detail.html:70
|
||||||
#: assets/templates/assets/system_user_list.html:53
|
#: assets/templates/assets/system_user_list.html:53
|
||||||
#: assets/templates/assets/user_asset_list.html:169
|
#: assets/templates/assets/user_asset_list.html:169
|
||||||
#: terminal/templates/terminal/session_list.html:31
|
#: terminal/templates/terminal/session_list.html:31
|
||||||
#: terminal/templates/terminal/session_list.html:75
|
|
||||||
msgid "Protocol"
|
msgid "Protocol"
|
||||||
msgstr "协议"
|
msgstr "协议"
|
||||||
|
|
||||||
#: assets/models/asset.py:119 assets/serializers/asset.py:63
|
#: assets/models/asset.py:189 assets/serializers/asset.py:63
|
||||||
#: assets/templates/assets/asset_create.html:24
|
#: assets/templates/assets/asset_create.html:24
|
||||||
msgid "Protocols"
|
msgid "Protocols"
|
||||||
msgstr "协议组"
|
msgstr "协议组"
|
||||||
|
|
||||||
#: assets/models/asset.py:120 assets/templates/assets/asset_detail.html:104
|
#: assets/models/asset.py:190 assets/templates/assets/asset_detail.html:104
|
||||||
#: assets/templates/assets/user_asset_list.html:170
|
#: assets/templates/assets/user_asset_list.html:170
|
||||||
msgid "Platform"
|
msgid "Platform"
|
||||||
msgstr "系统平台"
|
msgstr "系统平台"
|
||||||
|
|
||||||
#: assets/models/asset.py:123 assets/models/cmd_filter.py:21
|
#: assets/models/asset.py:193 assets/models/cmd_filter.py:21
|
||||||
#: assets/models/domain.py:54 assets/models/label.py:22
|
#: assets/models/domain.py:54 assets/models/label.py:22
|
||||||
#: assets/templates/assets/asset_detail.html:112
|
#: assets/templates/assets/asset_detail.html:112
|
||||||
#: assets/templates/assets/user_asset_list.html:174
|
#: assets/templates/assets/user_asset_list.html:174
|
||||||
msgid "Is active"
|
msgid "Is active"
|
||||||
msgstr "激活"
|
msgstr "激活"
|
||||||
|
|
||||||
#: assets/models/asset.py:129 assets/templates/assets/asset_detail.html:68
|
#: assets/models/asset.py:199 assets/templates/assets/asset_detail.html:68
|
||||||
msgid "Public IP"
|
msgid "Public IP"
|
||||||
msgstr "公网IP"
|
msgstr "公网IP"
|
||||||
|
|
||||||
#: assets/models/asset.py:130 assets/templates/assets/asset_detail.html:120
|
#: assets/models/asset.py:200 assets/templates/assets/asset_detail.html:120
|
||||||
msgid "Asset number"
|
msgid "Asset number"
|
||||||
msgstr "资产编号"
|
msgstr "资产编号"
|
||||||
|
|
||||||
#: assets/models/asset.py:133 assets/templates/assets/asset_detail.html:84
|
#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:84
|
||||||
msgid "Vendor"
|
msgid "Vendor"
|
||||||
msgstr "制造商"
|
msgstr "制造商"
|
||||||
|
|
||||||
#: assets/models/asset.py:134 assets/templates/assets/asset_detail.html:88
|
#: assets/models/asset.py:204 assets/templates/assets/asset_detail.html:88
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "型号"
|
msgstr "型号"
|
||||||
|
|
||||||
#: assets/models/asset.py:135 assets/templates/assets/asset_detail.html:116
|
#: assets/models/asset.py:205 assets/templates/assets/asset_detail.html:116
|
||||||
msgid "Serial number"
|
msgid "Serial number"
|
||||||
msgstr "序列号"
|
msgstr "序列号"
|
||||||
|
|
||||||
#: assets/models/asset.py:137
|
#: assets/models/asset.py:207
|
||||||
msgid "CPU model"
|
msgid "CPU model"
|
||||||
msgstr "CPU型号"
|
msgstr "CPU型号"
|
||||||
|
|
||||||
#: assets/models/asset.py:138
|
#: assets/models/asset.py:208
|
||||||
#: xpack/plugins/license/templates/license/license_detail.html:80
|
#: xpack/plugins/license/templates/license/license_detail.html:80
|
||||||
msgid "CPU count"
|
msgid "CPU count"
|
||||||
msgstr "CPU数量"
|
msgstr "CPU数量"
|
||||||
|
|
||||||
#: assets/models/asset.py:139
|
#: assets/models/asset.py:209
|
||||||
msgid "CPU cores"
|
msgid "CPU cores"
|
||||||
msgstr "CPU核数"
|
msgstr "CPU核数"
|
||||||
|
|
||||||
#: assets/models/asset.py:140
|
#: assets/models/asset.py:210
|
||||||
msgid "CPU vcpus"
|
msgid "CPU vcpus"
|
||||||
msgstr "CPU总数"
|
msgstr "CPU总数"
|
||||||
|
|
||||||
#: assets/models/asset.py:141 assets/templates/assets/asset_detail.html:96
|
#: assets/models/asset.py:211 assets/templates/assets/asset_detail.html:96
|
||||||
msgid "Memory"
|
msgid "Memory"
|
||||||
msgstr "内存"
|
msgstr "内存"
|
||||||
|
|
||||||
#: assets/models/asset.py:142
|
#: assets/models/asset.py:212
|
||||||
msgid "Disk total"
|
msgid "Disk total"
|
||||||
msgstr "硬盘大小"
|
msgstr "硬盘大小"
|
||||||
|
|
||||||
#: assets/models/asset.py:143
|
#: assets/models/asset.py:213
|
||||||
msgid "Disk info"
|
msgid "Disk info"
|
||||||
msgstr "硬盘信息"
|
msgstr "硬盘信息"
|
||||||
|
|
||||||
#: assets/models/asset.py:145 assets/templates/assets/asset_detail.html:108
|
#: assets/models/asset.py:215 assets/templates/assets/asset_detail.html:108
|
||||||
#: assets/templates/assets/user_asset_list.html:171
|
#: assets/templates/assets/user_asset_list.html:171
|
||||||
msgid "OS"
|
msgid "OS"
|
||||||
msgstr "操作系统"
|
msgstr "操作系统"
|
||||||
|
|
||||||
#: assets/models/asset.py:146
|
#: assets/models/asset.py:216
|
||||||
msgid "OS version"
|
msgid "OS version"
|
||||||
msgstr "系统版本"
|
msgstr "系统版本"
|
||||||
|
|
||||||
#: assets/models/asset.py:147
|
#: assets/models/asset.py:217
|
||||||
msgid "OS arch"
|
msgid "OS arch"
|
||||||
msgstr "系统架构"
|
msgstr "系统架构"
|
||||||
|
|
||||||
#: assets/models/asset.py:148
|
#: assets/models/asset.py:218
|
||||||
msgid "Hostname raw"
|
msgid "Hostname raw"
|
||||||
msgstr "主机名原始"
|
msgstr "主机名原始"
|
||||||
|
|
||||||
#: assets/models/asset.py:150 assets/templates/assets/asset_create.html:46
|
#: assets/models/asset.py:220 assets/templates/assets/asset_create.html:46
|
||||||
#: assets/templates/assets/asset_detail.html:227 templates/_nav.html:26
|
#: assets/templates/assets/asset_detail.html:227 templates/_nav.html:26
|
||||||
msgid "Labels"
|
msgid "Labels"
|
||||||
msgstr "标签管理"
|
msgstr "标签管理"
|
||||||
|
@ -1003,7 +998,6 @@ msgid "Operator"
|
||||||
msgstr "运营商"
|
msgstr "运营商"
|
||||||
|
|
||||||
#: assets/models/cluster.py:36 assets/models/group.py:34
|
#: assets/models/cluster.py:36 assets/models/group.py:34
|
||||||
#: perms/utils/asset_permission.py:106
|
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "默认"
|
msgstr "默认"
|
||||||
|
|
||||||
|
@ -1136,7 +1130,7 @@ msgstr "默认资产组"
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "用户"
|
msgstr "用户"
|
||||||
|
|
||||||
#: assets/models/label.py:19 assets/models/node.py:246
|
#: assets/models/label.py:19 assets/models/node.py:245
|
||||||
#: assets/templates/assets/label_list.html:15 settings/models.py:30
|
#: assets/templates/assets/label_list.html:15 settings/models.py:30
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "值"
|
msgstr "值"
|
||||||
|
@ -1145,11 +1139,11 @@ msgstr "值"
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "分类"
|
msgstr "分类"
|
||||||
|
|
||||||
#: assets/models/node.py:245
|
#: assets/models/node.py:244
|
||||||
msgid "Key"
|
msgid "Key"
|
||||||
msgstr "键"
|
msgstr "键"
|
||||||
|
|
||||||
#: assets/models/node.py:303
|
#: assets/models/node.py:302
|
||||||
msgid "New node"
|
msgid "New node"
|
||||||
msgstr "新节点"
|
msgstr "新节点"
|
||||||
|
|
||||||
|
@ -1208,12 +1202,6 @@ msgstr "登录模式"
|
||||||
msgid "%(value)s is not an even number"
|
msgid "%(value)s is not an even number"
|
||||||
msgstr "%(value)s is not an even number"
|
msgstr "%(value)s is not an even number"
|
||||||
|
|
||||||
#: assets/serializers/admin_user.py:36 assets/serializers/asset.py:64
|
|
||||||
#: assets/serializers/asset_user.py:29 assets/serializers/system_user.py:31
|
|
||||||
#: assets/templates/assets/_asset_user_list.html:18
|
|
||||||
msgid "Connectivity"
|
|
||||||
msgstr "连接"
|
|
||||||
|
|
||||||
#: assets/serializers/asset.py:21
|
#: assets/serializers/asset.py:21
|
||||||
msgid "Protocol format should {}/{}"
|
msgid "Protocol format should {}/{}"
|
||||||
msgstr "协议格式 {}/{}"
|
msgstr "协议格式 {}/{}"
|
||||||
|
@ -1222,6 +1210,11 @@ msgstr "协议格式 {}/{}"
|
||||||
msgid "Protocol duplicate: {}"
|
msgid "Protocol duplicate: {}"
|
||||||
msgstr "协议重复: {}"
|
msgstr "协议重复: {}"
|
||||||
|
|
||||||
|
#: assets/serializers/asset.py:64 assets/serializers/asset_user.py:29
|
||||||
|
#: assets/templates/assets/_asset_user_list.html:18
|
||||||
|
msgid "Connectivity"
|
||||||
|
msgstr "连接"
|
||||||
|
|
||||||
#: assets/serializers/asset.py:90
|
#: assets/serializers/asset.py:90
|
||||||
msgid "Hardware info"
|
msgid "Hardware info"
|
||||||
msgstr "硬件信息"
|
msgstr "硬件信息"
|
||||||
|
@ -1247,19 +1240,19 @@ msgstr "ssh公钥"
|
||||||
msgid "private key invalid"
|
msgid "private key invalid"
|
||||||
msgstr "密钥不合法"
|
msgstr "密钥不合法"
|
||||||
|
|
||||||
#: assets/serializers/node.py:33
|
#: assets/serializers/node.py:32
|
||||||
msgid "The same level node name cannot be the same"
|
msgid "The same level node name cannot be the same"
|
||||||
msgstr "同级别节点名字不能重复"
|
msgstr "同级别节点名字不能重复"
|
||||||
|
|
||||||
#: assets/serializers/system_user.py:32
|
#: assets/serializers/system_user.py:31
|
||||||
msgid "Login mode display"
|
msgid "Login mode display"
|
||||||
msgstr "登录模式显示"
|
msgstr "登录模式显示"
|
||||||
|
|
||||||
#: assets/serializers/system_user.py:67
|
#: assets/serializers/system_user.py:66
|
||||||
msgid "* Automatic login mode must fill in the username."
|
msgid "* Automatic login mode must fill in the username."
|
||||||
msgstr "自动登录模式,必须填写用户名"
|
msgstr "自动登录模式,必须填写用户名"
|
||||||
|
|
||||||
#: assets/serializers/system_user.py:76
|
#: assets/serializers/system_user.py:75
|
||||||
msgid "Password or private key required"
|
msgid "Password or private key required"
|
||||||
msgstr "密码或密钥密码需要一个"
|
msgstr "密码或密钥密码需要一个"
|
||||||
|
|
||||||
|
@ -1401,7 +1394,7 @@ msgid "Update asset user auth"
|
||||||
msgstr "更新资产用户认证信息"
|
msgstr "更新资产用户认证信息"
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_user_auth_update_modal.html:23
|
#: assets/templates/assets/_asset_user_auth_update_modal.html:23
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:101
|
#: xpack/plugins/change_auth_plan/forms.py:102
|
||||||
msgid "Please input password"
|
msgid "Please input password"
|
||||||
msgstr "请输入密码"
|
msgstr "请输入密码"
|
||||||
|
|
||||||
|
@ -1490,19 +1483,19 @@ msgstr "重命名节点"
|
||||||
msgid "Delete node"
|
msgid "Delete node"
|
||||||
msgstr "删除节点"
|
msgstr "删除节点"
|
||||||
|
|
||||||
#: assets/templates/assets/_node_tree.html:155
|
#: assets/templates/assets/_node_tree.html:154
|
||||||
msgid "Create node failed"
|
msgid "Create node failed"
|
||||||
msgstr "创建节点失败"
|
msgstr "创建节点失败"
|
||||||
|
|
||||||
#: assets/templates/assets/_node_tree.html:167
|
#: assets/templates/assets/_node_tree.html:166
|
||||||
msgid "Have child node, cancel"
|
msgid "Have child node, cancel"
|
||||||
msgstr "存在子节点,不能删除"
|
msgstr "存在子节点,不能删除"
|
||||||
|
|
||||||
#: assets/templates/assets/_node_tree.html:169
|
#: assets/templates/assets/_node_tree.html:168
|
||||||
msgid "Have assets, cancel"
|
msgid "Have assets, cancel"
|
||||||
msgstr "存在资产,不能删除"
|
msgstr "存在资产,不能删除"
|
||||||
|
|
||||||
#: assets/templates/assets/_node_tree.html:243
|
#: assets/templates/assets/_node_tree.html:242
|
||||||
msgid "Rename success"
|
msgid "Rename success"
|
||||||
msgstr "重命名成功"
|
msgstr "重命名成功"
|
||||||
|
|
||||||
|
@ -1582,14 +1575,14 @@ msgstr "替换资产的管理员"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_detail.html:91
|
#: assets/templates/assets/admin_user_detail.html:91
|
||||||
#: perms/templates/perms/asset_permission_asset.html:116
|
#: perms/templates/perms/asset_permission_asset.html:116
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:109
|
#: xpack/plugins/change_auth_plan/forms.py:110
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:112
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:112
|
||||||
msgid "Select nodes"
|
msgid "Select nodes"
|
||||||
msgstr "选择节点"
|
msgstr "选择节点"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_detail.html:100
|
#: assets/templates/assets/admin_user_detail.html:100
|
||||||
#: assets/templates/assets/asset_detail.html:207
|
#: assets/templates/assets/asset_detail.html:207
|
||||||
#: assets/templates/assets/asset_list.html:396
|
#: assets/templates/assets/asset_list.html:395
|
||||||
#: assets/templates/assets/cmd_filter_detail.html:106
|
#: assets/templates/assets/cmd_filter_detail.html:106
|
||||||
#: assets/templates/assets/system_user_assets.html:100
|
#: assets/templates/assets/system_user_assets.html:100
|
||||||
#: assets/templates/assets/system_user_detail.html:182
|
#: assets/templates/assets/system_user_detail.html:182
|
||||||
|
@ -1611,24 +1604,24 @@ msgstr "选择节点"
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "确认"
|
msgstr "确认"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:7
|
#: assets/templates/assets/admin_user_list.html:5
|
||||||
msgid ""
|
msgid ""
|
||||||
"Admin users are asset (charged server) on the root, or have NOPASSWD: ALL "
|
"Admin users are asset (charged server) on the root, or have NOPASSWD: ALL "
|
||||||
"sudo permissions users, "
|
"sudo permissions users, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,"
|
"管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:8
|
#: assets/templates/assets/admin_user_list.html:6
|
||||||
msgid ""
|
msgid ""
|
||||||
"Jumpserver users of the system using the user to `push system user`, `get "
|
"Jumpserver users of the system using the user to `push system user`, `get "
|
||||||
"assets hardware information`, etc. "
|
"assets hardware information`, etc. "
|
||||||
msgstr "Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。"
|
msgstr "Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:9
|
#: assets/templates/assets/admin_user_list.html:7
|
||||||
msgid "You can set any one for Windows or other hardware."
|
msgid "You can set any one for Windows or other hardware."
|
||||||
msgstr "Windows或其它硬件可以随意设置一个"
|
msgstr "Windows或其它硬件可以随意设置一个"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:19
|
#: assets/templates/assets/admin_user_list.html:17
|
||||||
#: assets/templates/assets/asset_list.html:68
|
#: assets/templates/assets/asset_list.html:68
|
||||||
#: assets/templates/assets/system_user_list.html:23
|
#: assets/templates/assets/system_user_list.html:23
|
||||||
#: audits/templates/audits/login_log_list.html:85
|
#: audits/templates/audits/login_log_list.html:85
|
||||||
|
@ -1638,7 +1631,7 @@ msgstr "Windows或其它硬件可以随意设置一个"
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "导出"
|
msgstr "导出"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:24
|
#: assets/templates/assets/admin_user_list.html:22
|
||||||
#: assets/templates/assets/asset_list.html:73
|
#: assets/templates/assets/asset_list.html:73
|
||||||
#: assets/templates/assets/system_user_list.html:28
|
#: assets/templates/assets/system_user_list.html:28
|
||||||
#: settings/templates/settings/_ldap_list_users_modal.html:100
|
#: settings/templates/settings/_ldap_list_users_modal.html:100
|
||||||
|
@ -1649,20 +1642,13 @@ msgstr "导出"
|
||||||
msgid "Import"
|
msgid "Import"
|
||||||
msgstr "导入"
|
msgstr "导入"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:39
|
#: assets/templates/assets/admin_user_list.html:37
|
||||||
#: assets/views/admin_user.py:50
|
#: assets/views/admin_user.py:50
|
||||||
msgid "Create admin user"
|
msgid "Create admin user"
|
||||||
msgstr "创建管理用户"
|
msgstr "创建管理用户"
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:52
|
#: assets/templates/assets/admin_user_list.html:163
|
||||||
#: assets/templates/assets/system_user_list.html:58
|
#: assets/templates/assets/admin_user_list.html:194
|
||||||
#: ops/templates/ops/adhoc_history.html:54
|
|
||||||
#: ops/templates/ops/task_history.html:60
|
|
||||||
msgid "Ratio"
|
|
||||||
msgstr "比例"
|
|
||||||
|
|
||||||
#: assets/templates/assets/admin_user_list.html:165
|
|
||||||
#: assets/templates/assets/admin_user_list.html:196
|
|
||||||
#: assets/templates/assets/asset_list.html:268
|
#: assets/templates/assets/asset_list.html:268
|
||||||
#: assets/templates/assets/asset_list.html:305
|
#: assets/templates/assets/asset_list.html:305
|
||||||
#: assets/templates/assets/system_user_list.html:225
|
#: assets/templates/assets/system_user_list.html:225
|
||||||
|
@ -1807,7 +1793,7 @@ msgstr "仅显示当前节点资产"
|
||||||
msgid "Displays all child node assets"
|
msgid "Displays all child node assets"
|
||||||
msgstr "显示所有子节点资产"
|
msgstr "显示所有子节点资产"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:390
|
#: assets/templates/assets/asset_list.html:389
|
||||||
#: assets/templates/assets/system_user_list.html:166
|
#: assets/templates/assets/system_user_list.html:166
|
||||||
#: users/templates/users/user_detail.html:382
|
#: users/templates/users/user_detail.html:382
|
||||||
#: users/templates/users/user_detail.html:408
|
#: users/templates/users/user_detail.html:408
|
||||||
|
@ -1818,11 +1804,11 @@ msgstr "显示所有子节点资产"
|
||||||
msgid "Are you sure?"
|
msgid "Are you sure?"
|
||||||
msgstr "你确认吗?"
|
msgstr "你确认吗?"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:391
|
#: assets/templates/assets/asset_list.html:390
|
||||||
msgid "This will delete the selected assets !!!"
|
msgid "This will delete the selected assets !!!"
|
||||||
msgstr "删除选择资产"
|
msgstr "删除选择资产"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:394
|
#: assets/templates/assets/asset_list.html:393
|
||||||
#: assets/templates/assets/system_user_list.html:170
|
#: assets/templates/assets/system_user_list.html:170
|
||||||
#: settings/templates/settings/terminal_setting.html:166
|
#: settings/templates/settings/terminal_setting.html:166
|
||||||
#: users/templates/users/user_detail.html:386
|
#: users/templates/users/user_detail.html:386
|
||||||
|
@ -1836,16 +1822,16 @@ msgstr "删除选择资产"
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "取消"
|
msgstr "取消"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:407
|
#: assets/templates/assets/asset_list.html:406
|
||||||
msgid "Asset Deleted."
|
msgid "Asset Deleted."
|
||||||
msgstr "已被删除"
|
msgstr "已被删除"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:408
|
#: assets/templates/assets/asset_list.html:407
|
||||||
#: assets/templates/assets/asset_list.html:412
|
#: assets/templates/assets/asset_list.html:411
|
||||||
msgid "Asset Delete"
|
msgid "Asset Delete"
|
||||||
msgstr "删除"
|
msgstr "删除"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:411
|
#: assets/templates/assets/asset_list.html:410
|
||||||
msgid "Asset Deleting failed."
|
msgid "Asset Deleting failed."
|
||||||
msgstr "删除失败"
|
msgstr "删除失败"
|
||||||
|
|
||||||
|
@ -2650,7 +2636,7 @@ msgstr "不能包含特殊字符"
|
||||||
msgid "This field must be unique."
|
msgid "This field must be unique."
|
||||||
msgstr "字段必须唯一"
|
msgstr "字段必须唯一"
|
||||||
|
|
||||||
#: jumpserver/views.py:190
|
#: jumpserver/views.py:191
|
||||||
msgid ""
|
msgid ""
|
||||||
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
|
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
|
||||||
"configure nginx for url distribution,</div> </div>If you see this page, "
|
"configure nginx for url distribution,</div> </div>If you see this page, "
|
||||||
|
@ -2853,6 +2839,11 @@ msgstr "执行历史"
|
||||||
msgid "F/S/T"
|
msgid "F/S/T"
|
||||||
msgstr "失败/成功/总"
|
msgstr "失败/成功/总"
|
||||||
|
|
||||||
|
#: ops/templates/ops/adhoc_history.html:54
|
||||||
|
#: ops/templates/ops/task_history.html:60
|
||||||
|
msgid "Ratio"
|
||||||
|
msgstr "比例"
|
||||||
|
|
||||||
#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:142
|
#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:142
|
||||||
msgid "Run history detail"
|
msgid "Run history detail"
|
||||||
msgstr "执行历史详情"
|
msgstr "执行历史详情"
|
||||||
|
@ -3005,6 +2996,14 @@ msgstr "命令执行"
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr "组织"
|
msgstr "组织"
|
||||||
|
|
||||||
|
#: perms/api/user_permission.py:206
|
||||||
|
msgid "ungrouped"
|
||||||
|
msgstr "未分组"
|
||||||
|
|
||||||
|
#: perms/api/user_permission.py:211
|
||||||
|
msgid "empty"
|
||||||
|
msgstr "空"
|
||||||
|
|
||||||
#: perms/forms/asset_permission.py:65 perms/forms/remote_app_permission.py:34
|
#: perms/forms/asset_permission.py:65 perms/forms/remote_app_permission.py:34
|
||||||
#: perms/models/asset_permission.py:102 perms/models/base.py:37
|
#: perms/models/asset_permission.py:102 perms/models/base.py:37
|
||||||
#: perms/templates/perms/asset_permission_list.html:47
|
#: perms/templates/perms/asset_permission_list.html:47
|
||||||
|
@ -3207,10 +3206,6 @@ msgstr "添加用户"
|
||||||
msgid "Add user group to this permission"
|
msgid "Add user group to this permission"
|
||||||
msgstr "添加用户组"
|
msgstr "添加用户组"
|
||||||
|
|
||||||
#: perms/utils/asset_permission.py:115
|
|
||||||
msgid "Empty"
|
|
||||||
msgstr "空"
|
|
||||||
|
|
||||||
#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:64
|
#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:64
|
||||||
#: perms/views/asset_permission.py:81 perms/views/asset_permission.py:98
|
#: perms/views/asset_permission.py:81 perms/views/asset_permission.py:98
|
||||||
#: perms/views/asset_permission.py:135 perms/views/asset_permission.py:168
|
#: perms/views/asset_permission.py:135 perms/views/asset_permission.py:168
|
||||||
|
@ -5313,25 +5308,28 @@ msgid "Password length"
|
||||||
msgstr "密码长度"
|
msgstr "密码长度"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:45
|
#: xpack/plugins/change_auth_plan/forms.py:45
|
||||||
msgid "* For security, please do not change root user's password"
|
#: xpack/plugins/change_auth_plan/models.py:213
|
||||||
msgstr "* 为了安全,请不要更改root用户的密码"
|
#, fuzzy
|
||||||
|
#| msgid "For security, do not change {} user's password"
|
||||||
|
msgid "* For security, do not change {} user's password"
|
||||||
|
msgstr "* 为了安全,禁止更改 {} 用户的密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:54
|
#: xpack/plugins/change_auth_plan/forms.py:55
|
||||||
msgid "* Please enter custom password"
|
msgid "* Please enter custom password"
|
||||||
msgstr "* 请输入自定义密码"
|
msgstr "* 请输入自定义密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:63
|
#: xpack/plugins/change_auth_plan/forms.py:64
|
||||||
msgid "* Please enter a valid crontab expression"
|
msgid "* Please enter a valid crontab expression"
|
||||||
msgstr "* 请输入有效的 crontab 表达式"
|
msgstr "* 请输入有效的 crontab 表达式"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:116
|
#: xpack/plugins/change_auth_plan/forms.py:117
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:60
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:60
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:81
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:81
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17
|
||||||
msgid "Periodic perform"
|
msgid "Periodic perform"
|
||||||
msgstr "定时执行"
|
msgstr "定时执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:120
|
#: xpack/plugins/change_auth_plan/forms.py:121
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tips: The username of the user on the asset to be modified. if the user "
|
"Tips: The username of the user on the asset to be modified. if the user "
|
||||||
"exists, change the password; If the user does not exist, create the user."
|
"exists, change the password; If the user does not exist, create the user."
|
||||||
|
@ -5339,11 +5337,11 @@ msgstr ""
|
||||||
"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果"
|
"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果"
|
||||||
"用户不存在,则创建用户。"
|
"用户不存在,则创建用户。"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:124
|
#: xpack/plugins/change_auth_plan/forms.py:125
|
||||||
msgid "Tips: (Units: hour)"
|
msgid "Tips: (Units: hour)"
|
||||||
msgstr "提示:(单位: 时)"
|
msgstr "提示:(单位: 时)"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:125
|
#: xpack/plugins/change_auth_plan/forms.py:126
|
||||||
msgid ""
|
msgid ""
|
||||||
"eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux "
|
"eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux "
|
||||||
"crontab expressions <min hour day month week> (<a href='https://tool.lu/"
|
"crontab expressions <min hour day month week> (<a href='https://tool.lu/"
|
||||||
|
@ -5396,10 +5394,6 @@ msgstr "定期执行"
|
||||||
msgid "Password rules"
|
msgid "Password rules"
|
||||||
msgstr "密码规则"
|
msgstr "密码规则"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:213
|
|
||||||
msgid "For security, do not change {} user's password"
|
|
||||||
msgstr "* 为了安全,禁止更改 {} 用户的密码"
|
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:217
|
#: xpack/plugins/change_auth_plan/models.py:217
|
||||||
msgid "Assets is empty, please add the asset"
|
msgid "Assets is empty, please add the asset"
|
||||||
msgstr "资产为空,请添加资产"
|
msgstr "资产为空,请添加资产"
|
||||||
|
@ -5768,7 +5762,7 @@ msgid "Interface settings"
|
||||||
msgstr "界面设置"
|
msgstr "界面设置"
|
||||||
|
|
||||||
#: xpack/plugins/interface/templates/interface/interface.html:15
|
#: xpack/plugins/interface/templates/interface/interface.html:15
|
||||||
#: xpack/plugins/interface/views.py:25
|
#: xpack/plugins/interface/views.py:24 xpack/plugins/interface/views.py:25
|
||||||
msgid "Interface setting"
|
msgid "Interface setting"
|
||||||
msgstr "界面设置"
|
msgstr "界面设置"
|
||||||
|
|
||||||
|
@ -5791,26 +5785,22 @@ msgstr "恢复默认成功!"
|
||||||
msgid "Restore default failed."
|
msgid "Restore default failed."
|
||||||
msgstr "恢复默认失败!"
|
msgstr "恢复默认失败!"
|
||||||
|
|
||||||
#: xpack/plugins/interface/views.py:24
|
|
||||||
msgid "Interface"
|
|
||||||
msgstr "界面"
|
|
||||||
|
|
||||||
#: xpack/plugins/interface/views.py:51
|
#: xpack/plugins/interface/views.py:51
|
||||||
msgid "It is already in the default setting state!"
|
msgid "It is already in the default setting state!"
|
||||||
msgstr "当前已经是初始化状态!"
|
msgstr "当前已经是初始化状态!"
|
||||||
|
|
||||||
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:98
|
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94
|
||||||
#: xpack/plugins/license/templates/license/license_detail.html:50
|
#: xpack/plugins/license/templates/license/license_detail.html:50
|
||||||
#: xpack/plugins/license/templates/license/license_detail.html:55
|
#: xpack/plugins/license/templates/license/license_detail.html:55
|
||||||
#: xpack/plugins/license/views.py:32
|
#: xpack/plugins/license/views.py:32
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "许可证"
|
msgstr "许可证"
|
||||||
|
|
||||||
#: xpack/plugins/license/models.py:75
|
#: xpack/plugins/license/models.py:74
|
||||||
msgid "Standard edition"
|
msgid "Standard edition"
|
||||||
msgstr "标准版"
|
msgstr "标准版"
|
||||||
|
|
||||||
#: xpack/plugins/license/models.py:77
|
#: xpack/plugins/license/models.py:76
|
||||||
msgid "Enterprise edition"
|
msgid "Enterprise edition"
|
||||||
msgstr "企业版"
|
msgstr "企业版"
|
||||||
|
|
||||||
|
@ -5898,7 +5888,9 @@ msgstr "无效的许可证"
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "管理员"
|
msgstr "管理员"
|
||||||
|
|
||||||
#: xpack/plugins/orgs/meta.py:8
|
#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26
|
||||||
|
#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60
|
||||||
|
#: xpack/plugins/orgs/views.py:77
|
||||||
msgid "Organizations"
|
msgid "Organizations"
|
||||||
msgstr "组织管理"
|
msgstr "组织管理"
|
||||||
|
|
||||||
|
@ -5915,11 +5907,6 @@ msgstr "添加管理员"
|
||||||
msgid "Create organization "
|
msgid "Create organization "
|
||||||
msgstr "创建组织"
|
msgstr "创建组织"
|
||||||
|
|
||||||
#: xpack/plugins/orgs/views.py:26 xpack/plugins/orgs/views.py:43
|
|
||||||
#: xpack/plugins/orgs/views.py:60 xpack/plugins/orgs/views.py:77
|
|
||||||
msgid "Orgs"
|
|
||||||
msgstr "组织管理"
|
|
||||||
|
|
||||||
#: xpack/plugins/orgs/views.py:27
|
#: xpack/plugins/orgs/views.py:27
|
||||||
msgid "Org list"
|
msgid "Org list"
|
||||||
msgstr "组织列表"
|
msgstr "组织列表"
|
||||||
|
@ -5949,6 +5936,15 @@ msgstr "密码匣子"
|
||||||
msgid "vault create"
|
msgid "vault create"
|
||||||
msgstr "创建"
|
msgstr "创建"
|
||||||
|
|
||||||
|
#~ msgid "* For security, please do not change root user's password"
|
||||||
|
#~ msgstr "* 为了安全,请不要更改root用户的密码"
|
||||||
|
|
||||||
|
#~ msgid "Interface"
|
||||||
|
#~ msgstr "界面"
|
||||||
|
|
||||||
|
#~ msgid "Orgs"
|
||||||
|
#~ msgstr "组织管理"
|
||||||
|
|
||||||
#~ msgid "Org"
|
#~ msgid "Org"
|
||||||
#~ msgstr "组织"
|
#~ msgstr "组织"
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
)
|
)
|
||||||
|
|
||||||
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 = []
|
|
||||||
|
|
||||||
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
|
||||||
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):
|
class UserGroupGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsAsTreeApi):
|
||||||
serializer_class = TreeNodeSerializer
|
def get_object(self):
|
||||||
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):
|
|
||||||
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)
|
||||||
group = get_object_or_404(UserGroup, id=user_group_id)
|
return user_group
|
||||||
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):
|
class UserGroupGrantedNodeAssetsApi(UserGrantedNodeAssetsApi):
|
||||||
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)
|
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||||
util = AssetPermissionUtil(user_group)
|
return 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)
|
|
||||||
util = RemoteAppPermissionUtil(user_group)
|
|
||||||
queryset = util.get_remote_apps()
|
|
||||||
return queryset
|
|
||||||
|
|
|
@ -2,40 +2,44 @@
|
||||||
#
|
#
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import uuid
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import Q
|
||||||
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 (
|
from rest_framework.generics import (
|
||||||
ListAPIView, get_object_or_404, RetrieveAPIView
|
ListAPIView, get_object_or_404, RetrieveAPIView
|
||||||
)
|
)
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
|
||||||
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
|
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
|
||||||
from common.tree import TreeNodeSerializer
|
from common.tree import TreeNodeSerializer
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger, get_object_or_none
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
|
AssetPermissionUtil, ParserNode,
|
||||||
)
|
)
|
||||||
|
from .. import const
|
||||||
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
|
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
|
||||||
from .. import serializers, const
|
from .. import serializers
|
||||||
from ..mixins import AssetsFilterMixin
|
|
||||||
from ..models import Action
|
from ..models import Action
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserGrantedAssetsApi', 'UserGrantedNodesApi',
|
'UserGrantedAssetsApi', 'UserGrantedNodesApi',
|
||||||
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
|
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
|
||||||
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
|
'ValidateUserAssetPermissionApi', 'UserGrantedNodesAsTreeApi',
|
||||||
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
|
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserPermissionCacheMixin:
|
class UserPermissionCacheMixin:
|
||||||
cache_policy = '0'
|
cache_policy = '0'
|
||||||
RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_{}'
|
RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
|
||||||
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
||||||
_object = None
|
_object = None
|
||||||
|
|
||||||
|
@ -130,12 +134,61 @@ class UserPermissionCacheMixin:
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView):
|
class GrantAssetsMixin:
|
||||||
|
serializer_class = serializers.AssetGrantedSerializer
|
||||||
|
|
||||||
|
def get_serializer(self, queryset, many=True):
|
||||||
|
assets_ids = []
|
||||||
|
system_users_ids = set()
|
||||||
|
for asset in queryset:
|
||||||
|
assets_ids.append(asset["id"])
|
||||||
|
system_users_ids.update(set(asset["system_users"]))
|
||||||
|
assets = Asset.objects.filter(id__in=assets_ids).only(
|
||||||
|
*self.serializer_class.Meta.only_fields
|
||||||
|
)
|
||||||
|
assets_map = {asset.id: asset for asset in assets}
|
||||||
|
system_users = SystemUser.objects.filter(id__in=system_users_ids).only(
|
||||||
|
*self.serializer_class.system_users_only_fields
|
||||||
|
)
|
||||||
|
system_users_map = {s.id: s for s in system_users}
|
||||||
|
data = []
|
||||||
|
for item in queryset:
|
||||||
|
i = item["id"]
|
||||||
|
asset = assets_map.get(i)
|
||||||
|
if not asset:
|
||||||
|
continue
|
||||||
|
|
||||||
|
_system_users = item["system_users"]
|
||||||
|
system_users_granted = []
|
||||||
|
for sid, action in _system_users.items():
|
||||||
|
system_user = system_users_map.get(sid)
|
||||||
|
if not system_user:
|
||||||
|
continue
|
||||||
|
system_user.actions = action
|
||||||
|
system_users_granted.append(system_user)
|
||||||
|
asset.system_users_granted = system_users_granted
|
||||||
|
data.append(asset)
|
||||||
|
return super().get_serializer(data, many=True)
|
||||||
|
|
||||||
|
def search_queryset(self, assets):
|
||||||
|
search = self.request.query_params.get("search")
|
||||||
|
if not search:
|
||||||
|
return assets
|
||||||
|
|
||||||
|
assets_map = {asset['id']: asset for asset in assets}
|
||||||
|
assets_ids = set(assets_map.keys())
|
||||||
|
assets_ids_search = Asset.objects.filter(id__in=assets_ids).filter(
|
||||||
|
Q(hostname__icontains=search) | Q(ip__icontains=search)
|
||||||
|
).values_list('id', flat=True)
|
||||||
|
assets_ids &= set(assets_ids_search)
|
||||||
|
return [assets_map.get(asset_id) for asset_id in assets_ids]
|
||||||
|
|
||||||
|
|
||||||
|
class UserGrantedAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
|
||||||
"""
|
"""
|
||||||
用户授权的所有资产
|
用户授权的所有资产
|
||||||
"""
|
"""
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
serializer_class = serializers.AssetGrantedSerializer
|
|
||||||
pagination_class = LimitOffsetPagination
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
@ -147,17 +200,10 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = []
|
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
||||||
assets = util.get_assets()
|
queryset = util.get_assets()
|
||||||
for asset, system_users in assets.items():
|
queryset = self.search_queryset(queryset)
|
||||||
system_users_granted = []
|
|
||||||
for system_user, actions in system_users.items():
|
|
||||||
system_user.actions = actions
|
|
||||||
system_users_granted.append(system_user)
|
|
||||||
asset.system_users_granted = system_users_granted
|
|
||||||
queryset.append(asset)
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
|
@ -166,12 +212,39 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
|
||||||
return super().get_permissions()
|
return super().get_permissions()
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView):
|
class NodesWithUngroupMixin:
|
||||||
|
util = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ungrouped_node(ungroup_key):
|
||||||
|
return Node(key=ungroup_key, id=const.UNGROUPED_NODE_ID,
|
||||||
|
value=_("ungrouped"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_empty_node():
|
||||||
|
return Node(key=const.EMPTY_NODE_KEY, id=const.EMPTY_NODE_ID,
|
||||||
|
value=_("empty"))
|
||||||
|
|
||||||
|
def add_ungrouped_nodes(self, node_map, node_keys):
|
||||||
|
ungroup_key = '1:-1'
|
||||||
|
for key in node_keys:
|
||||||
|
if key.endswith('-1'):
|
||||||
|
ungroup_key = key
|
||||||
|
break
|
||||||
|
ungroup_node = self.get_ungrouped_node(ungroup_key)
|
||||||
|
empty_node = self.get_empty_node()
|
||||||
|
node_map[ungroup_key] = ungroup_node
|
||||||
|
node_map[const.EMPTY_NODE_KEY] = empty_node
|
||||||
|
|
||||||
|
|
||||||
|
class UserGrantedNodesApi(UserPermissionCacheMixin, NodesWithUngroupMixin, ListAPIView):
|
||||||
"""
|
"""
|
||||||
查询用户授权的所有节点的API
|
查询用户授权的所有节点的API
|
||||||
"""
|
"""
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
serializer_class = NodeSerializer
|
serializer_class = NodeSerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
only_fields = NodeSerializer.Meta.only_fields
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
user_id = self.kwargs.get('pk', '')
|
user_id = self.kwargs.get('pk', '')
|
||||||
|
@ -181,11 +254,31 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def get_nodes(self, nodes_with_assets):
|
||||||
|
node_keys = [n["key"] for n in nodes_with_assets]
|
||||||
|
nodes = Node.objects.filter(key__in=node_keys).only(
|
||||||
|
*self.only_fields
|
||||||
|
)
|
||||||
|
nodes_map = {n.key: n for n in nodes}
|
||||||
|
self.add_ungrouped_nodes(nodes_map, node_keys)
|
||||||
|
|
||||||
|
_nodes = []
|
||||||
|
for n in nodes_with_assets:
|
||||||
|
key = n["key"]
|
||||||
|
node = nodes_map.get(key)
|
||||||
|
node._assets_amount = n["assets_amount"]
|
||||||
|
_nodes.append(node)
|
||||||
|
return _nodes
|
||||||
|
|
||||||
|
def get_serializer(self, nodes_with_assets, many=True):
|
||||||
|
nodes = self.get_nodes(nodes_with_assets)
|
||||||
|
return super().get_serializer(nodes, many=True)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
||||||
nodes = util.get_nodes()
|
nodes_with_assets = self.util.get_nodes_with_assets()
|
||||||
return nodes
|
return nodes_with_assets
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
if self.kwargs.get('pk') is None:
|
if self.kwargs.get('pk') is None:
|
||||||
|
@ -193,12 +286,30 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView):
|
||||||
return super().get_permissions()
|
return super().get_permissions()
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView):
|
class UserGrantedNodesAsTreeApi(UserGrantedNodesApi):
|
||||||
|
serializer_class = TreeNodeSerializer
|
||||||
|
only_fields = ParserNode.nodes_only_fields
|
||||||
|
|
||||||
|
def get_serializer(self, nodes_with_assets, many=True):
|
||||||
|
nodes = self.get_nodes(nodes_with_assets)
|
||||||
|
queryset = []
|
||||||
|
for node in nodes:
|
||||||
|
data = ParserNode.parse_node_to_tree_node(node)
|
||||||
|
queryset.append(data)
|
||||||
|
return self.get_serializer_class()(queryset, many=many)
|
||||||
|
|
||||||
|
|
||||||
|
class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, NodesWithUngroupMixin, ListAPIView):
|
||||||
"""
|
"""
|
||||||
用户授权的节点并带着节点下资产的api
|
用户授权的节点并带着节点下资产的api
|
||||||
"""
|
"""
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
serializer_class = serializers.NodeGrantedSerializer
|
serializer_class = serializers.NodeGrantedSerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
|
nodes_only_fields = serializers.NodeGrantedSerializer.Meta.only_fields
|
||||||
|
assets_only_fields = serializers.NodeGrantedSerializer.assets_only_fields
|
||||||
|
system_users_only_fields = serializers.NodeGrantedSerializer.system_users_only_fields
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
user_id = self.kwargs.get('pk', '')
|
user_id = self.kwargs.get('pk', '')
|
||||||
|
@ -208,78 +319,127 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_maps(self, nodes_items):
|
||||||
|
"""
|
||||||
|
查库,并加入构造的ungrouped节点
|
||||||
|
:return:
|
||||||
|
({asset.id: asset}, {node.key: node}, {system_user.id: system_user})
|
||||||
|
"""
|
||||||
|
_nodes_keys = set()
|
||||||
|
_assets_ids = set()
|
||||||
|
_system_users_ids = set()
|
||||||
|
for item in nodes_items:
|
||||||
|
_nodes_keys.add(item["key"])
|
||||||
|
_assets_ids.update(set(item["assets"].keys()))
|
||||||
|
for _system_users_id in item["assets"].values():
|
||||||
|
_system_users_ids.update(_system_users_id.keys())
|
||||||
|
|
||||||
|
_nodes = Node.objects.filter(key__in=_nodes_keys).only(
|
||||||
|
*self.nodes_only_fields
|
||||||
|
)
|
||||||
|
_assets = Asset.objects.filter(id__in=_assets_ids).only(
|
||||||
|
*self.assets_only_fields
|
||||||
|
)
|
||||||
|
_system_users = SystemUser.objects.filter(id__in=_system_users_ids).only(
|
||||||
|
*self.system_users_only_fields
|
||||||
|
)
|
||||||
|
_nodes_map = {n.key: n for n in _nodes}
|
||||||
|
self.add_ungrouped_nodes(_nodes_map, _nodes_keys)
|
||||||
|
_assets_map = {a.id: a for a in _assets}
|
||||||
|
_system_users_map = {s.id: s for s in _system_users}
|
||||||
|
return _nodes_map, _assets_map, _system_users_map
|
||||||
|
|
||||||
|
def get_serializer_queryset(self, nodes_items):
|
||||||
|
"""
|
||||||
|
将id转为object,同时构造queryset
|
||||||
|
:param nodes_items:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'key': node.key,
|
||||||
|
'assets_amount': 10
|
||||||
|
'assets': {
|
||||||
|
asset.id: {
|
||||||
|
system_user.id: actions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"""
|
||||||
queryset = []
|
queryset = []
|
||||||
user = self.get_object()
|
_node_map, _assets_map, _system_users_map = self.get_maps(nodes_items)
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
for item in nodes_items:
|
||||||
nodes = util.get_nodes_with_assets()
|
key = item["key"]
|
||||||
for node, _assets in nodes.items():
|
node = _node_map.get(key)
|
||||||
assets = _assets.keys()
|
if not node:
|
||||||
for k, v in _assets.items():
|
continue
|
||||||
k.system_users_granted = v
|
node._assets_amount = item["assets_amount"]
|
||||||
node.assets_granted = assets
|
assets_granted = []
|
||||||
|
for asset_id, system_users_ids_action in item["assets"].items():
|
||||||
|
asset = _assets_map.get(asset_id)
|
||||||
|
if not asset:
|
||||||
|
continue
|
||||||
|
system_user_granted = []
|
||||||
|
for system_user_id, action in system_users_ids_action.items():
|
||||||
|
system_user = _system_users_map.get(system_user_id)
|
||||||
|
if not system_user:
|
||||||
|
continue
|
||||||
|
system_user.actions = action
|
||||||
|
system_user_granted.append(system_user)
|
||||||
|
asset.system_users_granted = system_user_granted
|
||||||
|
assets_granted.append(asset)
|
||||||
|
node.assets_granted = assets_granted
|
||||||
queryset.append(node)
|
queryset.append(node)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def sort_assets(self, queryset):
|
def get_serializer(self, nodes_items, many=True):
|
||||||
for node in queryset:
|
queryset = self.get_serializer_queryset(nodes_items)
|
||||||
node.assets_granted = super().sort_assets(node.assets_granted)
|
return super().get_serializer(queryset, many=many)
|
||||||
return queryset
|
|
||||||
|
|
||||||
def get_permissions(self):
|
|
||||||
if self.kwargs.get('pk') is None:
|
|
||||||
self.permission_classes = (IsValidUser,)
|
|
||||||
return super().get_permissions()
|
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView):
|
|
||||||
serializer_class = TreeNodeSerializer
|
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
|
||||||
show_assets = True
|
|
||||||
system_user_id = None
|
|
||||||
|
|
||||||
def get_permissions(self):
|
|
||||||
if self.kwargs.get('pk') is None:
|
|
||||||
self.permission_classes = (IsValidUser,)
|
|
||||||
return super().get_permissions()
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
user_id = self.kwargs.get('pk', '')
|
|
||||||
if not user_id:
|
|
||||||
user = self.request.user
|
|
||||||
else:
|
|
||||||
user = get_object_or_404(User, id=user_id)
|
|
||||||
return user
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = []
|
|
||||||
self.show_assets = self.request.query_params.get('show_assets', '1') == '1'
|
|
||||||
self.system_user_id = self.request.query_params.get('system_user')
|
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
||||||
if self.system_user_id:
|
system_user_id = self.request.query_params.get('system_user')
|
||||||
util.filter_permissions(
|
if system_user_id:
|
||||||
system_users=self.system_user_id
|
self.util.filter_permissions(
|
||||||
|
system_users=system_user_id
|
||||||
)
|
)
|
||||||
nodes = util.get_nodes_with_assets()
|
nodes_items = self.util.get_nodes_with_assets()
|
||||||
for node, assets in nodes.items():
|
return nodes_items
|
||||||
data = parse_node_to_tree_node(node)
|
|
||||||
|
def get_permissions(self):
|
||||||
|
if self.kwargs.get('pk') is None:
|
||||||
|
self.permission_classes = (IsValidUser,)
|
||||||
|
return super().get_permissions()
|
||||||
|
|
||||||
|
|
||||||
|
class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsApi):
|
||||||
|
serializer_class = TreeNodeSerializer
|
||||||
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
|
system_user_id = None
|
||||||
|
nodes_only_fields = ParserNode.nodes_only_fields
|
||||||
|
assets_only_fields = ParserNode.assets_only_fields
|
||||||
|
system_users_only_fields = ParserNode.system_users_only_fields
|
||||||
|
|
||||||
|
def get_serializer(self, nodes_items, many=True):
|
||||||
|
_queryset = super().get_serializer_queryset(nodes_items)
|
||||||
|
queryset = []
|
||||||
|
|
||||||
|
for node in _queryset:
|
||||||
|
data = ParserNode.parse_node_to_tree_node(node)
|
||||||
queryset.append(data)
|
queryset.append(data)
|
||||||
if not self.show_assets:
|
for asset in node.assets_granted:
|
||||||
continue
|
system_users = asset.system_users_granted
|
||||||
for asset, system_users in assets.items():
|
data = ParserNode.parse_asset_to_tree_node(node, asset, system_users)
|
||||||
data = parse_asset_to_tree_node(node, asset, system_users)
|
|
||||||
queryset.append(data)
|
queryset.append(data)
|
||||||
queryset = sorted(queryset)
|
queryset = sorted(queryset)
|
||||||
return queryset
|
return self.serializer_class(queryset, many=True)
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView):
|
class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
|
||||||
"""
|
"""
|
||||||
查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
|
查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
|
||||||
"""
|
"""
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
serializer_class = serializers.AssetGrantedSerializer
|
|
||||||
pagination_class = LimitOffsetPagination
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
@ -291,25 +451,31 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_node_key(self):
|
||||||
user = self.get_object()
|
|
||||||
node_id = self.kwargs.get('node_id')
|
node_id = self.kwargs.get('node_id')
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
|
||||||
nodes = util.get_nodes_with_assets()
|
|
||||||
if str(node_id) == const.UNGROUPED_NODE_ID:
|
if str(node_id) == const.UNGROUPED_NODE_ID:
|
||||||
node = util.tree.ungrouped_node
|
key = self.util.tree.ungrouped_key
|
||||||
elif str(node_id) == const.EMPTY_NODE_ID:
|
elif str(node_id) == const.EMPTY_NODE_ID:
|
||||||
node = util.tree.empty_node
|
key = const.EMPTY_NODE_KEY
|
||||||
else:
|
else:
|
||||||
node = get_object_or_404(Node, id=node_id)
|
node = get_object_or_404(Node, id=node_id)
|
||||||
if node == util.tree.root_node:
|
key = node.key
|
||||||
assets = util.get_assets()
|
return key
|
||||||
else:
|
|
||||||
assets = nodes.get(node, {})
|
|
||||||
for asset, system_users in assets.items():
|
|
||||||
asset.system_users_granted = system_users
|
|
||||||
|
|
||||||
assets = list(assets.keys())
|
def get_queryset(self):
|
||||||
|
user = self.get_object()
|
||||||
|
self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
||||||
|
key = self.get_node_key()
|
||||||
|
nodes_items = self.util.get_nodes_with_assets()
|
||||||
|
assets_system_users = {}
|
||||||
|
for item in nodes_items:
|
||||||
|
if item["key"] == key:
|
||||||
|
assets_system_users = item["assets"]
|
||||||
|
break
|
||||||
|
assets = []
|
||||||
|
for asset_id, system_users in assets_system_users.items():
|
||||||
|
assets.append({"id": asset_id, "system_users": system_users})
|
||||||
|
assets = self.search_queryset(assets)
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
|
@ -318,92 +484,6 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List
|
||||||
return super().get_permissions()
|
return super().get_permissions()
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
|
|
||||||
"""
|
|
||||||
获取用户自己授权节点下子节点的api
|
|
||||||
"""
|
|
||||||
permission_classes = (IsValidUser,)
|
|
||||||
serializer_class = serializers.AssetPermissionNodeSerializer
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return self.request.user
|
|
||||||
|
|
||||||
def get_children_queryset(self):
|
|
||||||
user = self.get_object()
|
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
|
||||||
node_id = self.request.query_params.get('id')
|
|
||||||
nodes_granted = util.get_nodes_with_assets()
|
|
||||||
if not nodes_granted:
|
|
||||||
return []
|
|
||||||
root_nodes = [node for node in nodes_granted.keys() if node.is_root()]
|
|
||||||
|
|
||||||
queryset = []
|
|
||||||
if node_id and node_id in [str(node.id) for node in nodes_granted]:
|
|
||||||
node = [node for node in nodes_granted if str(node.id) == node_id][0]
|
|
||||||
elif len(root_nodes) == 1:
|
|
||||||
node = root_nodes[0]
|
|
||||||
node.assets_amount = len(nodes_granted[node])
|
|
||||||
queryset.append(node)
|
|
||||||
else:
|
|
||||||
for node in root_nodes:
|
|
||||||
node.assets_amount = len(nodes_granted[node])
|
|
||||||
queryset.append(node)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
children = []
|
|
||||||
for child in node.get_children():
|
|
||||||
if child in nodes_granted:
|
|
||||||
child.assets_amount = len(nodes_granted[node])
|
|
||||||
children.append(child)
|
|
||||||
children = sorted(children, key=lambda x: x.value)
|
|
||||||
queryset.extend(children)
|
|
||||||
fake_nodes = []
|
|
||||||
for asset, system_users in nodes_granted[node].items():
|
|
||||||
fake_node = asset.as_node()
|
|
||||||
fake_node.assets_amount = 0
|
|
||||||
system_users = [s for s in system_users if asset.has_protocol(s.protocol)]
|
|
||||||
fake_node.asset.system_users_granted = system_users
|
|
||||||
fake_node.key = node.key + ':0'
|
|
||||||
fake_nodes.append(fake_node)
|
|
||||||
fake_nodes = sorted(fake_nodes, key=lambda x: x.value)
|
|
||||||
queryset.extend(fake_nodes)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def get_search_queryset(self, keyword):
|
|
||||||
user = self.get_object()
|
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
|
||||||
nodes_granted = util.get_nodes_with_assets()
|
|
||||||
queryset = []
|
|
||||||
for node, assets in nodes_granted.items():
|
|
||||||
matched_assets = []
|
|
||||||
node_matched = node.value.lower().find(keyword.lower()) >= 0
|
|
||||||
asset_has_matched = False
|
|
||||||
for asset, system_users in assets.items():
|
|
||||||
asset_matched = (asset.hostname.lower().find(keyword.lower()) >= 0) \
|
|
||||||
or (asset.ip.find(keyword.lower()) >= 0)
|
|
||||||
if node_matched or asset_matched:
|
|
||||||
asset_has_matched = True
|
|
||||||
fake_node = asset.as_node()
|
|
||||||
fake_node.assets_amount = 0
|
|
||||||
system_users = [s for s in system_users if
|
|
||||||
asset.has_protocol(s.protocol)]
|
|
||||||
fake_node.asset.system_users_granted = system_users
|
|
||||||
fake_node.key = node.key + ':0'
|
|
||||||
matched_assets.append(fake_node)
|
|
||||||
if asset_has_matched:
|
|
||||||
node.assets_amount = len(matched_assets)
|
|
||||||
queryset.append(node)
|
|
||||||
queryset.extend(sorted(matched_assets, key=lambda x: x.value))
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
keyword = self.request.query_params.get('search')
|
|
||||||
if keyword:
|
|
||||||
return self.get_search_queryset(keyword)
|
|
||||||
else:
|
|
||||||
return self.get_children_queryset()
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
|
class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
|
|
||||||
|
@ -412,24 +492,24 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
|
||||||
asset_id = request.query_params.get('asset_id', '')
|
asset_id = request.query_params.get('asset_id', '')
|
||||||
system_id = request.query_params.get('system_user_id', '')
|
system_id = request.query_params.get('system_user_id', '')
|
||||||
action_name = request.query_params.get('action_name', '')
|
action_name = request.query_params.get('action_name', '')
|
||||||
|
cache_policy = self.request.query_params.get("cache_policy", '0')
|
||||||
|
|
||||||
|
try:
|
||||||
|
asset_id = uuid.UUID(asset_id)
|
||||||
|
system_id = uuid.UUID(system_id)
|
||||||
|
except ValueError:
|
||||||
|
return Response({'msg': False}, status=403)
|
||||||
|
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
asset = get_object_or_404(Asset, id=asset_id)
|
util = AssetPermissionUtil(user, cache_policy=cache_policy)
|
||||||
su = get_object_or_404(SystemUser, id=system_id)
|
assets = util.get_assets()
|
||||||
|
for asset in assets:
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
if asset_id == asset["id"]:
|
||||||
granted_assets = util.get_assets()
|
action = asset["system_users"].get(system_id)
|
||||||
granted_system_users = granted_assets.get(asset, {})
|
if action and action_name in Action.value_to_choices(action):
|
||||||
|
|
||||||
if su not in granted_system_users:
|
|
||||||
return Response({'msg': False}, status=403)
|
|
||||||
|
|
||||||
action = granted_system_users[su]
|
|
||||||
choices = Action.value_to_choices(action)
|
|
||||||
if action_name not in choices:
|
|
||||||
return Response({'msg': False}, status=403)
|
|
||||||
|
|
||||||
return Response({'msg': True}, status=200)
|
return Response({'msg': True}, status=200)
|
||||||
|
break
|
||||||
|
return Response({'msg': False}, status=403)
|
||||||
|
|
||||||
|
|
||||||
class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView):
|
class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView):
|
||||||
|
|
|
@ -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):
|
||||||
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 @@
|
||||||
|
|
||||||
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 _
|
||||||
|
|
||||||
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):
|
||||||
|
|
||||||
@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 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
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,87 +23,56 @@ 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
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Asset
|
||||||
|
only_fields = [
|
||||||
|
"id", "hostname", "ip", "protocols", "os", 'domain',
|
||||||
|
"platform", "org_id",
|
||||||
|
]
|
||||||
|
fields = only_fields + ['system_users_granted', 'system_users_join', "org_name"]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_system_users_join(obj):
|
def get_system_users_join(obj):
|
||||||
system_users = [s.username for s in obj.system_users_granted]
|
system_users = [s.username for s in obj.system_users_granted]
|
||||||
return ', '.join(system_users)
|
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:
|
|
||||||
model = Node
|
|
||||||
fields = [
|
|
||||||
'id', 'key', 'value', 'asset', 'is_node', 'org_id',
|
|
||||||
'tree_id', 'tree_parent', 'assets_amount',
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_assets_amount(obj):
|
|
||||||
return obj.assets_amount
|
|
||||||
|
|
||||||
@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):
|
||||||
"""
|
"""
|
||||||
授权资产组
|
授权资产组
|
||||||
"""
|
"""
|
||||||
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):
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'key', 'value',
|
'id', 'name', 'key', 'value',
|
||||||
]
|
]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
class ActionsSerializer(serializers.Serializer):
|
class ActionsSerializer(serializers.Serializer):
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# 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
|
||||||
|
|
||||||
|
|
||||||
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 = [
|
||||||
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 = [
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -1,80 +1,65 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import json
|
import json
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
import time
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from orgs.utils import set_to_root_org
|
from orgs.utils import set_to_root_org
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger, timeit
|
||||||
from common.tree import TreeNode
|
from common.tree import TreeNode
|
||||||
|
from assets.utils import NodeUtil
|
||||||
from .. import const
|
from .. import const
|
||||||
from ..models import AssetPermission, Action
|
from ..models import AssetPermission, Action
|
||||||
from ..hands import Node, Asset
|
from ..hands import Node, Asset
|
||||||
from assets.utils import NodeUtil
|
from .stack import PermSystemUserNodeUtil, PermAssetsAmountUtil
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets',
|
'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets',
|
||||||
'parse_asset_to_tree_node', 'parse_node_to_tree_node',
|
'ParserNode',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TreeNodeCounter(NodeUtil):
|
|
||||||
def __init__(self, nodes):
|
|
||||||
self.__nodes = nodes
|
|
||||||
super().__init__(with_assets_amount=True)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return self.__nodes
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
logger.debug("Call {} end, using: {:.2}s".format(func.__name__, using))
|
|
||||||
return result
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateTree:
|
class GenerateTree:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
nodes = {
|
nodes = {
|
||||||
"<node1>": {
|
node.key: {
|
||||||
"system_users": {
|
"system_users": {
|
||||||
"system_user": action,
|
system_user.id: actions,
|
||||||
"system_user2": action,
|
},
|
||||||
|
"assets": set([asset.id,]),
|
||||||
},
|
},
|
||||||
"assets": set([<asset_instance>]),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assets = {
|
assets = {
|
||||||
"<asset_instance2>": {
|
asset.id: {
|
||||||
"system_user": action,
|
system_user.id: actions,
|
||||||
"system_user2": action,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
self._node_util = None
|
self._node_util = None
|
||||||
self.nodes = defaultdict(lambda: {"system_users": defaultdict(int), "assets": set(), "assets_amount": 0})
|
self.nodes = defaultdict(lambda: {
|
||||||
|
"system_users": defaultdict(int), "assets": set(),
|
||||||
|
"assets_amount": 0, "all_assets": set(),
|
||||||
|
})
|
||||||
self.assets = defaultdict(lambda: defaultdict(int))
|
self.assets = defaultdict(lambda: defaultdict(int))
|
||||||
self._root_node = None
|
self._root_node = None
|
||||||
self._ungroup_node = None
|
self._ungroup_node = None
|
||||||
self._nodes_with_assets = None
|
self._nodes_with_assets = None
|
||||||
|
self._all_assets_nodes_key = None
|
||||||
|
self._asset_counter = 0
|
||||||
|
self._system_user_counter = 0
|
||||||
|
self._nodes_assets_counter = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def node_util(self):
|
def node_util(self):
|
||||||
|
@ -82,115 +67,160 @@ class GenerateTree:
|
||||||
self._node_util = NodeUtil()
|
self._node_util = NodeUtil()
|
||||||
return self._node_util
|
return self._node_util
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def key_sort(key):
|
||||||
|
key_list = [int(i) for i in key.split(':')]
|
||||||
|
return len(key_list), key_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def root_node(self):
|
def root_key(self):
|
||||||
if self._root_node:
|
if self._root_node:
|
||||||
return self._root_node
|
return self._root_node
|
||||||
all_nodes = self.nodes.keys()
|
all_keys = self.nodes.keys()
|
||||||
# 如果没有授权节点,就放到默认的根节点下
|
# 如果没有授权节点,就放到默认的根节点下
|
||||||
if not all_nodes:
|
if not all_keys:
|
||||||
return None
|
return None
|
||||||
root_node = min(all_nodes)
|
root_key = min(all_keys, key=self.key_sort)
|
||||||
self._root_node = root_node
|
self._root_key = root_key
|
||||||
return root_node
|
return root_key
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ungrouped_node(self):
|
def all_assets_nodes_keys(self):
|
||||||
|
if not self._all_assets_nodes_key:
|
||||||
|
self._all_assets_nodes_key = Asset.get_all_nodes_keys()
|
||||||
|
return self._all_assets_nodes_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ungrouped_key(self):
|
||||||
if self._ungroup_node:
|
if self._ungroup_node:
|
||||||
return self._ungroup_node
|
return self._ungroup_node
|
||||||
node_id = const.UNGROUPED_NODE_ID
|
if self.root_key:
|
||||||
if self.root_node:
|
node_key = "{}:{}".format(self.root_key, '-1')
|
||||||
node_key = "{}:{}".format(self.root_node.key, self.root_node.child_mark)
|
|
||||||
else:
|
else:
|
||||||
node_key = '0:0'
|
node_key = '1:-1'
|
||||||
node_value = _("Default")
|
self._ungroup_node = node_key
|
||||||
node = Node(id=node_id, key=node_key, value=node_value)
|
return node_key
|
||||||
self.add_node(node, {})
|
|
||||||
self._ungroup_node = node
|
|
||||||
return node
|
|
||||||
|
|
||||||
@property
|
@timeit
|
||||||
def empty_node(self):
|
def add_assets_without_system_users(self, assets_ids):
|
||||||
node_id = const.EMPTY_NODE_ID
|
for asset_id in assets_ids:
|
||||||
value = _('Empty')
|
self.add_asset(asset_id, {})
|
||||||
node = Node(id=node_id, value=value)
|
|
||||||
return node
|
@timeit
|
||||||
|
def add_assets(self, assets_ids_with_system_users):
|
||||||
|
for asset_id, system_users_ids in assets_ids_with_system_users.items():
|
||||||
|
self.add_asset(asset_id, system_users_ids)
|
||||||
|
|
||||||
# @timeit
|
# @timeit
|
||||||
def add_assets_without_system_users(self, assets):
|
def add_asset(self, asset_id, system_users_ids=None):
|
||||||
for asset in assets:
|
"""
|
||||||
self.add_asset(asset, {})
|
:param asset_id:
|
||||||
|
:param system_users_ids: {system_user.id: actions, }
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not system_users_ids:
|
||||||
|
system_users_ids = defaultdict(int)
|
||||||
|
|
||||||
#@timeit
|
# 获取已有资产的系统用户和actions,并更新到最新系统用户信息中
|
||||||
def add_assets(self, assets):
|
old_system_users_ids = self.assets[asset_id]
|
||||||
for asset, system_users in assets.items():
|
for system_user_id, action in old_system_users_ids.items():
|
||||||
self.add_asset(asset, system_users)
|
system_users_ids[system_user_id] |= action
|
||||||
|
|
||||||
# #@timeit
|
asset_nodes_keys = self.all_assets_nodes_keys.get(asset_id, [])
|
||||||
def add_asset(self, asset, system_users=None):
|
# {asset.id: [node.key, ], }
|
||||||
nodes = asset.nodes.all()
|
# 获取用户在的节点
|
||||||
nodes = self.node_util.get_nodes_by_queryset(nodes)
|
in_nodes = set(self.nodes.keys()) & set(asset_nodes_keys)
|
||||||
if not system_users:
|
|
||||||
system_users = defaultdict(int)
|
|
||||||
else:
|
|
||||||
system_users = {k: v for k, v in system_users.items()}
|
|
||||||
_system_users = self.assets[asset]
|
|
||||||
for system_user, action in _system_users.items():
|
|
||||||
system_users[system_user] |= action
|
|
||||||
|
|
||||||
# 获取父节点们
|
|
||||||
parents = self.node_util.get_nodes_parents(nodes, with_self=True)
|
|
||||||
for node in parents:
|
|
||||||
_system_users = self.nodes[node]["system_users"]
|
|
||||||
self.nodes[node]["assets_amount"] += 1
|
|
||||||
for system_user, action in _system_users.items():
|
|
||||||
system_users[system_user] |= action
|
|
||||||
|
|
||||||
# 过滤系统用户的协议
|
|
||||||
system_users = {s: v for s, v in system_users.items() if asset.has_protocol(s.protocol)}
|
|
||||||
self.assets[asset] = system_users
|
|
||||||
|
|
||||||
in_nodes = set(self.nodes.keys()) & set(nodes)
|
|
||||||
if not in_nodes:
|
if not in_nodes:
|
||||||
self.nodes[self.ungrouped_node]["assets_amount"] += 1
|
self.nodes[self.ungrouped_key]["assets"].add(asset_id)
|
||||||
self.nodes[self.ungrouped_node]["assets"].add(system_users)
|
self.assets[asset_id] = system_users_ids
|
||||||
return
|
return
|
||||||
|
|
||||||
for node in in_nodes:
|
# 遍历用户应该在的节点
|
||||||
self.nodes[node]["assets"].add(asset)
|
for key in in_nodes:
|
||||||
|
# 把自己加入到树上的节点中
|
||||||
|
self.nodes[key]["assets"].add(asset_id)
|
||||||
|
# 获取自己所在节点的系统用户,并添加进去
|
||||||
|
node_system_users_ids = self.nodes[key]["system_users"]
|
||||||
|
for system_user_id, action in node_system_users_ids.items():
|
||||||
|
system_users_ids[system_user_id] |= action
|
||||||
|
self.assets[asset_id] = system_users_ids
|
||||||
|
|
||||||
def add_node(self, node, system_users=None):
|
def add_node(self, node_key, system_users_ids=None):
|
||||||
if not system_users:
|
"""
|
||||||
system_users = defaultdict(int)
|
:param node_key: node.key
|
||||||
self.nodes[node]["system_users"] = system_users
|
:param system_users_ids: {system_user.id: actions,}
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not system_users_ids:
|
||||||
|
system_users_ids = defaultdict(int)
|
||||||
|
self.nodes[node_key]["system_users"] = system_users_ids
|
||||||
|
|
||||||
# 添加树节点
|
# 添加树节点
|
||||||
#@timeit
|
@timeit
|
||||||
def add_nodes(self, nodes):
|
def add_nodes(self, nodes_keys_with_system_users_ids):
|
||||||
_nodes = nodes.keys()
|
"""
|
||||||
family = self.node_util.get_family(_nodes, with_children=True)
|
:param nodes_keys_with_system_users_ids:
|
||||||
for node in family:
|
{node.key: {system_user.id: actions,}, }
|
||||||
self.add_node(node, nodes.get(node, {}))
|
:return:
|
||||||
|
"""
|
||||||
|
util = PermSystemUserNodeUtil()
|
||||||
|
family = util.get_nodes_family_and_system_users(nodes_keys_with_system_users_ids)
|
||||||
|
for key, system_users in family.items():
|
||||||
|
self.add_node(key, system_users)
|
||||||
|
|
||||||
def get_assets(self):
|
def get_assets(self):
|
||||||
return dict(self.assets)
|
"""
|
||||||
|
:return:
|
||||||
|
[
|
||||||
|
{"id": asset.id, "system_users": {system_user.id: actions, }},
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
assets = []
|
||||||
|
for asset_id, system_users in self.assets.items():
|
||||||
|
assets.append({"id": asset_id, "system_users": system_users})
|
||||||
|
return assets
|
||||||
|
|
||||||
#@timeit
|
@timeit
|
||||||
def get_nodes_with_assets(self):
|
def get_nodes_with_assets(self):
|
||||||
|
"""
|
||||||
|
:return:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'key': node.key,
|
||||||
|
'assets_amount': 10
|
||||||
|
'assets': {
|
||||||
|
asset.id: {
|
||||||
|
system_user.id: actions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"""
|
||||||
if self._nodes_with_assets:
|
if self._nodes_with_assets:
|
||||||
return self._nodes_with_assets
|
return self._nodes_with_assets
|
||||||
nodes = {}
|
util = PermAssetsAmountUtil()
|
||||||
for node, values in self.nodes.items():
|
nodes_with_assets_amount = util.compute_nodes_assets_amount(self.nodes)
|
||||||
node._assets_amount = values["assets_amount"]
|
nodes = []
|
||||||
nodes[node] = {asset: self.assets.get(asset, {}) for asset in values["assets"]}
|
for key, values in nodes_with_assets_amount.items():
|
||||||
|
assets = {asset_id: self.assets.get(asset_id) for asset_id in values["assets"]}
|
||||||
|
nodes.append({
|
||||||
|
"key": key, "assets": assets,
|
||||||
|
"assets_amount": values["assets_amount"]
|
||||||
|
})
|
||||||
# 如果返回空节点,页面构造授权资产树报错
|
# 如果返回空节点,页面构造授权资产树报错
|
||||||
if not nodes:
|
if not nodes:
|
||||||
nodes[self.empty_node] = {}
|
nodes.append({
|
||||||
|
"key": const.EMPTY_NODE_KEY, "assets": {}, "assets_amount": 0
|
||||||
|
})
|
||||||
|
nodes.sort(key=lambda n: self.key_sort(n["key"]))
|
||||||
self._nodes_with_assets = nodes
|
self._nodes_with_assets = nodes
|
||||||
return dict(nodes)
|
return nodes
|
||||||
|
|
||||||
def get_nodes(self):
|
def get_nodes(self):
|
||||||
return list(self.nodes.keys())
|
nodes = list(self.nodes.keys())
|
||||||
|
if not nodes:
|
||||||
|
nodes.append(const.EMPTY_NODE_KEY)
|
||||||
|
return list(nodes)
|
||||||
|
|
||||||
|
|
||||||
def get_user_permissions(user, include_group=True):
|
def get_user_permissions(user, include_group=True):
|
||||||
|
@ -228,8 +258,8 @@ def get_system_user_permissions(system_user):
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionCacheMixin:
|
class AssetPermissionCacheMixin:
|
||||||
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_'
|
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_V2_'
|
||||||
CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_'
|
CACHE_META_KEY_PREFIX = '_ASSET_PERM_META_KEY_V2_'
|
||||||
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
||||||
CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh'))
|
CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh'))
|
||||||
cache_policy = '1'
|
cache_policy = '1'
|
||||||
|
@ -283,6 +313,7 @@ class AssetPermissionCacheMixin:
|
||||||
return self.get_cache_key('SYSTEM_USER')
|
return self.get_cache_key('SYSTEM_USER')
|
||||||
|
|
||||||
def get_resource_from_cache(self, resource):
|
def get_resource_from_cache(self, resource):
|
||||||
|
logger.debug("Try get resource from cache")
|
||||||
key_map = {
|
key_map = {
|
||||||
"assets": self.asset_key,
|
"assets": self.asset_key,
|
||||||
"nodes": self.node_key,
|
"nodes": self.node_key,
|
||||||
|
@ -294,18 +325,22 @@ class AssetPermissionCacheMixin:
|
||||||
raise ValueError("Not a valid resource: {}".format(resource))
|
raise ValueError("Not a valid resource: {}".format(resource))
|
||||||
cached = cache.get(key)
|
cached = cache.get(key)
|
||||||
if not cached:
|
if not cached:
|
||||||
|
logger.debug("Not found resource cache, update it")
|
||||||
self.update_cache()
|
self.update_cache()
|
||||||
cached = cache.get(key)
|
cached = cache.get(key)
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
def get_resource(self, resource):
|
def get_resource(self, resource):
|
||||||
if self._is_using_cache():
|
if self._is_using_cache():
|
||||||
|
logger.debug("Using cache to get resource")
|
||||||
return self.get_resource_from_cache(resource)
|
return self.get_resource_from_cache(resource)
|
||||||
elif self._is_refresh_cache():
|
elif self._is_refresh_cache():
|
||||||
|
logger.debug("Need refresh cache")
|
||||||
self.expire_cache()
|
self.expire_cache()
|
||||||
data = self.get_resource_from_cache(resource)
|
data = self.get_resource_from_cache(resource)
|
||||||
return data
|
return data
|
||||||
else:
|
else:
|
||||||
|
logger.debug("Not using cache get source")
|
||||||
return self.get_resource_without_cache(resource)
|
return self.get_resource_without_cache(resource)
|
||||||
|
|
||||||
def get_resource_without_cache(self, resource):
|
def get_resource_without_cache(self, resource):
|
||||||
|
@ -430,88 +465,91 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
self._permissions = permissions
|
self._permissions = permissions
|
||||||
return permissions
|
return permissions
|
||||||
|
|
||||||
#@timeit
|
@timeit
|
||||||
def filter_permissions(self, **filters):
|
def filter_permissions(self, **filters):
|
||||||
filters_json = json.dumps(filters, sort_keys=True)
|
filters_json = json.dumps(filters, sort_keys=True)
|
||||||
self._permissions = self.permissions.filter(**filters)
|
self._permissions = self.permissions.filter(**filters)
|
||||||
self._filter_id = md5(filters_json.encode()).hexdigest()
|
self._filter_id = md5(filters_json.encode()).hexdigest()
|
||||||
|
|
||||||
#@timeit
|
@timeit
|
||||||
def get_nodes_direct(self):
|
def get_nodes_direct(self):
|
||||||
"""
|
"""
|
||||||
返回用户/组授权规则直接关联的节点
|
返回直接授权的节点,
|
||||||
:return: {node1: {system_user1: {'actions': set()},}}
|
并将节点添加到tree.nodes中,并将节点下的资产添加到tree.assets中
|
||||||
|
:return:
|
||||||
|
{node.key: {system_user.id: actions,}, }
|
||||||
"""
|
"""
|
||||||
if self._nodes_direct:
|
if self._nodes_direct:
|
||||||
return self._nodes_direct
|
return self._nodes_direct
|
||||||
nodes = defaultdict(lambda: defaultdict(int))
|
nodes_keys = defaultdict(lambda: defaultdict(int))
|
||||||
for perm in self.permissions:
|
for perm in self.permissions:
|
||||||
actions = [perm.actions]
|
actions = [perm.actions]
|
||||||
system_users = perm.system_users.all()
|
system_users_ids = [s.id for s in perm.system_users.all()]
|
||||||
_nodes = perm.nodes.all()
|
_nodes_keys = [n.key for n in perm.nodes.all()]
|
||||||
for node, system_user, action in itertools.product(_nodes, system_users, actions):
|
iterable = itertools.product(_nodes_keys, system_users_ids, actions)
|
||||||
nodes[node][system_user] |= action
|
for node_key, sys_id, action in iterable:
|
||||||
self.tree.add_nodes(nodes)
|
nodes_keys[node_key][sys_id] |= action
|
||||||
self._nodes_direct = nodes
|
|
||||||
return nodes
|
self.tree.add_nodes(nodes_keys)
|
||||||
|
|
||||||
|
pattern = set()
|
||||||
|
for key in nodes_keys:
|
||||||
|
pattern.add(r'^{0}$|^{0}:'.format(key))
|
||||||
|
pattern = '|'.join(list(pattern))
|
||||||
|
if pattern:
|
||||||
|
assets_ids = Asset.objects.filter(
|
||||||
|
nodes__key__regex=pattern
|
||||||
|
).values_list("id", flat=True).distinct()
|
||||||
|
else:
|
||||||
|
assets_ids = []
|
||||||
|
self.tree.add_assets_without_system_users(assets_ids)
|
||||||
|
self._nodes_direct = nodes_keys
|
||||||
|
return nodes_keys
|
||||||
|
|
||||||
def get_nodes_without_cache(self):
|
def get_nodes_without_cache(self):
|
||||||
self.get_assets_direct()
|
self.get_assets_without_cache()
|
||||||
return self.tree.get_nodes()
|
return self.tree.get_nodes()
|
||||||
|
|
||||||
#@timeit
|
@timeit
|
||||||
def get_assets_direct(self):
|
def get_assets_direct(self):
|
||||||
"""
|
"""
|
||||||
返回用户授权规则直接关联的资产
|
返回直接授权的资产,
|
||||||
:return: {asset1: {system_user1: 1,}}
|
并添加到tree.assets中
|
||||||
|
:return:
|
||||||
|
{asset.id: {system_user.id: actions, }, }
|
||||||
"""
|
"""
|
||||||
if self._assets_direct:
|
if self._assets_direct:
|
||||||
return self._assets_direct
|
return self._assets_direct
|
||||||
assets = defaultdict(lambda: defaultdict(int))
|
assets_ids = defaultdict(lambda: defaultdict(int))
|
||||||
for perm in self.permissions:
|
for perm in self.permissions:
|
||||||
actions = [perm.actions]
|
actions = [perm.actions]
|
||||||
_assets = perm.assets.valid().only(*self.assets_only)
|
_assets_ids = [a.id for a in perm.assets.all()]
|
||||||
system_users = perm.system_users.all()
|
system_users_ids = [s.id for s in perm.system_users.all()]
|
||||||
iterable = itertools.product(_assets, system_users, actions)
|
iterable = itertools.product(_assets_ids, system_users_ids, actions)
|
||||||
for asset, system_user, action in iterable:
|
for asset_id, sys_id, action in iterable:
|
||||||
assets[asset][system_user] |= action
|
assets_ids[asset_id][sys_id] |= action
|
||||||
self.tree.add_assets(assets)
|
self.tree.add_assets(assets_ids)
|
||||||
self._assets_direct = assets
|
self._assets_direct = assets_ids
|
||||||
return assets
|
return assets_ids
|
||||||
|
|
||||||
#@timeit
|
@timeit
|
||||||
def get_assets_without_cache(self):
|
def get_assets_without_cache(self):
|
||||||
"""
|
"""
|
||||||
:return: {asset1: set(system_user1,)}
|
:return:
|
||||||
|
[
|
||||||
|
{"id": asset.id, "system_users": {system_user.id: actions, }},
|
||||||
|
]
|
||||||
"""
|
"""
|
||||||
if self._assets:
|
if self._assets:
|
||||||
return self._assets
|
return self._assets
|
||||||
|
self.get_nodes_direct()
|
||||||
self.get_assets_direct()
|
self.get_assets_direct()
|
||||||
nodes = self.get_nodes_direct()
|
|
||||||
pattern = set()
|
|
||||||
for node in nodes:
|
|
||||||
pattern.add(r'^{0}$|^{0}:'.format(node.key))
|
|
||||||
pattern = '|'.join(list(pattern))
|
|
||||||
if pattern:
|
|
||||||
assets = Asset.objects.filter(nodes__key__regex=pattern).valid() \
|
|
||||||
.prefetch_related('nodes')\
|
|
||||||
.only(*self.assets_only)\
|
|
||||||
.distinct()
|
|
||||||
else:
|
|
||||||
assets = []
|
|
||||||
assets = list(assets)
|
|
||||||
self.tree.add_assets_without_system_users(assets)
|
|
||||||
assets = self.tree.get_assets()
|
assets = self.tree.get_assets()
|
||||||
self._assets = assets
|
self._assets = assets
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
#@timeit
|
@timeit
|
||||||
def get_nodes_with_assets_without_cache(self):
|
def get_nodes_with_assets_without_cache(self):
|
||||||
"""
|
|
||||||
返回节点并且包含资产
|
|
||||||
{"node": {"asset": {"system_user": 1})}}
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.get_assets_without_cache()
|
self.get_assets_without_cache()
|
||||||
nodes_assets = self.tree.get_nodes_with_assets()
|
nodes_assets = self.tree.get_nodes_with_assets()
|
||||||
return nodes_assets
|
return nodes_assets
|
||||||
|
@ -545,6 +583,14 @@ def sort_assets(assets, order_by='hostname', reverse=False):
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
|
|
||||||
|
class ParserNode:
|
||||||
|
nodes_only_fields = ("key", "value", "id")
|
||||||
|
assets_only_fields = ("platform", "hostname", "id", "ip", "protocols")
|
||||||
|
system_users_only_fields = (
|
||||||
|
"id", "name", "username", "protocol", "priority", "login_mode",
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_node_to_tree_node(node):
|
def parse_node_to_tree_node(node):
|
||||||
name = '{} ({})'.format(node.value, node.assets_amount)
|
name = '{} ({})'.format(node.value, node.assets_amount)
|
||||||
data = {
|
data = {
|
||||||
|
@ -566,7 +612,7 @@ def parse_node_to_tree_node(node):
|
||||||
tree_node = TreeNode(**data)
|
tree_node = TreeNode(**data)
|
||||||
return tree_node
|
return tree_node
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_asset_to_tree_node(node, asset, system_users):
|
def parse_asset_to_tree_node(node, asset, system_users):
|
||||||
icon_skin = 'file'
|
icon_skin = 'file'
|
||||||
if asset.platform.lower() == 'windows':
|
if asset.platform.lower() == 'windows':
|
||||||
|
@ -574,7 +620,7 @@ def parse_asset_to_tree_node(node, asset, system_users):
|
||||||
elif asset.platform.lower() == 'linux':
|
elif asset.platform.lower() == 'linux':
|
||||||
icon_skin = 'linux'
|
icon_skin = 'linux'
|
||||||
_system_users = []
|
_system_users = []
|
||||||
for system_user, action in system_users.items():
|
for system_user in system_users:
|
||||||
_system_users.append({
|
_system_users.append({
|
||||||
'id': system_user.id,
|
'id': system_user.id,
|
||||||
'name': system_user.name,
|
'name': system_user.name,
|
||||||
|
@ -582,7 +628,7 @@ def parse_asset_to_tree_node(node, asset, system_users):
|
||||||
'protocol': system_user.protocol,
|
'protocol': system_user.protocol,
|
||||||
'priority': system_user.priority,
|
'priority': system_user.priority,
|
||||||
'login_mode': system_user.login_mode,
|
'login_mode': system_user.login_mode,
|
||||||
'actions': [Action.value_to_choices(action)],
|
'actions': [Action.value_to_choices(system_user.actions)],
|
||||||
})
|
})
|
||||||
data = {
|
data = {
|
||||||
'id': str(asset.id),
|
'id': str(asset.id),
|
||||||
|
@ -601,9 +647,6 @@ def parse_asset_to_tree_node(node, asset, system_users):
|
||||||
'ip': asset.ip,
|
'ip': asset.ip,
|
||||||
'protocols': asset.protocols_as_list,
|
'protocols': asset.protocols_as_list,
|
||||||
'platform': asset.platform,
|
'platform': asset.platform,
|
||||||
'domain': None if not asset.domain else asset.domain.id,
|
|
||||||
'is_active': asset.is_active,
|
|
||||||
'comment': asset.comment
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
# -*- 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
|
|
@ -27,97 +27,7 @@ signer = get_signer()
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class AuthMixin:
|
||||||
ROLE_ADMIN = 'Admin'
|
|
||||||
ROLE_USER = 'User'
|
|
||||||
ROLE_APP = 'App'
|
|
||||||
ROLE_AUDITOR = 'Auditor'
|
|
||||||
|
|
||||||
ROLE_CHOICES = (
|
|
||||||
(ROLE_ADMIN, _('Administrator')),
|
|
||||||
(ROLE_USER, _('User')),
|
|
||||||
(ROLE_APP, _('Application')),
|
|
||||||
(ROLE_AUDITOR, _("Auditor"))
|
|
||||||
)
|
|
||||||
OTP_LEVEL_CHOICES = (
|
|
||||||
(0, _('Disable')),
|
|
||||||
(1, _('Enable')),
|
|
||||||
(2, _("Force enable")),
|
|
||||||
)
|
|
||||||
SOURCE_LOCAL = 'local'
|
|
||||||
SOURCE_LDAP = 'ldap'
|
|
||||||
SOURCE_OPENID = 'openid'
|
|
||||||
SOURCE_RADIUS = 'radius'
|
|
||||||
SOURCE_CHOICES = (
|
|
||||||
(SOURCE_LOCAL, 'Local'),
|
|
||||||
(SOURCE_LDAP, 'LDAP/AD'),
|
|
||||||
(SOURCE_OPENID, 'OpenID'),
|
|
||||||
(SOURCE_RADIUS, 'Radius'),
|
|
||||||
)
|
|
||||||
|
|
||||||
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
|
|
||||||
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
|
||||||
username = models.CharField(
|
|
||||||
max_length=128, unique=True, verbose_name=_('Username')
|
|
||||||
)
|
|
||||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
|
||||||
email = models.EmailField(
|
|
||||||
max_length=128, unique=True, verbose_name=_('Email')
|
|
||||||
)
|
|
||||||
groups = models.ManyToManyField(
|
|
||||||
'users.UserGroup', related_name='users',
|
|
||||||
blank=True, verbose_name=_('User group')
|
|
||||||
)
|
|
||||||
role = models.CharField(
|
|
||||||
choices=ROLE_CHOICES, default='User', max_length=10,
|
|
||||||
blank=True, verbose_name=_('Role')
|
|
||||||
)
|
|
||||||
avatar = models.ImageField(
|
|
||||||
upload_to="avatar", null=True, verbose_name=_('Avatar')
|
|
||||||
)
|
|
||||||
wechat = models.CharField(
|
|
||||||
max_length=128, blank=True, verbose_name=_('Wechat')
|
|
||||||
)
|
|
||||||
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=_('MFA')
|
|
||||||
)
|
|
||||||
otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True)
|
|
||||||
# Todo: Auto generate key, let user download
|
|
||||||
private_key = fields.EncryptTextField(
|
|
||||||
blank=True, null=True, verbose_name=_('Private key')
|
|
||||||
)
|
|
||||||
public_key = fields.EncryptTextField(
|
|
||||||
blank=True, null=True, verbose_name=_('Public key')
|
|
||||||
)
|
|
||||||
comment = models.TextField(
|
|
||||||
blank=True, null=True, verbose_name=_('Comment')
|
|
||||||
)
|
|
||||||
is_first_login = models.BooleanField(default=True)
|
|
||||||
date_expired = models.DateTimeField(
|
|
||||||
default=date_expired_default, blank=True, null=True,
|
|
||||||
db_index=True, verbose_name=_('Date expired')
|
|
||||||
)
|
|
||||||
created_by = models.CharField(
|
|
||||||
max_length=30, default='', verbose_name=_('Created by')
|
|
||||||
)
|
|
||||||
source = models.CharField(
|
|
||||||
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
|
|
||||||
verbose_name=_('Source')
|
|
||||||
)
|
|
||||||
date_password_last_updated = models.DateTimeField(
|
|
||||||
auto_now_add=True, blank=True, null=True,
|
|
||||||
verbose_name=_('Date password last updated')
|
|
||||||
)
|
|
||||||
|
|
||||||
user_cache_key_prefix = '_User_{}'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{0.name}({0.username})'.format(self)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def password_raw(self):
|
def password_raw(self):
|
||||||
raise AttributeError('Password raw is not a readable attribute')
|
raise AttributeError('Password raw is not a readable attribute')
|
||||||
|
@ -134,9 +44,12 @@ class User(AbstractUser):
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
self._set_password = True
|
self._set_password = True
|
||||||
if self.can_update_password():
|
if self.can_update_password():
|
||||||
return super().set_password(raw_password)
|
self.date_password_last_updated = timezone.now()
|
||||||
|
super().set_password(raw_password)
|
||||||
|
self.save()
|
||||||
else:
|
else:
|
||||||
error = _("User auth from {}, go there change password").format(self.source)
|
error = _("User auth from {}, go there change password").format(
|
||||||
|
self.source)
|
||||||
raise PermissionError(error)
|
raise PermissionError(error)
|
||||||
|
|
||||||
def can_update_password(self):
|
def can_update_password(self):
|
||||||
|
@ -146,9 +59,6 @@ class User(AbstractUser):
|
||||||
from ..utils import check_otp_code
|
from ..utils import check_otp_code
|
||||||
return check_otp_code(self.otp_secret_key, code)
|
return check_otp_code(self.otp_secret_key, code)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('users:user-detail', args=(self.id,))
|
|
||||||
|
|
||||||
def is_public_key_valid(self):
|
def is_public_key_valid(self):
|
||||||
"""
|
"""
|
||||||
Check if the user's ssh public key is valid.
|
Check if the user's ssh public key is valid.
|
||||||
|
@ -158,36 +68,12 @@ class User(AbstractUser):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
|
||||||
def groups_display(self):
|
|
||||||
return ' '.join([group.name for group in self.groups.all()])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def role_display(self):
|
|
||||||
return self.get_role_display()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def source_display(self):
|
|
||||||
return self.get_source_display()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_expired(self):
|
|
||||||
if self.date_expired and self.date_expired < timezone.now():
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_valid(self):
|
|
||||||
if self.is_active and not self.is_expired:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def public_key_obj(self):
|
def public_key_obj(self):
|
||||||
class PubKey(object):
|
class PubKey(object):
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
if self.public_key:
|
if self.public_key:
|
||||||
import sshpubkeys
|
import sshpubkeys
|
||||||
try:
|
try:
|
||||||
|
@ -196,6 +82,52 @@ class User(AbstractUser):
|
||||||
pass
|
pass
|
||||||
return PubKey()
|
return PubKey()
|
||||||
|
|
||||||
|
def reset_password(self, new_password):
|
||||||
|
self.set_password(new_password)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_password_expired(self):
|
||||||
|
interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME
|
||||||
|
date_expired = self.date_password_last_updated + timezone.timedelta(
|
||||||
|
days=int(interval))
|
||||||
|
return date_expired
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password_expired_remain_days(self):
|
||||||
|
date_remain = self.date_password_expired - timezone.now()
|
||||||
|
return date_remain.days
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password_has_expired(self):
|
||||||
|
if self.is_local and self.password_expired_remain_days < 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password_will_expired(self):
|
||||||
|
if self.is_local and self.password_expired_remain_days < 5:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class RoleMixin:
|
||||||
|
ROLE_ADMIN = 'Admin'
|
||||||
|
ROLE_USER = 'User'
|
||||||
|
ROLE_APP = 'App'
|
||||||
|
ROLE_AUDITOR = 'Auditor'
|
||||||
|
|
||||||
|
ROLE_CHOICES = (
|
||||||
|
(ROLE_ADMIN, _('Administrator')),
|
||||||
|
(ROLE_USER, _('User')),
|
||||||
|
(ROLE_APP, _('Application')),
|
||||||
|
(ROLE_AUDITOR, _("Auditor"))
|
||||||
|
)
|
||||||
|
role = ROLE_USER
|
||||||
|
|
||||||
|
@property
|
||||||
|
def role_display(self):
|
||||||
|
return self.get_role_display()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_superuser(self):
|
def is_superuser(self):
|
||||||
if self.role == 'Admin':
|
if self.role == 'Admin':
|
||||||
|
@ -251,41 +183,21 @@ class User(AbstractUser):
|
||||||
def is_staff(self, value):
|
def is_staff(self, value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@classmethod
|
||||||
def is_local(self):
|
def create_app_user(cls, name, comment):
|
||||||
return self.source == self.SOURCE_LOCAL
|
app = cls.objects.create(
|
||||||
|
username=name, name=name, email='{}@local.domain'.format(name),
|
||||||
|
is_active=False, role='App', comment=comment,
|
||||||
|
is_first_login=False, created_by='System'
|
||||||
|
)
|
||||||
|
access_key = app.create_access_key()
|
||||||
|
return app, access_key
|
||||||
|
|
||||||
@property
|
|
||||||
def date_password_expired(self):
|
|
||||||
interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME
|
|
||||||
date_expired = self.date_password_last_updated + timezone.timedelta(
|
|
||||||
days=int(interval))
|
|
||||||
return date_expired
|
|
||||||
|
|
||||||
@property
|
class TokenMixin:
|
||||||
def password_expired_remain_days(self):
|
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
|
||||||
date_remain = self.date_password_expired - timezone.now()
|
email = ''
|
||||||
return date_remain.days
|
id = None
|
||||||
|
|
||||||
@property
|
|
||||||
def password_has_expired(self):
|
|
||||||
if self.is_local and self.password_expired_remain_days < 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def password_will_expired(self):
|
|
||||||
if self.is_local and self.password_expired_remain_days < 5:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if not self.name:
|
|
||||||
self.name = self.username
|
|
||||||
if self.username == 'admin':
|
|
||||||
self.role = 'Admin'
|
|
||||||
self.is_active = True
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_token(self):
|
def private_token(self):
|
||||||
|
@ -333,31 +245,12 @@ class User(AbstractUser):
|
||||||
def access_key(self):
|
def access_key(self):
|
||||||
return self.access_keys.first()
|
return self.access_keys.first()
|
||||||
|
|
||||||
def is_member_of(self, user_group):
|
|
||||||
if user_group in self.groups.all():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def avatar_url(self):
|
|
||||||
admin_default = settings.STATIC_URL + "img/avatar/admin.png"
|
|
||||||
user_default = settings.STATIC_URL + "img/avatar/user.png"
|
|
||||||
if self.avatar:
|
|
||||||
return self.avatar.url
|
|
||||||
if self.is_superuser:
|
|
||||||
return admin_default
|
|
||||||
else:
|
|
||||||
return user_default
|
|
||||||
|
|
||||||
def generate_reset_token(self):
|
def generate_reset_token(self):
|
||||||
letter = string.ascii_letters + string.digits
|
letter = string.ascii_letters + string.digits
|
||||||
token = ''.join([random.choice(letter) for _ in range(50)])
|
token = ''.join([random.choice(letter) for _ in range(50)])
|
||||||
self.set_cache(token)
|
self.set_cache(token)
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def set_cache(self, token):
|
|
||||||
key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
|
|
||||||
cache.set(key, {'id': self.id, 'email': self.email}, 3600)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_reset_password_token(cls, token):
|
def validate_reset_password_token(cls, token):
|
||||||
try:
|
try:
|
||||||
|
@ -371,11 +264,25 @@ class User(AbstractUser):
|
||||||
user = None
|
user = None
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def set_cache(self, token):
|
||||||
|
key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
|
||||||
|
cache.set(key, {'id': self.id, 'email': self.email}, 3600)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def expired_reset_password_token(cls, token):
|
def expired_reset_password_token(cls, token):
|
||||||
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
|
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
|
||||||
cache.delete(key)
|
cache.delete(key)
|
||||||
|
|
||||||
|
|
||||||
|
class MFAMixin:
|
||||||
|
otp_level = 0
|
||||||
|
otp_secret_key = ''
|
||||||
|
OTP_LEVEL_CHOICES = (
|
||||||
|
(0, _('Disable')),
|
||||||
|
(1, _('Enable')),
|
||||||
|
(2, _("Force enable")),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def otp_enabled(self):
|
def otp_enabled(self):
|
||||||
return self.otp_force_enabled or self.otp_level > 0
|
return self.otp_force_enabled or self.otp_level > 0
|
||||||
|
@ -397,39 +304,130 @@ class User(AbstractUser):
|
||||||
self.otp_level = 0
|
self.otp_level = 0
|
||||||
self.otp_secret_key = None
|
self.otp_secret_key = None
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return OrderedDict({
|
|
||||||
'id': self.id,
|
|
||||||
'username': self.username,
|
|
||||||
'name': self.name,
|
|
||||||
'email': self.email,
|
|
||||||
'is_active': self.is_active,
|
|
||||||
'is_superuser': self.is_superuser,
|
|
||||||
'role': self.get_role_display(),
|
|
||||||
'groups': [group.name for group in self.groups.all()],
|
|
||||||
'source': self.get_source_display(),
|
|
||||||
'wechat': self.wechat,
|
|
||||||
'phone': self.phone,
|
|
||||||
'otp_level': self.otp_level,
|
|
||||||
'comment': self.comment,
|
|
||||||
'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S') \
|
|
||||||
if self.date_expired is not None else None
|
|
||||||
})
|
|
||||||
|
|
||||||
@classmethod
|
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
||||||
def create_app_user(cls, name, comment):
|
SOURCE_LOCAL = 'local'
|
||||||
app = cls.objects.create(
|
SOURCE_LDAP = 'ldap'
|
||||||
username=name, name=name, email='{}@local.domain'.format(name),
|
SOURCE_OPENID = 'openid'
|
||||||
is_active=False, role='App', comment=comment,
|
SOURCE_RADIUS = 'radius'
|
||||||
is_first_login=False, created_by='System'
|
SOURCE_CHOICES = (
|
||||||
|
(SOURCE_LOCAL, 'Local'),
|
||||||
|
(SOURCE_LDAP, 'LDAP/AD'),
|
||||||
|
(SOURCE_OPENID, 'OpenID'),
|
||||||
|
(SOURCE_RADIUS, 'Radius'),
|
||||||
)
|
)
|
||||||
access_key = app.create_access_key()
|
|
||||||
return app, access_key
|
|
||||||
|
|
||||||
def reset_password(self, new_password):
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
self.set_password(new_password)
|
username = models.CharField(
|
||||||
self.date_password_last_updated = timezone.now()
|
max_length=128, unique=True, verbose_name=_('Username')
|
||||||
self.save()
|
)
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
|
email = models.EmailField(
|
||||||
|
max_length=128, unique=True, verbose_name=_('Email')
|
||||||
|
)
|
||||||
|
groups = models.ManyToManyField(
|
||||||
|
'users.UserGroup', related_name='users',
|
||||||
|
blank=True, verbose_name=_('User group')
|
||||||
|
)
|
||||||
|
role = models.CharField(
|
||||||
|
choices=RoleMixin.ROLE_CHOICES, default='User', max_length=10,
|
||||||
|
blank=True, verbose_name=_('Role')
|
||||||
|
)
|
||||||
|
avatar = models.ImageField(
|
||||||
|
upload_to="avatar", null=True, verbose_name=_('Avatar')
|
||||||
|
)
|
||||||
|
wechat = models.CharField(
|
||||||
|
max_length=128, blank=True, verbose_name=_('Wechat')
|
||||||
|
)
|
||||||
|
phone = models.CharField(
|
||||||
|
max_length=20, blank=True, null=True, verbose_name=_('Phone')
|
||||||
|
)
|
||||||
|
otp_level = models.SmallIntegerField(
|
||||||
|
default=0, choices=MFAMixin.OTP_LEVEL_CHOICES, verbose_name=_('MFA')
|
||||||
|
)
|
||||||
|
otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True)
|
||||||
|
# Todo: Auto generate key, let user download
|
||||||
|
private_key = fields.EncryptTextField(
|
||||||
|
blank=True, null=True, verbose_name=_('Private key')
|
||||||
|
)
|
||||||
|
public_key = fields.EncryptTextField(
|
||||||
|
blank=True, null=True, verbose_name=_('Public key')
|
||||||
|
)
|
||||||
|
comment = models.TextField(
|
||||||
|
blank=True, null=True, verbose_name=_('Comment')
|
||||||
|
)
|
||||||
|
is_first_login = models.BooleanField(default=True)
|
||||||
|
date_expired = models.DateTimeField(
|
||||||
|
default=date_expired_default, blank=True, null=True,
|
||||||
|
db_index=True, verbose_name=_('Date expired')
|
||||||
|
)
|
||||||
|
created_by = models.CharField(
|
||||||
|
max_length=30, default='', verbose_name=_('Created by')
|
||||||
|
)
|
||||||
|
source = models.CharField(
|
||||||
|
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
|
||||||
|
verbose_name=_('Source')
|
||||||
|
)
|
||||||
|
date_password_last_updated = models.DateTimeField(
|
||||||
|
auto_now_add=True, blank=True, null=True,
|
||||||
|
verbose_name=_('Date password last updated')
|
||||||
|
)
|
||||||
|
|
||||||
|
user_cache_key_prefix = '_User_{}'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{0.name}({0.username})'.format(self)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('users:user-detail', args=(self.id,))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def groups_display(self):
|
||||||
|
return ' '.join([group.name for group in self.groups.all()])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def source_display(self):
|
||||||
|
return self.get_source_display()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_expired(self):
|
||||||
|
if self.date_expired and self.date_expired < timezone.now():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
if self.is_active and not self.is_expired:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_local(self):
|
||||||
|
return self.source == self.SOURCE_LOCAL
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.name:
|
||||||
|
self.name = self.username
|
||||||
|
if self.username == 'admin':
|
||||||
|
self.role = 'Admin'
|
||||||
|
self.is_active = True
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def is_member_of(self, user_group):
|
||||||
|
if user_group in self.groups.all():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def avatar_url(self):
|
||||||
|
admin_default = settings.STATIC_URL + "img/avatar/admin.png"
|
||||||
|
user_default = settings.STATIC_URL + "img/avatar/user.png"
|
||||||
|
if self.avatar:
|
||||||
|
return self.avatar.url
|
||||||
|
if self.is_superuser:
|
||||||
|
return admin_default
|
||||||
|
else:
|
||||||
|
return user_default
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.pk == 1 or self.username == 'admin':
|
if self.pk == 1 or self.username == 'admin':
|
||||||
|
|
|
@ -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 @@
|
||||||
<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;
|
||||||
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() {
|
||||||
$(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() {
|
||||||
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}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue