Unverified Commit c8623c3b authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #2947 from jumpserver/dev

Dev
parents e6037303 9d54baac
...@@ -107,6 +107,21 @@ function hiddenFields(){ ...@@ -107,6 +107,21 @@ function hiddenFields(){
}); });
$('.' + app_type + '-fields').removeClass('hidden'); $('.' + app_type + '-fields').removeClass('hidden');
} }
function constructParams(data) {
var typeList = ['chrome', 'mysql_workbench', 'vmware_client', 'custom'];
var params = {};
for (var type in typeList){
if (data.type === type){
for (var k in data){
if (k.startsWith(data.type)){
params[k] = data[k]
}
}
break
}
}
return params;
}
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
closeOnSelect: true closeOnSelect: true
...@@ -118,6 +133,28 @@ $(document).ready(function () { ...@@ -118,6 +133,28 @@ $(document).ready(function () {
.on('change', app_type_id, function(){ .on('change', app_type_id, function(){
hiddenFields(); hiddenFields();
setDefaultValue(); setDefaultValue();
}); })
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url "api-applications:remote-app-list" %}';
var redirect_to = '{% url "applications:remote-app-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url "api-applications:remote-app-detail" object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
data["params"] = constructParams(data);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
;
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -46,6 +46,7 @@ class RemoteAppCreateView(PermissionsMixin, SuccessMessageMixin, CreateView): ...@@ -46,6 +46,7 @@ class RemoteAppCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
context = { context = {
'app': _('Applications'), 'app': _('Applications'),
'action': _('Create RemoteApp'), 'action': _('Create RemoteApp'),
'type': 'create'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -68,6 +69,7 @@ class RemoteAppUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView): ...@@ -68,6 +69,7 @@ class RemoteAppUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
context = { context = {
'app': _('Applications'), 'app': _('Applications'),
'action': _('Update RemoteApp'), 'action': _('Update RemoteApp'),
'type': 'update'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
......
...@@ -13,11 +13,11 @@ ...@@ -13,11 +13,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from django.db.models import Count from django.db.models import Count
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins import OrgBulkModelViewSet
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import Label from ..models import Label
from .. import serializers from .. import serializers
...@@ -27,7 +27,7 @@ logger = get_logger(__file__) ...@@ -27,7 +27,7 @@ logger = get_logger(__file__)
__all__ = ['LabelViewSet'] __all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet): class LabelViewSet(OrgBulkModelViewSet):
filter_fields = ("name", "value") filter_fields = ("name", "value")
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
......
...@@ -131,7 +131,7 @@ class NodeChildrenAsTreeApi(generics.ListAPIView): ...@@ -131,7 +131,7 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
if not include_assets: if not include_assets:
return queryset return queryset
assets = self.node.get_assets().only( assets = self.node.get_assets().only(
"id", "hostname", "ip", 'platform', "os", "org_id", "id", "hostname", "ip", 'platform', "os", "org_id", "protocols",
) )
for asset in assets: for asset in assets:
queryset.append(asset.as_tree_node(self.node)) queryset.append(asset.as_tree_node(self.node))
......
...@@ -29,9 +29,9 @@ class ProtocolForm(forms.Form): ...@@ -29,9 +29,9 @@ class ProtocolForm(forms.Form):
class AssetCreateForm(OrgModelForm): class AssetCreateForm(OrgModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if not self.data: nodes_field = self.fields['nodes']
nodes_field = self.fields['nodes'] nodes_field.choices = ((n.id, n.full_value) for n in
nodes_field._queryset = Node.get_queryset() Node.get_queryset())
class Meta: class Meta:
model = Asset model = Asset
......
# Generated by Django 2.1.7 on 2019-07-11 12:18
import common.fields.model
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0034_auto_20190705_1348'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='authbook',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='gateway',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='systemuser',
name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
),
]
...@@ -6,7 +6,8 @@ import uuid ...@@ -6,7 +6,8 @@ import uuid
import logging import logging
import random import random
from functools import reduce from functools import reduce
from collections import OrderedDict from collections import OrderedDict, defaultdict
from django.core.cache import cache
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -96,7 +97,53 @@ class ProtocolsMixin: ...@@ -96,7 +97,53 @@ class ProtocolsMixin:
return self.protocols_as_dict.get("ssh", 22) return self.protocols_as_dict.get("ssh", 22)
class Asset(ProtocolsMixin, OrgModelMixin): class NodesRelationMixin:
NODES_CACHE_KEY = 'ASSET_NODES_{}'
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
CACHE_TIME = 3600 * 24 * 7
id = ""
_all_nodes_keys = None
@classmethod
def get_all_nodes_keys(cls):
"""
:return: {asset.id: [node.key, ]}
"""
from .node import Node
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cached = cache.get(cache_key)
if cached:
return cached
assets = Asset.objects.all().only('id').prefetch_related(
models.Prefetch('nodes', queryset=Node.objects.all().only('key'))
)
assets_nodes_keys = {}
for asset in assets:
assets_nodes_keys[asset.id] = [n.key for n in asset.nodes.all()]
cache.set(cache_key, assets_nodes_keys, cls.CACHE_TIME)
return assets_nodes_keys
@classmethod
def expire_all_nodes_keys_cache(cls):
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cache.delete(cache_key)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Important # Important
PLATFORM_CHOICES = ( PLATFORM_CHOICES = (
('Linux', 'Linux'), ('Linux', 'Linux'),
...@@ -182,20 +229,6 @@ class Asset(ProtocolsMixin, OrgModelMixin): ...@@ -182,20 +229,6 @@ class Asset(ProtocolsMixin, OrgModelMixin):
def is_support_ansible(self): def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",) return self.has_protocol('ssh') and self.platform not in ("Other",)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
@property @property
def cpu_info(self): def cpu_info(self):
info = "" info = ""
......
...@@ -28,7 +28,7 @@ class AssetUser(OrgModelMixin): ...@@ -28,7 +28,7 @@ class AssetUser(OrgModelMixin):
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric]) username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
......
...@@ -212,14 +212,12 @@ class AssetsAmountMixin: ...@@ -212,14 +212,12 @@ class AssetsAmountMixin:
if cached is not None: if cached is not None:
return cached return cached
assets_amount = self.get_all_assets().count() assets_amount = self.get_all_assets().count()
self.assets_amount = assets_amount cache.set(cache_key, assets_amount, self.cache_time)
return assets_amount return assets_amount
@assets_amount.setter @assets_amount.setter
def assets_amount(self, value): def assets_amount(self, value):
self._assets_amount = value self._assets_amount = value
cache_key = self._assets_amount_cache_key.format(self.key)
cache.set(cache_key, value, self.cache_time)
def expire_assets_amount(self): def expire_assets_amount(self):
ancestor_keys = self.get_ancestor_keys(with_self=True) ancestor_keys = self.get_ancestor_keys(with_self=True)
......
...@@ -117,16 +117,6 @@ class SystemUser(AssetUser): ...@@ -117,16 +117,6 @@ class SystemUser(AssetUser):
def __str__(self): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
def to_json(self):
return {
'id': self.id,
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'priority': self.priority,
'auto_push': self.auto_push,
}
@property @property
def login_mode_display(self): def login_mode_display(self):
return self.get_login_mode_display() return self.get_login_mode_display()
......
...@@ -21,19 +21,15 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -21,19 +21,15 @@ 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',
] ]
read_only_fields = ['date_created', 'date_updated', 'created_by', 'assets_amount']
extra_kwargs = { extra_kwargs = {
'password': {"write_only": True}, 'password': {"write_only": True},
'private_key': {"write_only": True}, 'private_key': {"write_only": True},
'public_key': {"write_only": True}, 'public_key': {"write_only": True},
'date_created': {'read_only': True},
'date_updated': {'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 ...@@ -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 ...@@ -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):
...@@ -14,7 +15,11 @@ class DomainSerializer(BulkOrgResourceModelSerializer): ...@@ -14,7 +15,11 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = Domain model = Domain
fields = '__all__' fields = [
'id', 'name', 'asset_count', 'gateway_count', 'comment', 'assets',
'date_created'
]
read_only_fields = ( 'asset_count', 'gateway_count', 'date_created')
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
@staticmethod @staticmethod
...@@ -26,14 +31,14 @@ class DomainSerializer(BulkOrgResourceModelSerializer): ...@@ -26,14 +31,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',
] ]
......
...@@ -13,7 +13,13 @@ class LabelSerializer(BulkOrgResourceModelSerializer): ...@@ -13,7 +13,13 @@ class LabelSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = Label model = Label
fields = '__all__' fields = [
'id', 'name', 'value', 'category', 'is_active', 'comment',
'date_created', 'asset_count', 'assets', 'get_category_display'
]
read_only_fields = (
'category', 'date_created', 'asset_count', 'get_category_display'
)
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
@staticmethod @staticmethod
......
...@@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer): ...@@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = Node model = Node
fields = [ only_fields = ['id', 'key', 'value', 'org_id']
'id', 'key', 'value', 'assets_amount', 'org_id', fields = only_fields + ['assets_amount']
]
read_only_fields = [ read_only_fields = [
'key', 'assets_amount', 'org_id', 'key', 'assets_amount', 'org_id',
] ]
......
...@@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'id', 'name', 'username', 'password', 'public_key', 'private_key', 'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol', 'login_mode', 'login_mode_display', 'priority', 'protocol',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
'assets_amount', 'connectivity_amount', 'auto_generate_key' 'assets_amount', 'auto_generate_key'
] ]
extra_kwargs = { extra_kwargs = {
'password': {"write_only": True}, 'password': {"write_only": True},
'public_key': {"write_only": True}, 'public_key': {"write_only": True},
'private_key': {"write_only": True}, 'private_key': {"write_only": True},
'assets_amount': {'label': _('Asset')}, 'assets_amount': {'label': _('Asset')},
'connectivity_amount': {'label': _('Connectivity')},
'login_mode_display': {'label': _('Login mode display')}, 'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True}, 'created_by': {'read_only': True},
} }
......
...@@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs): ...@@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through) @receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs): def on_asset_node_changed(sender, instance=None, **kwargs):
logger.debug("Asset nodes change signal received") logger.debug("Asset nodes change signal received")
Asset.expire_all_nodes_keys_cache()
if isinstance(instance, Asset): if isinstance(instance, Asset):
if kwargs['action'] == 'pre_remove': if kwargs['action'] == 'pre_remove':
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
......
...@@ -70,7 +70,7 @@ function showAuth() { ...@@ -70,7 +70,7 @@ function showAuth() {
var msg = "{% trans 'Get auth info error' %}"; var msg = "{% trans 'Get auth info error' %}";
toastr.error(msg) toastr.error(msg)
}; };
APIUpdateAttr({ requestApi({
url: url, url: url,
method: "GET", method: "GET",
success: success, success: success,
......
...@@ -141,7 +141,7 @@ $(document).ready(function(){ ...@@ -141,7 +141,7 @@ $(document).ready(function(){
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400') window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'GET', method: 'GET',
success: success, success: success,
......
...@@ -235,7 +235,7 @@ function onRename(event, treeId, treeNode, isCancel){ ...@@ -235,7 +235,7 @@ function onRename(event, treeId, treeNode, isCancel){
if (isCancel){ if (isCancel){
return return
} }
APIUpdateAttr({ requestApi({
url: url, url: url,
body: JSON.stringify(data), body: JSON.stringify(data),
method: "PATCH", method: "PATCH",
...@@ -274,7 +274,7 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) { ...@@ -274,7 +274,7 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id); var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id);
var body = {nodes: treeNodesIds}; var body = {nodes: treeNodesIds};
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: "PUT", method: "PUT",
body: JSON.stringify(body) body: JSON.stringify(body)
...@@ -291,42 +291,6 @@ function defaultCallback(action) { ...@@ -291,42 +291,6 @@ function defaultCallback(action) {
$(document).ready(function () { $(document).ready(function () {
}) })
.on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
if (!current_node_id) {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-show-current-asset', function(){ .on('click', '.btn-show-current-asset', function(){
hideRMenu(); hideRMenu();
$(this).css('display', 'none'); $(this).css('display', 'none');
...@@ -341,17 +305,5 @@ $(document).ready(function () { ...@@ -341,17 +305,5 @@ $(document).ready(function () {
setCookie('show_current_asset', ''); setCookie('show_current_asset', '');
location.reload(); location.reload();
}) })
.on('click', '.btn-test-connective', function () {
hideRMenu();
})
.on('click', '#menu_refresh_assets_amount', function () {
hideRMenu();
var url = "{% url 'api-assets:refresh-assets-amount' %}";
APIUpdateAttr({
'url': url,
'method': 'GET'
});
window.location.reload();
})
</script> </script>
\ No newline at end of file
...@@ -228,6 +228,7 @@ $(document).ready(function () { ...@@ -228,6 +228,7 @@ $(document).ready(function () {
var form = $("form"); var form = $("form");
var data = form.serializeObject(); var data = form.serializeObject();
objectAttrsIsList(data, ['cmd_filters']);
objectAttrsIsBool(data, ["auto_generate_key", "auto_push"]); objectAttrsIsBool(data, ["auto_generate_key", "auto_push"]);
data["private_key"] = $("#id_private_key_file").data('file'); data["private_key"] = $("#id_private_key_file").data('file');
......
...@@ -88,7 +88,7 @@ $(document).ready(function () { ...@@ -88,7 +88,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400') window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'GET', method: 'GET',
success: success, success: success,
......
...@@ -131,7 +131,7 @@ function replaceNodeAssetsAdminUser(nodes) { ...@@ -131,7 +131,7 @@ function replaceNodeAssetsAdminUser(nodes) {
// clear jumpserver.groups_selected // clear jumpserver.groups_selected
jumpserver.nodes_selected = {}; jumpserver.nodes_selected = {};
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
{% load i18n static %} {% load i18n static %}
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
{# Windows或其它硬件可以随意设置一个#}
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%} {% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%} {% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
{% trans 'You can set any one for Windows or other hardware.' %} {% trans 'You can set any one for Windows or other hardware.' %}
...@@ -47,9 +45,9 @@ ...@@ -47,9 +45,9 @@
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th> <th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th> {# <th class="text-center">{% trans 'Reachable' %}</th>#}
<th class="text-center">{% trans 'Unreachable' %}</th> {# <th class="text-center">{% trans 'Unreachable' %}</th>#}
<th class="text-center">{% trans 'Ratio' %}</th> {# <th class="text-center">{% trans 'Ratio' %}</th>#}
<th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -73,44 +71,44 @@ function initTable() { ...@@ -73,44 +71,44 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id); return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}}, }},
{targets: 4, createdCell: function (td, cellData) { {#{targets: 4, createdCell: function (td, cellData) {#}
var innerHtml = ""; {# var innerHtml = "";#}
var data = cellData.reachable; {# var data = cellData.reachable;#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-navy'>" + data + "</span>"; {# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html(innerHtml) {# $(td).html(innerHtml)#}
}}, {#}},#}
{targets: 5, createdCell: function (td, cellData) { {#{targets: 5, createdCell: function (td, cellData) {#}
var data = cellData.unreachable; {# var data = cellData.unreachable;#}
var innerHtml = ""; {# var innerHtml = "";#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-danger'>" + data + "</span>"; {# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 6, createdCell: function (td, cellData, rowData) { {#{targets: 6, createdCell: function (td, cellData, rowData) {#}
var val = 0; {# var val = 0;#}
var innerHtml = ""; {# var innerHtml = "";#}
var total = rowData.assets_amount; {# var total = rowData.assets_amount;#}
var reachable = cellData.reachable; {# var reachable = cellData.reachable;#}
if (total !== 0) { {# if (total !== 0) {#}
val = reachable/total * 100; {# val = reachable/total * 100;#}
} {# }#}
{##}
if (val === 100) { {# if (val === 100) {#}
innerHtml = "<span class='text-navy'>" + val + "% </span>"; {# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
} else { {# } else {#}
var num = new Number(val); {# var num = new Number(val);#}
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; {# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 8, createdCell: function (td, cellData, rowData) { {targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
...@@ -118,7 +116,7 @@ function initTable() { ...@@ -118,7 +116,7 @@ function initTable() {
ajax_url: '{% url "api-assets:admin-user-list" %}', ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [ columns: [
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, {data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#}
{data: "comment"}, {data: "id"} {data: "comment"}, {data: "id"}
] ]
}; };
......
...@@ -84,7 +84,7 @@ $(document).ready(function () { ...@@ -84,7 +84,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400') window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'GET', method: 'GET',
success: success, success: success,
......
...@@ -137,7 +137,6 @@ $(document).ready(function () { ...@@ -137,7 +137,6 @@ $(document).ready(function () {
protocolRef.val(protocolShould); protocolRef.val(protocolShould);
protocolRef.trigger("change") protocolRef.trigger("change")
} }
}) })
.on("click", ".btn-protocol.btn-del", function () { .on("click", ".btn-protocol.btn-del", function () {
$(this).parent().parent().remove(); $(this).parent().parent().remove();
......
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
</tr> </tr>
<tr> <tr>
<td>{% trans 'Protocol' %}</td> <td>{% trans 'Protocol' %}</td>
<td>{{ asset.protocols }}</td> <td><b>{{ asset.protocols }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Admin user' %}:</td> <td>{% trans 'Admin user' %}:</td>
...@@ -267,7 +267,7 @@ function updateAssetNodes(nodes) { ...@@ -267,7 +267,7 @@ function updateAssetNodes(nodes) {
// clear jumpserver.groups_selected // clear jumpserver.groups_selected
jumpserver.nodes_selected = {}; jumpserver.nodes_selected = {};
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -282,7 +282,7 @@ function refreshAssetHardware() { ...@@ -282,7 +282,7 @@ function refreshAssetHardware() {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600') window.open(url, '', 'width=800,height=600')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
success: success, success: success,
method: 'GET' method: 'GET'
...@@ -306,7 +306,7 @@ $(document).ready(function () { ...@@ -306,7 +306,7 @@ $(document).ready(function () {
}; };
var success = '{% trans "Update successfully!" %}'; var success = '{% trans "Update successfully!" %}';
var status = $(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").text(); var status = $(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").text();
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success_message: success success_message: success
...@@ -360,7 +360,7 @@ $(document).ready(function () { ...@@ -360,7 +360,7 @@ $(document).ready(function () {
window.open(url, '', 'width=800,height=600') window.open(url, '', 'width=800,height=600')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'GET', method: 'GET',
success: success success: success
......
...@@ -167,7 +167,7 @@ function initTable() { ...@@ -167,7 +167,7 @@ function initTable() {
}}, }},
{targets: 5, createdCell: function (td, cellData, rowData) { {targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData); var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
}} }}
], ],
...@@ -325,8 +325,7 @@ $(document).ready(function(){ ...@@ -325,8 +325,7 @@ $(document).ready(function(){
} }
window.open(url, '_self'); window.open(url, '_self');
}) })
.on('click', '.btn-asset-delete', function () {
.on('click', '.btn_asset_delete', function () {
var $this = $(this); var $this = $(this);
var $data_table = $("#asset_list_table").DataTable(); var $data_table = $("#asset_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html(); var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
...@@ -361,7 +360,7 @@ $(document).ready(function(){ ...@@ -361,7 +360,7 @@ $(document).ready(function(){
setTimeout( function () { setTimeout( function () {
window.location.reload();}, 500); window.location.reload();}, 500);
} }
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(data), body: JSON.stringify(data),
...@@ -378,7 +377,7 @@ $(document).ready(function(){ ...@@ -378,7 +377,7 @@ $(document).ready(function(){
setTimeout( function () { setTimeout( function () {
window.location.reload();}, 300); window.location.reload();}, 300);
} }
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(data), body: JSON.stringify(data),
...@@ -398,7 +397,7 @@ $(document).ready(function(){ ...@@ -398,7 +397,7 @@ $(document).ready(function(){
},function () { },function () {
function success(data) { function success(data) {
url = setUrlParam(the_url, 'spm', data.spm); url = setUrlParam(the_url, 'spm', data.spm);
APIUpdateAttr({ requestApi({
url:url, url:url,
method:'DELETE', method:'DELETE',
success:refreshTag, success:refreshTag,
...@@ -411,7 +410,7 @@ $(document).ready(function(){ ...@@ -411,7 +410,7 @@ $(document).ready(function(){
var msg = "{% trans 'Asset Deleting failed.' %}"; var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error"); swal("{% trans 'Asset Delete' %}", msg, "error");
} }
APIUpdateAttr({ requestApi({
url: "{% url 'api-common:resources-cache' %}", url: "{% url 'api-common:resources-cache' %}",
method:'POST', method:'POST',
body:JSON.stringify(data), body:JSON.stringify(data),
...@@ -429,7 +428,7 @@ $(document).ready(function(){ ...@@ -429,7 +428,7 @@ $(document).ready(function(){
var url = "{% url 'assets:asset-bulk-update' %}"; var url = "{% url 'assets:asset-bulk-update' %}";
location.href= setUrlParam(url, 'spm', data.spm); location.href= setUrlParam(url, 'spm', data.spm);
} }
APIUpdateAttr({ requestApi({
url: "{% url 'api-common:resources-cache' %}", url: "{% url 'api-common:resources-cache' %}",
method:'POST', method:'POST',
body:JSON.stringify(data), body:JSON.stringify(data),
...@@ -453,7 +452,7 @@ $(document).ready(function(){ ...@@ -453,7 +452,7 @@ $(document).ready(function(){
asset_table.ajax.reload() asset_table.ajax.reload()
}; };
APIUpdateAttr({ requestApi({
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/', 'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
'method': 'PUT', 'method': 'PUT',
'body': JSON.stringify(data), 'body': JSON.stringify(data),
...@@ -501,7 +500,7 @@ $(document).ready(function(){ ...@@ -501,7 +500,7 @@ $(document).ready(function(){
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id); url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
} }
APIUpdateAttr({ requestApi({
'url': url, 'url': url,
'method': 'PUT', 'method': 'PUT',
'body': JSON.stringify(data), 'body': JSON.stringify(data),
...@@ -513,6 +512,40 @@ $(document).ready(function(){ ...@@ -513,6 +512,40 @@ $(document).ready(function(){
update_node_action = "add" update_node_action = "add"
}).on('click', '#menu_asset_move', function () { }).on('click', '#menu_asset_move', function () {
update_node_action = "move" update_node_action = "move"
}).on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
if (!current_node_id) {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
requestApi({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
}).on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
requestApi({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
}) })
</script> </script>
......
...@@ -18,3 +18,29 @@ ...@@ -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 %}
\ No newline at end of file
...@@ -136,7 +136,7 @@ function updateCMDFilterSystemUsers(system_users) { ...@@ -136,7 +136,7 @@ function updateCMDFilterSystemUsers(system_users) {
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
method: 'PATCH', method: 'PATCH',
......
...@@ -70,5 +70,25 @@ $(document).ready(function(){ ...@@ -70,5 +70,25 @@ $(document).ready(function(){
content_help_ref.html(content_origin_help_text); content_help_ref.html(content_origin_help_text);
} }
}) })
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var the_url = '{% url "api-assets:cmd-filter-rule-list" filter_pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", data.filter);
var redirect_to = '{% url "assets:cmd-filter-rule-list" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", data.filter);
var method = "POST";
{% if request_type == "update" %}
the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=DEFAULT_PK pk=rule.id %}'.replace('{{ DEFAULT_PK }}', data.filter);
method = "PUT";
{% endif %}
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -48,5 +48,26 @@ $(document).ready(function () { ...@@ -48,5 +48,26 @@ $(document).ready(function () {
$("#asset_list_modal").modal('hide'); $("#asset_list_modal").modal('hide');
}) })
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var method = "POST";
var the_url = '{% url "api-assets:domain-list" %}';
var redirect_to = '{% url "assets:domain-list" %}';
{% if type == "update" %}
the_url = '{% url 'api-assets:domain-detail' pk=object.id %}';
method = "PUT";
{% endif %}
objectAttrsIsList(data, ['assets']);
var props = {
url:the_url,
data:data,
method:method,
form:form,
redirect_to:redirect_to
};
formSubmit(props);
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -134,7 +134,7 @@ $(document).ready(function(){ ...@@ -134,7 +134,7 @@ $(document).ready(function(){
var data = $("#test_gateway_form").serializeObject(); var data = $("#test_gateway_form").serializeObject();
var uid = data.gateway_id; var uid = data.gateway_id;
var the_url = '{% url "api-assets:test-gateway-connective" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid); var the_url = '{% url "api-assets:test-gateway-connective" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: "POST", method: "POST",
body: JSON.stringify({'port': parseInt(data.port)}), body: JSON.stringify({'port': parseInt(data.port)}),
......
...@@ -95,6 +95,32 @@ function protocolChange() { ...@@ -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();
}); });
......
...@@ -51,5 +51,26 @@ $(document).ready(function () { ...@@ -51,5 +51,26 @@ $(document).ready(function () {
$('#id_assets').val(assets).trigger('change'); $('#id_assets').val(assets).trigger('change');
$("#asset_list_modal").modal('hide'); $("#asset_list_modal").modal('hide');
}) })
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-assets:label-list' %}';
var redirect_to = '{% url "assets:label-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url 'api-assets:label-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['assets']);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -146,7 +146,7 @@ function updateSystemUserNode(nodes) { ...@@ -146,7 +146,7 @@ function updateSystemUserNode(nodes) {
// clear jumpserver.nodes_selected // clear jumpserver.nodes_selected
jumpserver.nodes_selected = {}; jumpserver.nodes_selected = {};
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -206,7 +206,7 @@ $(document).ready(function () { ...@@ -206,7 +206,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400') window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
error: error, error: error,
method: 'GET', method: 'GET',
...@@ -226,7 +226,7 @@ $(document).ready(function () { ...@@ -226,7 +226,7 @@ $(document).ready(function () {
var error = function (data) { var error = function (data) {
alert(data) alert(data)
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'GET', method: 'GET',
success: success, success: success,
...@@ -243,7 +243,7 @@ $(document).ready(function () { ...@@ -243,7 +243,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400') window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
error: error, error: error,
method: 'GET', method: 'GET',
......
...@@ -212,7 +212,7 @@ function updateCommandFilters(command_filters) { ...@@ -212,7 +212,7 @@ function updateCommandFilters(command_filters) {
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -235,7 +235,7 @@ $(document).ready(function () { ...@@ -235,7 +235,7 @@ $(document).ready(function () {
var body = { var body = {
'auto_push': checked 'auto_push': checked
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
...@@ -254,7 +254,7 @@ $(document).ready(function () { ...@@ -254,7 +254,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400') window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'GET', method: 'GET',
success: success, success: success,
...@@ -268,7 +268,7 @@ $(document).ready(function () { ...@@ -268,7 +268,7 @@ $(document).ready(function () {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600') window.open(url, '', 'width=800,height=600')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'GET', method: 'GET',
success: success, success: success,
......
...@@ -53,9 +53,9 @@ ...@@ -53,9 +53,9 @@
<th class="text-center">{% trans 'Protocol' %}</th> <th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Login mode' %}</th> <th class="text-center">{% trans 'Login mode' %}</th>
<th class="text-center">{% trans 'Asset' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th> {# <th class="text-center">{% trans 'Reachable' %}</th>#}
<th class="text-center">{% trans 'Unreachable' %}</th> {# <th class="text-center">{% trans 'Unreachable' %}</th>#}
<th class="text-center">{% trans 'Ratio' %}</th> {# <th class="text-center">{% trans 'Ratio' %}</th>#}
<th class="text-center">{% trans 'Comment' %}</th> <th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -78,44 +78,44 @@ function initTable() { ...@@ -78,44 +78,44 @@ function initTable() {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
{targets: 6, createdCell: function (td, cellData) { {#{targets: 6, createdCell: function (td, cellData) {#}
var innerHtml = ""; {# var innerHtml = "";#}
var data = cellData.reachable; {# var data = cellData.reachable;#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-navy'>" + data + "</span>"; {# innerHtml = "<span class='text-navy'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html(innerHtml) {# $(td).html(innerHtml)#}
}}, {#}},#}
{targets: 7, createdCell: function (td, cellData) { {#{targets: 7, createdCell: function (td, cellData) {#}
var data = cellData.unreachable; {# var data = cellData.unreachable;#}
var innerHtml = ""; {# var innerHtml = "";#}
if (data !== 0) { {# if (data !== 0) {#}
innerHtml = "<span class='text-danger'>" + data + "</span>"; {# innerHtml = "<span class='text-danger'>" + data + "</span>";#}
} else { {# } else {#}
innerHtml = "<span>" + data + "</span>"; {# innerHtml = "<span>" + data + "</span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 8, createdCell: function (td, cellData, rowData) { {#{targets: 8, createdCell: function (td, cellData, rowData) {#}
var val = 0; {# var val = 0;#}
var innerHtml = ""; {# var innerHtml = "";#}
var total = rowData.assets_amount; {# var total = rowData.assets_amount;#}
var reachable = cellData.reachable; {# var reachable = cellData.reachable;#}
if (total && total !== 0) { {# if (total && total !== 0) {#}
val = reachable/total * 100; {# val = reachable/total * 100;#}
} {# }#}
{##}
if (val === 100) { {# if (val === 100) {#}
innerHtml = "<span class='text-navy'>" + val + "% </span>"; {# innerHtml = "<span class='text-navy'>" + val + "% </span>";#}
} else { {# } else {#}
var num = new Number(val); {# var num = new Number(val);#}
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; {# innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";#}
} {# }#}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); {# $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');#}
}}, {#}},#}
{targets: 10, createdCell: function (td, cellData, rowData) { {targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
...@@ -124,7 +124,7 @@ function initTable() { ...@@ -124,7 +124,7 @@ function initTable() {
ajax_url: '{% url "api-assets:system-user-list" %}', ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [ columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" }, {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" },
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "comment" }, {data: "id" } {data: "comment" }, {data: "id" }
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
...@@ -182,7 +182,7 @@ $(document).ready(function(){ ...@@ -182,7 +182,7 @@ $(document).ready(function(){
swal("{% trans 'System Users Delete' %}", msg, "error"); swal("{% trans 'System Users Delete' %}", msg, "error");
}; };
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list); var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail}); requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload(); $data_table.ajax.reload();
jumpserver.checked = false; jumpserver.checked = false;
}); });
......
...@@ -11,48 +11,7 @@ ...@@ -11,48 +11,7 @@
{% block content %} {% block content %}
<div class="wrapper wrapper-content"> <div class="wrapper wrapper-content">
<div class="row"> <div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px"> {% include 'users/_granted_assets.html' %}
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="user_assets_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</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 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div> </div>
</div> </div>
...@@ -62,130 +21,52 @@ ...@@ -62,130 +21,52 @@
{% 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 assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1";
var inited = false; var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1';
var url; var showAssetHref = false; // Need input default true
var actions = {
targets: 4, createdCell: function (td, cellData) {
function initTable() { var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +
if (inited){ '" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>';
return $(td).html(conn_btn)
} else { }};
inited = true;
}
url = "{% url 'api-perms:my-assets' %}?cache_policy=1";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
$(td).html(detail_btn.replace("rowData_id", rowData.id));
}},
{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 = [];
$.each(cellData, function (id, data) {
users.push(data.name);
});
$(td).html(users.join(', '))
}},
{targets: 5, 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);
$(td).html(conn_btn)
}}
],
ajax_url: url,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false},
{data: "id", orderable: false}
]
};
asset_table = jumpserver.initServerSideDataTable(options);
return asset_table
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1';
var node_id = treeNode.meta.node.id;
url = url.replace("{{ DEFAULT_PK }}", node_id);
setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true,
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
var zNodes = [];
$.get(treeUrl, function(data, status){
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rootNodeAddDom(zTree, function () {
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
initTree();
});
});
}
$(document).ready(function () { $(document).ready(function () {
initTree(); initTree();
initTable(); initTable();
}).on('click', '.labels li', function () { }).on('click', '.labels li', function () {
var val = $(this).text(); var val = $(this).text();
$("#user_assets_table_filter input").val(val); $("#user_assets_table_filter input").val(val);
asset_table.search(val).draw(); assetTable.search(val).draw();
}) })
.on('click', '.asset_detail', function() { .on('click', '.asset-detail', function(e) {
var data = asset_table.ajax.json(); e.preventDefault();
var asset_id = this.getAttribute("asset-id"); var data = assetTable.ajax.json();
var assetId = $(this).data("asset");
var trs = ''; var trs = '';
var desc = { var desc = {
'hostname': "{% trans 'Hostname' %}", 'hostname': "{% trans 'Hostname' %}",
'ip': "{% trans 'IP' %}", 'ip': "{% trans 'IP' %}",
'port': "{% trans 'Port' %}", 'protocols': "{% trans 'Protocols' %}",
'protocol': "{% trans 'Protocol' %}",
'platform': "{% trans 'Platform' %}", 'platform': "{% trans 'Platform' %}",
'os': "{% trans 'OS' %}",
'system_users_join': "{% trans 'System user' %}", 'system_users_join': "{% trans 'System user' %}",
'domain': "{% trans 'Domain' %}", 'domain': "{% trans 'Domain' %}",
'is_active': "{% trans 'Is active' %}",
'comment': "{% trans 'Comment' %}"
{#'date_joined': "{% trans 'Date joined' %}",#}
}; };
$.each(data.results, function(index, value){ var value;
if(value.id === asset_id){ for (var i = 0; i < data.results.length; i++) {
value = data.results[i];
if(value.id === assetId){
for(var i in desc){ for(var i in desc){
trs += "<tr class='no-borders-tr'>\n" + trs += "<tr class='no-borders-tr'>\n" +
"<td>"+ desc[i] + ":</td>"+ "<td>"+ desc[i] + ":</td>"+
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" + "<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
"</tr>"; "</tr>";
} }
break
} }
}); };
$('#asset_detail_tbody').html(trs) $('#asset_detail_tbody').html(trs)
$('#user_asset_detail_modal').modal();
}); });
function toggle() { function toggle() {
...@@ -204,5 +85,4 @@ function toggle() { ...@@ -204,5 +85,4 @@ function toggle() {
} }
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
import time import time
from django.db.models import Prefetch from functools import reduce
from django.db.models import Prefetch, Q
from common.utils import get_object_or_none, get_logger from common.utils import get_object_or_none, get_logger
from common.struct import Stack from common.struct import Stack
...@@ -21,24 +22,34 @@ def get_system_user_by_id(id): ...@@ -21,24 +22,34 @@ def get_system_user_by_id(id):
return system_user return system_user
class LabelFilter: class LabelFilterMixin:
def filter_queryset(self, queryset): def get_filter_labels_ids(self):
queryset = super().filter_queryset(queryset) query_params = self.request.query_params
query_keys = self.request.query_params.keys() query_keys = query_params.keys()
all_label_keys = Label.objects.values_list('name', flat=True) all_label_keys = Label.objects.values_list('name', flat=True)
valid_keys = set(all_label_keys) & set(query_keys) valid_keys = set(all_label_keys) & set(query_keys)
labels_query = {}
for key in valid_keys: if not valid_keys:
labels_query[key] = self.request.query_params.get(key) return []
conditions = [] labels_query = [
for k, v in labels_query.items(): {"name": key, "value": query_params[key]}
query = {'labels__name': k, 'labels__value': v} for key in valid_keys
conditions.append(query) ]
args = [Q(**kwargs) for kwargs in labels_query]
if conditions: args = reduce(lambda x, y: x | y, args)
for kwargs in conditions: labels_id = Label.objects.filter(args).values_list('id', flat=True)
queryset = queryset.filter(**kwargs) return labels_id
class LabelFilter(LabelFilterMixin):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
labels_ids = self.get_filter_labels_ids()
if not labels_ids:
return queryset
for labels_id in labels_ids:
queryset = queryset.filter(labels=labels_id)
return queryset return queryset
...@@ -104,7 +115,7 @@ class NodeUtil: ...@@ -104,7 +115,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 +156,69 @@ class NodeUtil: ...@@ -145,29 +156,69 @@ class NodeUtil:
def nodes(self): def nodes(self):
return list(self._nodes.values()) return list(self._nodes.values())
def get_family_by_key(self, key):
tree_nodes = set()
node = self.get_node_by_key(key)
if not node:
return []
tree_nodes.update(node._parents)
tree_nodes.add(node)
tree_nodes.update(node._all_children)
return list(tree_nodes)
# 使用给定节点生成一颗树 # 使用给定节点生成一颗树
# 找到他们的祖先节点 # 找到他们的祖先节点
# 可选找到他们的子孙节点 # 可选找到他们的子孙节点
def get_family(self, nodes, with_children=False): def get_family(self, node):
tree_nodes = set() return self.get_family_by_key(node.key)
for n in nodes:
node = self.get_node_by_key(n.key) def get_family_keys_by_key(self, key):
if not node: nodes = self.get_family_by_key(key)
continue return [n.key for n in nodes]
tree_nodes.update(node._parents)
tree_nodes.add(node) def get_some_nodes_family_by_keys(self, keys):
if with_children: family = set()
tree_nodes.update(node._children) for key in keys:
return list(tree_nodes) family.update(self.get_family_by_key(key))
return family
def get_nodes_parents(self, nodes, with_self=True): def get_some_nodes_family_keys_by_keys(self, keys):
family = self.get_some_nodes_family_by_keys(keys)
return [n.key for n in family]
def get_nodes_parents_by_key(self, key, with_self=True):
parents = set() parents = set()
for n in nodes: node = self.get_node_by_key(key)
node = self.get_node_by_key(n.key) if not node:
parents.update(set(node._parents)) return []
if with_self: parents.update(set(node._parents))
parents.add(node) if with_self:
return parents parents.add(node)
return list(parents)
def get_node_parents(self, node, with_self=True):
return self.get_nodes_parents_by_key(node.key, with_self=with_self)
def get_nodes_parents_keys_by_key(self, key, with_self=True):
nodes = self.get_nodes_parents_by_key(key, with_self=with_self)
return [n.key for n in nodes]
def get_all_children_by_key(self, key, with_self=True):
children = set()
node = self.get_node_by_key(key)
if not node:
return []
children.update(set(node._all_children))
if with_self:
children.add(node)
return list(children)
def get_children(self, node, with_self=True):
return self.get_all_children_by_key(node.key, with_self=with_self)
def get_children_keys_by_key(self, key, with_self=True):
nodes = self.get_all_children_by_key(key, with_self=with_self)
return [n.key for n in nodes]
def test_node_tree(): def test_node_tree():
......
...@@ -69,7 +69,7 @@ class UserAssetListView(PermissionsMixin, TemplateView): ...@@ -69,7 +69,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
context = { context = {
'action': _('My assets'), 'action': _('My assets'),
'labels': Label.objects.all().order_by('name'), 'labels': Label.objects.all().order_by('name'),
'system_users': SystemUser.objects.all(), 'show_actions': True
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
......
...@@ -47,6 +47,7 @@ class CommandFilterCreateView(PermissionsMixin, CreateView): ...@@ -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): ...@@ -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)
...@@ -136,6 +138,7 @@ class CommandFilterRuleCreateView(PermissionsMixin, CreateView): ...@@ -136,6 +138,7 @@ class CommandFilterRuleCreateView(PermissionsMixin, CreateView):
'app': _('Assets'), 'app': _('Assets'),
'action': _('Create command filter rule'), 'action': _('Create command filter rule'),
'object': self.cmd_filter, 'object': self.cmd_filter,
'request_type': 'create'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -170,6 +173,8 @@ class CommandFilterRuleUpdateView(PermissionsMixin, UpdateView): ...@@ -170,6 +173,8 @@ class CommandFilterRuleUpdateView(PermissionsMixin, UpdateView):
'app': _('Assets'), 'app': _('Assets'),
'action': _('Update command filter rule'), 'action': _('Update command filter rule'),
'object': self.cmd_filter, 'object': self.cmd_filter,
'rule': self.get_object(),
'request_type': 'update'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
\ No newline at end of file
...@@ -46,6 +46,7 @@ class DomainCreateView(PermissionsMixin, CreateView): ...@@ -46,6 +46,7 @@ class DomainCreateView(PermissionsMixin, CreateView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Create domain'), 'action': _('Create domain'),
'type': 'create'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -63,6 +64,7 @@ class DomainUpdateView(PermissionsMixin, UpdateView): ...@@ -63,6 +64,7 @@ class DomainUpdateView(PermissionsMixin, UpdateView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Update domain'), 'action': _('Update domain'),
'type': 'update'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -132,6 +134,7 @@ class DomainGatewayCreateView(PermissionsMixin, CreateView): ...@@ -132,6 +134,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 +155,7 @@ class DomainGatewayUpdateView(PermissionsMixin, UpdateView): ...@@ -152,6 +155,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)
...@@ -44,6 +44,7 @@ class LabelCreateView(PermissionsMixin, CreateView): ...@@ -44,6 +44,7 @@ class LabelCreateView(PermissionsMixin, CreateView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Create label'), 'action': _('Create label'),
'type': 'create'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -71,6 +72,7 @@ class LabelUpdateView(PermissionsMixin, UpdateView): ...@@ -71,6 +72,7 @@ class LabelUpdateView(PermissionsMixin, UpdateView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Update label'), 'action': _('Update label'),
'type': 'update'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
......
...@@ -38,7 +38,7 @@ $(document).ready(function () { ...@@ -38,7 +38,7 @@ $(document).ready(function () {
var error = function () { var error = function () {
$("#mfa_error").addClass("text-danger").html(codeError); $("#mfa_error").addClass("text-danger").html(codeError);
}; };
APIUpdateAttr({ requestApi({
url: url, url: url,
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
......
...@@ -4,11 +4,13 @@ import os ...@@ -4,11 +4,13 @@ import os
import uuid import uuid
from django.core.cache import cache from django.core.cache import cache
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import generics, serializers from rest_framework import generics, serializers
from .http import HttpResponseTemporaryRedirect
from .const import KEY_CACHE_RESOURCES_ID from .const import KEY_CACHE_RESOURCES_ID
__all__ = [ __all__ = [
...@@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView): ...@@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView):
cache_key = KEY_CACHE_RESOURCES_ID.format(spm) cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
cache.set(cache_key, resources_id, 300) cache.set(cache_key, resources_id, 300)
return Response({'spm': spm}) return Response({'spm': spm})
@csrf_exempt
def redirect_plural_name_api(request, *args, **kwargs):
resource = kwargs.get("resource", "")
full_path = request.get_full_path()
full_path = full_path.replace(resource, resource+"s", 1)
return HttpResponseTemporaryRedirect(full_path)
# -*- coding: utf-8 -*-
#
from django.http import HttpResponse
from django.utils.encoding import iri_to_uri
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
...@@ -145,13 +145,13 @@ class NeedMFAVerify(permissions.BasePermission): ...@@ -145,13 +145,13 @@ class NeedMFAVerify(permissions.BasePermission):
return False return False
class CanUpdateSuperUser(permissions.BasePermission): class CanUpdateDeleteSuperUser(permissions.BasePermission):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if request.method in ['GET', 'OPTIONS']: if request.method in ['GET', 'OPTIONS']:
return True return True
if str(request.user.id) == str(obj.id): elif request.method == 'DELETE' and str(request.user.id) == str(obj.id):
return False return False
if request.user.is_superuser: elif request.user.is_superuser:
return True return True
if hasattr(obj, 'is_superuser') and obj.is_superuser: if hasattr(obj, 'is_superuser') and obj.is_superuser:
return False return False
......
...@@ -46,12 +46,17 @@ class TreeNode: ...@@ -46,12 +46,17 @@ class TreeNode:
def __gt__(self, other): def __gt__(self, other):
if self.isParent and not other.isParent: if self.isParent and not other.isParent:
return False result = False
elif not self.isParent and other.isParent: elif not self.isParent and other.isParent:
return True result = True
if self.pId != other.pId: elif self.pId != other.pId:
return self.pId > other.pId result = self.pId > other.pId
return self.name > other.name else:
result = self.name > other.name
return result
def __le__(self, other):
return not self.__gt__(other)
def __eq__(self, other): def __eq__(self, other):
return self.id == other.id return self.id == other.id
...@@ -74,7 +79,7 @@ class Tree: ...@@ -74,7 +79,7 @@ class Tree:
raise ValueError("Parent must not be node parent") raise ValueError("Parent must not be node parent")
node.pId = parent.id node.pId = parent.id
parent.isParent = True parent.isParent = True
self.nodes[node.id] = node self.nodes[node.key] = node
def get_nodes(self): def get_nodes(self):
return sorted(self.nodes.values()) return sorted(self.nodes.values())
......
...@@ -7,6 +7,7 @@ import logging ...@@ -7,6 +7,7 @@ import logging
import datetime import datetime
import uuid import uuid
from functools import wraps from functools import wraps
import time
import copy import copy
import ipaddress import ipaddress
...@@ -179,3 +180,18 @@ def random_string(length): ...@@ -179,3 +180,18 @@ def random_string(length):
charset = string.ascii_letters + string.digits charset = string.ascii_letters + string.digits
s = [random.choice(charset) for i in range(length)] s = [random.choice(charset) for i in range(length)]
return ''.join(s) return ''.join(s)
logger = get_logger(__name__)
def timeit(func):
def wrapper(*args, **kwargs):
logger.debug("Start call: {}".format(func.__name__))
now = time.time()
result = func(*args, **kwargs)
using = (time.time() - now) * 1000
msg = "Call {} end, using: {:.1f}ms".format(func.__name__, using)
logger.debug(msg)
return result
return wrapper
...@@ -359,6 +359,7 @@ defaults = { ...@@ -359,6 +359,7 @@ defaults = {
'TERMINAL_TELNET_REGEX': '', 'TERMINAL_TELNET_REGEX': '',
'TERMINAL_COMMAND_STORAGE': {}, 'TERMINAL_COMMAND_STORAGE': {},
'SECURITY_MFA_AUTH': False, 'SECURITY_MFA_AUTH': False,
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
'SECURITY_LOGIN_LIMIT_COUNT': 7, 'SECURITY_LOGIN_LIMIT_COUNT': 7,
'SECURITY_LOGIN_LIMIT_TIME': 30, 'SECURITY_LOGIN_LIMIT_TIME': 30,
'SECURITY_MAX_IDLE_TIME': 30, 'SECURITY_MAX_IDLE_TIME': 30,
......
...@@ -568,7 +568,7 @@ SECURITY_PASSWORD_RULES = [ ...@@ -568,7 +568,7 @@ SECURITY_PASSWORD_RULES = [
'SECURITY_PASSWORD_SPECIAL_CHAR' 'SECURITY_PASSWORD_SPECIAL_CHAR'
] ]
SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL
SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH
TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH
TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL
......
...@@ -2,7 +2,7 @@ import datetime ...@@ -2,7 +2,7 @@ import datetime
import re import re
import time import time
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponseRedirect
from django.conf import settings from django.conf import settings
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from django.utils import timezone from django.utils import timezone
...@@ -13,13 +13,14 @@ from rest_framework.response import Response ...@@ -13,13 +13,14 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.encoding import iri_to_uri
from users.models import User from users.models import User
from assets.models import Asset from assets.models import Asset
from terminal.models import Session from terminal.models import Session
from orgs.utils import current_org from orgs.utils import current_org
from common.permissions import PermissionsMixin, IsValidUser from common.permissions import PermissionsMixin, IsValidUser
from common.http import HttpResponseTemporaryRedirect
class IndexView(PermissionsMixin, TemplateView): class IndexView(PermissionsMixin, TemplateView):
...@@ -203,14 +204,6 @@ class I18NView(View): ...@@ -203,14 +204,6 @@ class I18NView(View):
api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$') api_url_pattern = re.compile(r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$')
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
@csrf_exempt @csrf_exempt
def redirect_format_api(request, *args, **kwargs): def redirect_format_api(request, *args, **kwargs):
_path, query = request.path, request.GET.urlencode() _path, query = request.path, request.GET.urlencode()
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -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-15 14:43+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,17 +76,17 @@ msgstr "运行参数" ...@@ -76,17 +76,17 @@ 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:319 assets/models/authbook.py:24
#: assets/serializers/admin_user.py:35 assets/serializers/asset_user.py:81 #: assets/serializers/admin_user.py:32 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
#: assets/templates/assets/system_user_list.html:55 audits/models.py:19 #: assets/templates/assets/system_user_list.html:55 audits/models.py:19
#: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:71 #: audits/templates/audits/ftp_log_list.html:71
#: perms/forms/asset_permission.py:68 perms/models/asset_permission.py:76 #: perms/forms/asset_permission.py:69 perms/models/asset_permission.py:78
#: perms/templates/perms/asset_permission_create_update.html:45 #: perms/templates/perms/asset_permission_create_update.html:45
#: perms/templates/perms/asset_permission_list.html:48 #: perms/templates/perms/asset_permission_list.html:48
#: perms/templates/perms/asset_permission_list.html:117 #: perms/templates/perms/asset_permission_list.html:117
...@@ -95,7 +95,7 @@ msgstr "运行参数" ...@@ -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
...@@ -112,11 +112,11 @@ msgstr "资产" ...@@ -112,11 +112,11 @@ msgstr "资产"
#: applications/templates/applications/remote_app_detail.html:61 #: applications/templates/applications/remote_app_detail.html:61
#: applications/templates/applications/remote_app_list.html:23 #: applications/templates/applications/remote_app_list.html:23
#: applications/templates/applications/user_remote_app_list.html:19 #: applications/templates/applications/user_remote_app_list.html:19
#: assets/models/user.py:160 assets/templates/assets/user_asset_list.html:172 #: assets/models/user.py:150 assets/templates/assets/user_asset_list.html:52
#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:72 #: audits/templates/audits/ftp_log_list.html:72
#: perms/forms/asset_permission.py:74 perms/models/asset_permission.py:78 #: perms/forms/asset_permission.py:75 perms/models/asset_permission.py:80
#: perms/models/asset_permission.py:103 #: perms/models/asset_permission.py:114
#: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:50 #: perms/templates/perms/asset_permission_list.html:50
#: perms/templates/perms/asset_permission_list.html:71 #: perms/templates/perms/asset_permission_list.html:71
...@@ -126,6 +126,7 @@ msgstr "资产" ...@@ -126,6 +126,7 @@ msgstr "资产"
#: terminal/templates/terminal/command_list.html:67 #: terminal/templates/terminal/command_list.html:67
#: terminal/templates/terminal/session_list.html:29 #: terminal/templates/terminal/session_list.html:29
#: terminal/templates/terminal/session_list.html:73 #: terminal/templates/terminal/session_list.html:73
#: users/templates/users/_granted_assets.html:26
#: xpack/plugins/orgs/templates/orgs/org_list.html:19 #: xpack/plugins/orgs/templates/orgs/org_list.html:19
msgid "System user" msgid "System user"
msgstr "系统用户" msgstr "系统用户"
...@@ -139,7 +140,7 @@ msgstr "系统用户" ...@@ -139,7 +140,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
...@@ -166,14 +167,14 @@ msgstr "系统用户" ...@@ -166,14 +167,14 @@ msgstr "系统用户"
#: settings/templates/settings/terminal_setting.html:105 terminal/models.py:22 #: settings/templates/settings/terminal_setting.html:105 terminal/models.py:22
#: terminal/models.py:258 terminal/templates/terminal/terminal_detail.html:43 #: terminal/models.py:258 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:64 users/templates/users/_select_user_modal.html:13 #: users/models/user.py:324 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63 #: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:35 #: users/templates/users/user_group_list.html:35
#: 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 +206,7 @@ msgstr "参数" ...@@ -205,7 +206,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:198 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
...@@ -214,10 +215,10 @@ msgstr "参数" ...@@ -214,10 +215,10 @@ msgstr "参数"
#: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100 #: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:14 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:14
#: perms/models/asset_permission.py:106 perms/models/base.py:41 #: perms/models/asset_permission.py:117 perms/models/base.py:41
#: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/asset_permission_detail.html:98
#: perms/templates/perms/remote_app_permission_detail.html:90 #: perms/templates/perms/remote_app_permission_detail.html:90
#: users/models/user.py:105 users/serializers/v1.py:116 #: users/models/user.py:365 users/serializers/v1.py:120
#: users/templates/users/user_detail.html:111 #: users/templates/users/user_detail.html:111
#: xpack/plugins/change_auth_plan/models.py:106 #: xpack/plugins/change_auth_plan/models.py:106
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
...@@ -229,7 +230,7 @@ msgstr "创建者" ...@@ -229,7 +230,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:199 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
...@@ -237,7 +238,7 @@ msgstr "创建者" ...@@ -237,7 +238,7 @@ msgstr "创建者"
#: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:96 #: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64
#: orgs/models.py:15 perms/models/asset_permission.py:107 #: orgs/models.py:15 perms/models/asset_permission.py:118
#: perms/models/base.py:42 #: perms/models/base.py:42
#: perms/templates/perms/asset_permission_detail.html:94 #: perms/templates/perms/asset_permission_detail.html:94
#: perms/templates/perms/remote_app_permission_detail.html:86 #: perms/templates/perms/remote_app_permission_detail.html:86
...@@ -257,12 +258,12 @@ msgstr "创建日期" ...@@ -257,12 +258,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:200 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
...@@ -271,15 +272,14 @@ msgstr "创建日期" ...@@ -271,15 +272,14 @@ msgstr "创建日期"
#: assets/templates/assets/domain_gateway_list.html:72 #: assets/templates/assets/domain_gateway_list.html:72
#: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/domain_list.html:28
#: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:59 #: assets/templates/assets/system_user_list.html:59 ops/models/adhoc.py:43
#: assets/templates/assets/user_asset_list.html:175 ops/models/adhoc.py:43 #: orgs/models.py:16 perms/models/asset_permission.py:119
#: orgs/models.py:16 perms/models/asset_permission.py:108
#: perms/models/base.py:43 #: perms/models/base.py:43
#: perms/templates/perms/asset_permission_detail.html:102 #: perms/templates/perms/asset_permission_detail.html:102
#: perms/templates/perms/remote_app_permission_detail.html:94 #: perms/templates/perms/remote_app_permission_detail.html:94
#: settings/models.py:34 terminal/models.py:32 #: settings/models.py:34 terminal/models.py:32
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
#: users/models/user.py:97 users/templates/users/user_detail.html:127 #: users/models/user.py:357 users/templates/users/user_detail.html:127
#: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:37 #: users/templates/users/user_group_list.html:37
#: users/templates/users/user_profile.html:134 #: users/templates/users/user_profile.html:134
...@@ -412,8 +412,8 @@ msgstr "详情" ...@@ -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 "更新" ...@@ -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,15 +515,14 @@ msgstr "创建远程应用" ...@@ -515,15 +515,14 @@ 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
#: assets/templates/assets/domain_gateway_list.html:73 #: assets/templates/assets/domain_gateway_list.html:73
#: assets/templates/assets/domain_list.html:29 #: assets/templates/assets/domain_list.html:29
#: assets/templates/assets/label_list.html:17 #: assets/templates/assets/label_list.html:17
#: assets/templates/assets/system_user_list.html:60 #: assets/templates/assets/system_user_list.html:60 audits/models.py:38
#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38
#: audits/templates/audits/operate_log_list.html:41 #: audits/templates/audits/operate_log_list.html:41
#: audits/templates/audits/operate_log_list.html:67 #: audits/templates/audits/operate_log_list.html:67
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
...@@ -537,6 +536,7 @@ msgstr "创建远程应用" ...@@ -537,6 +536,7 @@ msgstr "创建远程应用"
#: settings/templates/settings/terminal_setting.html:107 #: settings/templates/settings/terminal_setting.html:107
#: terminal/templates/terminal/session_list.html:36 #: terminal/templates/terminal/session_list.html:36
#: terminal/templates/terminal/terminal_list.html:36 #: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/_granted_assets.html:28
#: users/templates/users/user_group_list.html:38 #: users/templates/users/user_group_list.html:38
#: users/templates/users/user_list.html:41 #: users/templates/users/user_list.html:41
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60
...@@ -549,8 +549,8 @@ msgid "Action" ...@@ -549,8 +549,8 @@ msgid "Action"
msgstr "动作" msgstr "动作"
#: applications/templates/applications/user_remote_app_list.html:57 #: applications/templates/applications/user_remote_app_list.html:57
#: assets/templates/assets/user_asset_list.html:100 #: assets/templates/assets/user_asset_list.html:31
#: perms/models/asset_permission.py:28 #: perms/models/asset_permission.py:30
msgid "Connect" msgid "Connect"
msgstr "连接" msgstr "连接"
...@@ -598,42 +598,36 @@ msgid "Test if the assets under the node are connectable: {}" ...@@ -598,42 +598,36 @@ 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
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:164
#: 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
#: settings/templates/settings/replay_storage_create.html:59 #: settings/templates/settings/replay_storage_create.html:59
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:169
#: 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
#: perms/models/asset_permission.py:77 #: perms/models/asset_permission.py:79
#: xpack/plugins/change_auth_plan/models.py:72 #: xpack/plugins/change_auth_plan/models.py:72
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:173
#: 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
...@@ -646,28 +640,28 @@ msgstr "管理用户" ...@@ -646,28 +640,28 @@ msgstr "管理用户"
#: assets/templates/assets/asset_create.html:48 #: assets/templates/assets/asset_create.html:48
#: assets/templates/assets/asset_create.html:50 #: assets/templates/assets/asset_create.html:50
#: assets/templates/assets/asset_list.html:85 #: assets/templates/assets/asset_list.html:85
#: assets/templates/assets/user_asset_list.html:33 #: users/templates/users/_granted_assets.html:16
#: xpack/plugins/orgs/templates/orgs/org_list.html:20 #: xpack/plugins/orgs/templates/orgs/org_list.html:20
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:168
#: 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:53
#: xpack/plugins/orgs/templates/orgs/org_list.html:17 #: xpack/plugins/orgs/templates/orgs/org_list.html:17
msgid "Domain" 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:253
#: 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:72 perms/forms/asset_permission.py:79
#: perms/models/asset_permission.py:101 #: perms/models/asset_permission.py:112
#: 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
...@@ -695,13 +689,13 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, ...@@ -695,13 +689,13 @@ 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:78
#: 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 "选择资产"
#: assets/forms/cmd_filter.py:37 assets/serializers/cmd_filter.py:34 #: assets/forms/cmd_filter.py:37 assets/serializers/cmd_filter.py:40
msgid "Content should not be contain: {}" msgid "Content should not be contain: {}"
msgstr "内容不能包含: {}" msgstr "内容不能包含: {}"
...@@ -719,7 +713,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" ...@@ -719,7 +713,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
...@@ -730,11 +724,11 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" ...@@ -730,11 +724,11 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: perms/templates/perms/asset_permission_user.html:55 #: perms/templates/perms/asset_permission_user.html:55
#: perms/templates/perms/remote_app_permission_user.html:54 #: perms/templates/perms/remote_app_permission_user.html:54
#: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:14 #: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:14
#: users/models/user.py:62 users/templates/users/_select_user_modal.html:14 #: users/models/user.py:322 users/templates/users/_select_user_modal.html:14
#: 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
...@@ -769,7 +763,7 @@ msgstr "密码" ...@@ -769,7 +763,7 @@ msgstr "密码"
#: assets/forms/user.py:29 assets/serializers/asset_user.py:70 #: assets/forms/user.py:29 assets/serializers/asset_user.py:70
#: assets/templates/assets/_asset_user_auth_update_modal.html:27 #: assets/templates/assets/_asset_user_auth_update_modal.html:27
#: users/models/user.py:91 #: users/models/user.py:351
msgid "Private key" msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
...@@ -809,135 +803,129 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写" ...@@ -809,135 +803,129 @@ 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:159 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
#: assets/templates/assets/asset_detail.html:64 #: assets/templates/assets/asset_detail.html:64
#: assets/templates/assets/asset_list.html:97 #: assets/templates/assets/asset_list.html:97
#: assets/templates/assets/domain_gateway_list.html:68 #: assets/templates/assets/domain_gateway_list.html:68
#: assets/templates/assets/user_asset_list.html:45 #: assets/templates/assets/user_asset_list.html:49
#: assets/templates/assets/user_asset_list.html:167
#: audits/templates/audits/login_log_list.html:54 #: audits/templates/audits/login_log_list.html:54
#: perms/templates/perms/asset_permission_asset.html:55 settings/forms.py:140 #: perms/templates/perms/asset_permission_asset.html:58 settings/forms.py:140
#: users/templates/users/user_granted_asset.html:45 #: users/templates/users/_granted_assets.html:25
#: users/templates/users/user_group_granted_asset.html:45
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:51 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:51
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
#: assets/models/asset.py:113 assets/serializers/asset_user.py:27 #: assets/models/asset.py:160 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
#: assets/templates/assets/_asset_user_list.html:14 #: assets/templates/assets/_asset_user_list.html:14
#: assets/templates/assets/asset_detail.html:60 #: assets/templates/assets/asset_detail.html:60
#: assets/templates/assets/asset_list.html:96 #: assets/templates/assets/asset_list.html:96
#: assets/templates/assets/user_asset_list.html:44 #: assets/templates/assets/user_asset_list.html:48
#: assets/templates/assets/user_asset_list.html:166 #: perms/templates/perms/asset_permission_asset.html:57
#: perms/templates/perms/asset_permission_asset.html:54
#: perms/templates/perms/asset_permission_list.html:69 settings/forms.py:139 #: perms/templates/perms/asset_permission_list.html:69 settings/forms.py:139
#: users/templates/users/user_granted_asset.html:44 #: users/templates/users/_granted_assets.html:24
#: users/templates/users/user_group_granted_asset.html:44
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:116 assets/models/domain.py:51 #: assets/models/asset.py:163 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
#: 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:166 assets/serializers/asset.py:63
#: assets/templates/assets/asset_create.html:24 #: assets/templates/assets/asset_create.html:24
#: assets/templates/assets/user_asset_list.html:50
#: perms/serializers/user_permission.py:38
msgid "Protocols" msgid "Protocols"
msgstr "协议组" msgstr "协议组"
#: assets/models/asset.py:120 assets/templates/assets/asset_detail.html:104 #: assets/models/asset.py:167 assets/templates/assets/asset_detail.html:104
#: assets/templates/assets/user_asset_list.html:170 #: assets/templates/assets/user_asset_list.html:51
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
#: assets/models/asset.py:123 assets/models/cmd_filter.py:21 #: assets/models/asset.py:170 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
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
#: assets/models/asset.py:129 assets/templates/assets/asset_detail.html:68 #: assets/models/asset.py:176 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:177 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:180 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:181 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:182 assets/templates/assets/asset_detail.html:116
msgid "Serial number" msgid "Serial number"
msgstr "序列号" msgstr "序列号"
#: assets/models/asset.py:137 #: assets/models/asset.py:184
msgid "CPU model" msgid "CPU model"
msgstr "CPU型号" msgstr "CPU型号"
#: assets/models/asset.py:138 #: assets/models/asset.py:185
#: 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:186
msgid "CPU cores" msgid "CPU cores"
msgstr "CPU核数" msgstr "CPU核数"
#: assets/models/asset.py:140 #: assets/models/asset.py:187
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:188 assets/templates/assets/asset_detail.html:96
msgid "Memory" msgid "Memory"
msgstr "内存" msgstr "内存"
#: assets/models/asset.py:142 #: assets/models/asset.py:189
msgid "Disk total" msgid "Disk total"
msgstr "硬盘大小" msgstr "硬盘大小"
#: assets/models/asset.py:143 #: assets/models/asset.py:190
msgid "Disk info" msgid "Disk info"
msgstr "硬盘信息" msgstr "硬盘信息"
#: assets/models/asset.py:145 assets/templates/assets/asset_detail.html:108 #: assets/models/asset.py:192 assets/templates/assets/asset_detail.html:108
#: assets/templates/assets/user_asset_list.html:171
msgid "OS" msgid "OS"
msgstr "操作系统" msgstr "操作系统"
#: assets/models/asset.py:146 #: assets/models/asset.py:193
msgid "OS version" msgid "OS version"
msgstr "系统版本" msgstr "系统版本"
#: assets/models/asset.py:147 #: assets/models/asset.py:194
msgid "OS arch" msgid "OS arch"
msgstr "系统架构" msgstr "系统架构"
#: assets/models/asset.py:148 #: assets/models/asset.py:195
msgid "Hostname raw" msgid "Hostname raw"
msgstr "主机名原始" msgstr "主机名原始"
#: assets/models/asset.py:150 assets/templates/assets/asset_create.html:46 #: assets/models/asset.py:197 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 "标签管理"
...@@ -981,7 +969,7 @@ msgstr "带宽" ...@@ -981,7 +969,7 @@ msgstr "带宽"
msgid "Contact" msgid "Contact"
msgstr "联系人" msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:83 #: assets/models/cluster.py:22 users/models/user.py:343
#: users/templates/users/user_detail.html:76 #: users/templates/users/user_detail.html:76
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
...@@ -1003,12 +991,11 @@ msgid "Operator" ...@@ -1003,12 +991,11 @@ 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 "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14 #: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:453 #: users/models/user.py:451
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
...@@ -1115,7 +1102,7 @@ msgstr "默认资产组" ...@@ -1115,7 +1102,7 @@ msgstr "默认资产组"
#: audits/templates/audits/password_change_log_list.html:50 #: audits/templates/audits/password_change_log_list.html:50
#: ops/templates/ops/command_execution_list.html:35 #: ops/templates/ops/command_execution_list.html:35
#: ops/templates/ops/command_execution_list.html:60 #: ops/templates/ops/command_execution_list.html:60
#: perms/forms/asset_permission.py:62 perms/forms/remote_app_permission.py:31 #: perms/forms/asset_permission.py:63 perms/forms/remote_app_permission.py:31
#: perms/models/base.py:36 #: perms/models/base.py:36
#: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_create_update.html:41
#: perms/templates/perms/asset_permission_list.html:46 #: perms/templates/perms/asset_permission_list.html:46
...@@ -1127,8 +1114,8 @@ msgstr "默认资产组" ...@@ -1127,8 +1114,8 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:65 #: terminal/templates/terminal/command_list.html:65
#: terminal/templates/terminal/session_list.html:27 #: terminal/templates/terminal/session_list.html:27
#: terminal/templates/terminal/session_list.html:71 users/forms.py:316 #: terminal/templates/terminal/session_list.html:71 users/forms.py:316
#: users/models/user.py:38 users/models/user.py:441 users/serializers/v1.py:105 #: users/models/user.py:121 users/models/user.py:439
#: users/templates/users/user_group_detail.html:78 #: users/serializers/v1.py:109 users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:36 users/views/user.py:251 #: users/templates/users/user_group_list.html:36 users/views/user.py:251
#: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/forms.py:26
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113
...@@ -1136,7 +1123,7 @@ msgstr "默认资产组" ...@@ -1136,7 +1123,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:244
#: 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 +1132,11 @@ msgstr "值" ...@@ -1145,11 +1132,11 @@ msgstr "值"
msgid "Category" msgid "Category"
msgstr "分类" msgstr "分类"
#: assets/models/node.py:245 #: assets/models/node.py:243
msgid "Key" msgid "Key"
msgstr "键" msgstr "键"
#: assets/models/node.py:303 #: assets/models/node.py:301
msgid "New node" msgid "New node"
msgstr "新节点" msgstr "新节点"
...@@ -1171,13 +1158,13 @@ msgstr "手动登录" ...@@ -1171,13 +1158,13 @@ msgstr "手动登录"
#: assets/views/asset.py:57 assets/views/asset.py:106 assets/views/asset.py:133 #: assets/views/asset.py:57 assets/views/asset.py:106 assets/views/asset.py:133
#: assets/views/asset.py:173 assets/views/asset.py:203 #: assets/views/asset.py:173 assets/views/asset.py:203
#: assets/views/cmd_filter.py:31 assets/views/cmd_filter.py:48 #: assets/views/cmd_filter.py:31 assets/views/cmd_filter.py:48
#: assets/views/cmd_filter.py:65 assets/views/cmd_filter.py:82 #: assets/views/cmd_filter.py:66 assets/views/cmd_filter.py:84
#: assets/views/cmd_filter.py:102 assets/views/cmd_filter.py:136 #: assets/views/cmd_filter.py:104 assets/views/cmd_filter.py:138
#: assets/views/cmd_filter.py:170 assets/views/domain.py:30 #: assets/views/cmd_filter.py:173 assets/views/domain.py:30
#: assets/views/domain.py:47 assets/views/domain.py:64 #: assets/views/domain.py:47 assets/views/domain.py:65
#: assets/views/domain.py:78 assets/views/domain.py:104 #: assets/views/domain.py:80 assets/views/domain.py:106
#: assets/views/domain.py:133 assets/views/domain.py:153 #: assets/views/domain.py:135 assets/views/domain.py:156
#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:72 #: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73
#: assets/views/system_user.py:29 assets/views/system_user.py:46 #: assets/views/system_user.py:29 assets/views/system_user.py:46
#: assets/views/system_user.py:63 assets/views/system_user.py:79 #: assets/views/system_user.py:63 assets/views/system_user.py:79
#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:68 #: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:68
...@@ -1208,12 +1195,6 @@ msgstr "登录模式" ...@@ -1208,12 +1195,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 +1203,11 @@ msgstr "协议格式 {}/{}" ...@@ -1222,6 +1203,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 "硬件信息"
...@@ -1235,7 +1221,7 @@ msgid "Backend" ...@@ -1235,7 +1221,7 @@ msgid "Backend"
msgstr "后端" msgstr "后端"
#: assets/serializers/asset_user.py:66 users/forms.py:263 #: assets/serializers/asset_user.py:66 users/forms.py:263
#: users/models/user.py:94 users/templates/users/first_login.html:42 #: users/models/user.py:354 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:46 #: users/templates/users/user_password_update.html:46
#: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43 #: users/templates/users/user_profile_update.html:43
...@@ -1247,19 +1233,19 @@ msgstr "ssh公钥" ...@@ -1247,19 +1233,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 "密码或密钥密码需要一个"
...@@ -1370,8 +1356,6 @@ msgstr "选择资产" ...@@ -1370,8 +1356,6 @@ msgstr "选择资产"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:21 #: assets/templates/assets/_asset_group_bulk_update_modal.html:21
#: assets/templates/assets/cmd_filter_detail.html:89 #: assets/templates/assets/cmd_filter_detail.html:89
#: assets/templates/assets/cmd_filter_list.html:26 #: assets/templates/assets/cmd_filter_list.html:26
#: assets/templates/assets/user_asset_list.html:47
#: users/templates/users/user_granted_asset.html:47
msgid "System users" msgid "System users"
msgstr "系统用户" msgstr "系统用户"
...@@ -1401,7 +1385,7 @@ msgid "Update asset user auth" ...@@ -1401,7 +1385,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 +1474,19 @@ msgstr "重命名节点" ...@@ -1490,19 +1474,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 "重命名成功"
...@@ -1581,15 +1565,15 @@ msgid "Replace node assets admin user with this" ...@@ -1581,15 +1565,15 @@ msgid "Replace node assets admin user with this"
msgstr "替换资产的管理员" 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:103
#: 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 +1595,24 @@ msgstr "选择节点" ...@@ -1611,24 +1595,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 +1622,7 @@ msgstr "Windows或其它硬件可以随意设置一个" ...@@ -1638,7 +1622,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 +1633,13 @@ msgstr "导出" ...@@ -1649,20 +1633,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
...@@ -1719,8 +1696,7 @@ msgid "Date joined" ...@@ -1719,8 +1696,7 @@ msgid "Date joined"
msgstr "创建日期" msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:150 #: assets/templates/assets/asset_detail.html:150
#: assets/templates/assets/user_asset_list.html:46 #: perms/models/asset_permission.py:115 perms/models/base.py:38
#: perms/models/asset_permission.py:104 perms/models/base.py:38
#: perms/templates/perms/asset_permission_create_update.html:55 #: perms/templates/perms/asset_permission_create_update.html:55
#: perms/templates/perms/asset_permission_detail.html:120 #: perms/templates/perms/asset_permission_detail.html:120
#: perms/templates/perms/remote_app_permission_create_update.html:54 #: perms/templates/perms/remote_app_permission_create_update.html:54
...@@ -1728,8 +1704,6 @@ msgstr "创建日期" ...@@ -1728,8 +1704,6 @@ msgstr "创建日期"
#: terminal/templates/terminal/terminal_list.html:34 #: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18 #: users/templates/users/_select_user_modal.html:18
#: users/templates/users/user_detail.html:144 #: users/templates/users/user_detail.html:144
#: users/templates/users/user_granted_asset.html:46
#: users/templates/users/user_group_granted_asset.html:46
#: users/templates/users/user_profile.html:63 #: users/templates/users/user_profile.html:63
msgid "Active" msgid "Active"
msgstr "激活中" msgstr "激活中"
...@@ -1807,7 +1781,7 @@ msgstr "仅显示当前节点资产" ...@@ -1807,7 +1781,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 +1792,11 @@ msgstr "显示所有子节点资产" ...@@ -1818,11 +1792,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 +1810,16 @@ msgstr "删除选择资产" ...@@ -1836,16 +1810,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 "删除失败"
...@@ -1895,7 +1869,7 @@ msgid "Create command filter" ...@@ -1895,7 +1869,7 @@ msgid "Create command filter"
msgstr "创建命令过滤器" msgstr "创建命令过滤器"
#: assets/templates/assets/cmd_filter_rule_list.html:33 #: assets/templates/assets/cmd_filter_rule_list.html:33
#: assets/views/cmd_filter.py:103 #: assets/views/cmd_filter.py:105
msgid "Command filter rule list" msgid "Command filter rule list"
msgstr "命令过滤器规则列表" msgstr "命令过滤器规则列表"
...@@ -1922,7 +1896,7 @@ msgid "Gateway list" ...@@ -1922,7 +1896,7 @@ msgid "Gateway list"
msgstr "网关列表" msgstr "网关列表"
#: assets/templates/assets/domain_gateway_list.html:56 #: assets/templates/assets/domain_gateway_list.html:56
#: assets/views/domain.py:134 #: assets/views/domain.py:136
msgid "Create gateway" msgid "Create gateway"
msgstr "创建网关" msgstr "创建网关"
...@@ -2070,19 +2044,19 @@ msgstr "批量更新资产" ...@@ -2070,19 +2044,19 @@ msgstr "批量更新资产"
msgid "Command filter list" msgid "Command filter list"
msgstr "命令过滤器列表" msgstr "命令过滤器列表"
#: assets/views/cmd_filter.py:66 #: assets/views/cmd_filter.py:67
msgid "Update command filter" msgid "Update command filter"
msgstr "更新命令过滤器" msgstr "更新命令过滤器"
#: assets/views/cmd_filter.py:83 #: assets/views/cmd_filter.py:85
msgid "Command filter detail" msgid "Command filter detail"
msgstr "命令过滤器详情" msgstr "命令过滤器详情"
#: assets/views/cmd_filter.py:137 #: assets/views/cmd_filter.py:139
msgid "Create command filter rule" msgid "Create command filter rule"
msgstr "创建命令过滤器规则" msgstr "创建命令过滤器规则"
#: assets/views/cmd_filter.py:171 #: assets/views/cmd_filter.py:174
msgid "Update command filter rule" msgid "Update command filter rule"
msgstr "更新命令过滤器规则" msgstr "更新命令过滤器规则"
...@@ -2090,19 +2064,19 @@ msgstr "更新命令过滤器规则" ...@@ -2090,19 +2064,19 @@ msgstr "更新命令过滤器规则"
msgid "Domain list" msgid "Domain list"
msgstr "网域列表" msgstr "网域列表"
#: assets/views/domain.py:65 #: assets/views/domain.py:66
msgid "Update domain" msgid "Update domain"
msgstr "更新网域" msgstr "更新网域"
#: assets/views/domain.py:79 #: assets/views/domain.py:81
msgid "Domain detail" msgid "Domain detail"
msgstr "网域详情" msgstr "网域详情"
#: assets/views/domain.py:105 #: assets/views/domain.py:107
msgid "Domain gateway list" msgid "Domain gateway list"
msgstr "域网关列表" msgstr "域网关列表"
#: assets/views/domain.py:154 #: assets/views/domain.py:157
msgid "Update gateway" msgid "Update gateway"
msgstr "创建网关" msgstr "创建网关"
...@@ -2110,11 +2084,11 @@ msgstr "创建网关" ...@@ -2110,11 +2084,11 @@ msgstr "创建网关"
msgid "Label list" msgid "Label list"
msgstr "标签列表" msgstr "标签列表"
#: assets/views/label.py:55 #: assets/views/label.py:56
msgid "Tips: Avoid using label names reserved internally: {}" msgid "Tips: Avoid using label names reserved internally: {}"
msgstr "提示: 请避免使用内部预留标签名: {}" msgstr "提示: 请避免使用内部预留标签名: {}"
#: assets/views/label.py:73 #: assets/views/label.py:74
msgid "Update label" msgid "Update label"
msgstr "更新标签" msgstr "更新标签"
...@@ -2232,7 +2206,7 @@ msgstr "Agent" ...@@ -2232,7 +2206,7 @@ msgstr "Agent"
#: audits/models.py:99 audits/templates/audits/login_log_list.html:56 #: audits/models.py:99 audits/templates/audits/login_log_list.html:56
#: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms.py:175 users/models/user.py:86 #: users/forms.py:175 users/models/user.py:346
#: users/templates/users/first_login.html:45 #: users/templates/users/first_login.html:45
msgid "MFA" msgid "MFA"
msgstr "MFA" msgstr "MFA"
...@@ -2445,7 +2419,7 @@ msgstr "代码错误" ...@@ -2445,7 +2419,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/login.html:27 #: authentication/templates/authentication/login.html:27
#: authentication/templates/authentication/login_otp.html:27 #: authentication/templates/authentication/login_otp.html:27
#: users/templates/users/reset_password.html:25 #: users/templates/users/reset_password.html:25
#: xpack/plugins/interface/models.py:39 #: xpack/plugins/interface/models.py:36
msgid "Welcome to the Jumpserver open source fortress" msgid "Welcome to the Jumpserver open source fortress"
msgstr "欢迎使用Jumpserver开源堡垒机" msgstr "欢迎使用Jumpserver开源堡垒机"
...@@ -2650,7 +2624,7 @@ msgstr "不能包含特殊字符" ...@@ -2650,7 +2624,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 +2827,11 @@ msgstr "执行历史" ...@@ -2853,6 +2827,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,63 +2984,71 @@ msgstr "命令执行" ...@@ -3005,63 +2984,71 @@ msgstr "命令执行"
msgid "Organization" msgid "Organization"
msgstr "组织" msgstr "组织"
#: perms/forms/asset_permission.py:65 perms/forms/remote_app_permission.py:34 #: perms/api/mixin.py:128
#: perms/models/asset_permission.py:102 perms/models/base.py:37 msgid "ungrouped"
msgstr "未分组"
#: perms/api/mixin.py:133
msgid "empty"
msgstr "空"
#: perms/forms/asset_permission.py:66 perms/forms/remote_app_permission.py:34
#: perms/models/asset_permission.py:113 perms/models/base.py:37
#: perms/templates/perms/asset_permission_list.html:47 #: perms/templates/perms/asset_permission_list.html:47
#: perms/templates/perms/asset_permission_list.html:67 #: perms/templates/perms/asset_permission_list.html:67
#: perms/templates/perms/asset_permission_list.html:114 #: perms/templates/perms/asset_permission_list.html:114
#: perms/templates/perms/remote_app_permission_list.html:16 #: perms/templates/perms/remote_app_permission_list.html:16
#: templates/_nav.html:14 users/forms.py:286 users/models/group.py:26 #: templates/_nav.html:14 users/forms.py:286 users/models/group.py:26
#: users/models/user.py:70 users/templates/users/_select_user_modal.html:16 #: users/models/user.py:330 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:213 #: users/templates/users/user_detail.html:213
#: users/templates/users/user_list.html:38 #: users/templates/users/user_list.html:38
#: xpack/plugins/orgs/templates/orgs/org_list.html:15 #: xpack/plugins/orgs/templates/orgs/org_list.html:15
msgid "User group" msgid "User group"
msgstr "用户组" msgstr "用户组"
#: perms/forms/asset_permission.py:81 #: perms/forms/asset_permission.py:82
msgid "" msgid ""
"Tips: The RDP protocol does not support separate controls for uploading or " "Tips: The RDP protocol does not support separate controls for uploading or "
"downloading files" "downloading files"
msgstr "提示:RDP 协议不支持单独控制上传或下载文件" msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
#: perms/forms/asset_permission.py:91 perms/forms/remote_app_permission.py:47 #: perms/forms/asset_permission.py:92 perms/forms/remote_app_permission.py:47
msgid "User or group at least one required" msgid "User or group at least one required"
msgstr "用户和用户组至少选一个" msgstr "用户和用户组至少选一个"
#: perms/forms/asset_permission.py:100 #: perms/forms/asset_permission.py:101
msgid "Asset or group at least one required" msgid "Asset or group at least one required"
msgstr "资产和节点至少选一个" msgstr "资产和节点至少选一个"
#: perms/models/asset_permission.py:27 settings/forms.py:143 #: perms/models/asset_permission.py:29 settings/forms.py:143
msgid "All" msgid "All"
msgstr "全部" msgstr "全部"
#: perms/models/asset_permission.py:29 #: perms/models/asset_permission.py:31
msgid "Upload file" msgid "Upload file"
msgstr "上传文件" msgstr "上传文件"
#: perms/models/asset_permission.py:30 #: perms/models/asset_permission.py:32
msgid "Download file" msgid "Download file"
msgstr "下载文件" msgstr "下载文件"
#: perms/models/asset_permission.py:31 #: perms/models/asset_permission.py:33
msgid "Upload download" msgid "Upload download"
msgstr "上传下载" msgstr "上传下载"
#: perms/models/asset_permission.py:80 #: perms/models/asset_permission.py:82
msgid "Actions" msgid "Actions"
msgstr "动作" msgstr "动作"
#: perms/models/asset_permission.py:84 perms/models/asset_permission.py:114 #: perms/models/asset_permission.py:86 perms/models/asset_permission.py:125
#: templates/_nav.html:44 #: templates/_nav.html:44
msgid "Asset permission" msgid "Asset permission"
msgstr "资产授权" msgstr "资产授权"
#: perms/models/asset_permission.py:105 perms/models/base.py:40 #: perms/models/asset_permission.py:116 perms/models/base.py:40
#: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/asset_permission_detail.html:90
#: perms/templates/perms/remote_app_permission_detail.html:82 #: perms/templates/perms/remote_app_permission_detail.html:82
#: users/models/user.py:102 users/templates/users/user_detail.html:107 #: users/models/user.py:362 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:116 #: users/templates/users/user_profile.html:116
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
...@@ -3088,11 +3075,11 @@ msgstr "用户或用户组" ...@@ -3088,11 +3075,11 @@ msgstr "用户或用户组"
msgid "Assets and node" msgid "Assets and node"
msgstr "资产或节点" msgstr "资产或节点"
#: perms/templates/perms/asset_permission_asset.html:80 #: perms/templates/perms/asset_permission_asset.html:70
msgid "Add asset to this permission" msgid "Add asset to this permission"
msgstr "添加资产" msgstr "添加资产"
#: perms/templates/perms/asset_permission_asset.html:97 #: perms/templates/perms/asset_permission_asset.html:84
#: perms/templates/perms/asset_permission_detail.html:157 #: perms/templates/perms/asset_permission_detail.html:157
#: perms/templates/perms/asset_permission_user.html:97 #: perms/templates/perms/asset_permission_user.html:97
#: perms/templates/perms/asset_permission_user.html:125 #: perms/templates/perms/asset_permission_user.html:125
...@@ -3108,11 +3095,11 @@ msgstr "添加资产" ...@@ -3108,11 +3095,11 @@ msgstr "添加资产"
msgid "Add" msgid "Add"
msgstr "添加" msgstr "添加"
#: perms/templates/perms/asset_permission_asset.html:108 #: perms/templates/perms/asset_permission_asset.html:95
msgid "Add node to this permission" msgid "Add node to this permission"
msgstr "添加节点" msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125 #: perms/templates/perms/asset_permission_asset.html:112
#: users/templates/users/user_detail.html:230 #: users/templates/users/user_detail.html:230
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:121 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:121
msgid "Join" msgid "Join"
...@@ -3207,19 +3194,15 @@ msgstr "添加用户" ...@@ -3207,19 +3194,15 @@ 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:169
#: perms/views/remote_app_permission.py:33 #: perms/views/remote_app_permission.py:33
#: perms/views/remote_app_permission.py:49 #: perms/views/remote_app_permission.py:49
#: perms/views/remote_app_permission.py:65 #: perms/views/remote_app_permission.py:66
#: perms/views/remote_app_permission.py:79 #: perms/views/remote_app_permission.py:81
#: perms/views/remote_app_permission.py:106 #: perms/views/remote_app_permission.py:108
#: perms/views/remote_app_permission.py:143 templates/_nav.html:41 #: perms/views/remote_app_permission.py:145 templates/_nav.html:41
#: xpack/plugins/orgs/templates/orgs/org_list.html:21 #: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Perms" msgid "Perms"
msgstr "权限管理" msgstr "权限管理"
...@@ -3244,7 +3227,7 @@ msgstr "资产授权详情" ...@@ -3244,7 +3227,7 @@ msgstr "资产授权详情"
msgid "Asset permission user list" msgid "Asset permission user list"
msgstr "资产授权用户列表" msgstr "资产授权用户列表"
#: perms/views/asset_permission.py:169 #: perms/views/asset_permission.py:170
msgid "Asset permission asset list" msgid "Asset permission asset list"
msgstr "资产授权资产列表" msgstr "资产授权资产列表"
...@@ -3256,19 +3239,19 @@ msgstr "远程应用授权列表" ...@@ -3256,19 +3239,19 @@ msgstr "远程应用授权列表"
msgid "Create RemoteApp permission" msgid "Create RemoteApp permission"
msgstr "创建远程应用授权规则" msgstr "创建远程应用授权规则"
#: perms/views/remote_app_permission.py:66 #: perms/views/remote_app_permission.py:67
msgid "Update RemoteApp permission" msgid "Update RemoteApp permission"
msgstr "更新远程应用授权规则" msgstr "更新远程应用授权规则"
#: perms/views/remote_app_permission.py:80 #: perms/views/remote_app_permission.py:82
msgid "RemoteApp permission detail" msgid "RemoteApp permission detail"
msgstr "远程应用授权详情" msgstr "远程应用授权详情"
#: perms/views/remote_app_permission.py:107 #: perms/views/remote_app_permission.py:109
msgid "RemoteApp permission user list" msgid "RemoteApp permission user list"
msgstr "远程应用授权用户列表" msgstr "远程应用授权用户列表"
#: perms/views/remote_app_permission.py:144 #: perms/views/remote_app_permission.py:146
msgid "RemoteApp permission RemoteApp list" msgid "RemoteApp permission RemoteApp list"
msgstr "远程应用授权远程应用列表" msgstr "远程应用授权远程应用列表"
...@@ -3471,35 +3454,45 @@ msgstr "批量命令" ...@@ -3471,35 +3454,45 @@ msgstr "批量命令"
msgid "Allow user batch execute commands" msgid "Allow user batch execute commands"
msgstr "允许用户批量执行命令" msgstr "允许用户批量执行命令"
#: settings/forms.py:198 #: settings/forms.py:196
msgid "Service account registration"
msgstr "终端注册"
#: settings/forms.py:197
msgid ""
"Allow using bootstrap token register service account, when terminal setup, "
"can disable it"
msgstr "允许使用bootstrap token注册终端, 当终端注册成功后可以禁止"
#: settings/forms.py:203
msgid "Limit the number of login failures" msgid "Limit the number of login failures"
msgstr "限制登录失败次数" msgstr "限制登录失败次数"
#: settings/forms.py:202 #: settings/forms.py:207
msgid "No logon interval" msgid "No logon interval"
msgstr "禁止登录时间间隔" msgstr "禁止登录时间间隔"
#: settings/forms.py:204 #: settings/forms.py:209
msgid "" msgid ""
"Tip: (unit/minute) if the user has failed to log in for a limited number of " "Tip: (unit/minute) if the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval." "times, no login is allowed during this time interval."
msgstr "" msgstr ""
"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
#: settings/forms.py:211 #: settings/forms.py:216
msgid "Connection max idle time" msgid "Connection max idle time"
msgstr "SSH最大空闲时间" msgstr "SSH最大空闲时间"
#: settings/forms.py:213 #: settings/forms.py:218
msgid "" msgid ""
"If idle time more than it, disconnect connection(only ssh now) Unit: minute" "If idle time more than it, disconnect connection(only ssh now) Unit: minute"
msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)"
#: settings/forms.py:219 #: settings/forms.py:224
msgid "Password expiration time" msgid "Password expiration time"
msgstr "密码过期时间" msgstr "密码过期时间"
#: settings/forms.py:221 #: settings/forms.py:226
msgid "" msgid ""
"Tip: (unit: day) If the user does not update the password during the time, " "Tip: (unit: day) If the user does not update the password during the time, "
"the user password will expire failure;The password expiration reminder mail " "the user password will expire failure;The password expiration reminder mail "
...@@ -3509,81 +3502,81 @@ msgstr "" ...@@ -3509,81 +3502,81 @@ msgstr ""
"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期"
"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户"
#: settings/forms.py:230 #: settings/forms.py:235
msgid "Password minimum length" msgid "Password minimum length"
msgstr "密码最小长度 " msgstr "密码最小长度 "
#: settings/forms.py:234 #: settings/forms.py:239
msgid "Must contain capital letters" msgid "Must contain capital letters"
msgstr "必须包含大写字母" msgstr "必须包含大写字母"
#: settings/forms.py:236 #: settings/forms.py:241
msgid "" msgid ""
"After opening, the user password changes and resets must contain uppercase " "After opening, the user password changes and resets must contain uppercase "
"letters" "letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母" msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: settings/forms.py:241 #: settings/forms.py:246
msgid "Must contain lowercase letters" msgid "Must contain lowercase letters"
msgstr "必须包含小写字母" msgstr "必须包含小写字母"
#: settings/forms.py:242 #: settings/forms.py:247
msgid "" msgid ""
"After opening, the user password changes and resets must contain lowercase " "After opening, the user password changes and resets must contain lowercase "
"letters" "letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母" msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: settings/forms.py:247 #: settings/forms.py:252
msgid "Must contain numeric characters" msgid "Must contain numeric characters"
msgstr "必须包含数字字符" msgstr "必须包含数字字符"
#: settings/forms.py:248 #: settings/forms.py:253
msgid "" msgid ""
"After opening, the user password changes and resets must contain numeric " "After opening, the user password changes and resets must contain numeric "
"characters" "characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符" msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: settings/forms.py:253 #: settings/forms.py:258
msgid "Must contain special characters" msgid "Must contain special characters"
msgstr "必须包含特殊字符" msgstr "必须包含特殊字符"
#: settings/forms.py:254 #: settings/forms.py:259
msgid "" msgid ""
"After opening, the user password changes and resets must contain special " "After opening, the user password changes and resets must contain special "
"characters" "characters"
msgstr "开启后,用户密码修改、重置必须包含特殊字符" msgstr "开启后,用户密码修改、重置必须包含特殊字符"
#: settings/forms.py:261 #: settings/forms.py:266
msgid "Create user email subject" msgid "Create user email subject"
msgstr "创建用户邮件的主题" msgstr "创建用户邮件的主题"
#: settings/forms.py:262 #: settings/forms.py:267
msgid "" msgid ""
"Tips: When creating a user, send the subject of the email (eg:Create account " "Tips: When creating a user, send the subject of the email (eg:Create account "
"successfully)" "successfully)"
msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)"
#: settings/forms.py:266 #: settings/forms.py:271
msgid "Create user honorific" msgid "Create user honorific"
msgstr "创建用户邮件的敬语" msgstr "创建用户邮件的敬语"
#: settings/forms.py:267 #: settings/forms.py:272
msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)"
msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)"
#: settings/forms.py:272 #: settings/forms.py:277
msgid "Create user email content" msgid "Create user email content"
msgstr "创建用户邮件的内容" msgstr "创建用户邮件的内容"
#: settings/forms.py:273 #: settings/forms.py:278
msgid "Tips:When creating a user, send the content of the email" msgid "Tips:When creating a user, send the content of the email"
msgstr "提示: 创建用户时,发送设置密码邮件的内容" msgstr "提示: 创建用户时,发送设置密码邮件的内容"
#: settings/forms.py:276 #: settings/forms.py:281
msgid "Signature" msgid "Signature"
msgstr "署名" msgstr "署名"
#: settings/forms.py:277 #: settings/forms.py:282
msgid "Tips: Email signature (eg:jumpserver)" msgid "Tips: Email signature (eg:jumpserver)"
msgstr "提示: 邮件的署名 (例如: jumpserver)" msgstr "提示: 邮件的署名 (例如: jumpserver)"
...@@ -3601,7 +3594,7 @@ msgid "Please submit the LDAP configuration before import" ...@@ -3601,7 +3594,7 @@ msgid "Please submit the LDAP configuration before import"
msgstr "请先提交LDAP配置再进行导入" msgstr "请先提交LDAP配置再进行导入"
#: settings/templates/settings/_ldap_list_users_modal.html:39 #: settings/templates/settings/_ldap_list_users_modal.html:39
#: users/models/user.py:66 users/templates/users/user_detail.html:71 #: users/models/user.py:326 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59 #: users/templates/users/user_profile.html:59
msgid "Email" msgid "Email"
msgstr "邮件" msgstr "邮件"
...@@ -3935,7 +3928,7 @@ msgstr "" ...@@ -3935,7 +3928,7 @@ msgstr ""
" " " "
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:45 #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:45
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:96 #: users/views/group.py:63 users/views/group.py:81 users/views/group.py:98
#: users/views/login.py:154 users/views/user.py:68 users/views/user.py:85 #: users/views/login.py:154 users/views/user.py:68 users/views/user.py:85
#: users/views/user.py:129 users/views/user.py:196 users/views/user.py:218 #: users/views/user.py:129 users/views/user.py:196 users/views/user.py:218
#: users/views/user.py:270 users/views/user.py:311 #: users/views/user.py:270 users/views/user.py:311
...@@ -4389,7 +4382,7 @@ msgstr "你没有权限" ...@@ -4389,7 +4382,7 @@ msgstr "你没有权限"
msgid "Could not reset self otp, use profile reset instead" msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置" msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
#: users/forms.py:33 users/models/user.py:74 #: users/forms.py:33 users/models/user.py:334
#: users/templates/users/_select_user_modal.html:15 #: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87 #: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:37 #: users/templates/users/user_list.html:37
...@@ -4417,7 +4410,7 @@ msgstr "添加到用户组" ...@@ -4417,7 +4410,7 @@ msgstr "添加到用户组"
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:91 #: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:95
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
...@@ -4502,57 +4495,57 @@ msgstr "复制你的公钥到这里" ...@@ -4502,57 +4495,57 @@ msgstr "复制你的公钥到这里"
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
#: users/models/user.py:37 users/models/user.py:449 #: users/models/user.py:50 users/templates/users/user_update.html:22
#: users/views/login.py:46 users/views/login.py:107 users/views/user.py:283
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/models/user.py:120 users/models/user.py:447
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:39 #: users/models/user.py:122
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: users/models/user.py:40 #: users/models/user.py:123
msgid "Auditor" msgid "Auditor"
msgstr "审计员" msgstr "审计员"
#: users/models/user.py:43 users/templates/users/user_profile.html:92 #: users/models/user.py:281 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:159 #: users/templates/users/user_profile.html:159
#: users/templates/users/user_profile.html:162 #: users/templates/users/user_profile.html:162
msgid "Disable" msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: users/models/user.py:44 users/templates/users/user_profile.html:90 #: users/models/user.py:282 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:166 #: users/templates/users/user_profile.html:166
msgid "Enable" msgid "Enable"
msgstr "启用" msgstr "启用"
#: users/models/user.py:45 users/templates/users/user_profile.html:88 #: users/models/user.py:283 users/templates/users/user_profile.html:88
msgid "Force enable" msgid "Force enable"
msgstr "强制启用" msgstr "强制启用"
#: users/models/user.py:77 #: users/models/user.py:337
msgid "Avatar" msgid "Avatar"
msgstr "头像" msgstr "头像"
#: users/models/user.py:80 users/templates/users/user_detail.html:82 #: users/models/user.py:340 users/templates/users/user_detail.html:82
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:109 users/templates/users/user_detail.html:103 #: users/models/user.py:369 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:39 #: users/templates/users/user_list.html:39
#: users/templates/users/user_profile.html:100 #: users/templates/users/user_profile.html:100
msgid "Source" msgid "Source"
msgstr "用户来源" msgstr "用户来源"
#: users/models/user.py:113 #: users/models/user.py:373
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最后更新密码日期" msgstr "最后更新密码日期"
#: users/models/user.py:139 users/templates/users/user_update.html:22 #: users/models/user.py:450
#: users/views/login.py:46 users/views/login.py:107 users/views/user.py:283
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/models/user.py:452
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
...@@ -4588,7 +4581,7 @@ msgstr "头像路径" ...@@ -4588,7 +4581,7 @@ msgstr "头像路径"
msgid "Role limit to {}" msgid "Role limit to {}"
msgstr "角色只能为 {}" msgstr "角色只能为 {}"
#: users/serializers/v1.py:63 #: users/serializers/v1.py:67
msgid "Password does not match security rules" msgid "Password does not match security rules"
msgstr "密码不满足安全规则" msgstr "密码不满足安全规则"
...@@ -4633,7 +4626,7 @@ msgid "Import user groups" ...@@ -4633,7 +4626,7 @@ msgid "Import user groups"
msgstr "导入用户组" msgstr "导入用户组"
#: users/templates/users/_user_groups_update_modal.html:4 #: users/templates/users/_user_groups_update_modal.html:4
#: users/views/group.py:63 #: users/views/group.py:64
msgid "Update user group" msgid "Update user group"
msgstr "更新用户组" msgstr "更新用户组"
...@@ -4720,14 +4713,14 @@ msgid "Reset password" ...@@ -4720,14 +4713,14 @@ msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
#: users/templates/users/reset_password.html:59 #: users/templates/users/reset_password.html:59
#: users/templates/users/user_create.html:15 #: users/templates/users/user_create.html:13
#: users/templates/users/user_password_update.html:61 #: users/templates/users/user_password_update.html:61
#: users/templates/users/user_update.html:13 #: users/templates/users/user_update.html:13
msgid "Your password must satisfy" msgid "Your password must satisfy"
msgstr "您的密码必须满足:" msgstr "您的密码必须满足:"
#: users/templates/users/reset_password.html:60 #: users/templates/users/reset_password.html:60
#: users/templates/users/user_create.html:16 #: users/templates/users/user_create.html:14
#: users/templates/users/user_password_update.html:62 #: users/templates/users/user_password_update.html:62
#: users/templates/users/user_update.html:14 #: users/templates/users/user_update.html:14
msgid "Password strength" msgid "Password strength"
...@@ -4738,42 +4731,42 @@ msgid "Password again" ...@@ -4738,42 +4731,42 @@ msgid "Password again"
msgstr "再次输入密码" msgstr "再次输入密码"
#: users/templates/users/reset_password.html:105 #: users/templates/users/reset_password.html:105
#: users/templates/users/user_create.html:35 #: users/templates/users/user_create.html:33
#: users/templates/users/user_password_update.html:99 #: users/templates/users/user_password_update.html:99
#: users/templates/users/user_update.html:46 #: users/templates/users/user_update.html:46
msgid "Very weak" msgid "Very weak"
msgstr "很弱" msgstr "很弱"
#: users/templates/users/reset_password.html:106 #: users/templates/users/reset_password.html:106
#: users/templates/users/user_create.html:36 #: users/templates/users/user_create.html:34
#: users/templates/users/user_password_update.html:100 #: users/templates/users/user_password_update.html:100
#: users/templates/users/user_update.html:47 #: users/templates/users/user_update.html:47
msgid "Weak" msgid "Weak"
msgstr "弱" msgstr "弱"
#: users/templates/users/reset_password.html:107 #: users/templates/users/reset_password.html:107
#: users/templates/users/user_create.html:37 #: users/templates/users/user_create.html:35
#: users/templates/users/user_password_update.html:101 #: users/templates/users/user_password_update.html:101
#: users/templates/users/user_update.html:48 #: users/templates/users/user_update.html:48
msgid "Normal" msgid "Normal"
msgstr "正常" msgstr "正常"
#: users/templates/users/reset_password.html:108 #: users/templates/users/reset_password.html:108
#: users/templates/users/user_create.html:38 #: users/templates/users/user_create.html:36
#: users/templates/users/user_password_update.html:102 #: users/templates/users/user_password_update.html:102
#: users/templates/users/user_update.html:49 #: users/templates/users/user_update.html:49
msgid "Medium" msgid "Medium"
msgstr "一般" msgstr "一般"
#: users/templates/users/reset_password.html:109 #: users/templates/users/reset_password.html:109
#: users/templates/users/user_create.html:39 #: users/templates/users/user_create.html:37
#: users/templates/users/user_password_update.html:103 #: users/templates/users/user_password_update.html:103
#: users/templates/users/user_update.html:50 #: users/templates/users/user_update.html:50
msgid "Strong" msgid "Strong"
msgstr "强" msgstr "强"
#: users/templates/users/reset_password.html:110 #: users/templates/users/reset_password.html:110
#: users/templates/users/user_create.html:40 #: users/templates/users/user_create.html:38
#: users/templates/users/user_password_update.html:104 #: users/templates/users/user_password_update.html:104
#: users/templates/users/user_update.html:51 #: users/templates/users/user_update.html:51
msgid "Very strong" msgid "Very strong"
...@@ -4885,7 +4878,7 @@ msgstr "重置用户MFA成功" ...@@ -4885,7 +4878,7 @@ msgstr "重置用户MFA成功"
#: users/templates/users/user_group_detail.html:22 #: users/templates/users/user_group_detail.html:22
#: users/templates/users/user_group_granted_asset.html:18 #: users/templates/users/user_group_granted_asset.html:18
#: users/views/group.py:80 #: users/views/group.py:82
msgid "User group detail" msgid "User group detail"
msgstr "用户组详情" msgstr "用户组详情"
...@@ -5227,7 +5220,7 @@ msgstr "密码或密钥不合法" ...@@ -5227,7 +5220,7 @@ msgstr "密码或密钥不合法"
msgid "User group list" msgid "User group list"
msgstr "用户组列表" msgstr "用户组列表"
#: users/views/group.py:97 #: users/views/group.py:99
msgid "User group granted asset" msgid "User group granted asset"
msgstr "用户组授权资产" msgstr "用户组授权资产"
...@@ -5313,25 +5306,28 @@ msgid "Password length" ...@@ -5313,25 +5306,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 +5335,11 @@ msgstr "" ...@@ -5339,11 +5335,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 +5392,6 @@ msgstr "定期执行" ...@@ -5396,10 +5392,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 +5760,7 @@ msgid "Interface settings" ...@@ -5768,7 +5760,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 +5783,22 @@ msgstr "恢复默认成功!" ...@@ -5791,26 +5783,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 +5886,9 @@ msgstr "无效的许可证" ...@@ -5898,7 +5886,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 +5905,6 @@ msgstr "添加管理员" ...@@ -5915,11 +5905,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 +5934,15 @@ msgstr "密码匣子" ...@@ -5949,6 +5934,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 "组织"
......
...@@ -255,7 +255,7 @@ function execute() { ...@@ -255,7 +255,7 @@ function execute() {
} }
} }
APIUpdateAttr({ requestApi({
url: url, url: url,
body: JSON.stringify(data), body: JSON.stringify(data),
method: 'POST', method: 'POST',
......
...@@ -109,7 +109,7 @@ $(document).ready(function() { ...@@ -109,7 +109,7 @@ $(document).ready(function() {
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400') window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
error: error, error: error,
method: 'GET', method: 'GET',
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
from django.utils import timezone from django.utils import timezone
from django.db.models import Q from django.db.models import Q
from rest_framework.views import Response from rest_framework.views import Response
from rest_framework.generics import RetrieveUpdateAPIView from django.shortcuts import get_object_or_404
from rest_framework.generics import RetrieveUpdateAPIView, ListAPIView
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
...@@ -20,7 +21,7 @@ from .. import serializers ...@@ -20,7 +21,7 @@ from .. import serializers
__all__ = [ __all__ = [
'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi', 'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi',
'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi', 'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi',
'AssetPermissionAddAssetApi', 'AssetPermissionAddAssetApi', 'AssetPermissionAssetsApi',
] ]
...@@ -232,3 +233,22 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): ...@@ -232,3 +233,22 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
return Response({"msg": "ok"}) return Response({"msg": "ok"})
else: else:
return Response({"error": serializer.errors}) return Response({"error": serializer.errors})
class AssetPermissionAssetsApi(ListAPIView):
permission_classes = (IsOrgAdmin,)
pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetPermissionAssetsSerializer
filter_fields = ("hostname", "ip")
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(AssetPermission, pk=pk)
def get_queryset(self):
perm = self.get_object()
assets = perm.get_all_assets().only(
*self.serializer_class.Meta.only_fields
)
return assets
# -*- coding: utf-8 -*-
#
from functools import reduce
from hashlib import md5
from django.core.cache import cache
from django.db.models import Q
from django.conf import settings
from rest_framework.views import Response
from django.utils.translation import ugettext as _
from common.utils import get_logger
from assets.utils import LabelFilterMixin
from ..utils import (
AssetPermissionUtil
)
from .. import const
from ..hands import Asset, Node, SystemUser, Label
from .. import serializers
logger = get_logger(__name__)
__all__ = ['UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin']
class UserPermissionCacheMixin:
cache_policy = '0'
RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
_object = None
def get_object(self):
return None
# 内部使用可控制缓存
def _get_object(self):
if not self._object:
self._object = self.get_object()
return self._object
def get_object_id(self):
obj = self._get_object()
if obj:
return str(obj.id)
return None
def get_request_md5(self):
path = self.request.path
query = {k: v for k, v in self.request.GET.items()}
query.pop("_", None)
query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
full_path = "{}?{}".format(path, query)
return md5(full_path.encode()).hexdigest()
def get_meta_cache_id(self):
obj = self._get_object()
util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
meta_cache_id = util.cache_meta.get('id')
return meta_cache_id
def get_response_cache_id(self):
obj_id = self.get_object_id()
request_md5 = self.get_request_md5()
meta_cache_id = self.get_meta_cache_id()
resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
return resp_cache_id
def get_response_from_cache(self):
# 没有数据缓冲
meta_cache_id = self.get_meta_cache_id()
if not meta_cache_id:
logger.debug("Not get meta id: {}".format(meta_cache_id))
return None
# 从响应缓冲里获取响应
key = self.get_response_key()
data = cache.get(key)
if not data:
logger.debug("Not get response from cache: {}".format(key))
return None
logger.debug("Get user permission from cache: {}".format(self.get_object()))
response = Response(data)
return response
def expire_response_cache(self):
obj_id = self.get_object_id()
expire_cache_id = '{}_{}'.format(obj_id, '*')
key = self.RESP_CACHE_KEY.format(expire_cache_id)
cache.delete_pattern(key)
def get_response_key(self):
resp_cache_id = self.get_response_cache_id()
key = self.RESP_CACHE_KEY.format(resp_cache_id)
return key
def set_response_to_cache(self, response):
key = self.get_response_key()
cache.set(key, response.data, self.CACHE_TIME)
logger.debug("Set response to cache: {}".format(key))
def get(self, request, *args, **kwargs):
self.cache_policy = request.GET.get('cache_policy', '0')
obj = self._get_object()
if obj is None:
logger.debug("Not get response from cache: obj is none")
return super().get(request, *args, **kwargs)
if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
return super().get(request, *args, **kwargs)
elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
self.expire_response_cache()
logger.debug("Try get response from cache")
resp = self.get_response_from_cache()
if not resp:
resp = super().get(request, *args, **kwargs)
self.set_response_to_cache(resp)
return resp
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 GrantAssetsMixin(LabelFilterMixin):
serializer_class = serializers.AssetGrantedSerializer
def get_serializer_queryset(self, queryset):
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 data
def get_serializer(self, queryset_list, many=True):
data = self.get_serializer_queryset(queryset_list)
return super().get_serializer(data, many=True)
def search_queryset(self, assets_items):
search = self.request.query_params.get("search")
if not search:
return assets_items
assets_map = {asset['id']: asset for asset in assets_items}
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)
return [assets_map.get(asset_id) for asset_id in assets_ids_search]
def filter_queryset_by_label(self, assets_items):
labels_id = self.get_filter_labels_ids()
if not labels_id:
return assets_items
assets_map = {asset['id']: asset for asset in assets_items}
assets_matched = Asset.objects.filter(id__in=assets_map.keys())
for label_id in labels_id:
assets_matched = assets_matched.filter(labels=label_id)
assets_ids_matched = assets_matched.values_list('id', flat=True)
return [assets_map.get(asset_id) for asset_id in assets_ids_matched]
def sort_queryset(self, assets_items):
order_by = self.request.query_params.get('order', 'hostname')
if order_by not in ['hostname', '-hostname', 'ip', '-ip']:
order_by = 'hostname'
assets_map = {asset['id']: asset for asset in assets_items}
assets_ids_search = Asset.objects.filter(id__in=assets_map.keys())\
.order_by(order_by)\
.values_list('id', flat=True)
return [assets_map.get(asset_id) for asset_id in assets_ids_search]
def filter_queryset(self, assets_items):
assets_items = self.search_queryset(assets_items)
assets_items = self.filter_queryset_by_label(assets_items)
assets_items = self.sort_queryset(assets_items)
return assets_items
\ No newline at end of file
...@@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): ...@@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
else: else:
return Response({"error": serializer.errors}) return Response({"error": serializer.errors})
...@@ -2,153 +2,67 @@ ...@@ -2,153 +2,67 @@
# #
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.generics import (
ListAPIView, get_object_or_404,
)
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer from ..hands import UserGroup
from ..utils import ( from .. import serializers
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
RemoteAppPermissionUtil,
)
from ..hands import (
UserGroup, Node, NodeSerializer, RemoteAppSerializer,
)
from .. import serializers, const
from .user_permission import (
UserGrantedAssetsApi, UserGrantedNodesApi, UserGrantedNodesWithAssetsApi,
UserGrantedNodesWithAssetsAsTreeApi, UserGrantedNodeAssetsApi,
UserGrantedNodesAsTreeApi,
)
__all__ = [ __all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi',
'UserGroupGrantedNodesWithAssetsAsTreeApi', 'UserGroupGrantedNodesWithAssetsAsTreeApi', 'UserGroupGrantedNodesAsTreeApi',
'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)
return user_group
def get_queryset(self):
group_id = self.kwargs.get('pk', '')
queryset = []
if group_id: class UserGroupGrantedNodesAsTreeApi(UserGrantedNodesAsTreeApi):
group = get_object_or_404(UserGroup, id=group_id) def get_object(self):
util = AssetPermissionUtil(group) user_group_id = self.kwargs.get('pk', '')
nodes = util.get_nodes_with_assets() user_group = get_object_or_404(UserGroup, id=user_group_id)
return nodes.keys() return user_group
return queryset
class UserGroupGrantedNodesWithAssetsApi(ListAPIView): class UserGroupGrantedNodesWithAssetsApi(UserGrantedNodesWithAssetsApi):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
def get_queryset(self): def get_object(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
queryset = [] user_group = get_object_or_404(UserGroup, id=user_group_id)
return user_group
if not user_group_id:
return queryset
class UserGroupGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsAsTreeApi):
def get_object(self):
user_group_id = self.kwargs.get('pk', '')
user_group = get_object_or_404(UserGroup, id=user_group_id) user_group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(user_group) return user_group
nodes = util.get_nodes_with_assets()
for node, _assets in nodes.items():
assets = _assets.keys()
for asset, system_users in _assets.items():
asset.system_users_granted = system_users
node.assets_granted = assets
queryset.append(node)
return queryset
class UserGroupGrantedNodesWithAssetsAsTreeApi(ListAPIView):
serializer_class = TreeNodeSerializer
permission_classes = (IsOrgAdminOrAppUser,)
show_assets = True
system_user_id = None
def get(self, request, *args, **kwargs):
self.show_assets = request.query_params.get('show_assets', '1') == '1'
self.system_user_id = request.query_params.get('system_user')
return super().get(request, *args, **kwargs)
def get_queryset(self): class UserGroupGrantedNodeAssetsApi(UserGrantedNodeAssetsApi):
user_group_id = self.kwargs.get('pk', '')
queryset = []
group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(group)
if self.system_user_id:
util.filter_permissions(system_users=self.system_user_id)
nodes = util.get_nodes_with_assets()
for node, assets in nodes.items():
data = parse_node_to_tree_node(node)
queryset.append(data)
if not self.show_assets:
continue
for asset, system_users in assets.items():
data = parse_asset_to_tree_node(node, asset, system_users)
queryset.append(data)
queryset = sorted(queryset)
return queryset
class UserGroupGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
def get_queryset(self): def get_object(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
node_id = self.kwargs.get('node_id')
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = AssetPermissionUtil(user_group)
if str(node_id) == const.UNGROUPED_NODE_ID:
node = util.tree.ungrouped_node
else:
node = get_object_or_404(Node, id=node_id)
nodes = util.get_nodes_with_assets()
assets = nodes.get(node, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
return assets
# RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView):
permission_classes = (IsOrgAdmin, )
serializer_class = RemoteAppSerializer
def get_queryset(self):
queryset = []
user_group_id = self.kwargs.get('pk')
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id) user_group = get_object_or_404(UserGroup, id=user_group_id)
util = RemoteAppPermissionUtil(user_group) return user_group
queryset = util.get_remote_apps()
return queryset
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
# #
import time import time
import traceback import traceback
from hashlib import md5 from functools import reduce
from django.core.cache import cache import uuid
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 (
...@@ -16,126 +16,30 @@ from common.permissions import IsValidUser, IsOrgAdminOrAppUser ...@@ -16,126 +16,30 @@ 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
from ..utils import ( from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, AssetPermissionUtil, ParserNode,
) )
from .mixin import UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
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 UserGrantedAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
cache_policy = '0'
RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_{}'
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
_object = None
def get_object(self):
return None
# 内部使用可控制缓存
def _get_object(self):
if not self._object:
self._object = self.get_object()
return self._object
def get_object_id(self):
obj = self._get_object()
if obj:
return str(obj.id)
return None
def get_request_md5(self):
path = self.request.path
query = {k: v for k, v in self.request.GET.items()}
query.pop("_", None)
query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
full_path = "{}?{}".format(path, query)
return md5(full_path.encode()).hexdigest()
def get_meta_cache_id(self):
obj = self._get_object()
util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
meta_cache_id = util.cache_meta.get('id')
return meta_cache_id
def get_response_cache_id(self):
obj_id = self.get_object_id()
request_md5 = self.get_request_md5()
meta_cache_id = self.get_meta_cache_id()
resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
return resp_cache_id
def get_response_from_cache(self):
# 没有数据缓冲
meta_cache_id = self.get_meta_cache_id()
if not meta_cache_id:
logger.debug("Not get meta id: {}".format(meta_cache_id))
return None
# 从响应缓冲里获取响应
key = self.get_response_key()
data = cache.get(key)
if not data:
logger.debug("Not get response from cache: {}".format(key))
return None
logger.debug("Get user permission from cache: {}".format(self.get_object()))
response = Response(data)
return response
def expire_response_cache(self):
obj_id = self.get_object_id()
expire_cache_id = '{}_{}'.format(obj_id, '*')
key = self.RESP_CACHE_KEY.format(expire_cache_id)
cache.delete_pattern(key)
def get_response_key(self):
resp_cache_id = self.get_response_cache_id()
key = self.RESP_CACHE_KEY.format(resp_cache_id)
return key
def set_response_to_cache(self, response):
key = self.get_response_key()
cache.set(key, response.data, self.CACHE_TIME)
logger.debug("Set response to cache: {}".format(key))
def get(self, request, *args, **kwargs):
self.cache_policy = request.GET.get('cache_policy', '0')
obj = self._get_object()
if obj is None:
logger.debug("Not get response from cache: obj is none")
return super().get(request, *args, **kwargs)
if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
return super().get(request, *args, **kwargs)
elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
logger.debug("Not get resp from cache: {}".format(self.cache_policy))
self.expire_response_cache()
logger.debug("Try get response from cache")
resp = self.get_response_from_cache()
if not resp:
resp = super().get(request, *args, **kwargs)
self.set_response_to_cache(resp)
return resp
class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, 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 +51,9 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV ...@@ -147,17 +51,9 @@ 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():
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,26 +62,47 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV ...@@ -166,26 +62,47 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
return super().get_permissions() return super().get_permissions()
class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView): class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, GrantAssetsMixin, ListAPIView):
""" """
查询用户授权的所有节点的API 查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
""" """
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = NodeSerializer pagination_class = LimitOffsetPagination
def get_object(self): def get_object(self):
user_id = self.kwargs.get('pk', '') user_id = self.kwargs.get('pk', '')
if user_id: if user_id:
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
else: else:
user = self.request.user user = self.request.user
return user return user
def get_node_key(self):
node_id = self.kwargs.get('node_id')
if str(node_id) == const.UNGROUPED_NODE_ID:
key = self.util.tree.ungrouped_key
elif str(node_id) == const.EMPTY_NODE_ID:
key = const.EMPTY_NODE_KEY
else:
node = get_object_or_404(Node, id=node_id)
key = node.key
return key
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() key = self.get_node_key()
return nodes 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})
return assets
def get_permissions(self): def get_permissions(self):
if self.kwargs.get('pk') is None: if self.kwargs.get('pk') is None:
...@@ -193,38 +110,48 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView): ...@@ -193,38 +110,48 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView):
return super().get_permissions() return super().get_permissions()
class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView): class UserGrantedNodesApi(UserPermissionCacheMixin, NodesWithUngroupMixin, ListAPIView):
""" """
用户授权的节点并带着节点下资产的api 查询用户授权的所有节点的API
""" """
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.NodeGrantedSerializer 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', '')
if not user_id: if user_id:
user = self.request.user
else:
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
else:
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):
queryset = []
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_with_assets() nodes_with_assets = self.util.get_nodes_with_assets()
for node, _assets in nodes.items(): return nodes_with_assets
assets = _assets.keys()
for k, v in _assets.items():
k.system_users_granted = v
node.assets_granted = assets
queryset.append(node)
return queryset
def sort_assets(self, queryset):
for node in queryset:
node.assets_granted = super().sort_assets(node.assets_granted)
return queryset
def get_permissions(self): def get_permissions(self):
if self.kwargs.get('pk') is None: if self.kwargs.get('pk') is None:
...@@ -232,16 +159,30 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ...@@ -232,16 +159,30 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
return super().get_permissions() return super().get_permissions()
class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView): class UserGrantedNodesAsTreeApi(UserGrantedNodesApi):
serializer_class = TreeNodeSerializer 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
"""
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
show_assets = True serializer_class = serializers.NodeGrantedSerializer
system_user_id = None pagination_class = LimitOffsetPagination
def get_permissions(self): nodes_only_fields = serializers.NodeGrantedSerializer.Meta.only_fields
if self.kwargs.get('pk') is None: assets_only_fields = serializers.NodeGrantedSerializer.assets_only_fields
self.permission_classes = (IsValidUser,) system_users_only_fields = serializers.NodeGrantedSerializer.system_users_only_fields
return super().get_permissions()
def get_object(self): def get_object(self):
user_id = self.kwargs.get('pk', '') user_id = self.kwargs.get('pk', '')
...@@ -251,66 +192,92 @@ class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView) ...@@ -251,66 +192,92 @@ class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView)
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 = []
self.show_assets = self.request.query_params.get('show_assets', '1') == '1' _node_map, _assets_map, _system_users_map = self.get_maps(nodes_items)
self.system_user_id = self.request.query_params.get('system_user') for item in nodes_items:
user = self.get_object() key = item["key"]
util = AssetPermissionUtil(user, cache_policy=self.cache_policy) node = _node_map.get(key)
if self.system_user_id: if not node:
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 continue
for asset, system_users in assets.items(): node._assets_amount = item["assets_amount"]
data = parse_asset_to_tree_node(node, asset, system_users) assets_granted = []
queryset.append(data) for asset_id, system_users_ids_action in item["assets"].items():
queryset = sorted(queryset) 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)
return queryset return queryset
def get_serializer(self, nodes_items, many=True):
class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIView): queryset = self.get_serializer_queryset(nodes_items)
""" return super().get_serializer(queryset, many=many)
查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer
pagination_class = LimitOffsetPagination
def get_object(self):
user_id = self.kwargs.get('pk', '')
if user_id:
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
return user
def get_queryset(self): def get_queryset(self):
user = self.get_object() user = self.get_object()
node_id = self.kwargs.get('node_id') self.util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy) system_user_id = self.request.query_params.get('system_user')
nodes = util.get_nodes_with_assets() if system_user_id:
if str(node_id) == const.UNGROUPED_NODE_ID: self.util.filter_permissions(
node = util.tree.ungrouped_node system_users=system_user_id
elif str(node_id) == const.EMPTY_NODE_ID: )
node = util.tree.empty_node nodes_items = self.util.get_nodes_with_assets()
else: return nodes_items
node = get_object_or_404(Node, id=node_id)
if node == util.tree.root_node:
assets = util.get_assets()
else:
assets = nodes.get(node, {})
for asset, system_users in assets.items():
asset.system_users_granted = system_users
assets = list(assets.keys())
return assets
def get_permissions(self): def get_permissions(self):
if self.kwargs.get('pk') is None: if self.kwargs.get('pk') is None:
...@@ -318,90 +285,27 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List ...@@ -318,90 +285,27 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List
return super().get_permissions() return super().get_permissions()
class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView): class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesWithAssetsApi):
""" serializer_class = TreeNodeSerializer
获取用户自己授权节点下子节点的api permission_classes = (IsOrgAdminOrAppUser,)
""" system_user_id = None
permission_classes = (IsValidUser,) nodes_only_fields = ParserNode.nodes_only_fields
serializer_class = serializers.AssetPermissionNodeSerializer assets_only_fields = ParserNode.assets_only_fields
system_users_only_fields = ParserNode.system_users_only_fields
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): def get_serializer(self, nodes_items, many=True):
user = self.get_object() _queryset = super().get_serializer_queryset(nodes_items)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
nodes_granted = util.get_nodes_with_assets()
queryset = [] 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): for node in _queryset:
keyword = self.request.query_params.get('search') data = ParserNode.parse_node_to_tree_node(node)
if keyword: queryset.append(data)
return self.get_search_queryset(keyword) for asset in node.assets_granted:
else: system_users = asset.system_users_granted
return self.get_children_queryset() data = ParserNode.parse_asset_to_tree_node(node, asset, system_users)
queryset.append(data)
queryset = sorted(queryset)
return self.serializer_class(queryset, many=True)
class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
...@@ -412,24 +316,24 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): ...@@ -412,24 +316,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')
user = get_object_or_404(User, id=user_id) try:
asset = get_object_or_404(Asset, id=asset_id) asset_id = uuid.UUID(asset_id)
su = get_object_or_404(SystemUser, id=system_id) system_id = uuid.UUID(system_id)
except ValueError:
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
granted_assets = util.get_assets()
granted_system_users = granted_assets.get(asset, {})
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': False}, status=403)
return Response({'msg': True}, status=200) user = get_object_or_404(User, id=user_id)
util = AssetPermissionUtil(user, cache_policy=cache_policy)
assets = util.get_assets()
for asset in assets:
if asset_id == asset["id"]:
action = asset["system_users"].get(system_id)
if action and action_name in Action.value_to_choices(action):
return Response({'msg': True}, status=200)
break
return Response({'msg': False}, status=403)
class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView): class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView):
...@@ -442,16 +346,12 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView ...@@ -442,16 +346,12 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView
system_id = self.request.query_params.get('system_user_id', '') system_id = self.request.query_params.get('system_user_id', '')
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)
su = get_object_or_404(SystemUser, id=system_id)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy) util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
granted_assets = util.get_assets() assets = util.get_assets()
granted_system_users = granted_assets.get(asset, {}) actions = 0
for asset in assets:
_object = {} if asset_id == asset["id"]:
if su not in granted_system_users: actions = asset["system_users"].get(system_id, 0)
_object['actions'] = 0 break
else: return {"actions": actions}
_object['actions'] = granted_system_users[su]
return _object
...@@ -13,13 +13,13 @@ from ..utils import ( ...@@ -13,13 +13,13 @@ from ..utils import (
RemoteAppPermissionUtil, construct_remote_apps_tree_root, RemoteAppPermissionUtil, construct_remote_apps_tree_root,
parse_remote_app_to_tree_node, parse_remote_app_to_tree_node,
) )
from ..hands import User, RemoteApp, RemoteAppSerializer from ..hands import User, RemoteApp, RemoteAppSerializer, UserGroup
from ..mixins import RemoteAppFilterMixin from ..mixins import RemoteAppFilterMixin
__all__ = [ __all__ = [
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi', 'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
'UserGrantedRemoteAppsAsTreeApi', 'UserGrantedRemoteAppsAsTreeApi', 'UserGroupGrantedRemoteAppsApi',
] ]
...@@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView): ...@@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView):
if remote_app not in remote_apps: if remote_app not in remote_apps:
return Response({'msg': False}, status=403) return Response({'msg': False}, status=403)
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
# RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser, )
serializer_class = RemoteAppSerializer
def get_queryset(self):
queryset = []
user_group_id = self.kwargs.get('pk')
if not user_group_id:
return queryset
user_group = get_object_or_404(UserGroup, id=user_group_id)
util = RemoteAppPermissionUtil(user_group)
queryset = util.get_remote_apps()
return queryset
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002" UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002"
EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003" EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003"
EMPTY_NODE_KEY = "1:-2"
...@@ -41,6 +41,9 @@ class AssetPermissionForm(OrgModelForm): ...@@ -41,6 +41,9 @@ class AssetPermissionForm(OrgModelForm):
users_field = self.fields.get('users') users_field = self.fields.get('users')
users_field.queryset = current_org.get_org_users() users_field.queryset = current_org.get_org_users()
nodes_field = self.fields['nodes']
nodes_field.choices = ((n.id, n.full_value) for n in Node.get_queryset())
# 前端渲染优化, 防止过多资产 # 前端渲染优化, 防止过多资产
if not self.data: if not self.data:
instance = kwargs.get('instance') instance = kwargs.get('instance')
...@@ -49,8 +52,6 @@ class AssetPermissionForm(OrgModelForm): ...@@ -49,8 +52,6 @@ class AssetPermissionForm(OrgModelForm):
assets_field.queryset = instance.assets.all() assets_field.queryset = instance.assets.all()
else: else:
assets_field.queryset = Asset.objects.none() assets_field.queryset = Asset.objects.none()
nodes_field = self.fields['nodes']
nodes_field._queryset = Node.get_queryset()
class Meta: class Meta:
model = AssetPermission model = AssetPermission
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# #
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node from assets.models import Asset, SystemUser, Node, Label
from assets.serializers import NodeSerializer from assets.serializers import NodeSerializer
from applications.serializers import RemoteAppSerializer from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp from applications.models import RemoteApp
......
...@@ -2,10 +2,12 @@ import uuid ...@@ -2,10 +2,12 @@ import uuid
from functools import reduce from functools import reduce
from django.db import models from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ 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,14 +87,23 @@ class AssetPermission(BasePermission): ...@@ -85,14 +87,23 @@ 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()) args = [Q(granted_by_permissions=self)]
for node in self.nodes.all(): pattern = set()
_assets = node.get_all_assets() nodes_keys = self.nodes.all().values_list('key', flat=True)
set_or_append_attr_bulk(_assets, 'inherit', node.value) for key in nodes_keys:
assets.update(set(_assets)) pattern.add(r'^{0}$|^{0}:'.format(key))
pattern = '|'.join(list(pattern))
if pattern:
args.append(Q(nodes__key__regex=pattern))
args = reduce(lambda x, y: x | y, args)
assets = Asset.objects.filter(args)
return assets return assets
......
...@@ -6,11 +6,12 @@ from rest_framework import serializers ...@@ -6,11 +6,12 @@ from rest_framework import serializers
from common.fields import StringManyToManyField from common.fields import StringManyToManyField
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins import BulkOrgResourceModelSerializer
from perms.models import AssetPermission, Action from perms.models import AssetPermission, Action
from assets.models import Asset
__all__ = [ __all__ = [
'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer',
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'ActionsField', 'ActionsField', 'AssetPermissionAssetsSerializer',
] ]
...@@ -70,3 +71,11 @@ class AssetPermissionUpdateAssetSerializer(serializers.ModelSerializer): ...@@ -70,3 +71,11 @@ class AssetPermissionUpdateAssetSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AssetPermission model = AssetPermission
fields = ['id', 'assets'] fields = ['id', 'assets']
class AssetPermissionAssetsSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
only_fields = ['id', 'hostname', 'ip']
fields = tuple(only_fields)
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
# #
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from assets.models import Node, SystemUser from assets.models import Node, SystemUser, Asset
from assets.serializers import AssetSerializer from assets.serializers import ProtocolsField
from .asset_permission import ActionsField from .asset_permission import ActionsField
__all__ = [ __all__ = [
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'GrantedNodeSerializer',
'NodeGrantedSerializer', 'AssetGrantedSerializer', 'NodeGrantedSerializer', 'AssetGrantedSerializer',
'ActionsSerializer', 'ActionsSerializer', 'AssetSystemUserSerializer',
] ]
...@@ -23,58 +23,36 @@ class AssetSystemUserSerializer(serializers.ModelSerializer): ...@@ -23,58 +23,36 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = SystemUser model = SystemUser
fields = ( only_fields = (
'id', 'name', 'username', 'priority', "actions", 'id', 'name', 'username', 'priority',
'protocol', 'login_mode', 'protocol', 'login_mode',
) )
fields = list(only_fields) + ["actions"]
read_only_fields = fields
class AssetGrantedSerializer(AssetSerializer): class AssetGrantedSerializer(serializers.ModelSerializer):
""" """
被授权资产的数据结构 被授权资产的数据结构
""" """
protocols = ProtocolsField(label=_('Protocols'), required=False, read_only=True)
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True) system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField() system_users_join = serializers.SerializerMethodField()
system_users_only_fields = AssetSystemUserSerializer.Meta.only_fields
@staticmethod
def get_system_users_join(obj):
system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
def get_field_names(self, declared_fields, info):
fields = (
"id", "hostname", "ip", "protocols",
"system_users_granted", "is_active", "system_users_join", "os",
'domain', "platform", "comment", "org_id", "org_name",
)
return fields
class AssetPermissionNodeSerializer(serializers.ModelSerializer):
asset = AssetGrantedSerializer(required=False)
assets_amount = serializers.SerializerMethodField()
tree_id = serializers.SerializerMethodField()
tree_parent = serializers.SerializerMethodField()
class Meta: class Meta:
model = Node model = Asset
fields = [ only_fields = [
'id', 'key', 'value', 'asset', 'is_node', 'org_id', "id", "hostname", "ip", "protocols", "os", 'domain',
'tree_id', 'tree_parent', 'assets_amount', "platform", "org_id",
] ]
fields = only_fields + ['system_users_granted', 'system_users_join', "org_name"]
read_only_fields = fields
@staticmethod @staticmethod
def get_assets_amount(obj): def get_system_users_join(obj):
return obj.assets_amount system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
@staticmethod
def get_tree_id(obj):
return obj.key
@staticmethod
def get_tree_parent(obj):
return obj.parent_key
class NodeGrantedSerializer(serializers.ModelSerializer): class NodeGrantedSerializer(serializers.ModelSerializer):
...@@ -82,28 +60,19 @@ class NodeGrantedSerializer(serializers.ModelSerializer): ...@@ -82,28 +60,19 @@ class NodeGrantedSerializer(serializers.ModelSerializer):
授权资产组 授权资产组
""" """
assets_granted = AssetGrantedSerializer(many=True, read_only=True) assets_granted = AssetGrantedSerializer(many=True, read_only=True)
assets_amount = serializers.SerializerMethodField() assets_amount = serializers.ReadOnlyField()
parent = serializers.SerializerMethodField() name = serializers.ReadOnlyField(source='value')
name = serializers.SerializerMethodField()
assets_only_fields = AssetGrantedSerializer.Meta.only_fields
system_users_only_fields = AssetGrantedSerializer.system_users_only_fields
class Meta: class Meta:
model = Node model = Node
fields = [ only_fields = ['id', 'key', 'value', "org_id"]
'id', 'key', 'name', 'value', 'parent', fields = only_fields + [
'assets_granted', 'assets_amount', 'org_id', 'name', 'assets_granted', 'assets_amount',
] ]
read_only_fields = fields
@staticmethod
def get_assets_amount(obj):
return len(obj.assets_granted)
@staticmethod
def get_name(obj):
return obj.name
@staticmethod
def get_parent(obj):
return obj.parent.id
class GrantedNodeSerializer(serializers.ModelSerializer): class GrantedNodeSerializer(serializers.ModelSerializer):
...@@ -112,6 +81,7 @@ class GrantedNodeSerializer(serializers.ModelSerializer): ...@@ -112,6 +81,7 @@ class GrantedNodeSerializer(serializers.ModelSerializer):
fields = [ fields = [
'id', 'name', 'key', 'value', 'id', 'name', 'key', 'value',
] ]
read_only_fields = fields
class ActionsSerializer(serializers.Serializer): class ActionsSerializer(serializers.Serializer):
......
...@@ -48,29 +48,19 @@ ...@@ -48,29 +48,19 @@
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<table class="table table-hover"> <table class="table table-striped table-bordered table-hover" id="asset_list_table" style="width: 100%">
<thead> <thead>
<tr> <tr>
<th>{% trans 'Hostname' %}</th> <th class="text-center">
<th>{% trans 'IP' %}</th> <input type="checkbox" id="check_all" class="ipt_check_all" >
<th></th> </th>
</tr> <th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
</tr>
</thead> </thead>
<tbody> <tbody>
{% for asset in object_list %}
<tr>
<td>{{ asset.hostname }}</td>
<td>{{ asset.ip }}</td>
<td>
<button title="{{ asset.inherit }}" data-gid="{{ asset.id }}" class="btn btn-danger btn-xs btn-remove-asset {% if asset.inherit %} disabled {% endif %}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
<div class="row">
{% include '_pagination.html' %}
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -86,9 +76,6 @@ ...@@ -86,9 +76,6 @@
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td colspan="2"> <td colspan="2">
<select data-placeholder="{% trans 'Select assets' %}" class="select2" id="asset_select2" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="{% trans 'Select assets' %}" class="select2" id="asset_select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}">{{ asset }}</option>
{% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
...@@ -146,6 +133,7 @@ ...@@ -146,6 +133,7 @@
</div> </div>
</div> </div>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
...@@ -157,12 +145,12 @@ function addAssets(assets) { ...@@ -157,12 +145,12 @@ function addAssets(assets) {
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
}); });
} }
function removeAssets(assets) { function removeAssets(assets) {
var the_url = "{% url 'api-perms:asset-permission-remove-asset' pk=asset_permission.id %}"; var the_url = "{% url 'api-perms:asset-permission-remove-asset' pk=asset_permission.id %}";
...@@ -172,7 +160,7 @@ function removeAssets(assets) { ...@@ -172,7 +160,7 @@ function removeAssets(assets) {
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -184,16 +172,64 @@ function updateNodes(nodes, success) { ...@@ -184,16 +172,64 @@ function updateNodes(nodes, success) {
var body = { var body = {
nodes: nodes nodes: nodes
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
}); });
} }
var table;
function initAssetTable() {
var options = {
ele: $('#asset_list_table'),
toggle: true,
columnDefs: [
{
targets: 0, createdCell: function (td, cellData, rowData) {
var html = '<input type="checkbox" class="text-center ipt_check" id="id_' + cellData + '">';
$(td).html(html);
}
},
],
ajax_url: "{% url 'api-perms:asset-permission-assets' pk=object.id %}",
columns: [
{data: "id"}, {data: "hostname"}, {data: "ip"}
],
op_html: $('#actions').html()
};
table = jumpserver.initServerSideDataTable(options);
return table
}
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
table = initAssetTable();
$("#asset_select2").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
initSelectedAssets2Table('#asset_select2');
}
})
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
var options = [];
$('#asset_select2 option').each(function (i, v) {
options.push(v.value)
});
asset_table2.selected_rows.forEach(function (i) {
var name = i.hostname + '(' + i.ip + ')';
var option = new Option(name, i.id, false, true);
if (options.indexOf(i.id) === -1) {
$('#asset_select2').append(option).trigger('change');
}
});
$('#asset_select2').val(assets).trigger('change');
$("#asset_list_modal").modal('hide');
}) })
.on('click', '.btn-add-assets', function () { .on('click', '.btn-add-assets', function () {
var assets_selected = $("#asset_select2 option:selected").map(function () { var assets_selected = $("#asset_select2 option:selected").map(function () {
...@@ -237,7 +273,7 @@ $(document).ready(function () { ...@@ -237,7 +273,7 @@ $(document).ready(function () {
}); });
}; };
updateNodes(nodes, success); updateNodes(nodes, success);
}) })
.on('click', '.btn-remove-node', function () { .on('click', '.btn-remove-node', function () {
var $this = $(this); var $this = $(this);
var $tr = $this.closest('tr'); var $tr = $this.closest('tr');
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
</tr> </tr>
<tr> <tr>
<td>{% trans 'User group count' %}:</td> <td>{% trans 'User group count' %}:</td>
<td><b>{{ object.users.count }}</b></td> <td><b>{{ object.user_groups.count }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Asset count' %}:</td> <td>{% trans 'Asset count' %}:</td>
...@@ -187,7 +187,7 @@ function updateSystemUser(system_users) { ...@@ -187,7 +187,7 @@ function updateSystemUser(system_users) {
var body = { var body = {
system_users: Object.assign([], system_users) system_users: Object.assign([], system_users)
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
...@@ -247,7 +247,7 @@ $(document).ready(function () { ...@@ -247,7 +247,7 @@ $(document).ready(function () {
var body = { var body = {
'is_active': checked 'is_active': checked
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
......
...@@ -160,7 +160,7 @@ function addUsers(users) { ...@@ -160,7 +160,7 @@ function addUsers(users) {
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -175,7 +175,7 @@ function removeUser(users) { ...@@ -175,7 +175,7 @@ function removeUser(users) {
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -187,7 +187,7 @@ function updateGroup(groups) { ...@@ -187,7 +187,7 @@ function updateGroup(groups) {
var body = { var body = {
user_groups: groups user_groups: groups
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
......
...@@ -116,5 +116,28 @@ $(document).ready(function () { ...@@ -116,5 +116,28 @@ $(document).ready(function () {
$('#date_start').daterangepicker(dateOptions); $('#date_start').daterangepicker(dateOptions);
$('#date_expired').daterangepicker(dateOptions); $('#date_expired').daterangepicker(dateOptions);
}) })
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var method = "POST";
var the_url = '{% url "api-perms:remote-app-permission-list" %}';
var redirect_to = '{% url "perms:remote-app-permission-list" %}';
{% if type == "update" %}
the_url = '{% url "api-perms:remote-app-permission-detail" pk=object.id %}';
method = "PUT";
{% endif %}
objectAttrsIsList(data, ['users', 'user_groups', 'remote_apps']);
objectAttrsIsDatetime(data, ['date_expired', 'date_start']);
objectAttrsIsBool(data, ['is_active']);
var props = {
url:the_url,
data:data,
method:method,
form:form,
redirect_to:redirect_to
};
formSubmit(props);
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -160,7 +160,7 @@ $(document).ready(function () { ...@@ -160,7 +160,7 @@ $(document).ready(function () {
var body = { var body = {
'is_active': checked 'is_active': checked
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
......
...@@ -120,7 +120,7 @@ ...@@ -120,7 +120,7 @@
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
......
...@@ -158,7 +158,7 @@ ...@@ -158,7 +158,7 @@
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -172,7 +172,7 @@ ...@@ -172,7 +172,7 @@
var success = function(data) { var success = function(data) {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -183,7 +183,7 @@ ...@@ -183,7 +183,7 @@
var body = { var body = {
user_groups: groups user_groups: groups
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
......
# 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,37 @@ router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remot ...@@ -12,26 +13,37 @@ 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/tree/', 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/', api.UserGroupGrantedNodesAsTreeApi.as_view(), name='user-group-nodes-as-tree'),
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-assets/', api.UserGroupGrantedNodesWithAssetsApi.as_view(), name='user-group-nodes-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'),
...@@ -39,26 +51,29 @@ asset_permission_urlpatterns = [ ...@@ -39,26 +51,29 @@ asset_permission_urlpatterns = [
path('asset-permissions/<uuid:pk>/asset/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'), path('asset-permissions/<uuid:pk>/asset/remove/', api.AssetPermissionRemoveAssetApi.as_view(), name='asset-permission-remove-asset'),
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-permissions/<uuid:pk>/assets/', api.AssetPermissionAssetsApi.as_view(), name='asset-permission-assets'),
# 验证用户是否有某个资产和系统用户的权限 # 验证用户是否有某个资产和系统用户的权限
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 +82,11 @@ remote_app_permission_urlpatterns = [ ...@@ -67,7 +82,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
# 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_instance>]), "assets": set([asset.id,]),
} },
} }
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: ...@@ -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 self._ungroup_node: if not self._all_assets_nodes_key:
return self._ungroup_node self._all_assets_nodes_key = Asset.get_all_nodes_keys()
node_id = const.UNGROUPED_NODE_ID return self._all_assets_nodes_key
if self.root_node:
node_key = "{}:{}".format(self.root_node.key, self.root_node.child_mark)
else:
node_key = '0:0'
node_value = _("Default")
node = Node(id=node_id, key=node_key, value=node_value)
self.add_node(node, {})
self._ungroup_node = node
return node
@property @property
def empty_node(self): def ungrouped_key(self):
node_id = const.EMPTY_NODE_ID if self._ungroup_node:
value = _('Empty') return self._ungroup_node
node = Node(id=node_id, value=value) if self.root_key:
return node node_key = "{}:{}".format(self.root_key, '-1')
#@timeit
def add_assets_without_system_users(self, assets):
for asset in assets:
self.add_asset(asset, {})
#@timeit
def add_assets(self, assets):
for asset, system_users in assets.items():
self.add_asset(asset, system_users)
# #@timeit
def add_asset(self, asset, system_users=None):
nodes = asset.nodes.all()
nodes = self.node_util.get_nodes_by_queryset(nodes)
if not system_users:
system_users = defaultdict(int)
else: else:
system_users = {k: v for k, v in system_users.items()} node_key = '1:-1'
_system_users = self.assets[asset] self._ungroup_node = node_key
for system_user, action in _system_users.items(): return node_key
system_users[system_user] |= action
@timeit
# 获取父节点们 def add_assets_without_system_users(self, assets_ids):
parents = self.node_util.get_nodes_parents(nodes, with_self=True) for asset_id in assets_ids:
for node in parents: self.add_asset(asset_id, {})
_system_users = self.nodes[node]["system_users"]
self.nodes[node]["assets_amount"] += 1 @timeit
for system_user, action in _system_users.items(): def add_assets(self, assets_ids_with_system_users):
system_users[system_user] |= action for asset_id, system_users_ids in assets_ids_with_system_users.items():
self.add_asset(asset_id, system_users_ids)
# 过滤系统用户的协议
system_users = {s: v for s, v in system_users.items() if asset.has_protocol(s.protocol)} # @timeit
self.assets[asset] = system_users def add_asset(self, asset_id, system_users_ids=None):
"""
in_nodes = set(self.nodes.keys()) & set(nodes) :param asset_id:
:param system_users_ids: {system_user.id: actions, }
:return:
"""
if not system_users_ids:
system_users_ids = defaultdict(int)
# 获取已有资产的系统用户和actions,并更新到最新系统用户信息中
old_system_users_ids = self.assets[asset_id]
for system_user_id, action in old_system_users_ids.items():
system_users_ids[system_user_id] |= action
asset_nodes_keys = self.all_assets_nodes_keys.get(asset_id, [])
# {asset.id: [node.key, ], }
# 获取用户在的节点
in_nodes = set(self.nodes.keys()) & set(asset_nodes_keys)
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:
# 把自己加入到树上的节点中
def add_node(self, node, system_users=None): self.nodes[key]["assets"].add(asset_id)
if not system_users: # 获取自己所在节点的系统用户,并添加进去
system_users = defaultdict(int) node_system_users_ids = self.nodes[key]["system_users"]
self.nodes[node]["system_users"] = 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_key, system_users_ids=None):
"""
:param node_key: node.key
: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): ...@@ -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: ...@@ -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: ...@@ -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): ...@@ -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,67 +583,72 @@ def sort_assets(assets, order_by='hostname', reverse=False): ...@@ -545,67 +583,72 @@ def sort_assets(assets, order_by='hostname', reverse=False):
return assets return assets
def parse_node_to_tree_node(node): class ParserNode:
name = '{} ({})'.format(node.value, node.assets_amount) nodes_only_fields = ("key", "value", "id")
data = { assets_only_fields = ("platform", "hostname", "id", "ip", "protocols")
'id': node.key, system_users_only_fields = (
'name': name, "id", "name", "username", "protocol", "priority", "login_mode",
'title': name, )
'pId': node.parent_key,
'isParent': True, @staticmethod
'open': node.is_root(), def parse_node_to_tree_node(node):
'meta': { name = '{} ({})'.format(node.value, node.assets_amount)
'node': { data = {
"id": node.id, 'id': node.key,
"key": node.key, 'name': name,
"value": node.value, 'title': name,
}, 'pId': node.parent_key,
'type': 'node' 'isParent': True,
'open': node.is_root(),
'meta': {
'node': {
"id": node.id,
"key": node.key,
"value": node.value,
},
'type': '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': icon_skin = 'windows'
icon_skin = 'windows' elif asset.platform.lower() == 'linux':
elif asset.platform.lower() == 'linux': icon_skin = 'linux'
icon_skin = 'linux' _system_users = []
_system_users = [] for system_user in system_users:
for system_user, action in system_users.items(): _system_users.append({
_system_users.append({ 'id': system_user.id,
'id': system_user.id, 'name': system_user.name,
'name': system_user.name, 'username': system_user.username,
'username': system_user.username, '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(system_user.actions)],
'actions': [Action.value_to_choices(action)], })
}) data = {
data = { 'id': str(asset.id),
'id': str(asset.id), 'name': asset.hostname,
'name': asset.hostname, 'title': asset.ip,
'title': asset.ip, 'pId': node.key,
'pId': node.key, 'isParent': False,
'isParent': False, 'open': False,
'open': False, 'iconSkin': icon_skin,
'iconSkin': icon_skin, 'meta': {
'meta': { 'system_users': _system_users,
'system_users': _system_users, 'type': 'asset',
'type': 'asset', 'asset': {
'asset': { 'id': asset.id,
'id': asset.id, 'hostname': asset.hostname,
'hostname': asset.hostname, '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
},
} }
} tree_node = TreeNode(**data)
tree_node = TreeNode(**data) return tree_node
return tree_node
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from common.struct import Stack
from common.utils import timeit
from assets.utils import NodeUtil
class PermStackUtilMixin:
def __init__(self, debug=False):
self.stack = None
self._nodes = {}
self._debug = debug
@staticmethod
def sorted_by(node_dict):
return [int(i) for i in node_dict['key'].split(':')]
@staticmethod
def is_children(item1, item2):
key1 = item1["key"]
key2 = item2["key"]
return key2.startswith(key1 + ':') and (
len(key2.split(':')) - len(key1.split(':'))
) == 1
def debug(self, msg):
self._debug and print(msg)
class PermSystemUserNodeUtil(PermStackUtilMixin):
"""
self._nodes: {node.key: {system_user.id: actions,}}
"""
@timeit
def get_nodes_family_and_system_users(self, nodes_with_system_users):
"""
返回所有nodes_with_system_users中的node的家族节点的信息,
并子会继承祖先的系统用户和actions信息
:param nodes_with_system_users:
{node.key: {system_user.id: actions,}, }
:return:
{node.key: {system_user.id: actions,}, }
"""
node_util = NodeUtil()
_nodes_keys = nodes_with_system_users.keys()
family_keys = node_util.get_some_nodes_family_keys_by_keys(_nodes_keys)
nodes_items = []
for i in family_keys:
system_users = nodes_with_system_users.get(i, defaultdict(int))
item = {"key": i, "system_users": system_users}
nodes_items.append(item)
# 按照父子关系排序
nodes_items.sort(key=self.sorted_by)
nodes_items.append({"key": "", "system_users": defaultdict(int)})
self.stack = Stack()
for item in nodes_items:
self.debug("准备: {} 栈顶: {}".format(
item['key'], self.stack.top["key"] if self.stack.top else None)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while self.stack.top and not self.is_children(self.stack.top, item):
# 出栈
self.pop_from_stack_system_users()
# 入栈
self.push_to_stack_system_users(item)
# 出栈最后一个
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
return self._nodes
def push_to_stack_system_users(self, item):
"""
:param item:
{"key": node.key, "system_users": {system_user.id: actions,},}
"""
if not self.stack.is_empty():
item_system_users = item["system_users"]
for system_user, action in self.stack.top["system_users"].items():
# 更新栈顶的系统用户和action到将要入栈的item中
item_system_users[system_user] |= action
item["system_users"] = item_system_users
self.debug("入栈: {}".format(item['key']))
self.stack.push(item)
# 出栈
def pop_from_stack_system_users(self):
_node = self.stack.pop()
self._nodes[_node["key"]] = _node["system_users"]
self.debug("出栈: {} 栈顶: {}".format(_node['key'], self.stack.top['key'] if self.stack.top else None))
class PermAssetsAmountUtil(PermStackUtilMixin):
def push_to_stack_nodes_amount(self, item):
self.debug("入栈: {}".format(item['key']))
self.stack.push(item)
def pop_from_stack_nodes_amount(self):
_node = self.stack.pop()
self.debug("出栈: {} 栈顶: {}".format(
_node['key'], self.stack.top['key'] if self.stack.top else None)
)
_node["assets_amount"] = len(_node["all_assets"] | _node["assets"])
self._nodes[_node.pop("key")] = _node
if not self.stack.top:
return
self.stack.top["all_assets"]\
.update(_node["all_assets"] | _node["assets"])
def compute_nodes_assets_amount(self, nodes_with_assets):
self.stack = Stack()
nodes_items = []
for key, values in nodes_with_assets.items():
nodes_items.append({
"key": key, "assets": values["assets"],
"all_assets": values["all_assets"], "assets_amount": 0
})
nodes_items.sort(key=self.sorted_by)
nodes_items.append({"key": "", "assets": set(), "all_assets": set(), "assets_amount": 0})
self.stack = Stack()
for item in nodes_items:
self.debug("准备: {} 栈顶: {}".format(
item['key'], self.stack.top["key"] if self.stack.top else None)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while self.stack.top and not self.is_children(self.stack.top, item):
self.pop_from_stack_nodes_amount()
self.push_to_stack_nodes_amount(item)
# 出栈最后一个
self.debug("剩余: {}".format(', '.join([n["key"] for n in self.stack])))
return self._nodes
\ No newline at end of file
...@@ -163,12 +163,12 @@ class AssetPermissionAssetView(PermissionsMixin, ...@@ -163,12 +163,12 @@ class AssetPermissionAssetView(PermissionsMixin,
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
assets_granted = self.get_queryset() granted_nodes = self.object.nodes.all()
nodes_remain = [n for n in Node.get_queryset() if n not in granted_nodes]
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'action': _('Asset permission asset list'), 'action': _('Asset permission asset list'),
'assets_remain': Asset.objects.exclude(id__in=[a.id for a in assets_granted]), 'nodes_remain': nodes_remain,
'nodes_remain': Node.objects.exclude(granted_by_permissions=self.object),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
\ No newline at end of file
...@@ -48,6 +48,7 @@ class RemoteAppPermissionCreateView(PermissionsMixin, CreateView): ...@@ -48,6 +48,7 @@ class RemoteAppPermissionCreateView(PermissionsMixin, CreateView):
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'action': _('Create RemoteApp permission'), 'action': _('Create RemoteApp permission'),
'type': 'create'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -63,7 +64,8 @@ class RemoteAppPermissionUpdateView(PermissionsMixin, UpdateView): ...@@ -63,7 +64,8 @@ class RemoteAppPermissionUpdateView(PermissionsMixin, UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
'action': _('Update RemoteApp permission') 'action': _('Update RemoteApp permission'),
'type': 'update'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
......
...@@ -192,6 +192,11 @@ class SecuritySettingForm(BaseForm): ...@@ -192,6 +192,11 @@ class SecuritySettingForm(BaseForm):
required=False, label=_("Batch execute commands"), required=False, label=_("Batch execute commands"),
help_text=_("Allow user batch execute commands") help_text=_("Allow user batch execute commands")
) )
SECURITY_SERVICE_ACCOUNT_REGISTRATION = forms.BooleanField(
required=False, label=_("Service account registration"),
help_text=_("Allow using bootstrap token register service account, "
"when terminal setup, can disable it")
)
# limit login count # limit login count
SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField( SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField(
min_value=3, max_value=99999, min_value=3, max_value=99999,
......
...@@ -96,7 +96,7 @@ $(document).ready(function () { ...@@ -96,7 +96,7 @@ $(document).ready(function () {
function success(message) { function success(message) {
toastr.success(message.msg) toastr.success(message.msg)
} }
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(data), body: JSON.stringify(data),
method: "POST", method: "POST",
......
...@@ -100,7 +100,7 @@ $(document).ready(function () { ...@@ -100,7 +100,7 @@ $(document).ready(function () {
function success(message) { function success(message) {
toastr.success(message.msg) toastr.success(message.msg)
} }
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(data), body: JSON.stringify(data),
method: "POST", method: "POST",
...@@ -127,7 +127,7 @@ $(document).ready(function () { ...@@ -127,7 +127,7 @@ $(document).ready(function () {
function success(message) { function success(message) {
toastr.success(message.msg) toastr.success(message.msg)
} }
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify({'username_list':username_list}), body: JSON.stringify({'username_list':username_list}),
method: "POST", method: "POST",
......
...@@ -256,7 +256,7 @@ function formSubmit(props) { ...@@ -256,7 +256,7 @@ function formSubmit(props) {
}) })
} }
function APIUpdateAttr(props) { function requestApi(props) {
// props = {url: .., body: , success: , error: , method: ,} // props = {url: .., body: , success: , error: , method: ,}
props = props || {}; props = props || {};
var user_success_message = props.success_message; var user_success_message = props.success_message;
...@@ -328,7 +328,7 @@ function objectDelete(obj, name, url, redirectTo) { ...@@ -328,7 +328,7 @@ function objectDelete(obj, name, url, redirectTo) {
// swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error"); // swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
swal(gettext('Error'), "[ "+name+" ]" + gettext("Being used by the asset, please unbind the asset first."), "error"); swal(gettext('Error'), "[ "+name+" ]" + gettext("Being used by the asset, please unbind the asset first."), "error");
}; };
APIUpdateAttr({ requestApi({
url: url, url: url,
body: JSON.stringify(body), body: JSON.stringify(body),
method: 'DELETE', method: 'DELETE',
...@@ -369,7 +369,7 @@ function orgDelete(obj, name, url, redirectTo){ ...@@ -369,7 +369,7 @@ function orgDelete(obj, name, url, redirectTo){
swal(gettext("Error"), " [ "+ name + " ] " + gettext("Do not perform this operation under this organization. Try again after switching to another organization"), "error"); swal(gettext("Error"), " [ "+ name + " ] " + gettext("Do not perform this operation under this organization. Try again after switching to another organization"), "error");
} }
}; };
APIUpdateAttr({ requestApi({
url: url, url: url,
body: JSON.stringify(body), body: JSON.stringify(body),
method: 'DELETE', method: 'DELETE',
...@@ -1109,9 +1109,22 @@ function objectAttrsIsBool(obj, attrs) { ...@@ -1109,9 +1109,22 @@ function objectAttrsIsBool(obj, attrs) {
}) })
} }
function cleanDate(d) {
for (var i=0; i<2; i++) {
if (isNaN(Date.parse(d))) {
d = d.split('+')[0].trimRight();
} else {
return d
}
}
return ''
}
function formatDateAsCN(d) { function formatDateAsCN(d) {
d = cleanDate(d);
var date = new Date(d); var date = new Date(d);
return date.toISOString().replace("T", " ").replace(/\..*/, ""); var date_s = date.toLocaleString(navigator.language, {hour12: false});
return date_s.split("/").join('-')
} }
function getUrlParams(url) { function getUrlParams(url) {
...@@ -1137,6 +1150,8 @@ function getTimeUnits(u) { ...@@ -1137,6 +1150,8 @@ function getTimeUnits(u) {
} }
function timeOffset(a, b) { function timeOffset(a, b) {
a = cleanDate(a);
b = cleanDate(b);
var start = new Date(a); var start = new Date(a);
var end = new Date(b); var end = new Date(b);
var offset = (end - start)/1000; var offset = (end - start)/1000;
......
//! moment.js
//! version : 2.10.6
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! momentjs.com
!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Hc.apply(null,arguments)}function b(a){Hc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function f(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function g(a,b){for(var c in b)f(b,c)&&(a[c]=b[c]);return f(b,"toString")&&(a.toString=b.toString),f(b,"valueOf")&&(a.valueOf=b.valueOf),a}function h(a,b,c,d){return Ca(a,b,c,d,!0).utc()}function i(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function j(a){return null==a._pf&&(a._pf=i()),a._pf}function k(a){if(null==a._isValid){var b=j(a);a._isValid=!(isNaN(a._d.getTime())||!(b.overflow<0)||b.empty||b.invalidMonth||b.invalidWeekday||b.nullInput||b.invalidFormat||b.userInvalidated),a._strict&&(a._isValid=a._isValid&&0===b.charsLeftOver&&0===b.unusedTokens.length&&void 0===b.bigHour)}return a._isValid}function l(a){var b=h(NaN);return null!=a?g(j(b),a):j(b).userInvalidated=!0,b}function m(a,b){var c,d,e;if("undefined"!=typeof b._isAMomentObject&&(a._isAMomentObject=b._isAMomentObject),"undefined"!=typeof b._i&&(a._i=b._i),"undefined"!=typeof b._f&&(a._f=b._f),"undefined"!=typeof b._l&&(a._l=b._l),"undefined"!=typeof b._strict&&(a._strict=b._strict),"undefined"!=typeof b._tzm&&(a._tzm=b._tzm),"undefined"!=typeof b._isUTC&&(a._isUTC=b._isUTC),"undefined"!=typeof b._offset&&(a._offset=b._offset),"undefined"!=typeof b._pf&&(a._pf=j(b)),"undefined"!=typeof b._locale&&(a._locale=b._locale),Jc.length>0)for(c in Jc)d=Jc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function n(b){m(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),Kc===!1&&(Kc=!0,a.updateOffset(this),Kc=!1)}function o(a){return a instanceof n||null!=a&&null!=a._isAMomentObject}function p(a){return 0>a?Math.ceil(a):Math.floor(a)}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=p(b)),c}function r(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function s(){}function t(a){return a?a.toLowerCase().replace("_","-"):a}function u(a){for(var b,c,d,e,f=0;f<a.length;){for(e=t(a[f]).split("-"),b=e.length,c=t(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=v(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&r(e,c,!0)>=b-1)break;b--}f++}return null}function v(a){var b=null;if(!Lc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Ic._abbr,require("./locale/"+a),w(b)}catch(c){}return Lc[a]}function w(a,b){var c;return a&&(c="undefined"==typeof b?y(a):x(a,b),c&&(Ic=c)),Ic._abbr}function x(a,b){return null!==b?(b.abbr=a,Lc[a]=Lc[a]||new s,Lc[a].set(b),w(a),Lc[a]):(delete Lc[a],null)}function y(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Ic;if(!c(a)){if(b=v(a))return b;a=[a]}return u(a)}function z(a,b){var c=a.toLowerCase();Mc[c]=Mc[c+"s"]=Mc[b]=a}function A(a){return"string"==typeof a?Mc[a]||Mc[a.toLowerCase()]:void 0}function B(a){var b,c,d={};for(c in a)f(a,c)&&(b=A(c),b&&(d[b]=a[c]));return d}function C(b,c){return function(d){return null!=d?(E(this,b,d),a.updateOffset(this,c),this):D(this,b)}}function D(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function E(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function F(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=A(a),"function"==typeof this[a])return this[a](b);return this}function G(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function H(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Qc[a]=e),b&&(Qc[b[0]]=function(){return G(e.apply(this,arguments),b[1],b[2])}),c&&(Qc[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function I(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function J(a){var b,c,d=a.match(Nc);for(b=0,c=d.length;c>b;b++)Qc[d[b]]?d[b]=Qc[d[b]]:d[b]=I(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function K(a,b){return a.isValid()?(b=L(b,a.localeData()),Pc[b]=Pc[b]||J(b),Pc[b](a)):a.localeData().invalidDate()}function L(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Oc.lastIndex=0;d>=0&&Oc.test(a);)a=a.replace(Oc,c),Oc.lastIndex=0,d-=1;return a}function M(a){return"function"==typeof a&&"[object Function]"===Object.prototype.toString.call(a)}function N(a,b,c){dd[a]=M(b)?b:function(a){return a&&c?c:b}}function O(a,b){return f(dd,a)?dd[a](b._strict,b._locale):new RegExp(P(a))}function P(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=q(a)}),c=0;c<a.length;c++)ed[a[c]]=d}function R(a,b){Q(a,function(a,c,d,e){d._w=d._w||{},b(a,d._w,d,e)})}function S(a,b,c){null!=b&&f(ed,a)&&ed[a](b,c._a,c,a)}function T(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function U(a){return this._months[a.month()]}function V(a){return this._monthsShort[a.month()]}function W(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function X(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),T(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function Y(b){return null!=b?(X(this,b),a.updateOffset(this,!0),this):D(this,"Month")}function Z(){return T(this.year(),this.month())}function $(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[gd]<0||c[gd]>11?gd:c[hd]<1||c[hd]>T(c[fd],c[gd])?hd:c[id]<0||c[id]>24||24===c[id]&&(0!==c[jd]||0!==c[kd]||0!==c[ld])?id:c[jd]<0||c[jd]>59?jd:c[kd]<0||c[kd]>59?kd:c[ld]<0||c[ld]>999?ld:-1,j(a)._overflowDayOfYear&&(fd>b||b>hd)&&(b=hd),j(a).overflow=b),a}function _(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function aa(a,b){var c=!0;return g(function(){return c&&(_(a+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function ba(a,b){od[a]||(_(b),od[a]=!0)}function ca(a){var b,c,d=a._i,e=pd.exec(d);if(e){for(j(a).iso=!0,b=0,c=qd.length;c>b;b++)if(qd[b][1].exec(d)){a._f=qd[b][0];break}for(b=0,c=rd.length;c>b;b++)if(rd[b][1].exec(d)){a._f+=(e[6]||" ")+rd[b][0];break}d.match(ad)&&(a._f+="Z"),va(a)}else a._isValid=!1}function da(b){var c=sd.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(ca(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ea(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function fa(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function ga(a){return ha(a)?366:365}function ha(a){return a%4===0&&a%100!==0||a%400===0}function ia(){return ha(this.year())}function ja(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=Da(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ka(a){return ja(a,this._week.dow,this._week.doy).week}function la(){return this._week.dow}function ma(){return this._week.doy}function na(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function oa(a){var b=ja(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function pa(a,b,c,d,e){var f,g=6+e-d,h=fa(a,0,1+g),i=h.getUTCDay();return e>i&&(i+=7),c=null!=c?1*c:e,f=1+g+7*(b-1)-i+c,{year:f>0?a:a-1,dayOfYear:f>0?f:ga(a-1)+f}}function qa(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function ra(a,b,c){return null!=a?a:null!=b?b:c}function sa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function ta(a){var b,c,d,e,f=[];if(!a._d){for(d=sa(a),a._w&&null==a._a[hd]&&null==a._a[gd]&&ua(a),a._dayOfYear&&(e=ra(a._a[fd],d[fd]),a._dayOfYear>ga(e)&&(j(a)._overflowDayOfYear=!0),c=fa(e,0,a._dayOfYear),a._a[gd]=c.getUTCMonth(),a._a[hd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[id]&&0===a._a[jd]&&0===a._a[kd]&&0===a._a[ld]&&(a._nextDay=!0,a._a[id]=0),a._d=(a._useUTC?fa:ea).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[id]=24)}}function ua(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ra(b.GG,a._a[fd],ja(Da(),1,4).year),d=ra(b.W,1),e=ra(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ra(b.gg,a._a[fd],ja(Da(),f,g).year),d=ra(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=pa(c,d,e,g,f),a._a[fd]=h.year,a._dayOfYear=h.dayOfYear}function va(b){if(b._f===a.ISO_8601)return void ca(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=L(b._f,b._locale).match(Nc)||[],c=0;c<e.length;c++)f=e[c],d=(h.match(O(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Qc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),S(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[id]<=12&&b._a[id]>0&&(j(b).bigHour=void 0),b._a[id]=wa(b._locale,b._a[id],b._meridiem),ta(b),$(b)}function wa(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function xa(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e<a._f.length;e++)f=0,b=m({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._f=a._f[e],va(b),k(b)&&(f+=j(b).charsLeftOver,f+=10*j(b).unusedTokens.length,j(b).score=f,(null==d||d>f)&&(d=f,c=b));g(a,c||b)}function ya(a){if(!a._d){var b=B(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],ta(a)}}function za(a){var b=new n($(Aa(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Aa(a){var b=a._i,e=a._f;return a._locale=a._locale||y(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),o(b)?new n($(b)):(c(e)?xa(a):e?va(a):d(b)?a._d=b:Ba(a),a))}function Ba(b){var f=b._i;void 0===f?b._d=new Date:d(f)?b._d=new Date(+f):"string"==typeof f?da(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ta(b)):"object"==typeof f?ya(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ca(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,za(f)}function Da(a,b,c,d){return Ca(a,b,c,d,!1)}function Ea(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Da();for(d=b[0],e=1;e<b.length;++e)(!b[e].isValid()||b[e][a](d))&&(d=b[e]);return d}function Fa(){var a=[].slice.call(arguments,0);return Ea("isBefore",a)}function Ga(){var a=[].slice.call(arguments,0);return Ea("isAfter",a)}function Ha(a){var b=B(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=y(),this._bubble()}function Ia(a){return a instanceof Ha}function Ja(a,b){H(a,0,0,function(){var a=this.utcOffset(),c="+";return 0>a&&(a=-a,c="-"),c+G(~~(a/60),2)+b+G(~~a%60,2)})}function Ka(a){var b=(a||"").match(ad)||[],c=b[b.length-1]||[],d=(c+"").match(xd)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?e:-e}function La(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(o(b)||d(b)?+b:+Da(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Da(b).local()}function Ma(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Na(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ka(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ma(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?bb(this,Ya(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ma(this)}function Oa(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Pa(a){return this.utcOffset(0,a)}function Qa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ma(this),"m")),this}function Ra(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ka(this._i)),this}function Sa(a){return a=a?Da(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Ta(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ua(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var a={};if(m(a,this),a=Aa(a),a._a){var b=a._isUTC?h(a._a):Da(a._a);this._isDSTShifted=this.isValid()&&r(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Va(){return!this._isUTC}function Wa(){return this._isUTC}function Xa(){return this._isUTC&&0===this._offset}function Ya(a,b){var c,d,e,g=a,h=null;return Ia(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=yd.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[hd])*c,h:q(h[id])*c,m:q(h[jd])*c,s:q(h[kd])*c,ms:q(h[ld])*c}):(h=zd.exec(a))?(c="-"===h[1]?-1:1,g={y:Za(h[2],c),M:Za(h[3],c),d:Za(h[4],c),h:Za(h[5],c),m:Za(h[6],c),s:Za(h[7],c),w:Za(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=_a(Da(g.from),Da(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ha(g),Ia(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Za(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function $a(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function _a(a,b){var c;return b=La(b,a),a.isBefore(b)?c=$a(a,b):(c=$a(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function ab(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(ba(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ya(c,d),bb(this,e,a),this}}function bb(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&E(b,"Date",D(b,"Date")+g*d),h&&X(b,D(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function cb(a,b){var c=a||Da(),d=La(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(b&&b[f]||this.localeData().calendar(f,this,Da(c)))}function db(){return new n(this)}function eb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+this>+a):(c=o(a)?+a:+Da(a),c<+this.clone().startOf(b))}function fb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+a>+this):(c=o(a)?+a:+Da(a),+this.clone().endOf(b)<c)}function gb(a,b,c){return this.isAfter(a,c)&&this.isBefore(b,c)}function hb(a,b){var c;return b=A(b||"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+this===+a):(c=+Da(a),+this.clone().startOf(b)<=c&&c<=+this.clone().endOf(b))}function ib(a,b,c){var d,e,f=La(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=A(b),"year"===b||"month"===b||"quarter"===b?(e=jb(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:p(e)}function jb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function kb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function lb(){var a=this.clone().utc();return 0<a.year()&&a.year()<=9999?"function"==typeof Date.prototype.toISOString?this.toDate().toISOString():K(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):K(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function mb(b){var c=K(this,b||a.defaultFormat);return this.localeData().postformat(c)}function nb(a,b){return this.isValid()?Ya({to:this,from:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function ob(a){return this.from(Da(),a)}function pb(a,b){return this.isValid()?Ya({from:this,to:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function qb(a){return this.to(Da(),a)}function rb(a){var b;return void 0===a?this._locale._abbr:(b=y(a),null!=b&&(this._locale=b),this)}function sb(){return this._locale}function tb(a){switch(a=A(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function ub(a){return a=A(a),void 0===a||"millisecond"===a?this:this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms")}function vb(){return+this._d-6e4*(this._offset||0)}function wb(){return Math.floor(+this/1e3)}function xb(){return this._offset?new Date(+this):this._d}function yb(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function zb(){var a=this;return{years:a.year(),months:a.month(),date:a.date(),hours:a.hours(),minutes:a.minutes(),seconds:a.seconds(),milliseconds:a.milliseconds()}}function Ab(){return k(this)}function Bb(){return g({},j(this))}function Cb(){return j(this).overflow}function Db(a,b){H(0,[a,a.length],0,b)}function Eb(a,b,c){return ja(Da([a,11,31+b-c]),b,c).week}function Fb(a){var b=ja(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return null==a?b:this.add(a-b,"y")}function Gb(a){var b=ja(this,1,4).year;return null==a?b:this.add(a-b,"y")}function Hb(){return Eb(this.year(),1,4)}function Ib(){var a=this.localeData()._week;return Eb(this.year(),a.dow,a.doy)}function Jb(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Kb(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Lb(a){return this._weekdays[a.day()]}function Mb(a){return this._weekdaysShort[a.day()]}function Nb(a){return this._weekdaysMin[a.day()]}function Ob(a){var b,c,d;for(this._weekdaysParse=this._weekdaysParse||[],b=0;7>b;b++)if(this._weekdaysParse[b]||(c=Da([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Pb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Kb(a,this.localeData()),this.add(a-b,"d")):b}function Qb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Rb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Sb(a,b){H(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Tb(a,b){return b._meridiemParse}function Ub(a){return"p"===(a+"").toLowerCase().charAt(0)}function Vb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Wb(a,b){b[ld]=q(1e3*("0."+a))}function Xb(){return this._isUTC?"UTC":""}function Yb(){return this._isUTC?"Coordinated Universal Time":""}function Zb(a){return Da(1e3*a)}function $b(){return Da.apply(null,arguments).parseZone()}function _b(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function bc(){return this._invalidDate}function cc(a){return this._ordinal.replace("%d",a)}function dc(a){return a}function ec(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function gc(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function hc(a,b,c,d){var e=y(),f=h().set(d,b);return e[c](f,a)}function ic(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return hc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=hc(a,f,c,e);return g}function jc(a,b){return ic(a,b,"months",12,"month")}function kc(a,b){return ic(a,b,"monthsShort",12,"month")}function lc(a,b){return ic(a,b,"weekdays",7,"day")}function mc(a,b){return ic(a,b,"weekdaysShort",7,"day")}function nc(a,b){return ic(a,b,"weekdaysMin",7,"day")}function oc(){var a=this._data;return this._milliseconds=Wd(this._milliseconds),this._days=Wd(this._days),this._months=Wd(this._months),a.milliseconds=Wd(a.milliseconds),a.seconds=Wd(a.seconds),a.minutes=Wd(a.minutes),a.hours=Wd(a.hours),a.months=Wd(a.months),a.years=Wd(a.years),this}function pc(a,b,c,d){var e=Ya(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function qc(a,b){return pc(this,a,b,1)}function rc(a,b){return pc(this,a,b,-1)}function sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*sc(vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=p(f/1e3),i.seconds=a%60,b=p(a/60),i.minutes=b%60,c=p(b/60),i.hours=c%24,g+=p(c/24),e=p(uc(g)),h+=e,g-=sc(vc(e)),d=p(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function uc(a){return 4800*a/146097}function vc(a){return 146097*a/4800}function wc(a){var b,c,d=this._milliseconds;if(a=A(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*q(this._months/12)}function yc(a){return function(){return this.as(a)}}function zc(a){return a=A(a),this[a+"s"]()}function Ac(a){return function(){return this._data[a]}}function Bc(){return p(this.days()/7)}function Cc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Dc(a,b,c){var d=Ya(a).abs(),e=ke(d.as("s")),f=ke(d.as("m")),g=ke(d.as("h")),h=ke(d.as("d")),i=ke(d.as("M")),j=ke(d.as("y")),k=e<le.s&&["s",e]||1===f&&["m"]||f<le.m&&["mm",f]||1===g&&["h"]||g<le.h&&["hh",g]||1===h&&["d"]||h<le.d&&["dd",h]||1===i&&["M"]||i<le.M&&["MM",i]||1===j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,Cc.apply(null,k)}function Ec(a,b){return void 0===le[a]?!1:void 0===b?le[a]:(le[a]=b,!0)}function Fc(a){var b=this.localeData(),c=Dc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Gc(){var a,b,c,d=me(this._milliseconds)/1e3,e=me(this._days),f=me(this._months);a=p(d/60),b=p(a/60),d%=60,a%=60,c=p(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Hc,Ic,Jc=a.momentProperties=[],Kc=!1,Lc={},Mc={},Nc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Oc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Pc={},Qc={},Rc=/\d/,Sc=/\d\d/,Tc=/\d{3}/,Uc=/\d{4}/,Vc=/[+-]?\d{6}/,Wc=/\d\d?/,Xc=/\d{1,3}/,Yc=/\d{1,4}/,Zc=/[+-]?\d{1,6}/,$c=/\d+/,_c=/[+-]?\d+/,ad=/Z|[+-]\d\d:?\d\d/gi,bd=/[+-]?\d+(\.\d{1,3})?/,cd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,dd={},ed={},fd=0,gd=1,hd=2,id=3,jd=4,kd=5,ld=6;H("M",["MM",2],"Mo",function(){return this.month()+1}),H("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),H("MMMM",0,0,function(a){return this.localeData().months(this,a)}),z("month","M"),N("M",Wc),N("MM",Wc,Sc),N("MMM",cd),N("MMMM",cd),Q(["M","MM"],function(a,b){b[gd]=q(a)-1}),Q(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[gd]=e:j(c).invalidMonth=a});var md="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),nd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),od={};a.suppressDeprecationWarnings=!1;var pd=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,qd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],rd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],sd=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=aa("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),H(0,["YY",2],0,function(){return this.year()%100}),H(0,["YYYY",4],0,"year"),H(0,["YYYYY",5],0,"year"),H(0,["YYYYYY",6,!0],0,"year"),z("year","y"),N("Y",_c),N("YY",Wc,Sc),N("YYYY",Yc,Uc),N("YYYYY",Zc,Vc),N("YYYYYY",Zc,Vc),Q(["YYYYY","YYYYYY"],fd),Q("YYYY",function(b,c){c[fd]=2===b.length?a.parseTwoDigitYear(b):q(b)}),Q("YY",function(b,c){c[fd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return q(a)+(q(a)>68?1900:2e3)};var td=C("FullYear",!1);H("w",["ww",2],"wo","week"),H("W",["WW",2],"Wo","isoWeek"),z("week","w"),z("isoWeek","W"),N("w",Wc),N("ww",Wc,Sc),N("W",Wc),N("WW",Wc,Sc),R(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=q(a)});var ud={dow:0,doy:6};H("DDD",["DDDD",3],"DDDo","dayOfYear"),z("dayOfYear","DDD"),N("DDD",Xc),N("DDDD",Tc),Q(["DDD","DDDD"],function(a,b,c){c._dayOfYear=q(a)}),a.ISO_8601=function(){};var vd=aa("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return this>a?this:a}),wd=aa("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return a>this?this:a});Ja("Z",":"),Ja("ZZ",""),N("Z",ad),N("ZZ",ad),Q(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ka(a)});var xd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var yd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,zd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ya.fn=Ha.prototype;var Ad=ab(1,"add"),Bd=ab(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Cd=aa("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});H(0,["gg",2],0,function(){return this.weekYear()%100}),H(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Db("gggg","weekYear"),Db("ggggg","weekYear"),Db("GGGG","isoWeekYear"),Db("GGGGG","isoWeekYear"),z("weekYear","gg"),z("isoWeekYear","GG"),N("G",_c),N("g",_c),N("GG",Wc,Sc),N("gg",Wc,Sc),N("GGGG",Yc,Uc),N("gggg",Yc,Uc),N("GGGGG",Zc,Vc),N("ggggg",Zc,Vc),R(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=q(a)}),R(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),H("Q",0,0,"quarter"),z("quarter","Q"),N("Q",Rc),Q("Q",function(a,b){b[gd]=3*(q(a)-1)}),H("D",["DD",2],"Do","date"),z("date","D"),N("D",Wc),N("DD",Wc,Sc),N("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),Q(["D","DD"],hd),Q("Do",function(a,b){b[hd]=q(a.match(Wc)[0],10)});var Dd=C("Date",!0);H("d",0,"do","day"),H("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),H("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),H("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),H("e",0,0,"weekday"),H("E",0,0,"isoWeekday"),z("day","d"),z("weekday","e"),z("isoWeekday","E"),N("d",Wc),N("e",Wc),N("E",Wc),N("dd",cd),N("ddd",cd),N("dddd",cd),R(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:j(c).invalidWeekday=a}),R(["d","e","E"],function(a,b,c,d){b[d]=q(a)});var Ed="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Fd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Gd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");H("H",["HH",2],0,"hour"),H("h",["hh",2],0,function(){return this.hours()%12||12}),Sb("a",!0),Sb("A",!1),z("hour","h"),N("a",Tb),N("A",Tb),N("H",Wc),N("h",Wc),N("HH",Wc,Sc),N("hh",Wc,Sc),Q(["H","HH"],id),Q(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),Q(["h","hh"],function(a,b,c){b[id]=q(a),j(c).bigHour=!0});var Hd=/[ap]\.?m?\.?/i,Id=C("Hours",!0);H("m",["mm",2],0,"minute"),z("minute","m"),N("m",Wc),N("mm",Wc,Sc),Q(["m","mm"],jd);var Jd=C("Minutes",!1);H("s",["ss",2],0,"second"),z("second","s"),N("s",Wc),N("ss",Wc,Sc),Q(["s","ss"],kd);var Kd=C("Seconds",!1);H("S",0,0,function(){return~~(this.millisecond()/100)}),H(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,function(){return 10*this.millisecond()}),H(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),H(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),H(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),H(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),H(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),z("millisecond","ms"),N("S",Xc,Rc),N("SS",Xc,Sc),N("SSS",Xc,Tc);var Ld;for(Ld="SSSS";Ld.length<=9;Ld+="S")N(Ld,$c);for(Ld="S";Ld.length<=9;Ld+="S")Q(Ld,Wb);var Md=C("Milliseconds",!1);H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var Nd=n.prototype;Nd.add=Ad,Nd.calendar=cb,Nd.clone=db,Nd.diff=ib,Nd.endOf=ub,Nd.format=mb,Nd.from=nb,Nd.fromNow=ob,Nd.to=pb,Nd.toNow=qb,Nd.get=F,Nd.invalidAt=Cb,Nd.isAfter=eb,Nd.isBefore=fb,Nd.isBetween=gb,Nd.isSame=hb,Nd.isValid=Ab,Nd.lang=Cd,Nd.locale=rb,Nd.localeData=sb,Nd.max=wd,Nd.min=vd,Nd.parsingFlags=Bb,Nd.set=F,Nd.startOf=tb,Nd.subtract=Bd,Nd.toArray=yb,Nd.toObject=zb,Nd.toDate=xb,Nd.toISOString=lb,Nd.toJSON=lb,Nd.toString=kb,Nd.unix=wb,Nd.valueOf=vb,Nd.year=td,Nd.isLeapYear=ia,Nd.weekYear=Fb,Nd.isoWeekYear=Gb,Nd.quarter=Nd.quarters=Jb,Nd.month=Y,Nd.daysInMonth=Z,Nd.week=Nd.weeks=na,Nd.isoWeek=Nd.isoWeeks=oa,Nd.weeksInYear=Ib,Nd.isoWeeksInYear=Hb,Nd.date=Dd,Nd.day=Nd.days=Pb,Nd.weekday=Qb,Nd.isoWeekday=Rb,Nd.dayOfYear=qa,Nd.hour=Nd.hours=Id,Nd.minute=Nd.minutes=Jd,Nd.second=Nd.seconds=Kd,
Nd.millisecond=Nd.milliseconds=Md,Nd.utcOffset=Na,Nd.utc=Pa,Nd.local=Qa,Nd.parseZone=Ra,Nd.hasAlignedHourOffset=Sa,Nd.isDST=Ta,Nd.isDSTShifted=Ua,Nd.isLocal=Va,Nd.isUtcOffset=Wa,Nd.isUtc=Xa,Nd.isUTC=Xa,Nd.zoneAbbr=Xb,Nd.zoneName=Yb,Nd.dates=aa("dates accessor is deprecated. Use date instead.",Dd),Nd.months=aa("months accessor is deprecated. Use month instead",Y),Nd.years=aa("years accessor is deprecated. Use year instead",td),Nd.zone=aa("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Oa);var Od=Nd,Pd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Qd={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Rd="Invalid date",Sd="%d",Td=/\d{1,2}/,Ud={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Vd=s.prototype;Vd._calendar=Pd,Vd.calendar=_b,Vd._longDateFormat=Qd,Vd.longDateFormat=ac,Vd._invalidDate=Rd,Vd.invalidDate=bc,Vd._ordinal=Sd,Vd.ordinal=cc,Vd._ordinalParse=Td,Vd.preparse=dc,Vd.postformat=dc,Vd._relativeTime=Ud,Vd.relativeTime=ec,Vd.pastFuture=fc,Vd.set=gc,Vd.months=U,Vd._months=md,Vd.monthsShort=V,Vd._monthsShort=nd,Vd.monthsParse=W,Vd.week=ka,Vd._week=ud,Vd.firstDayOfYear=ma,Vd.firstDayOfWeek=la,Vd.weekdays=Lb,Vd._weekdays=Ed,Vd.weekdaysMin=Nb,Vd._weekdaysMin=Gd,Vd.weekdaysShort=Mb,Vd._weekdaysShort=Fd,Vd.weekdaysParse=Ob,Vd.isPM=Ub,Vd._meridiemParse=Hd,Vd.meridiem=Vb,w("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===q(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=aa("moment.lang is deprecated. Use moment.locale instead.",w),a.langData=aa("moment.langData is deprecated. Use moment.localeData instead.",y);var Wd=Math.abs,Xd=yc("ms"),Yd=yc("s"),Zd=yc("m"),$d=yc("h"),_d=yc("d"),ae=yc("w"),be=yc("M"),ce=yc("y"),de=Ac("milliseconds"),ee=Ac("seconds"),fe=Ac("minutes"),ge=Ac("hours"),he=Ac("days"),ie=Ac("months"),je=Ac("years"),ke=Math.round,le={s:45,m:45,h:22,d:26,M:11},me=Math.abs,ne=Ha.prototype;ne.abs=oc,ne.add=qc,ne.subtract=rc,ne.as=wc,ne.asMilliseconds=Xd,ne.asSeconds=Yd,ne.asMinutes=Zd,ne.asHours=$d,ne.asDays=_d,ne.asWeeks=ae,ne.asMonths=be,ne.asYears=ce,ne.valueOf=xc,ne._bubble=tc,ne.get=zc,ne.milliseconds=de,ne.seconds=ee,ne.minutes=fe,ne.hours=ge,ne.days=he,ne.weeks=Bc,ne.months=ie,ne.years=je,ne.humanize=Fc,ne.toISOString=Gc,ne.toString=Gc,ne.toJSON=Gc,ne.locale=rb,ne.localeData=sb,ne.toIsoString=aa("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Gc),ne.lang=Cd,H("X",0,0,"unix"),H("x",0,0,"valueOf"),N("x",_c),N("X",bd),Q("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),Q("x",function(a,b,c){c._d=new Date(q(a))}),a.version="2.10.6",b(Da),a.fn=Od,a.min=Fa,a.max=Ga,a.utc=h,a.unix=Zb,a.months=jc,a.isDate=d,a.locale=w,a.invalid=l,a.duration=Ya,a.isMoment=o,a.weekdays=lc,a.parseZone=$b,a.localeData=y,a.isDuration=Ia,a.monthsShort=kc,a.weekdaysMin=nc,a.defineLocale=x,a.weekdaysShort=mc,a.normalizeUnits=A,a.relativeTimeThreshold=Ec;var oe=a;return oe});
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.conf import settings
from rest_framework import serializers from rest_framework import serializers
from common.utils import get_request_ip from common.utils import get_request_ip
...@@ -27,6 +28,9 @@ class TerminalSerializer(serializers.ModelSerializer): ...@@ -27,6 +28,9 @@ class TerminalSerializer(serializers.ModelSerializer):
valid = super().is_valid(raise_exception=raise_exception) valid = super().is_valid(raise_exception=raise_exception)
if not valid: if not valid:
return valid return valid
if not settings.SECURITY_SERVICE_ACCOUNT_REGISTRATION:
error = {"error": "service account registration disabled"}
raise serializers.ValidationError(error)
data = {'name': self.validated_data.get('name')} data = {'name': self.validated_data.get('name')}
kwargs = {'data': data} kwargs = {'data': data}
if self.instance and self.instance.user: if self.instance and self.instance.user:
......
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
}, 300) }, 300)
} }
var the_url = "{% url 'api-terminal:tasks-list' %}"; var the_url = "{% url 'api-terminal:tasks-list' %}";
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'}); requestApi({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
} }
$(document).ready(function () { $(document).ready(function () {
$('.footable').footable(); $('.footable').footable();
......
...@@ -90,7 +90,7 @@ function terminateSession(data) { ...@@ -90,7 +90,7 @@ function terminateSession(data) {
} }
var success_message = '{% trans "Terminate task send, waiting ..." %}'; var success_message = '{% trans "Terminate task send, waiting ..." %}';
var the_url = "{% url 'api-terminal:kill-session' %}"; var the_url = "{% url 'api-terminal:kill-session' %}";
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'POST', method: 'POST',
body: JSON.stringify(data), body: JSON.stringify(data),
...@@ -174,7 +174,7 @@ function finishedSession(data) { ...@@ -174,7 +174,7 @@ function finishedSession(data) {
var success = function() { var success = function() {
location.reload(); location.reload();
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(data), body: JSON.stringify(data),
......
...@@ -14,7 +14,7 @@ from rest_framework.pagination import LimitOffsetPagination ...@@ -14,7 +14,7 @@ from rest_framework.pagination import LimitOffsetPagination
from common.permissions import ( from common.permissions import (
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser, IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser,
CanUpdateSuperUser, CanUpdateDeleteSuperUser,
) )
from common.mixins import IDInCacheFilterMixin from common.mixins import IDInCacheFilterMixin
from common.utils import get_logger from common.utils import get_logger
...@@ -38,7 +38,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): ...@@ -38,7 +38,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
search_fields = filter_fields search_fields = filter_fields
queryset = User.objects.exclude(role=User.ROLE_APP) queryset = User.objects.exclude(role=User.ROLE_APP)
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = (IsOrgAdmin, CanUpdateSuperUser) permission_classes = (IsOrgAdmin, CanUpdateDeleteSuperUser)
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
def send_created_signal(self, users): def send_created_signal(self, users):
......
...@@ -27,97 +27,7 @@ signer = get_signer() ...@@ -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,11 @@ class User(AbstractUser): ...@@ -134,9 +44,11 @@ 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)
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 +58,6 @@ class User(AbstractUser): ...@@ -146,9 +58,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.
...@@ -159,42 +68,65 @@ class User(AbstractUser): ...@@ -159,42 +68,65 @@ class User(AbstractUser):
return False return False
@property @property
def groups_display(self): def public_key_obj(self):
return ' '.join([group.name for group in self.groups.all()]) class PubKey(object):
def __getattr__(self, item):
return ''
if self.public_key:
import sshpubkeys
try:
return sshpubkeys.SSHKey(self.public_key)
except (TabError, TypeError):
pass
return PubKey()
def reset_password(self, new_password):
self.set_password(new_password)
self.save()
@property @property
def role_display(self): def date_password_expired(self):
return self.get_role_display() interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME
date_expired = self.date_password_last_updated + timezone.timedelta(
days=int(interval))
return date_expired
@property @property
def source_display(self): def password_expired_remain_days(self):
return self.get_source_display() date_remain = self.date_password_expired - timezone.now()
return date_remain.days
@property @property
def is_expired(self): def password_has_expired(self):
if self.date_expired and self.date_expired < timezone.now(): if self.is_local and self.password_expired_remain_days < 0:
return True return True
else: return False
return False
@property @property
def is_valid(self): def password_will_expired(self):
if self.is_active and not self.is_expired: if self.is_local and self.password_expired_remain_days < 5:
return True return True
return False 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 @property
def public_key_obj(self): def role_display(self):
class PubKey(object): return self.get_role_display()
def __getattr__(self, item):
return ''
if self.public_key:
import sshpubkeys
try:
return sshpubkeys.SSHKey(self.public_key)
except (TabError, TypeError):
pass
return PubKey()
@property @property
def is_superuser(self): def is_superuser(self):
...@@ -251,41 +183,21 @@ class User(AbstractUser): ...@@ -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),
@property is_active=False, role='App', comment=comment,
def date_password_expired(self): is_first_login=False, created_by='System'
interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME )
date_expired = self.date_password_last_updated + timezone.timedelta( access_key = app.create_access_key()
days=int(interval)) return app, access_key
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
def save(self, *args, **kwargs): class TokenMixin:
if not self.name: CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
self.name = self.username email = ''
if self.username == 'admin': id = None
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): ...@@ -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): ...@@ -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): ...@@ -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'),
access_key = app.create_access_key() (SOURCE_LDAP, 'LDAP/AD'),
return app, access_key (SOURCE_OPENID, 'OpenID'),
(SOURCE_RADIUS, 'Radius'),
)
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':
......
...@@ -36,7 +36,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -36,7 +36,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
'date_password_last_updated', 'date_expired', 'avatar_url', 'date_password_last_updated', 'date_expired', 'avatar_url',
] ]
extra_kwargs = { extra_kwargs = {
'password': {'write_only': True, 'required': False}, 'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True},
'public_key': {'write_only': True}, 'public_key': {'write_only': True},
'groups_display': {'label': _('Groups name')}, 'groups_display': {'label': _('Groups name')},
'source_display': {'label': _('Source name')}, 'source_display': {'label': _('Source name')},
...@@ -56,13 +56,17 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -56,13 +56,17 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
return value return value
@staticmethod def validate_password(self, password):
def validate_password(value):
from ..utils import check_password_rules from ..utils import check_password_rules
if not check_password_rules(value): password_strategy = self.initial_data.get('password_strategy')
if password_strategy == '0':
return
if password_strategy is None and not password:
return
if not check_password_rules(password):
msg = _('Password does not match security rules') msg = _('Password does not match security rules')
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
return value return password
@staticmethod @staticmethod
def change_password_to_raw(validated_data): def change_password_to_raw(validated_data):
......
{% load i18n %}
<div class="col-lg-3" style="padding-left: 0px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight">
<div class="mail-box-header">
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm labels dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels-menu">
</ul>
</div>
<table class="table table-striped table-bordered table-hover" id="user_assets_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
{% if show_actions %}
<th class="text-center">{% trans 'Action' %}</th>
{% endif %}
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<script>
var zTree;
var inited = false;
var url;
var assetTable;
var treeUrl = "NeedInput";
var assetTableUrl = 'NeedInput';
var selectUrl = 'NeedInput';
var showAssetHref = true; // Need input default true
var actions = {};
var labels = '';
var requesting = false;
function initTable() {
if (inited){
return assetTable
} else {
inited = true;
}
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var assetDetailUrl = '{% url 'assets:asset-detail' pk=DEFAULT_PK %}'
.replace("{{ DEFAULT_PK }}", rowData.id);
var detailBtn = '<a href="assetDetailUrl" class="asset-detail" data-asset="assetId">' + cellData + '</a>';
if (showAssetHref) {
cellData = detailBtn.replace("assetDetailUrl", assetDetailUrl);
} else {
detailBtn = detailBtn.replace("assetId", rowData.id);
cellData = detailBtn.replace("assetDetailUrl", "");
}
$(td).html(cellData);
}},
{targets: 3, createdCell: function (td, cellData) {
var users = [];
$.each(cellData, function (id, data) {
var name = htmlEscape(data.name);
users.push(name);
});
$(td).html(users.join(', '))
}},
],
ajax_url: assetTableUrl,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "system_users_granted", orderable: false},
{% if show_actions %}
{data: "id", orderable: false}
{% endif %}
]
};
{% if show_actions %}
options.columnDefs.push(actions);
{% endif %}
assetTable = jumpserver.initServerSideDataTable(options);
return assetTable
}
function onSelected(event, treeNode) {
var node_id = treeNode.meta.node.id;
url = selectUrl.replace("{{ DEFAULT_PK }}", node_id);
assetTable.ajax.url(url);
assetTable.ajax.reload();
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
$.get(treeUrl, function(data, status) {
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rootNodeAddDom(zTree, function () {
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
initTree();
});
});
}
function loadLabels() {
var labelListUrl = '{% url "api-assets:label-list" %}';
var label = '<li><a style="font-weight: bolder">labelName:labelValue</a></li>';
if (requesting) {
return
}
if (!labels) {
var data = {
url: labelListUrl,
method: "GET",
success: function (data) {
data.forEach(function (value) {
labels += label.replace("labelName", value.name).replace("labelValue", value.value)
});
$(".labels-menu").append(labels);
requesting = false;
},
error: function() {
requesting = false;
},
flash_message: false
};
requesting = true;
requestApi(data)
}
}
$(document).ready(function () {
loadLabels()
}).on('click', '.labels-menu li', function () {
var val = $(this).text();
$("#user_assets_table_filter input").val(val);
assetTable.search(val).draw();
})
</script>
...@@ -4,9 +4,7 @@ ...@@ -4,9 +4,7 @@
{% block user_template_title %}{% trans "Create user" %}{% endblock %} {% block user_template_title %}{% trans "Create user" %}{% endblock %}
{% block password %} {% block password %}
{% bootstrap_field form.password_strategy layout="horizontal" %} {% bootstrap_field form.password_strategy layout="horizontal" %}
<div class="form-group" id="custom_password"> {% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
</div>
{# 密码popover #} {# 密码popover #}
<div id="container"> <div id="container">
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;"> <div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
...@@ -29,7 +27,7 @@ function passwordCheck() { ...@@ -29,7 +27,7 @@ function passwordCheck() {
progress = $('#id_progress'), progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }}, password_check_rules = {{ password_check_rules|safe }},
minLength = 6, minLength = 6,
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34, top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) -77 + 34,
left = 377, left = 377,
i18n_fallback = { i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}", "veryWeak": "{% trans 'Very weak' %}",
...@@ -67,9 +65,9 @@ var password_strategy_radio_input = 'input[type=radio][name=password_strategy]'; ...@@ -67,9 +65,9 @@ var password_strategy_radio_input = 'input[type=radio][name=password_strategy]';
function passwordStrategyFieldsDisplay(){ function passwordStrategyFieldsDisplay(){
var val = $('input:radio[name="password_strategy"]:checked').val(); var val = $('input:radio[name="password_strategy"]:checked').val();
if(val === '0'){ if(val === '0'){
$('#custom_password').addClass('hidden') $('#id_password').parents('.form-group').addClass('hidden')
}else { }else {
$('#custom_password').removeClass('hidden') $('#id_password').parents('.form-group').removeClass('hidden')
} }
} }
$(document).ready(function () { $(document).ready(function () {
...@@ -78,7 +76,25 @@ $(document).ready(function () { ...@@ -78,7 +76,25 @@ $(document).ready(function () {
}).on('change', password_strategy_radio_input, function(){ }).on('change', password_strategy_radio_input, function(){
passwordStrategyFieldsDisplay() passwordStrategyFieldsDisplay()
}) })
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-users:user-list' %}';
var redirect_to = '{% url "users:user-list" %}';
var method = "POST";
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['groups']);
objectAttrsIsDatetime(data,['date_expired']);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -280,7 +280,7 @@ function updateUserGroups(groups) { ...@@ -280,7 +280,7 @@ function updateUserGroups(groups) {
// clear jumpserver.groups_selected // clear jumpserver.groups_selected
jumpserver.nodes_selected = {}; jumpserver.nodes_selected = {};
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -305,7 +305,7 @@ $(document).ready(function() { ...@@ -305,7 +305,7 @@ $(document).ready(function() {
'is_active': checked 'is_active': checked
}; };
var success = '{% trans "Update successfully!" %}'; var success = '{% trans "Update successfully!" %}';
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success_message: success success_message: success
...@@ -332,7 +332,7 @@ $(document).ready(function() { ...@@ -332,7 +332,7 @@ $(document).ready(function() {
'otp_secret_key': otp_secret_key 'otp_secret_key': otp_secret_key
}; };
var success = '{% trans "Update successfully!" %}'; var success = '{% trans "Update successfully!" %}';
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success_message: success success_message: success
...@@ -372,7 +372,7 @@ $(document).ready(function() { ...@@ -372,7 +372,7 @@ $(document).ready(function() {
var msg = "{% trans "An e-mail has been sent to the user`s mailbox." %}"; var msg = "{% trans "An e-mail has been sent to the user`s mailbox." %}";
swal("{% trans 'Reset password' %}", msg, "success"); swal("{% trans 'Reset password' %}", msg, "success");
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -398,7 +398,7 @@ $(document).ready(function() { ...@@ -398,7 +398,7 @@ $(document).ready(function() {
var msg = "{% trans 'The reset-ssh-public-key E-mail has been sent successfully. Please inform the user to update his new ssh public key.' %}"; var msg = "{% trans 'The reset-ssh-public-key E-mail has been sent successfully. Please inform the user to update his new ssh public key.' %}";
swal("{% trans 'Reset SSH public key' %}", msg, "success"); swal("{% trans 'Reset SSH public key' %}", msg, "success");
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: body, body: body,
success: success success: success
...@@ -441,7 +441,7 @@ $(document).ready(function() { ...@@ -441,7 +441,7 @@ $(document).ready(function() {
} }
); );
}; };
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail}); requestApi({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
}).on('click', '.btn-delete-user', function () { }).on('click', '.btn-delete-user', function () {
var $this = $(this); var $this = $(this);
var name = "{{ user_object.name }}"; var name = "{{ user_object.name }}";
...@@ -466,7 +466,7 @@ $(document).ready(function() { ...@@ -466,7 +466,7 @@ $(document).ready(function() {
} }
); );
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
...@@ -485,7 +485,7 @@ $(document).ready(function() { ...@@ -485,7 +485,7 @@ $(document).ready(function() {
doReset(); doReset();
}); });
}).on('click', '#btn-reset-mfa', function () { }).on('click', '#btn-reset-mfa', function () {
APIUpdateAttr({ requestApi({
url: "{% url 'api-users:user-reset-otp' pk=user_object.id %}", url: "{% url 'api-users:user-reset-otp' pk=user_object.id %}",
method: "GET", method: "GET",
success_message: "{% trans 'Reset user MFA success' %}" success_message: "{% trans 'Reset user MFA success' %}"
......
...@@ -23,35 +23,7 @@ ...@@ -23,35 +23,7 @@
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-lg-3" style="padding-left: 0px"> {% include 'users/_granted_assets.html' %}
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover" id="user_assets_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'System users' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -60,88 +32,9 @@ ...@@ -60,88 +32,9 @@
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var zTree; var assetTableUrl = "{% url 'api-perms:user-assets' pk=object.id %}?cache_policy=1";
var inited = false; var selectUrl = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1';
var url; var treeUrl = "{% url 'api-perms:user-nodes-as-tree' pk=object.id %}?&cache_policy=1";
var asset_table;
var treeUrl = "{% url 'api-perms:user-nodes-assets-as-tree' pk=object.id %}?show_assets=0&cache_policy=1";
function initTable() {
if (inited){
return asset_table
} else {
inited = true;
}
url = "{% url 'api-perms:user-assets' pk=object.id %}?cache_policy=1";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
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 = [];
$.each(cellData, function (id, data) {
var name = htmlEscape(data.name);
users.push(name);
});
$(td).html(users.join(', '))
}}
],
ajax_url: url,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false}
]
};
asset_table = jumpserver.initServerSideDataTable(options)
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1';
var node_id = treeNode.meta.node.id;
url = url.replace("{{ DEFAULT_PK }}", node_id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
$.get(treeUrl, function(data, status) {
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
rootNodeAddDom(zTree, function () {
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
initTree();
});
});
}
$(document).ready(function () { $(document).ready(function () {
initTree(); initTree();
......
...@@ -47,5 +47,26 @@ $(document).ready(function () { ...@@ -47,5 +47,26 @@ $(document).ready(function () {
closeOnSelect: false closeOnSelect: false
}); });
}) })
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-users:user-group-list' %}';
var redirect_to = '{% url "users:user-group-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url 'api-users:user-group-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['users']);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -142,7 +142,7 @@ function updateGroupMember(users) { ...@@ -142,7 +142,7 @@ function updateGroupMember(users) {
// clear jumpserver.selected_groups // clear jumpserver.selected_groups
jumpserver.users_selected = {}; jumpserver.users_selected = {};
}; };
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
body: JSON.stringify(body), body: JSON.stringify(body),
success: success success: success
......
...@@ -23,142 +23,21 @@ ...@@ -23,142 +23,21 @@
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-lg-3" style="padding-left: 0px"> {% include 'users/_granted_assets.html' %}
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover" id="user_assets_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var zTree; var treeUrl = "{% url 'api-perms:user-group-nodes-as-tree' pk=object.id %}?cache_policy=1";
var inited = false; var assetTableUrl = "{% url 'api-perms:user-group-assets' pk=object.id %}?cache_policy=1";
var url; var selectUrl = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1';
var asset_table; var showAssetHref = true; // Need input default true
function initTable() {
if (inited){
return asset_table
} else {
inited = true;
}
url = "{% url 'api-perms:user-group-assets' pk=object.id %}";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
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 = [];
$.each(cellData, function (id, data) {
var name = htmlEscape(data.name);
users.push(name);
});
$(td).html(users.join(', '))
}}
],
ajax_url: url,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false}
]
};
asset_table = jumpserver.initDataTable(options);
return asset_table
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}';
var node_id = treeNode.meta.node.id;
url = url.replace("{{ DEFAULT_PK }}", node_id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function selectQueryNode() {
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
var node_id;
if (query_node_id !== null) {
node_id = query_node_id
} else if (cookie_node_id !== null) {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
$.get("{% url 'api-perms:user-group-nodes-assets-as-tree' pk=object.id %}?show_assets=0", function(data, status) {
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
});
}
$(document).ready(function () { $(document).ready(function () {
initTree(); initTree();
......
...@@ -129,7 +129,7 @@ $(document).ready(function() { ...@@ -129,7 +129,7 @@ $(document).ready(function() {
swal("{% trans 'UserGroups Delete' %}", msg, "error"); swal("{% trans 'UserGroups Delete' %}", msg, "error");
}; };
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list); var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail}); requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
jumpserver.checked = false; jumpserver.checked = false;
}); });
} }
......
...@@ -222,7 +222,7 @@ $(document).ready(function(){ ...@@ -222,7 +222,7 @@ $(document).ready(function(){
setTimeout( function () { setTimeout( function () {
window.location.reload();}, 300); window.location.reload();}, 300);
} }
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(data), body: JSON.stringify(data),
...@@ -239,7 +239,7 @@ $(document).ready(function(){ ...@@ -239,7 +239,7 @@ $(document).ready(function(){
setTimeout( function () { setTimeout( function () {
window.location.reload();}, 300); window.location.reload();}, 300);
} }
APIUpdateAttr({ requestApi({
url: the_url, url: the_url,
method: 'PATCH', method: 'PATCH',
body: JSON.stringify(data), body: JSON.stringify(data),
...@@ -259,7 +259,7 @@ $(document).ready(function(){ ...@@ -259,7 +259,7 @@ $(document).ready(function(){
},function () { },function () {
function success(data) { function success(data) {
url = setUrlParam(the_url, 'spm', data.spm); url = setUrlParam(the_url, 'spm', data.spm);
APIUpdateAttr({ requestApi({
url:url, url:url,
method:'DELETE', method:'DELETE',
success:refreshTag, success:refreshTag,
...@@ -272,7 +272,7 @@ $(document).ready(function(){ ...@@ -272,7 +272,7 @@ $(document).ready(function(){
var msg = "{% trans 'User Deleting failed.' %}"; var msg = "{% trans 'User Deleting failed.' %}";
swal("{% trans 'User Delete' %}", msg, "error"); swal("{% trans 'User Delete' %}", msg, "error");
} }
APIUpdateAttr({ requestApi({
url: "{% url 'api-common:resources-cache' %}", url: "{% url 'api-common:resources-cache' %}",
method:'POST', method:'POST',
body:JSON.stringify(data), body:JSON.stringify(data),
...@@ -290,7 +290,7 @@ $(document).ready(function(){ ...@@ -290,7 +290,7 @@ $(document).ready(function(){
var url = "{% url 'users:user-bulk-update' %}"; var url = "{% url 'users:user-bulk-update' %}";
location.href= setUrlParam(url, 'spm', data.spm); location.href= setUrlParam(url, 'spm', data.spm);
} }
APIUpdateAttr({ requestApi({
url: "{% url 'api-common:resources-cache' %}", url: "{% url 'api-common:resources-cache' %}",
method:'POST', method:'POST',
body:JSON.stringify(data), body:JSON.stringify(data),
......
...@@ -29,58 +29,76 @@ ...@@ -29,58 +29,76 @@
{% block custom_foot_js %} {% block custom_foot_js %}
{{ block.super }} {{ block.super }}
<script> <script>
function passwordCheck() { function passwordCheck() {
if ($('#id_password').length != 1) { if ($('#id_password').length != 1) {
return return
} }
var el = $('#id_password_rules'), var el = $('#id_password_rules'),
idPassword = $('#id_password'), idPassword = $('#id_password'),
idPopover = $('#popover777'), idPopover = $('#popover777'),
container = $('#container'), container = $('#container'),
progress = $('#id_progress'), progress = $('#id_progress'),
password_check_rules = {{ password_check_rules|safe }}, password_check_rules = {{ password_check_rules|safe }},
minLength = 6, minLength = 6,
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34, top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
left = 377, left = 377,
i18n_fallback = { i18n_fallback = {
"veryWeak": "{% trans 'Very weak' %}", "veryWeak": "{% trans 'Very weak' %}",
"weak": "{% trans 'Weak' %}", "weak": "{% trans 'Weak' %}",
"normal": "{% trans 'Normal' %}", "normal": "{% trans 'Normal' %}",
"medium": "{% trans 'Medium' %}", "medium": "{% trans 'Medium' %}",
"strong": "{% trans 'Strong' %}", "strong": "{% trans 'Strong' %}",
"veryStrong": "{% trans 'Very strong' %}" "veryStrong": "{% trans 'Very strong' %}"
}; };
$.each(password_check_rules, function (idx, rules) { $.each(password_check_rules, function (idx, rules) {
if(rules.key === 'id_security_password_min_length'){ if(rules.key === 'id_security_password_min_length'){
minLength = rules.value minLength = rules.value
} }
}); });
// 初始化popover // 初始化popover
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback); initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
// 监听事件 // 监听事件
idPassword.on('focus', function () { idPassword.on('focus', function () {
idPopover.css('top', top); idPopover.css('top', top);
idPopover.css('left', left); idPopover.css('left', left);
idPopover.css('display', 'block'); idPopover.css('display', 'block');
}); });
idPassword.on('blur', function () { idPassword.on('blur', function () {
idPopover.css('display', 'none'); idPopover.css('display', 'none');
}); });
idPassword.on('keyup', function(){ idPassword.on('keyup', function(){
var password = idPassword.val(); var password = idPassword.val();
checkPasswordRules(password, minLength); checkPasswordRules(password, minLength);
}); });
} }
$(document).ready(function(){ $(document).ready(function(){
passwordCheck(); passwordCheck();
var origin_text = $("#password_help_text").text(); var origin_text = $("#password_help_text").text();
var new_text = origin_text.replace('{}', "{{ object.source_display }}"); var new_text = origin_text.replace('{}', "{{ object.source_display }}");
$("#password_help_text").html(new_text); $("#password_help_text").html(new_text);
}) })
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-users:user-detail' pk=object.id %}';
var redirect_to = '{% url "users:user-list" %}';
var method = "PUT";
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['groups']);
objectAttrsIsDatetime(data,['date_expired']);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -44,6 +44,7 @@ class UserGroupCreateView(PermissionsMixin, SuccessMessageMixin, CreateView): ...@@ -44,6 +44,7 @@ class UserGroupCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('Create user group'), 'action': _('Create user group'),
'type': 'create'
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -61,6 +62,7 @@ class UserGroupUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView): ...@@ -61,6 +62,7 @@ class UserGroupUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('Update user group'), 'action': _('Update user group'),
'type': 'update'
} }
kwargs.update(context) kwargs.update(context)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment