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

Asset meta (#3539)

- 更改了资产表单,影响
  - 资产创建和更新
- 增加了资产平台数据库,影响
  - 平台创建更新和删除
- 更改了资产的platform字段,又一个字符字段,改为一个外键,影响 
  - 资产创建和更新
  - 资产连接 [windows,linux]
  - 测试连接等ansible任务
  - 自动化云导入
- 更改了资产的序列化器,影响
  - 资产创建更新列表
- 统一了树列表基础模板,影响
  - 资产列表页,权限列表页,vault页,资产收集页
- 统一了导入导出组件,影响
  - 资产导入导出
  - 用户导入导出
  - 用户组导入导出
  - 系统用户导入导出
  - 管理用户导入导出
  - vault导出导出
  - 收集用户列表导入导出
- 修改用户更新密码信号,影响
  - 修改用户密码产生的改密日志

- 新增Model instance序列化工具函数,影响
  - 操作日志生成
- 修改api mixin,新增 serializer_classes字段,serializer_classes = {"default": "", "display": "", "list": .., "other_action": ""}, 根据用户请求的方式返回不同的serializer_class,影响

  - 用户的viewset
  - 资产权限的viewset
- 统一系统配置中的tab切换
- 统一没有nav的页面,影响
  - 重置密码
  - 忘记密码
  - 重置中设置密码
  - 独立的message页面
- 修改用户组列表页,不再返还用户组下的用户,仅有数量
- 组织的一些方法变为layzproperty,避免重复计算
- 修改用户组详情页,影响
  - 用户组增加删除用户

  
parent 4ac4b517
...@@ -4,24 +4,27 @@ ...@@ -4,24 +4,27 @@
import random import random
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import RetrieveAPIView
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from ..models import Asset, Node from ..models import Asset, Node, Platform
from .. import serializers from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \ from ..tasks import (
test_asset_connectivity_manual update_asset_hardware_info_manual, test_asset_connectivity_manual
)
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'AssetViewSet', 'AssetViewSet', 'AssetPlatformRetrieveApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi', 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi', 'AssetGatewayApi', 'AssetPlatformViewSet',
] ]
...@@ -53,6 +56,34 @@ class AssetViewSet(OrgBulkModelViewSet): ...@@ -53,6 +56,34 @@ class AssetViewSet(OrgBulkModelViewSet):
self.set_assets_node(assets) self.set_assets_node(assets)
class AssetPlatformRetrieveApi(RetrieveAPIView):
queryset = Platform.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.PlatformSerializer
def get_object(self):
asset_pk = self.kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_pk)
return asset.platform
class AssetPlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.PlatformSerializer
filterset_fields = ['name', 'base']
search_fields = ['name']
def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and \
obj.internal:
self.permission_denied(
request, message={"detail": "Internal platform"}
)
return super().check_object_permissions(request, obj)
class AssetRefreshHardwareApi(generics.RetrieveAPIView): class AssetRefreshHardwareApi(generics.RetrieveAPIView):
""" """
Refresh asset hardware info Refresh asset hardware info
......
...@@ -177,7 +177,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi): ...@@ -177,7 +177,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi):
if not include_assets: if not include_assets:
return queryset return queryset
assets = self.instance.get_assets().only( assets = self.instance.get_assets().only(
"id", "hostname", "ip", 'platform', "os", "id", "hostname", "ip", "os",
"org_id", "protocols", "org_id", "protocols",
) )
for asset in assets: for asset in assets:
......
...@@ -5,3 +5,4 @@ from .label import * ...@@ -5,3 +5,4 @@ from .label import *
from .user import * from .user import *
from .domain import * from .domain import *
from .cmd_filter import * from .cmd_filter import *
from .platform import *
...@@ -6,13 +6,13 @@ from django.utils.translation import gettext_lazy as _ ...@@ -6,13 +6,13 @@ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins.forms import OrgModelForm from orgs.mixins.forms import OrgModelForm
from ..models import Asset, Node from ..models import Asset
from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm', 'AssetCreateUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
] ]
...@@ -27,17 +27,27 @@ class ProtocolForm(forms.Form): ...@@ -27,17 +27,27 @@ class ProtocolForm(forms.Form):
) )
class AssetCreateForm(OrgModelForm): class AssetCreateUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.data: self.set_platform_to_name()
return self.set_fields_queryset()
def set_fields_queryset(self):
nodes_field = self.fields['nodes'] nodes_field = self.fields['nodes']
nodes_choices = []
if self.instance: if self.instance:
nodes_field.choices = [(n.id, n.full_value) for n in nodes_choices = [
self.instance.nodes.all()] (n.id, n.full_value) for n in
else: self.instance.nodes.all()
nodes_field.choices = [] ]
nodes_field.choices = nodes_choices
def set_platform_to_name(self):
platform_field = self.fields['platform']
platform_field.to_field_name = 'name'
if self.instance:
self.initial['platform'] = self.instance.platform.name
def add_nodes_initial(self, node): def add_nodes_initial(self, node):
nodes_field = self.fields['nodes'] nodes_field = self.fields['nodes']
...@@ -49,7 +59,7 @@ class AssetCreateForm(OrgModelForm): ...@@ -49,7 +59,7 @@ class AssetCreateForm(OrgModelForm):
fields = [ fields = [
'hostname', 'ip', 'public_ip', 'protocols', 'comment', 'hostname', 'ip', 'public_ip', 'protocols', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform', 'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain', 'domain', 'number',
] ]
widgets = { widgets = {
'nodes': forms.SelectMultiple(attrs={ 'nodes': forms.SelectMultiple(attrs={
...@@ -64,52 +74,8 @@ class AssetCreateForm(OrgModelForm): ...@@ -64,52 +74,8 @@ class AssetCreateForm(OrgModelForm):
'domain': forms.Select(attrs={ 'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain') 'class': 'select2', 'data-placeholder': _('Domain')
}), }),
} 'platform': forms.Select(attrs={
labels = { 'class': 'select2', 'data-placeholder': _('Platform')
'nodes': _("Node"),
}
help_texts = {
'hostname': GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu'
),
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
'domain': _("If your have some network not connect with each other, you can set domain")
}
class AssetUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.data:
return
nodes_field = self.fields['nodes']
if self.instance:
nodes_field.choices = ((n.id, n.full_value) for n in
self.instance.nodes.all())
else:
nodes_field.choices = []
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'nodes-select2', 'data-placeholder': _('Node')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}), }),
} }
labels = { labels = {
......
# -*- coding: utf-8 -*-
from django import forms
from django.utils.translation import ugettext_lazy as _
from ..models import Platform
__all__ = ['PlatformForm', 'PlatformMetaForm']
class PlatformMetaForm(forms.Form):
SECURITY_CHOICES = (
('rdp', "RDP"),
('nla', "NLA"),
('tls', 'TLS'),
('any', "Any"),
)
CONSOLE_CHOICES = (
(True, _('Yes')),
(False, _('No')),
)
security = forms.ChoiceField(
choices=SECURITY_CHOICES, initial='any', label=_("RDP security"),
required=False,
)
console = forms.ChoiceField(
choices=CONSOLE_CHOICES, initial=False, label=_("RDP console"),
required=False,
)
class PlatformForm(forms.ModelForm):
class Meta:
model = Platform
fields = [
'name', 'base', 'comment',
]
labels = {
'base': _("Base platform")
}
# Generated by Django 2.2.7 on 2019-12-06 07:26
import common.fields.model
from django.db import migrations, models
def create_internal_platform(apps, schema_editor):
model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
type_platforms = (
('Linux', 'Linux', None),
('Unix', 'Unix', None),
('MacOS', 'MacOS', None),
('BSD', 'BSD', None),
('Windows', 'Windows', None),
('Windows2016', 'Windows', {'security': 'tls'}),
('Other', 'Other', None),
)
for name, base, meta in type_platforms:
model.objects.using(db_alias).create(
name=name, base=base, internal=True, meta=meta
)
class Migration(migrations.Migration):
dependencies = [
('assets', '0043_auto_20191114_1111'),
]
operations = [
migrations.CreateModel(
name='Platform',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')),
('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')),
('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')),
('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')),
('internal', models.BooleanField(default=False, verbose_name='Internal')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
],
),
migrations.RunPython(create_internal_platform)
]
# Generated by Django 2.2.7 on 2019-12-06 08:07
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
def migrate_platform_to_asset_type(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
platform_model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
platforms = platform_model.objects.using(db_alias).all()
platforms_map = {p.name: p for p in platforms}
for name, p in platforms_map.items():
asset_model.objects.using(db_alias)\
.filter(_platform=name)\
.update(platform=p)
class Migration(migrations.Migration):
dependencies = [
('assets', '0044_platform'),
]
operations = [
migrations.RenameField(
model_name='asset',
old_name='platform',
new_name='_platform',
),
migrations.AddField(
model_name='asset',
name='platform',
field=models.ForeignKey(
default=assets.models.asset.Platform.default,
on_delete=django.db.models.deletion.PROTECT,
related_name='assets', to='assets.Platform',
verbose_name='Platform'),
),
migrations.RunPython(migrate_platform_to_asset_type),
migrations.RemoveField(
model_name='asset',
name='_platform',
),
]
...@@ -11,10 +11,12 @@ from collections import OrderedDict ...@@ -11,10 +11,12 @@ from collections import OrderedDict
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 _
from .utils import Connectivity from common.fields.model import JsonDictTextField
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager from orgs.mixins.models import OrgModelMixin, OrgManager
from .utils import Connectivity
__all__ = ['Asset', 'ProtocolsMixin'] __all__ = ['Asset', 'ProtocolsMixin', 'Platform']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -37,6 +39,13 @@ def default_node(): ...@@ -37,6 +39,13 @@ def default_node():
return None return None
class AssetManager(OrgManager):
def get_queryset(self):
return super().get_queryset().annotate(
platform_base=models.F('platform__base')
)
class AssetQuerySet(models.QuerySet): class AssetQuerySet(models.QuerySet):
def active(self): def active(self):
return self.filter(is_active=True) return self.filter(is_active=True)
...@@ -119,6 +128,41 @@ class NodesRelationMixin: ...@@ -119,6 +128,41 @@ class NodesRelationMixin:
return nodes return nodes
class Platform(models.Model):
CHARSET_CHOICES = (
('utf8', 'UTF-8'),
('gbk', 'GBK'),
)
BASE_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
@classmethod
def default(cls):
linux, created = cls.objects.get_or_create(
defaults={'name': 'Linux'}, name='Linux'
)
return linux.id
def __str__(self):
return self.name
class Meta:
verbose_name = _("Platform")
# ordering = ('name',)
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Important # Important
PLATFORM_CHOICES = ( PLATFORM_CHOICES = (
...@@ -138,9 +182,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): ...@@ -138,9 +182,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
choices=ProtocolsMixin.PROTOCOL_CHOICES, choices=ProtocolsMixin.PROTOCOL_CHOICES,
verbose_name=_('Protocol')) verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port')) port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols")) protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform')) platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
...@@ -175,7 +218,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): ...@@ -175,7 +218,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
objects = OrgManager.from_queryset(AssetQuerySet)() objects = AssetManager.from_queryset(AssetQuerySet)()
_connectivity = None _connectivity = None
def __str__(self): def __str__(self):
...@@ -191,19 +234,20 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): ...@@ -191,19 +234,20 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
return True, warning return True, warning
def is_windows(self): def is_windows(self):
if self.platform in ("Windows", "Windows2016"): return self.platform_base == "Windows"
return True
else:
return False
def is_unixlike(self): def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016", "Other"): if self.platform_base not in ("Windows", "Windows2016", "Other"):
return True return True
else: else:
return False return False
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_base not in ("Other",)
@lazyproperty
def platform_base(self):
return self.platform.base
@property @property
def cpu_info(self): def cpu_info(self):
...@@ -264,9 +308,9 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): ...@@ -264,9 +308,9 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
def as_tree_node(self, parent_node): def as_tree_node(self, parent_node):
from common.tree import TreeNode from common.tree import TreeNode
icon_skin = 'file' icon_skin = 'file'
if self.platform.lower() == 'windows': if self.platform_base.lower() == 'windows':
icon_skin = 'windows' icon_skin = 'windows'
elif self.platform.lower() == 'linux': elif self.platform_base.lower() == 'linux':
icon_skin = 'linux' icon_skin = 'linux'
data = { data = {
'id': str(self.id), 'id': str(self.id),
...@@ -283,7 +327,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): ...@@ -283,7 +327,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
'hostname': self.hostname, 'hostname': self.hostname,
'ip': self.ip, 'ip': self.ip,
'protocols': self.protocols_as_list, 'protocols': self.protocols_as_list,
'platform': self.platform, 'platform': self.platform_base,
} }
} }
} }
......
...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Asset, Node, Label from ..models import Asset, Node, Label, Platform
from ..const import ( from ..const import (
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN, GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
...@@ -16,7 +16,8 @@ from .base import ConnectivitySerializer ...@@ -16,7 +16,8 @@ from .base import ConnectivitySerializer
__all__ = [ __all__ = [
'AssetSerializer', 'AssetSimpleSerializer', 'AssetSerializer', 'AssetSimpleSerializer',
'ProtocolsField', 'ProtocolsField', 'PlatformSerializer',
'AssetDetailSerializer',
] ]
...@@ -65,6 +66,9 @@ class ProtocolsField(serializers.ListField): ...@@ -65,6 +66,9 @@ class ProtocolsField(serializers.ListField):
class AssetSerializer(BulkOrgResourceModelSerializer): class AssetSerializer(BulkOrgResourceModelSerializer):
platform = serializers.SlugRelatedField(
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
)
protocols = ProtocolsField(label=_('Protocols'), required=False) protocols = ProtocolsField(label=_('Protocols'), required=False)
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
...@@ -111,7 +115,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -111,7 +115,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
queryset = queryset.prefetch_related( queryset = queryset.prefetch_related(
Prefetch('nodes', queryset=Node.objects.all().only('id')), Prefetch('nodes', queryset=Node.objects.all().only('id')),
Prefetch('labels', queryset=Label.objects.all().only('id')), Prefetch('labels', queryset=Label.objects.all().only('id')),
).select_related('admin_user', 'domain') ).select_related('admin_user', 'domain', 'platform')
return queryset return queryset
def compatible_with_old_protocol(self, validated_data): def compatible_with_old_protocol(self, validated_data):
...@@ -139,6 +143,21 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -139,6 +143,21 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
return super().update(instance, validated_data) return super().update(instance, validated_data)
class PlatformSerializer(serializers.ModelSerializer):
meta = serializers.DictField(required=False, allow_null=True)
class Meta:
model = Platform
fields = [
'id', 'name', 'base', 'charset',
'internal', 'meta', 'comment'
]
class AssetDetailSerializer(AssetSerializer):
platform = PlatformSerializer(read_only=True)
class AssetSimpleSerializer(serializers.ModelSerializer): class AssetSimpleSerializer(serializers.ModelSerializer):
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
......
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import admin user" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:admin-user-list" %}{% endblock %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update admin user" %}{% endblock %}
\ No newline at end of file
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import assets" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %}
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<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;overflow: auto;max-height: 500px"> <div class="col-sm-3" id="split-left" style="padding-left: 3px;overflow: auto;max-height: 500px">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager "> <div class="file-manager ">
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-9 animated fadeInRight" id="split-right"> <div class="col-sm-9 animated fadeInRight" id="split-right">
<div class="mail-box-header"> <div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%"> <table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
<thead> <thead>
......
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import system user" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:system-user-list" %}{% endblock %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update system user" %}{% endblock %}
\ No newline at end of file
...@@ -5,28 +5,7 @@ ...@@ -5,28 +5,7 @@
{% 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. '%}
{% endblock %} {% endblock %}
{% block table_search %} {% block table_search %}
<div class="" style="float: right"> {% include '_csv_import_export.html' %}
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% endblock %} {% endblock %}
{% block table_container %} {% block table_container %}
...@@ -42,9 +21,6 @@ ...@@ -42,9 +21,6 @@
<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 'Unreachable' %}</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>
...@@ -52,8 +28,6 @@ ...@@ -52,8 +28,6 @@
<tbody> <tbody>
</tbody> </tbody>
</table> </table>
{% include 'assets/_admin_user_import_modal.html' %}
{% include 'assets/_admin_user_update_modal.html' %}
{% endblock %} {% endblock %}
{% block content_bottom_left %}{% endblock %} {% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
...@@ -80,14 +54,13 @@ function initTable() { ...@@ -80,14 +54,13 @@ function initTable() {
{data: "comment"}, {data: "id", orderable: false, width: "100px"} {data: "comment"}, {data: "id", orderable: false, width: "100px"}
] ]
}; };
admin_user_table = jumpserver.initServerSideDataTable(options); return jumpserver.initServerSideDataTable(options);
return admin_user_table
} }
$(document).ready(function(){ $(document).ready(function(){
initTable(); admin_user_table = initTable();
initCsvImportExport(admin_user_table, "{% trans "Admin user" %}")
}) })
.on('click', '.btn_admin_user_delete', function () { .on('click', '.btn_admin_user_delete', function () {
var $this = $(this); var $this = $(this);
var $data_table = $("#admin_user_list_table").DataTable(); var $data_table = $("#admin_user_list_table").DataTable();
...@@ -100,69 +73,5 @@ $(document).ready(function(){ ...@@ -100,69 +73,5 @@ $(document).ready(function(){
}, 3000); }, 3000);
}) })
.on('click', '.btn_export', function(){
var admin_users = admin_user_table.selected;
var data = {
'resources': admin_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:admin-user-list' %}",
format: "csv",
params: {
search: search
}
};
APIExportData(props);
}).on('click', '#btn_import_confirm',function () {
var url = "{% url 'api-assets:admin-user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#admin_user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function () {
var admin_users = admin_user_table.selected;
var data = {
'resources': admin_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:admin-user-list' %}?format=csv&template=update",
format: 'csv',
params: {
search: search
}
};
APIExportData(props);
})
.on('click', '#btn_update_confirm', function () {
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var url = "{% url 'api-assets:admin-user-list' %}";
var data_table = $('#admin_user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
</script> </script>
{% endblock %} {% endblock %}
{% extends '_base_create_update.html' %} {% load static %} {% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="platformForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.base layout="horizontal" %}
<div class="meta-config">
<div hidden class="windows-config">
{% bootstrap_field meta_form.security layout="horizontal" %}
{% bootstrap_field meta_form.console layout="horizontal" %}
</div>
</div>
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
function toggleWindowConfig() {
var baseRef = $("#id_base");
var windowConfigRef = $(".windows-config");
var windowConfigInputRef = windowConfigRef.find(".form-control");
if (baseRef.val().toLowerCase() === 'windows') {
windowConfigInputRef.attr('disabled', false);
windowConfigRef.show();
} else {
windowConfigInputRef.attr('disabled', true);
windowConfigRef.hide();
}
}
$(document).ready(function () {
toggleWindowConfig()
})
.on("change", "#id_base", function () {
toggleWindowConfig()
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var form = $("form");
var data = form.serializeObject();
var method = "POST";
var theUrl = '{% url "api-assets:platform-list" %}';
var redirectTo = '{% url "assets:platform-list" %}';
{% if type == "update" %}
theUrl = '{% url 'api-assets:platform-detail' pk=object.id %}';
method = "PUT";
{% endif %}
var metaData = $(".meta-config .form-control").serializeObject();
objectAttrsIsBool(metaData, ['console']);
var metaKeys = Object.keys(metaData);
metaKeys.forEach(function (k, v) {
delete data[k]
});
data.meta = metaData;
console.log(data);
var props = {
url: theUrl,
data: data,
method: method,
form: form,
redirect_to: redirectTo
};
formSubmit(props);
})
</script>
{% endblock %}
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:platform-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:platform-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ object.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Base platform' %}:</td>
<td><b>{{ object.base }}</b></td>
</tr>
<tr>
<td>{% trans 'Charset' %}:</td>
<td><b>{{ object.charset }}</b></td>
</tr>
<tr>
<td>{% trans 'Meta' %}:</td>
<td><b>{{ object.meta }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ object.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
{% endblock %}
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url "assets:platform-create" %}" class="btn btn-sm btn-primary"> {% trans "Create platform" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="platform_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Base platform' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
var platformTable = 0;
function initTable() {
var options = {
ele: $('#platform_list_table'),
columnDefs: [
{targets: 1, render: function (cellData, tp, rowData, meta) {
cellData = htmlEscape(cellData);
var detailBtn = '<a href="{% url "assets:platform-detail" pk=999 %}">' + cellData + '</a>';
return detailBtn.replace('999', rowData.id);
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var updateBtn = '<a href="{% url "assets:platform-update" pk=9999 %}" class="btn btn-xs m-l-xs btn-info" disabled>{% trans "Update" %}</a>'.replace('9999', cellData);
var delBtn = '<a class="btn btn-xs btn-danger m-l-xs btn-object-delete" data-uid="{{ DEFAULT_PK }}" disabled>{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
if (!rowData.internal) {
updateBtn = updateBtn.replace('disabled', '');
delBtn = delBtn.replace('disabled', '');
}
$(td).html(updateBtn + delBtn)
}}
],
ajax_url: '{% url "api-assets:platform-list" %}',
columns: [
{data: "id"}, {data: "name"}, {data: "base" },
{data: "comment"}, {data: "id", orderable: false, width: "100px"}
]
};
platformTable = jumpserver.initServerSideDataTable(options);
return platformTable
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-object-delete', function () {
var $this = $(this);
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var theUrl = '{% url "api-assets:platform-detail" pk=0 %}'.replace('0', uid);
objectDelete($this, name, theUrl);
setTimeout( function () {
platformTable.ajax.reload();
}, 3000);
})
</script>
{% endblock %}
...@@ -8,28 +8,7 @@ ...@@ -8,28 +8,7 @@
{% endblock %} {% endblock %}
{% block table_search %} {% block table_search %}
<div class="" style="float: right"> {% include '_csv_import_export.html' %}
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% endblock %} {% endblock %}
{% block table_container %} {% block table_container %}
...@@ -57,8 +36,6 @@ ...@@ -57,8 +36,6 @@
<tbody> <tbody>
</tbody> </tbody>
</table> </table>
{% include 'assets/_system_user_import_modal.html' %}
{% include 'assets/_system_user_update_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
...@@ -89,14 +66,13 @@ function initTable() { ...@@ -89,14 +66,13 @@ function initTable() {
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
system_user_table = jumpserver.initServerSideDataTable(options); return jumpserver.initServerSideDataTable(options);
return system_user_table
} }
$(document).ready(function(){ $(document).ready(function(){
initTable(); system_user_table = initTable();
initCsvImportExport(system_user_table, "{% trans 'System user' %}")
}) })
.on('click', '.btn_admin_user_delete', function () { .on('click', '.btn_admin_user_delete', function () {
var $this = $(this); var $this = $(this);
var $data_table = $('#cluster_list_table').DataTable(); var $data_table = $('#cluster_list_table').DataTable();
...@@ -108,125 +84,6 @@ $(document).ready(function(){ ...@@ -108,125 +84,6 @@ $(document).ready(function(){
$data_table.ajax.reload(); $data_table.ajax.reload();
}, 3000); }, 3000);
}) })
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#system_user_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (id_list === []) {
return false;
}
var the_url = "{% url 'api-assets:system-user-list' %}";
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected System Users !!!' %}",
type: "warning",
showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'System Users Deleted.' %}";
swal("{% trans 'System Users Delete' %}", msg, "success");
$('#system_user_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'System Users Deleting failed.' %}";
swal("{% trans 'System Users Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
{# TODO: bulk update the System Users #}
}
switch (action) {
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
default:
break;
}
})
.on('click', '.btn_export', function () {
var system_users = system_user_table.selected;
var data = {
'resources': system_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:system-user-list' %}",
format: "csv",
params:{
search:search
}
};
APIExportData(props);
})
.on('click', '#btn_import_confirm', function () {
var url = "{% url 'api-assets:system-user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans 'Please select file' %}");
return
}
var data_table = $('#system_user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function () {
var system_users = system_user_table.selected;
var data = {
'resources': system_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:system-user-list' %}?format=csv&template=update",
format: "csv",
params:{
search:search
}
};
APIExportData(props);
})
.on('click', '#btn_update_confirm', function () {
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var url = "{% url 'api-assets:system-user-list' %}";
var data_table = $('#system_user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
</script> </script>
{% endblock %} {% endblock %}
......
...@@ -12,6 +12,7 @@ app_name = 'assets' ...@@ -12,6 +12,7 @@ app_name = 'assets'
router = BulkRouter() router = BulkRouter()
router.register(r'assets', api.AssetViewSet, 'asset') router.register(r'assets', api.AssetViewSet, 'asset')
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user') router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
router.register(r'system-users', api.SystemUserViewSet, 'system-user') router.register(r'system-users', api.SystemUserViewSet, 'system-user')
router.register(r'labels', api.LabelViewSet, 'label') router.register(r'labels', api.LabelViewSet, 'label')
...@@ -37,6 +38,8 @@ urlpatterns = [ ...@@ -37,6 +38,8 @@ urlpatterns = [
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'), api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
path('assets/<uuid:pk>/gateway/', path('assets/<uuid:pk>/gateway/',
api.AssetGatewayApi.as_view(), name='asset-gateway'), api.AssetGatewayApi.as_view(), name='asset-gateway'),
path('assets/<uuid:pk>/platform/',
api.AssetPlatformRetrieveApi.as_view(), name='asset-platform-detail'),
path('asset-users/auth-info/', path('asset-users/auth-info/',
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'), api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
......
...@@ -16,6 +16,11 @@ urlpatterns = [ ...@@ -16,6 +16,11 @@ urlpatterns = [
# Asset user view # Asset user view
path('asset/<uuid:pk>/asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'), path('asset/<uuid:pk>/asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'),
path('platform/', views.PlatformListView.as_view(), name='platform-list'),
path('platform/create/', views.PlatformCreateView.as_view(), name='platform-create'),
path('platform/<int:pk>/', views.PlatformDetailView.as_view(), name='platform-detail'),
path('platform/<int:pk>/update/', views.PlatformUpdateView.as_view(), name='platform-update'),
# User asset view # User asset view
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'), path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
......
# coding:utf-8 # coding:utf-8
from .asset import * from .asset import *
from .platform import *
from .system_user import * from .system_user import *
from .admin_user import * from .admin_user import *
from .label import * from .label import *
......
...@@ -74,7 +74,7 @@ class UserAssetListView(PermissionsMixin, TemplateView): ...@@ -74,7 +74,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
class AssetCreateView(PermissionsMixin, FormMixin, TemplateView): class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
model = Asset model = Asset
form_class = forms.AssetCreateForm form_class = forms.AssetCreateUpdateForm
template_name = 'assets/asset_create.html' template_name = 'assets/asset_create.html'
success_url = reverse_lazy('assets:asset-list') success_url = reverse_lazy('assets:asset-list')
permission_classes = [IsOrgAdmin] permission_classes = [IsOrgAdmin]
...@@ -110,7 +110,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView): ...@@ -110,7 +110,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
class AssetUpdateView(PermissionsMixin, UpdateView): class AssetUpdateView(PermissionsMixin, UpdateView):
model = Asset model = Asset
form_class = forms.AssetUpdateForm form_class = forms.AssetCreateUpdateForm
template_name = 'assets/asset_update.html' template_name = 'assets/asset_update.html'
success_url = reverse_lazy('assets:asset-list') success_url = reverse_lazy('assets:asset-list')
permission_classes = [IsOrgAdmin] permission_classes = [IsOrgAdmin]
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.views.generic import TemplateView, CreateView, \ from django.views.generic import (
UpdateView, DeleteView, DetailView TemplateView, CreateView, UpdateView, DeleteView, DetailView
)
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
......
# -*- coding: utf-8 -*-
from django.views import generic
from django.utils.translation import ugettext as _
from common.permissions import PermissionsMixin, IsSuperUser
from ..models import Platform
from ..forms import PlatformForm, PlatformMetaForm
__all__ = [
'PlatformListView', 'PlatformUpdateView', 'PlatformCreateView',
'PlatformDetailView',
]
class PlatformListView(PermissionsMixin, generic.TemplateView):
template_name = 'assets/platform_list.html'
permission_classes = (IsSuperUser,)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'app': _('Assets'),
'action': _("Platform list"),
})
return context
class PlatformCreateView(PermissionsMixin, generic.CreateView):
form_class = PlatformForm
permission_classes = (IsSuperUser,)
template_name = 'assets/platform_create_update.html'
model = Platform
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
meta_form = PlatformMetaForm()
context.update({
'app': _('Assets'),
'action': _("Create platform"),
'meta_form': meta_form,
})
return context
class PlatformUpdateView(generic.UpdateView):
form_class = PlatformForm
permission_classes = (IsSuperUser,)
model = Platform
template_name = 'assets/platform_create_update.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
meta_form = PlatformMetaForm(initial=self.object.meta)
context.update({
'app': _('Assets'),
'action': _("Update platform"),
'type': 'update',
'meta_form': meta_form,
})
return context
class PlatformDetailView(generic.DetailView):
permission_classes = (IsSuperUser,)
model = Platform
template_name = 'assets/platform_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'app': _('Assets'),
'action': _("Platform detail"),
})
return context
...@@ -11,6 +11,7 @@ from rest_framework.request import Request ...@@ -11,6 +11,7 @@ from rest_framework.request import Request
from jumpserver.utils import current_request from jumpserver.utils import current_request
from common.utils import get_request_ip, get_logger, get_syslogger from common.utils import get_request_ip, get_logger, get_syslogger
from users.models import User from users.models import User
from users.signals import post_user_change_password
from authentication.signals import post_auth_failed, post_auth_success from authentication.signals import post_auth_failed, post_auth_success
from terminal.models import Session, Command from terminal.models import Session, Command
from common.utils.encode import model_to_json from common.utils.encode import model_to_json
...@@ -18,7 +19,7 @@ from . import models ...@@ -18,7 +19,7 @@ from . import models
from .tasks import write_login_log_async from .tasks import write_login_log_async
logger = get_logger(__name__) logger = get_logger(__name__)
sys_logger = get_syslogger("audits") sys_logger = get_syslogger(__name__)
json_render = JSONRenderer() json_render = JSONRenderer()
...@@ -50,7 +51,7 @@ def create_operate_log(action, sender, resource): ...@@ -50,7 +51,7 @@ def create_operate_log(action, sender, resource):
logger.error("Create operate log error: {}".format(e)) logger.error("Create operate log error: {}".format(e))
@receiver(post_save, dispatch_uid="my_unique_identifier") @receiver(post_save)
def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs): def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs):
if instance._meta.object_name == 'User' and \ if instance._meta.object_name == 'User' and \
update_fields and 'last_login' in update_fields: update_fields and 'last_login' in update_fields:
...@@ -62,21 +63,27 @@ def on_object_created_or_update(sender, instance=None, created=False, update_fie ...@@ -62,21 +63,27 @@ def on_object_created_or_update(sender, instance=None, created=False, update_fie
create_operate_log(action, sender, instance) create_operate_log(action, sender, instance)
@receiver(post_delete, dispatch_uid="my_unique_identifier") @receiver(post_delete)
def on_object_delete(sender, instance=None, **kwargs): def on_object_delete(sender, instance=None, **kwargs):
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance) create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance)
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier") @receiver(post_user_change_password, sender=User)
def on_user_change_password(sender, instance=None, **kwargs): def on_user_change_password(sender, user=None, **kwargs):
if hasattr(instance, '_set_password'): if not current_request:
if not current_request or not current_request.user.is_authenticated: remote_addr = '127.0.0.1'
return change_by = 'System'
with transaction.atomic(): else:
models.PasswordChangeLog.objects.create( remote_addr = get_request_ip(current_request)
user=instance, change_by=current_request.user, if not current_request.user.is_authenticated:
remote_addr=get_request_ip(current_request), change_by = str(user)
) else:
change_by = str(current_request.user)
with transaction.atomic():
models.PasswordChangeLog.objects.create(
user=str(user), change_by=change_by,
remote_addr=remote_addr,
)
def on_audits_log_create(sender, instance=None, **kwargs): def on_audits_log_create(sender, instance=None, **kwargs):
...@@ -95,7 +102,7 @@ def on_audits_log_create(sender, instance=None, **kwargs): ...@@ -95,7 +102,7 @@ def on_audits_log_create(sender, instance=None, **kwargs):
else: else:
return return
data = model_to_json(instance) data = model_to_json(instance, indent=None)
msg = "{} - {}".format(category, data) msg = "{} - {}".format(category, data)
sys_logger.info(msg) sys_logger.info(msg)
......
...@@ -102,47 +102,47 @@ ...@@ -102,47 +102,47 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script> <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
jumpserver.initStaticTable('#login_log_table'); jumpserver.initStaticTable('#login_log_table');
$('#date .input-daterange').datepicker({ $('#date .input-daterange').datepicker({
format: "yyyy-mm-dd", format: "yyyy-mm-dd",
todayBtn: "linked", todayBtn: "linked",
keyboardNavigation: false, keyboardNavigation: false,
forceParse: false, forceParse: false,
calendarWeeks: true, calendarWeeks: true,
autoclose: true autoclose: true
}); });
$('.select2').select2({ $('.select2').select2({
dropdownAutoWidth: true, dropdownAutoWidth: true,
width: 'auto' width: 'auto'
}); });
})
.on('click', '.btn_export', function () {
var date_from = $('#id_date_from').val();
var date_to = $('#id_date_to').val();
var user = $('.select2 option:selected').val();
var keyword = $('#search').val();
$.ajax({
url: "{% url "audits:login-log-export" %}",
method: 'POST',
data: JSON.stringify({
'date_from':date_from,
'date_to':date_to,
'user':user,
'keyword':keyword
}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
}) })
.on('click', '.btn_export', function () { })
var date_from = $('#id_date_from').val(); </script>
var date_to = $('#id_date_to').val();
var user = $('.select2 option:selected').val();
var keyword = $('#search').val();
$.ajax({
url: "{% url "audits:login-log-export" %}",
method: 'POST',
data: JSON.stringify({
'date_from':date_from,
'date_to':date_to,
'user':user,
'keyword':keyword
}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
})
</script>
{% endblock %} {% endblock %}
...@@ -2,11 +2,8 @@ ...@@ -2,11 +2,8 @@
# #
from django import forms from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from django.conf import settings
from users.utils import get_login_failed_count
class UserLoginForm(forms.Form): class UserLoginForm(forms.Form):
......
...@@ -27,10 +27,17 @@ class IDSpmFilterMixin: ...@@ -27,10 +27,17 @@ class IDSpmFilterMixin:
class SerializerMixin: class SerializerMixin:
def get_serializer_class(self): def get_serializer_class(self):
if self.request.method.lower() == 'get' and\ serializer_class = None
self.request.query_params.get('draw') \ if hasattr(self, 'serializer_classes') and \
and hasattr(self, 'serializer_display_class'): isinstance(self.serializer_classes, dict):
return self.serializer_display_class if self.action == 'list' and self.request.query_params.get('draw'):
serializer_class = self.serializer_classes.get('display')
if serializer_class is None:
serializer_class = self.serializer_classes.get(
self.action, self.serializer_classes.get('default')
)
if serializer_class:
return serializer_class
return super().get_serializer_class() return super().get_serializer_class()
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# #
import re import re
import os import os
import logging
from collections import defaultdict from collections import defaultdict
from django.conf import settings from django.conf import settings
from django.dispatch import receiver from django.dispatch import receiver
...@@ -10,13 +11,13 @@ from django.db import connection ...@@ -10,13 +11,13 @@ from django.db import connection
from django.conf import LazySettings from django.conf import LazySettings
from django.db.utils import ProgrammingError, OperationalError from django.db.utils import ProgrammingError, OperationalError
from jumpserver.utils import get_current_request
from common.utils import get_logger
from .local import thread_local from .local import thread_local
from .signals import django_ready from .signals import django_ready
pattern = re.compile(r'FROM `(\w+)`') pattern = re.compile(r'FROM `(\w+)`')
logger = get_logger(__name__) logger = logging.getLogger("jumpserver.common")
DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1' DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1'
...@@ -50,19 +51,29 @@ def on_request_finished_logging_db_query(sender, **kwargs): ...@@ -50,19 +51,29 @@ def on_request_finished_logging_db_query(sender, **kwargs):
counters['total'].time += float(time) counters['total'].time += float(time)
counters = sorted(counters.items(), key=lambda x: x[1]) counters = sorted(counters.items(), key=lambda x: x[1])
if not counters:
return
method = 'GET'
path = '/Unknown'
current_request = get_current_request()
if current_request:
method = current_request.method
path = current_request.get_full_path()
logger.debug(">>> [{}] {}".format(method, path))
for name, counter in counters: for name, counter in counters:
logger.debug("Query {:3} times using {:.2f}s {}".format( logger.debug("Query {:3} times using {:.2f}s {}".format(
counter.counter, counter.time, name) counter.counter, counter.time, name)
) )
@receiver(request_finished)
def on_request_finished_release_local(sender, **kwargs): def on_request_finished_release_local(sender, **kwargs):
thread_local.__release_local__() thread_local.__release_local__()
if settings.DEBUG and DEBUG_DB: if settings.DEBUG and DEBUG_DB:
request_finished.connect(on_request_finished_logging_db_query) request_finished.connect(on_request_finished_logging_db_query)
else:
request_finished.connect(on_request_finished_release_local)
@receiver(django_ready) @receiver(django_ready)
......
...@@ -27,12 +27,12 @@ def combine_seq(s1, s2, callback=None): ...@@ -27,12 +27,12 @@ def combine_seq(s1, s2, callback=None):
return seq return seq
def get_logger(name=None): def get_logger(name=''):
return logging.getLogger('jumpserver.%s' % name) return logging.getLogger('jumpserver.%s' % name)
def get_syslogger(name=None): def get_syslogger(name=''):
return logging.getLogger('jms.%s' % name) return logging.getLogger('syslog.%s' % name)
def timesince(dt, since='', default="just now"): def timesince(dt, since='', default="just now"):
......
...@@ -184,6 +184,7 @@ class Config(dict): ...@@ -184,6 +184,7 @@ class Config(dict):
'ASSETS_PERM_CACHE_ENABLE': False, 'ASSETS_PERM_CACHE_ENABLE': False,
'SYSLOG_ADDR': '', # '192.168.0.1:514' 'SYSLOG_ADDR': '', # '192.168.0.1:514'
'SYSLOG_FACILITY': 'user', 'SYSLOG_FACILITY': 'user',
'SYSLOG_SOCKTYPE': 2,
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False, 'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd', 'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
'FLOWER_URL': "127.0.0.1:5555", 'FLOWER_URL': "127.0.0.1:5555",
...@@ -282,10 +283,10 @@ class DynamicConfig: ...@@ -282,10 +283,10 @@ class DynamicConfig:
] ]
if self.get('AUTH_LDAP'): if self.get('AUTH_LDAP'):
backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend') backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend')
if self.get('AUTH_OPENID'): if self.static_config.get('AUTH_OPENID'):
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend') backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend')
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend') backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend')
if self.get('AUTH_RADIUS'): if self.static_config.get('AUTH_RADIUS'):
backends.insert(0, 'authentication.backends.radius.RadiusBackend') backends.insert(0, 'authentication.backends.radius.RadiusBackend')
return backends return backends
......
...@@ -81,7 +81,7 @@ LOGGING = { ...@@ -81,7 +81,7 @@ LOGGING = {
'propagate': False, 'propagate': False,
}, },
'jumpserver': { 'jumpserver': {
'handlers': ['console', 'file', 'syslog'], 'handlers': ['console', 'file'],
'level': LOG_LEVEL, 'level': LOG_LEVEL,
}, },
'ops.ansible_api': { 'ops.ansible_api': {
...@@ -92,7 +92,7 @@ LOGGING = { ...@@ -92,7 +92,7 @@ LOGGING = {
'handlers': ['console', 'file'], 'handlers': ['console', 'file'],
'level': "INFO", 'level': "INFO",
}, },
'jms.audits': { 'syslog': {
'handlers': ['syslog'], 'handlers': ['syslog'],
'level': 'INFO' 'level': 'INFO'
}, },
...@@ -110,6 +110,7 @@ if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2: ...@@ -110,6 +110,7 @@ if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
'class': 'logging.handlers.SysLogHandler', 'class': 'logging.handlers.SysLogHandler',
'facility': CONFIG.SYSLOG_FACILITY, 'facility': CONFIG.SYSLOG_FACILITY,
'address': (host, int(port)), 'address': (host, int(port)),
'socktype': CONFIG.SYSLOG_SOCKTYPE,
}) })
if not os.path.isdir(LOG_DIR): if not os.path.isdir(LOG_DIR):
......
This diff is collapsed.
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,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"> <div class="col-sm-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" <div class="ibox-content mailbox-content"
style="padding-top: 0;padding-left: 1px"> style="padding-top: 0;padding-left: 1px">
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-9 animated fadeInRight" id="split-right"> <div class="col-sm-9 animated fadeInRight" id="split-right">
<div class="tree-toggle"> <div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" <div class="btn btn-sm btn-primary tree-toggle-btn"
onclick="toggle()"> onclick="toggle()">
...@@ -87,14 +87,14 @@ ...@@ -87,14 +87,14 @@
style="height: 100%;width: 100%"></div> style="height: 100%;width: 100%"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-10"> <div class="col-sm-10">
<div class="input-group" <div class="input-group"
style="height: 100%; width: 100%"> style="height: 100%; width: 100%">
<textarea class="form-control" <textarea class="form-control"
id="command-text"></textarea> id="command-text"></textarea>
</div> </div>
</div> </div>
<div class="col-lg-2"> <div class="col-sm-2">
<select class="select2 form-control" <select class="select2 form-control"
id="system-users-select"> id="system-users-select">
{% for s in system_users %} {% for s in system_users %}
...@@ -199,12 +199,12 @@ ...@@ -199,12 +199,12 @@
function toggle() { function toggle() {
if (show === 0) { if (show === 0) {
$("#split-left").hide(500, function () { $("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12"); $("#split-right").attr("class", "col-sm-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x"); $("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1; show = 1;
}); });
} else { } else {
$("#split-right").attr("class", "col-lg-9"); $("#split-right").attr("class", "col-sm-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x"); $("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500); $("#split-left").show(500);
show = 0; show = 0;
......
...@@ -23,8 +23,10 @@ class AssetPermissionViewSet(OrgModelViewSet): ...@@ -23,8 +23,10 @@ class AssetPermissionViewSet(OrgModelViewSet):
资产授权列表的增删改查api 资产授权列表的增删改查api
""" """
model = AssetPermission model = AssetPermission
serializer_class = serializers.AssetPermissionCreateUpdateSerializer serializer_classes = {
serializer_display_class = serializers.AssetPermissionListSerializer 'default': serializers.AssetPermissionCreateUpdateSerializer,
'display': serializers.AssetPermissionListSerializer
}
filter_fields = ['name'] filter_fields = ['name']
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
......
{% extends 'base.html' %} {% extends '_base_asset_tree_list.html' %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
...@@ -12,56 +12,36 @@ ...@@ -12,56 +12,36 @@
.toggle { .toggle {
cursor: pointer; cursor: pointer;
} }
.detail-key {
width: 70px;
}
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block table_container %}
<div class="wrapper wrapper-content"> <div class="btn-group uc pull-left m-r-5">
<div class="row"> <button class="btn btn-sm btn-primary btn-create-permission">
<div class="col-lg-3" id="split-left" style="padding-left: 3px;padding-right: 0"> {% trans "Create permission" %}
{% include 'assets/_node_tree.html' %} </button>
</div> <button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
<div class="col-lg-9 animated fadeInRight" id="split-right"> <ul class="dropdown-menu">
<div class="tree-toggle"> <li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
<div class="btn btn-sm btn-primary tree-toggle-btn"> </ul>
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i> </div>
</div> <table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
</div> <thead>
<div class="mail-box-header"> <tr>
<div class="btn-group uc pull-left m-r-5"> <th></th>
<button class="btn btn-sm btn-primary btn-create-permission"> <th>{% trans 'Name' %}</th>
{% trans "Create permission" %} <th class="text-center">{% trans 'User' %}</th>
</button> <th class="text-center">{% trans 'User group' %}</th>
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button> <th class="text-center">{% trans 'Asset' %}</th>
<ul class="dropdown-menu"> <th class="text-center">{% trans 'Node'%}</th>
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li> <th class="text-center">{% trans 'System user' %}</th>
</ul> <th class="text-center">{% trans 'Validity' %}</th>
</div> <th class="text-center" >{% trans 'Action' %}</th>
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%"> </tr>
<thead> </thead>
<tr> <tbody>
<th></th> </tbody>
<th>{% trans 'Name' %}</th> </table>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Node'%}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center" >{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% include '_filter_dropdown.html' %} {% include '_filter_dropdown.html' %}
{% endblock %} {% endblock %}
......
...@@ -13,71 +13,74 @@ router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRela ...@@ -13,71 +13,74 @@ router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRela
router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation') router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation')
user_permission_urlpatterns = [ user_permission_urlpatterns = [
path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'), path('<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'), path('assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
# Assets as tree # Assets as tree
path('users/<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'), path('<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
path('users/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'), path('assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
# Nodes # Nodes
path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'), path('nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
# Node children # Node children
path('users/<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'), path('<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
path('users/nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'), path('nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
# Node as tree # Node as tree
path('users/<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'), path('<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
path('users/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'), path('nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
# Node with assets as tree # Node with assets as tree
path('users/<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'), path('<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
path('users/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'), path('nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
# Node children as tree # Node children as tree
path('users/<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'), path('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
path('users/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'), path('nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
# Node children with assets as tree # Node children with assets as tree
path('users/<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'), path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
path('users/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'), path('nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
# Node assets # Node assets
path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), path('<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'), path('nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
# Asset System users # Asset System users
path('users/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'), path('users/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'),
path('users/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), path('assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
] ]
user_group_permission_urlpatterns = [ user_group_permission_urlpatterns = [
# 查询某个用户组授权的资产和资产组 # 查询某个用户组授权的资产和资产组
path('user-groups/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), path('<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
path('user-groups/<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'), path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
path('user-groups/<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'), path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
path('user-groups/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
path('user-groups/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'), path('<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
] ]
asset_permission_urlpatterns = [ permission_urlpatterns = [
# Assets
path('', include(user_permission_urlpatterns)),
# 授权规则中授权的资产 # 授权规则中授权的资产
path('asset-permissions/<uuid:pk>/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'), path('<uuid:pk>/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'),
path('asset-permissions/<uuid:pk>/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'), path('<uuid:pk>/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'),
# 验证用户是否有某个资产和系统用户的权限 # 验证用户是否有某个资产和系统用户的权限
path('asset-permissions/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), path('user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), path('user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
# 刷新缓存 # 刷新缓存
path('asset-permissions/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'), path('cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
]
asset_permission_urlpatterns = [
# Assets
path('users/', include(user_permission_urlpatterns)),
path('user-groups/', include(user_group_permission_urlpatterns)),
path('asset-permissions/', include(permission_urlpatterns)),
] ]
asset_permission_urlpatterns += router.urls asset_permission_urlpatterns += router.urls
{% load i18n %}
<ul class="nav nav-tabs">
<li id="tab-basic">
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li id="tab-email" >
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li id="tab-email-content" >
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li id="tab-ldap">
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li id="tab-terminal">
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li id="tab-security">
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
<script>
$(document).ready(function () {
var path = location.pathname;
if (path.endsWith('/')) {
path = path.substring(0, path.length-1)
}
var pathList = path.split('/');
var tabId = pathList[pathList.length-1];
if (tabId === "settings") {
tabId = "basic"
}
tabId = "#tab-" + tabId;
$(tabId).addClass("active")
})
</script>
...@@ -10,26 +10,7 @@ ...@@ -10,26 +10,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> {% include 'settings/_setting_tabs.html' %}
<li class="active">
<a href="" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-12" style="padding-left:0"> <div class="col-sm-12" style="padding-left:0">
......
...@@ -10,53 +10,33 @@ ...@@ -10,53 +10,33 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> {% include 'settings/_setting_tabs.html' %}
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-12" style="padding-left:0"> <div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;"> <div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="alert alert-danger"> <div class="alert alert-danger">
{{ form.non_field_errors }} {{ form.non_field_errors }}
</div> </div>
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
<h3>{% trans "Create User setting" %}</h3> <h3>{% trans "Create User setting" %}</h3>
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SUBJECT layout="horizontal" %} {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SUBJECT layout="horizontal" %}
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_HONORIFIC layout="horizontal" %} {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_HONORIFIC layout="horizontal" %}
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_BODY layout="horizontal" %} {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_BODY layout="horizontal" %}
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE layout="horizontal" %} {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-4 col-sm-offset-2"> <div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button> <button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div> </div>
</form>
</div> </div>
</form>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -10,26 +10,7 @@ ...@@ -10,26 +10,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> {% include 'settings/_setting_tabs.html' %}
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li class="active">
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-12" style="padding-left:0"> <div class="col-sm-12" style="padding-left:0">
......
...@@ -10,26 +10,7 @@ ...@@ -10,26 +10,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> {% include 'settings/_setting_tabs.html' %}
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-12" style="padding-left:0"> <div class="col-sm-12" style="padding-left:0">
......
...@@ -10,26 +10,7 @@ ...@@ -10,26 +10,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> {% include 'settings/_setting_tabs.html' %}
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-12" style="padding-left:0"> <div class="col-sm-12" style="padding-left:0">
......
...@@ -15,30 +15,7 @@ ...@@ -15,30 +15,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> {% include 'settings/_setting_tabs.html' %}
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i
class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i
class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i
class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
</li>
</ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-12" style="padding-left:0"> <div class="col-sm-12" style="padding-left:0">
......
/*!
* Ladda
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2015 Hakim El Hattab, http://hakim.se
*/.ladda-button{position:relative}.ladda-button .ladda-spinner{position:absolute;z-index:2;display:inline-block;width:32px;height:32px;top:50%;margin-top:0;opacity:0;pointer-events:none}.ladda-button .ladda-label{position:relative;z-index:3}.ladda-button .ladda-progress{position:absolute;width:0;height:100%;left:0;top:0;background:rgba(0,0,0,0.2);visibility:hidden;opacity:0;-webkit-transition:0.1s linear all !important;-moz-transition:0.1s linear all !important;-ms-transition:0.1s linear all !important;-o-transition:0.1s linear all !important;transition:0.1s linear all !important}.ladda-button[data-loading] .ladda-progress{opacity:1;visibility:visible}.ladda-button,.ladda-button .ladda-spinner,.ladda-button .ladda-label{-webkit-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-moz-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-ms-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-o-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important}.ladda-button[data-style=zoom-in],.ladda-button[data-style=zoom-in] .ladda-spinner,.ladda-button[data-style=zoom-in] .ladda-label,.ladda-button[data-style=zoom-out],.ladda-button[data-style=zoom-out] .ladda-spinner,.ladda-button[data-style=zoom-out] .ladda-label{-webkit-transition:0.3s ease all !important;-moz-transition:0.3s ease all !important;-ms-transition:0.3s ease all !important;-o-transition:0.3s ease all !important;transition:0.3s ease all !important}.ladda-button[data-style=expand-right] .ladda-spinner{right:-6px}.ladda-button[data-style=expand-right][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-right][data-size="xs"] .ladda-spinner{right:-12px}.ladda-button[data-style=expand-right][data-loading]{padding-right:56px}.ladda-button[data-style=expand-right][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-right][data-loading][data-size="s"],.ladda-button[data-style=expand-right][data-loading][data-size="xs"]{padding-right:40px}.ladda-button[data-style=expand-left] .ladda-spinner{left:26px}.ladda-button[data-style=expand-left][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-left][data-size="xs"] .ladda-spinner{left:4px}.ladda-button[data-style=expand-left][data-loading]{padding-left:56px}.ladda-button[data-style=expand-left][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-left][data-loading][data-size="s"],.ladda-button[data-style=expand-left][data-loading][data-size="xs"]{padding-left:40px}.ladda-button[data-style=expand-up]{overflow:hidden}.ladda-button[data-style=expand-up] .ladda-spinner{top:-32px;left:50%;margin-left:0}.ladda-button[data-style=expand-up][data-loading]{padding-top:54px}.ladda-button[data-style=expand-up][data-loading] .ladda-spinner{opacity:1;top:26px;margin-top:0}.ladda-button[data-style=expand-up][data-loading][data-size="s"],.ladda-button[data-style=expand-up][data-loading][data-size="xs"]{padding-top:32px}.ladda-button[data-style=expand-up][data-loading][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-up][data-loading][data-size="xs"] .ladda-spinner{top:4px}.ladda-button[data-style=expand-down]{overflow:hidden}.ladda-button[data-style=expand-down] .ladda-spinner{top:62px;left:50%;margin-left:0}.ladda-button[data-style=expand-down][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-down][data-size="xs"] .ladda-spinner{top:40px}.ladda-button[data-style=expand-down][data-loading]{padding-bottom:54px}.ladda-button[data-style=expand-down][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-down][data-loading][data-size="s"],.ladda-button[data-style=expand-down][data-loading][data-size="xs"]{padding-bottom:32px}.ladda-button[data-style=slide-left]{overflow:hidden}.ladda-button[data-style=slide-left] .ladda-label{position:relative}.ladda-button[data-style=slide-left] .ladda-spinner{left:100%;margin-left:0}.ladda-button[data-style=slide-left][data-loading] .ladda-label{opacity:0;left:-100%}.ladda-button[data-style=slide-left][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-right]{overflow:hidden}.ladda-button[data-style=slide-right] .ladda-label{position:relative}.ladda-button[data-style=slide-right] .ladda-spinner{right:100%;margin-left:0;left:16px}.ladda-button[data-style=slide-right][data-loading] .ladda-label{opacity:0;left:100%}.ladda-button[data-style=slide-right][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-up]{overflow:hidden}.ladda-button[data-style=slide-up] .ladda-label{position:relative}.ladda-button[data-style=slide-up] .ladda-spinner{left:50%;margin-left:0;margin-top:1em}.ladda-button[data-style=slide-up][data-loading] .ladda-label{opacity:0;top:-1em}.ladda-button[data-style=slide-up][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=slide-down]{overflow:hidden}.ladda-button[data-style=slide-down] .ladda-label{position:relative}.ladda-button[data-style=slide-down] .ladda-spinner{left:50%;margin-left:0;margin-top:-2em}.ladda-button[data-style=slide-down][data-loading] .ladda-label{opacity:0;top:1em}.ladda-button[data-style=slide-down][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=zoom-out]{overflow:hidden}.ladda-button[data-style=zoom-out] .ladda-spinner{left:50%;margin-left:32px;-webkit-transform:scale(2.5);-moz-transform:scale(2.5);-ms-transform:scale(2.5);-o-transform:scale(2.5);transform:scale(2.5)}.ladda-button[data-style=zoom-out] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-out][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(0.5);-moz-transform:scale(0.5);-ms-transform:scale(0.5);-o-transform:scale(0.5);transform:scale(0.5)}.ladda-button[data-style=zoom-out][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=zoom-in]{overflow:hidden}.ladda-button[data-style=zoom-in] .ladda-spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-moz-transform:scale(0.2);-ms-transform:scale(0.2);-o-transform:scale(0.2);transform:scale(0.2)}.ladda-button[data-style=zoom-in] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-in][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(2.2);-moz-transform:scale(2.2);-ms-transform:scale(2.2);-o-transform:scale(2.2);transform:scale(2.2)}.ladda-button[data-style=zoom-in][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=contract]{overflow:hidden;width:100px}.ladda-button[data-style=contract] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract][data-loading]{border-radius:50%;width:52px}.ladda-button[data-style=contract][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=contract-overlay]{overflow:hidden;width:100px;box-shadow:0px 0px 0px 2000px transparent}.ladda-button[data-style=contract-overlay] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract-overlay][data-loading]{border-radius:50%;width:52px;box-shadow:0px 0px 0px 2000px rgba(0,0,0,0.8)}.ladda-button[data-style=contract-overlay][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract-overlay][data-loading] .ladda-spinner{opacity:1}
/*!
* Ladda including the default theme.
*//*!
* Ladda
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2015 Hakim El Hattab, http://hakim.se
*/.ladda-button{position:relative}.ladda-button .ladda-spinner{position:absolute;z-index:2;display:inline-block;width:32px;height:32px;top:50%;margin-top:0;opacity:0;pointer-events:none}.ladda-button .ladda-label{position:relative;z-index:3}.ladda-button .ladda-progress{position:absolute;width:0;height:100%;left:0;top:0;background:rgba(0,0,0,0.2);visibility:hidden;opacity:0;-webkit-transition:0.1s linear all !important;-moz-transition:0.1s linear all !important;-ms-transition:0.1s linear all !important;-o-transition:0.1s linear all !important;transition:0.1s linear all !important}.ladda-button[data-loading] .ladda-progress{opacity:1;visibility:visible}.ladda-button,.ladda-button .ladda-spinner,.ladda-button .ladda-label{-webkit-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-moz-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-ms-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-o-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important}.ladda-button[data-style=zoom-in],.ladda-button[data-style=zoom-in] .ladda-spinner,.ladda-button[data-style=zoom-in] .ladda-label,.ladda-button[data-style=zoom-out],.ladda-button[data-style=zoom-out] .ladda-spinner,.ladda-button[data-style=zoom-out] .ladda-label{-webkit-transition:0.3s ease all !important;-moz-transition:0.3s ease all !important;-ms-transition:0.3s ease all !important;-o-transition:0.3s ease all !important;transition:0.3s ease all !important}.ladda-button[data-style=expand-right] .ladda-spinner{right:-6px}.ladda-button[data-style=expand-right][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-right][data-size="xs"] .ladda-spinner{right:-12px}.ladda-button[data-style=expand-right][data-loading]{padding-right:56px}.ladda-button[data-style=expand-right][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-right][data-loading][data-size="s"],.ladda-button[data-style=expand-right][data-loading][data-size="xs"]{padding-right:40px}.ladda-button[data-style=expand-left] .ladda-spinner{left:26px}.ladda-button[data-style=expand-left][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-left][data-size="xs"] .ladda-spinner{left:4px}.ladda-button[data-style=expand-left][data-loading]{padding-left:56px}.ladda-button[data-style=expand-left][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-left][data-loading][data-size="s"],.ladda-button[data-style=expand-left][data-loading][data-size="xs"]{padding-left:40px}.ladda-button[data-style=expand-up]{overflow:hidden}.ladda-button[data-style=expand-up] .ladda-spinner{top:-32px;left:50%;margin-left:0}.ladda-button[data-style=expand-up][data-loading]{padding-top:54px}.ladda-button[data-style=expand-up][data-loading] .ladda-spinner{opacity:1;top:26px;margin-top:0}.ladda-button[data-style=expand-up][data-loading][data-size="s"],.ladda-button[data-style=expand-up][data-loading][data-size="xs"]{padding-top:32px}.ladda-button[data-style=expand-up][data-loading][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-up][data-loading][data-size="xs"] .ladda-spinner{top:4px}.ladda-button[data-style=expand-down]{overflow:hidden}.ladda-button[data-style=expand-down] .ladda-spinner{top:62px;left:50%;margin-left:0}.ladda-button[data-style=expand-down][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-down][data-size="xs"] .ladda-spinner{top:40px}.ladda-button[data-style=expand-down][data-loading]{padding-bottom:54px}.ladda-button[data-style=expand-down][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-down][data-loading][data-size="s"],.ladda-button[data-style=expand-down][data-loading][data-size="xs"]{padding-bottom:32px}.ladda-button[data-style=slide-left]{overflow:hidden}.ladda-button[data-style=slide-left] .ladda-label{position:relative}.ladda-button[data-style=slide-left] .ladda-spinner{left:100%;margin-left:0}.ladda-button[data-style=slide-left][data-loading] .ladda-label{opacity:0;left:-100%}.ladda-button[data-style=slide-left][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-right]{overflow:hidden}.ladda-button[data-style=slide-right] .ladda-label{position:relative}.ladda-button[data-style=slide-right] .ladda-spinner{right:100%;margin-left:0;left:16px}.ladda-button[data-style=slide-right][data-loading] .ladda-label{opacity:0;left:100%}.ladda-button[data-style=slide-right][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-up]{overflow:hidden}.ladda-button[data-style=slide-up] .ladda-label{position:relative}.ladda-button[data-style=slide-up] .ladda-spinner{left:50%;margin-left:0;margin-top:1em}.ladda-button[data-style=slide-up][data-loading] .ladda-label{opacity:0;top:-1em}.ladda-button[data-style=slide-up][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=slide-down]{overflow:hidden}.ladda-button[data-style=slide-down] .ladda-label{position:relative}.ladda-button[data-style=slide-down] .ladda-spinner{left:50%;margin-left:0;margin-top:-2em}.ladda-button[data-style=slide-down][data-loading] .ladda-label{opacity:0;top:1em}.ladda-button[data-style=slide-down][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=zoom-out]{overflow:hidden}.ladda-button[data-style=zoom-out] .ladda-spinner{left:50%;margin-left:32px;-webkit-transform:scale(2.5);-moz-transform:scale(2.5);-ms-transform:scale(2.5);-o-transform:scale(2.5);transform:scale(2.5)}.ladda-button[data-style=zoom-out] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-out][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(0.5);-moz-transform:scale(0.5);-ms-transform:scale(0.5);-o-transform:scale(0.5);transform:scale(0.5)}.ladda-button[data-style=zoom-out][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=zoom-in]{overflow:hidden}.ladda-button[data-style=zoom-in] .ladda-spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-moz-transform:scale(0.2);-ms-transform:scale(0.2);-o-transform:scale(0.2);transform:scale(0.2)}.ladda-button[data-style=zoom-in] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-in][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(2.2);-moz-transform:scale(2.2);-ms-transform:scale(2.2);-o-transform:scale(2.2);transform:scale(2.2)}.ladda-button[data-style=zoom-in][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=contract]{overflow:hidden;width:100px}.ladda-button[data-style=contract] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract][data-loading]{border-radius:50%;width:52px}.ladda-button[data-style=contract][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=contract-overlay]{overflow:hidden;width:100px;box-shadow:0px 0px 0px 2000px transparent}.ladda-button[data-style=contract-overlay] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract-overlay][data-loading]{border-radius:50%;width:52px;box-shadow:0px 0px 0px 2000px rgba(0,0,0,0.8)}.ladda-button[data-style=contract-overlay][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract-overlay][data-loading] .ladda-spinner{opacity:1}.ladda-button{background:#666;border:0;padding:14px 18px;font-size:18px;cursor:pointer;color:#fff;border-radius:2px;border:1px solid transparent;-webkit-appearance:none;-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent}.ladda-button:hover{border-color:rgba(0,0,0,0.07);background-color:#888}.ladda-button[data-color=green]{background:#2aca76}.ladda-button[data-color=green]:hover{background-color:#38d683}.ladda-button[data-color=blue]{background:#53b5e6}.ladda-button[data-color=blue]:hover{background-color:#69bfe9}.ladda-button[data-color=red]{background:#ea8557}.ladda-button[data-color=red]:hover{background-color:#ed956e}.ladda-button[data-color=purple]{background:#9973C2}.ladda-button[data-color=purple]:hover{background-color:#a685ca}.ladda-button[data-color=mint]{background:#16a085}.ladda-button[data-color=mint]:hover{background-color:#19b698}.ladda-button[disabled],.ladda-button[data-loading]{border-color:rgba(0,0,0,0.07)}.ladda-button[disabled],.ladda-button[disabled]:hover,.ladda-button[data-loading],.ladda-button[data-loading]:hover{cursor:default;background-color:#999}.ladda-button[data-size=xs]{padding:4px 8px}.ladda-button[data-size=xs] .ladda-label{font-size:0.7em}.ladda-button[data-size=s]{padding:6px 10px}.ladda-button[data-size=s] .ladda-label{font-size:0.9em}.ladda-button[data-size=l] .ladda-label{font-size:1.2em}.ladda-button[data-size=xl] .ladda-label{font-size:1.5em}
...@@ -177,7 +177,7 @@ function formSubmit(props) { ...@@ -177,7 +177,7 @@ function formSubmit(props) {
*/ */
props = props || {}; props = props || {};
var data = props.data || props.form.serializeObject(); var data = props.data || props.form.serializeObject();
var redirect_to = props.redirect_to; var redirectTo = props.redirect_to || props.redirectTo;
$.ajax({ $.ajax({
url: props.url, url: props.url,
type: props.method || 'POST', type: props.method || 'POST',
...@@ -185,12 +185,8 @@ function formSubmit(props) { ...@@ -185,12 +185,8 @@ function formSubmit(props) {
contentType: props.content_type || "application/json; charset=utf-8", contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json" dataType: props.data_type || "json"
}).done(function (data, textState, jqXHR) { }).done(function (data, textState, jqXHR) {
if (redirect_to) { if (redirectTo) {
if (props.message) { location.href = redirectTo;
var messages = "ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
setCookie("messages", messages)
}
location.href = redirect_to;
} else if (typeof props.success === 'function') { } else if (typeof props.success === 'function') {
return props.success(data, textState, jqXHR); return props.success(data, textState, jqXHR);
} }
...@@ -254,7 +250,6 @@ function formSubmit(props) { ...@@ -254,7 +250,6 @@ function formSubmit(props) {
} }
$('.has-error').get(0).scrollIntoView(); $('.has-error').get(0).scrollIntoView();
} }
}) })
} }
...@@ -1032,6 +1027,62 @@ function rootNodeAddDom(ztree, callback) { ...@@ -1032,6 +1027,62 @@ function rootNodeAddDom(ztree, callback) {
}) })
} }
function APIExportCSV(props) {
/*
{
listUrl:
objectsId:
template:
table:
params:
}
*/
var _listUrl = props.listUrl;
var _objectsId = props.objectsId;
var _template = props.template;
var _table = props.table;
var _params = props.params || {};
var tableParams = _table.ajax.params();
var exportUrl = setUrlParam(_listUrl, 'format', 'csv');
if (_template) {
exportUrl = setUrlParam(exportUrl, 'template', _template)
}
for (var k in tableParams) {
if (datatableInternalParams.includes(k)) {
continue
}
if (!tableParams[k]) {
continue
}
exportUrl = setUrlParam(exportUrl, k, tableParams[k])
}
for (var k in _params) {
exportUrl = setUrlParam(exportUrl, k, tableParams[k])
}
if (!_objectsId) {
console.log(exportUrl);
window.open(exportUrl);
return
}
requestApi({
url: '/api/v1/common/resources/cache/',
data: JSON.stringify({resources: _objectsId}),
method: "POST",
flash_message: false,
success: function (data) {
exportUrl = setUrlParam(exportUrl, 'spm', data.spm);
console.log(exportUrl);
window.open(exportUrl);
},
failed: function () {
toastr.error(gettext('Export failed'));
}
});
}
function APIExportData(props) { function APIExportData(props) {
props = props || {}; props = props || {};
$.ajax({ $.ajax({
...@@ -1081,6 +1132,7 @@ function APIImportData(props) { ...@@ -1081,6 +1132,7 @@ function APIImportData(props) {
}, },
error: function (error) { error: function (error) {
var data = error.responseJSON; var data = error.responseJSON;
console.log(data);
if (data instanceof Array) { if (data instanceof Array) {
var html = ''; var html = '';
var li = ''; var li = '';
...@@ -1141,8 +1193,8 @@ function objectAttrsIsBool(obj, attrs) { ...@@ -1141,8 +1193,8 @@ function objectAttrsIsBool(obj, attrs) {
attrs.forEach(function (attr) { attrs.forEach(function (attr) {
if (!obj[attr]) { if (!obj[attr]) {
obj[attr] = false obj[attr] = false
} else if (['on', '1'].includes(obj[attr])) { } else {
obj[attr] = true obj[attr] = ['on', '1', 'true', 'True'].includes(obj[attr]);
} }
}) })
} }
......
/*!
* Ladda for jQuery
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2015 Hakim El Hattab, http://hakim.se
*/
!function(a,b){if(void 0===b)return console.error("jQuery required for Ladda.jQuery");var c=[];b=b.extend(b,{ladda:function(b){"stopAll"===b&&a.stopAll()}}),b.fn=b.extend(b.fn,{ladda:function(d){var e=c.slice.call(arguments,1);return"bind"===d?(e.unshift(b(this).selector),a.bind.apply(a,e)):b(this).each(function(){var c,f=b(this);void 0===d?f.data("ladda",a.create(this)):(c=f.data("ladda"),c[d].apply(c,e))}),this}})}(this.Ladda,this.jQuery);
\ No newline at end of file
/*!
* Ladda 1.0.0 (2016-03-08, 09:31)
* http://lab.hakim.se/ladda
* MIT licensed
*
* Copyright (C) 2016 Hakim El Hattab, http://hakim.se
*/
!function(a,b){"object"==typeof exports?module.exports=b(require("spin.js")):"function"==typeof define&&define.amd?define(["spin"],b):a.Ladda=b(a.Spinner)}(this,function(a){"use strict";function b(a){if("undefined"==typeof a)return void console.warn("Ladda button target must be defined.");if(/ladda-button/i.test(a.className)||(a.className+=" ladda-button"),a.hasAttribute("data-style")||a.setAttribute("data-style","expand-right"),!a.querySelector(".ladda-label")){var b=document.createElement("span");b.className="ladda-label",i(a,b)}var c,d=a.querySelector(".ladda-spinner");d||(d=document.createElement("span"),d.className="ladda-spinner"),a.appendChild(d);var e,f={start:function(){return c||(c=g(a)),a.setAttribute("disabled",""),a.setAttribute("data-loading",""),clearTimeout(e),c.spin(d),this.setProgress(0),this},startAfter:function(a){return clearTimeout(e),e=setTimeout(function(){f.start()},a),this},stop:function(){return a.removeAttribute("disabled"),a.removeAttribute("data-loading"),clearTimeout(e),c&&(e=setTimeout(function(){c.stop()},1e3)),this},toggle:function(){return this.isLoading()?this.stop():this.start(),this},setProgress:function(b){b=Math.max(Math.min(b,1),0);var c=a.querySelector(".ladda-progress");0===b&&c&&c.parentNode?c.parentNode.removeChild(c):(c||(c=document.createElement("div"),c.className="ladda-progress",a.appendChild(c)),c.style.width=(b||0)*a.offsetWidth+"px")},enable:function(){return this.stop(),this},disable:function(){return this.stop(),a.setAttribute("disabled",""),this},isLoading:function(){return a.hasAttribute("data-loading")},remove:function(){clearTimeout(e),a.removeAttribute("disabled",""),a.removeAttribute("data-loading",""),c&&(c.stop(),c=null);for(var b=0,d=j.length;d>b;b++)if(f===j[b]){j.splice(b,1);break}}};return j.push(f),f}function c(a,b){for(;a.parentNode&&a.tagName!==b;)a=a.parentNode;return b===a.tagName?a:void 0}function d(a){for(var b=["input","textarea","select"],c=[],d=0;d<b.length;d++)for(var e=a.getElementsByTagName(b[d]),f=0;f<e.length;f++)e[f].hasAttribute("required")&&c.push(e[f]);return c}function e(a,e){e=e||{};var f=[];"string"==typeof a?f=h(document.querySelectorAll(a)):"object"==typeof a&&"string"==typeof a.nodeName&&(f=[a]);for(var g=0,i=f.length;i>g;g++)!function(){var a=f[g];if("function"==typeof a.addEventListener){var h=b(a),i=-1;a.addEventListener("click",function(b){var f=!0,g=c(a,"FORM");if("undefined"!=typeof g)if("function"==typeof g.checkValidity)f=g.checkValidity();else for(var j=d(g),k=0;k<j.length;k++)""===j[k].value.replace(/^\s+|\s+$/g,"")&&(f=!1),"checkbox"!==j[k].type&&"radio"!==j[k].type||j[k].checked||(f=!1),"email"===j[k].type&&(f=/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(j[k].value));f&&(h.startAfter(1),"number"==typeof e.timeout&&(clearTimeout(i),i=setTimeout(h.stop,e.timeout)),"function"==typeof e.callback&&e.callback.apply(null,[h]))},!1)}}()}function f(){for(var a=0,b=j.length;b>a;a++)j[a].stop()}function g(b){var c,d,e=b.offsetHeight;0===e&&(e=parseFloat(window.getComputedStyle(b).height)),e>32&&(e*=.8),b.hasAttribute("data-spinner-size")&&(e=parseInt(b.getAttribute("data-spinner-size"),10)),b.hasAttribute("data-spinner-color")&&(c=b.getAttribute("data-spinner-color")),b.hasAttribute("data-spinner-lines")&&(d=parseInt(b.getAttribute("data-spinner-lines"),10));var f=.2*e,g=.6*f,h=7>f?2:3;return new a({color:c||"#fff",lines:d||12,radius:f,length:g,width:h,zIndex:"auto",top:"auto",left:"auto",className:""})}function h(a){for(var b=[],c=0;c<a.length;c++)b.push(a[c]);return b}function i(a,b){var c=document.createRange();c.selectNodeContents(a),c.surroundContents(b),a.appendChild(b)}var j=[];return{bind:e,create:b,stopAll:f}});
\ No newline at end of file
!function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return l[e]||(m.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d<k.length;d++)if(c=k[d]+b,void 0!==e[c])return c;return void 0!==e[b]?b:void 0}function e(a,b){for(var c in b)a.style[d(a,c)||c]=b[c];return a}function f(a){for(var b=1;b<arguments.length;b++){var c=arguments[b];for(var d in c)void 0===a[d]&&(a[d]=c[d])}return a}function g(a,b){return"string"==typeof a?a:a[b%a.length]}function h(a){this.opts=f(a||{},h.defaults,n)}function i(){function c(b,c){return a("<"+b+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',c)}m.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d<e.childNodes.length&&(e=e.childNodes[b+d],e=e&&e.firstChild,e=e&&e.firstChild,e&&(e.opacity=c))}}var j,k=["webkit","Moz","ms","O"],l={},m=function(){var c=a("style",{type:"text/css"});return b(document.getElementsByTagName("head")[0],c),c.sheet||c.styleSheet}(),n={lines:12,length:7,width:5,radius:10,rotate:0,corners:1,color:"#000",direction:1,speed:1,trail:100,opacity:.25,fps:20,zIndex:2e9,className:"spinner",top:"50%",left:"50%",position:"absolute"};h.defaults={},f(h.prototype,{spin:function(b){this.stop();var c=this,d=c.opts,f=c.el=e(a(0,{className:d.className}),{position:d.position,width:0,zIndex:d.zIndex});d.radius+d.length+d.width;if(e(f,{left:d.left,top:d.top}),b&&b.insertBefore(f,b.firstChild||null),f.setAttribute("role","progressbar"),c.lines(f,c.opts),!j){var g,h=0,i=(d.lines-1)*(1-d.direction)/2,k=d.fps,l=k/d.speed,m=(1-d.opacity)/(l*d.trail/100),n=l/d.lines;!function o(){h++;for(var a=0;a<d.lines;a++)g=Math.max(1-(h+(d.lines-a)*n)%l*m,d.opacity),c.opacity(f,a*d.direction+i,g,d);c.timeout=c.el&&setTimeout(o,~~(1e3/k))}()}return c},stop:function(){var a=this.el;return a&&(clearTimeout(this.timeout),a.parentNode&&a.parentNode.removeChild(a),this.el=void 0),this},lines:function(d,f){function h(b,c){return e(a(),{position:"absolute",width:f.length+f.width+"px",height:f.width+"px",background:b,boxShadow:c,transformOrigin:"left",transform:"rotate("+~~(360/f.lines*k+f.rotate)+"deg) translate("+f.radius+"px,0)",borderRadius:(f.corners*f.width>>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k<f.lines;k++)i=e(a(),{position:"absolute",top:1+~(f.width/2)+"px",transform:f.hwaccel?"translate3d(0,0,0)":"",opacity:f.opacity,animation:j&&c(f.opacity,f.trail,l+k*f.direction,f.lines)+" "+1/f.speed+"s linear infinite"}),f.shadow&&b(i,e(h("#000","0 0 4px #000"),{top:"2px"})),b(d,b(i,h(g(f.color,k),"0 0 1px rgba(0,0,0,.1)")));return d},opacity:function(a,b,c){b<a.childNodes.length&&(a.childNodes[b].style.opacity=c)}});var o=e(a("group"),{behavior:"url(#default#VML)"});return!d(o,"transform")&&o.adj?i():j=d(o,"animation"),h});
\ No newline at end of file
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block help_message %}
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-sm-3" id="split-left" style="padding-left: 3px;padding-right: 0">
{% include 'assets/_node_tree.html' %}
</div>
<div class="col-sm-9 animated fadeInRight" id="split-right">
<div class="tree-toggle" style="z-index: 10">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggleSpliter()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
{% block table_container %}
<table class="table table-striped table-bordered table-hover" id="{% block table_id %}editable{% endblock %}" >
<thead>
<tr>
{% block table_head %} {% endblock %}
</tr>
</thead>
<tbody>
{% block table_body %} {% endblock %}
</tbody>
</table>
{% endblock %}
</div>
</div>
</div>
</div>
<script>
var showTree = 1;
function toggleSpliter() {
if (showTree === 1) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-sm-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
showTree = 1;
});
} else {
console.log("hide")
$("#split-right").attr("class", "col-sm-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
showTree = 0;
}
}
</script>
{% endblock %}
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<title>{% block html_title %}{% endblock %}</title>
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
<style>
.passwordBox {
max-width: 560px;
margin: 0 auto;
padding: 100px 20px 20px 20px;
}
</style>
{% block custom_head_css_js %} {% endblock %}
</head>
<body class="gray-bg">
<div class="passwordBox animated fadeInDown">
<div class="row">
<div class="col-md-12">
<div class="ibox-content">
<img src="{{ LOGO_URL }}" style="margin: auto" width="50" height="50">
<h2 class="font-bold" style="display: inline">{% block title %}{% endblock %}</h2>
<h1></h1>
{% block content %} {% endblock %}
</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-12">
{% include '_copyright.html' %}
</div>
</div>
</div>
</body>
{% block custom_foot_js %} {% endblock %}
</html>
{% load i18n %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li id="li_csv_export">
<a id="btn_csv_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li id="li_csv_import">
<a id="btn_csv_import" data-toggle="modal" data-target="#csv_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li id="li_csv_update">
<a id="btn_csv_update" data-toggle="modal" data-target="#csv_update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% include '_csv_import_modal.html' %}
{% include '_csv_update_modal.html' %}
<script>
var csvTable = null;
var csvListUrl = null;
var csvExportCallback = null;
function initCsvImportExport(table, objectType, listUrl, hide) {
csvTable = table;
$(".csv_object_type").html(objectType);
csvListUrl = listUrl ? listUrl : csvTable.ajax.url();
if (hide && hide.length > 0) {
hide.forEach(function (v) {
$("#li_csv_" + v).hide();
})
}
}
var datatableInternalParams = ['draw', 'limit', 'order', 'offset'];
$(document).ready(function () {
}).on('click', '#btn_csv_export', function () {
var selectedObjects = csvTable.selected;
function _export() {
APIExportCSV({
listUrl: csvListUrl,
objectsId: selectedObjects,
table: csvTable
});
}
if (csvExportCallback) {
csvExportCallback(_export)
} else {
_export();
}
})
</script>
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}csv_import_modal{% endblock %}
{% block modal_title%}<span class="csv_object_type">csv</span> {% trans 'Import' %}{% endblock %}
{% block modal_confirm_id %}btn_csv_import_confirm{% endblock %}
{% block modal_body %}
<form method="post" id="fm_import">
{% csrf_token %}
<div class="form-group">
<label class="control-label">{% trans "Download the imported template or use the exported CSV file format" %}</label>
<a id="csv_download_template" style="display: block">{% trans 'Download the import template' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_file">{% trans "Select the CSV file to import" %}</label>
<input id="id_csv_file" type="file" name="file" />
</div>
</form>
<div style="max-height: 300px;overflow: auto">
<p class="text-success" id="success_created"></p>
<p id="success_created_detail"></p>
<p class="text-danger" id="created_failed"></p>
<p id="created_failed_detail"></p>
</div>
<script>
$(document).ready(function () {
}).on("click", '#csv_download_template', function () {
var theUrl ="csvImportURL?format=csv&template=import&limit=1" ;
theUrl = theUrl.replace("csvImportURL", csvListUrl);
window.open(theUrl)
}).on('click', '#btn_csv_import_confirm', function () {
var file = document.getElementById('id_csv_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
APIImportData({
url: csvListUrl,
method: "POST",
body: file,
data_table: csvTable
});
})
</script>
{% endblock %}
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}csv_update_modal{% endblock %}
{% block modal_confirm_id %}btn_csv_update_confirm{% endblock %}
{% block modal_title%}<span class="csv_object_type">csv</span> {% trans 'Update' %}{% endblock %}
{% block modal_body %}
<form method="post" id="fm_import">
{% csrf_token %}
<div class="form-group">
<label class="control-label">{% trans "Download the update template or use the exported CSV file format" %}</label>
<a id="csv_download_update_template" style="display: block">{% trans 'Download the update template' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="update_file">{% trans "Select the CSV file to import" %}</label>
<input id="csv_update_file" type="file" name="file" />
</div>
</form>
<div>
<p class="text-warning" id="success_updated"></p>
<p id="success_updated_detail"></p>
<p class="text-danger" id="updated_failed"></p>
<p id="updated_failed_detail"></p>
</div>
<script>
$(document).ready(function () {
}).on('click', '#csv_download_update_template', function () {
var objectsId = csvTable.selected;
APIExportCSV({
listUrl: csvListUrl,
objectsId: objectsId,
template: 'update',
table: csvTable
});
}).on('click', '#btn_csv_update_confirm', function () {
var file = document.getElementById('csv_update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
APIImportData({
url: csvListUrl,
method: "PUT",
body: file,
data_table: csvTable
});
})
</script>
{% endblock %}
...@@ -45,6 +45,9 @@ ...@@ -45,6 +45,9 @@
<li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li> <li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li>
<li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Labels' %}</a></li> <li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Labels' %}</a></li>
<li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li> <li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li>
{% if request.user.is_superuser %}
<li id="platform"><a href="{% url 'assets:platform-list' %}">{% trans 'Platform list' %}</a></li>
{% endif %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
......
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> {{ JMS_TITLE }} </title>
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
{# <link rel="stylesheet" href="{% static 'fonts/font_otp/iconfont.css' %}" />#}
<link rel="stylesheet" href="{% static 'css/otp.css' %}" />
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
</head>
<body>
<header>
<div class="logo">
<a href="{% url 'index' %}">
<img src="{{ LOGO_URL }}" alt="" width="50px" height="50px"/>
</a>
<a href="{% url 'index' %}">{{ JMS_TITLE }}</a>
</div>
<div>
<a href="{% url 'index' %}">{% trans 'Home page' %}</a>
<b></b>
<a href="http://docs.jumpserver.org/zh/docs/">{% trans 'Docs' %}</a>
<b></b>
<a href="https://www.github.com/jumpserver/">GitHub</a>
</div>
</header>
<body>
{% block body %}
{% endblock %}
</body>
<footer>
<div class="" style="margin-top: 100px;">
{% include '_copyright.html' %}
</div>
</footer>
</body>
</html>
{% load i18n %} {% extends '_base_only_content.html' %}
{% load static %} {% load static %}
<!DOCTYPE html> {% load i18n %}
<html> {% block html_title %} {{ title }} {% endblock %}
{% block title %} {{ title }}{% endblock %}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script>
</head> {% block custom_head_css_js %}
<style>
.passwordBox {
max-width: 660px;
}
</style>
{% endblock %}
<body class="gray-bg"> {% block content %}
<div class="passwordBox2 animated fadeInDown"> <div>
<div class="row"> {% if errors %}
<div class="col-md-12"> <p>
<div class="ibox-content"> <div class="alert alert-danger">
<div> {{ errors }}
<img src="{{ LOGO_URL }}" style="margin: auto" width="82" height="82"> </div>
<h2 style="display: inline"> </p>
{{ JMS_TITLE }} {% endif %}
</h2>
</div>
{% if errors %}
<p>
<div class="alert alert-danger">
{{ errors }}
</div>
</p>
{% endif %}
{% if messages %} {% if messages %}
<p> <p>
<div class="alert alert-success" id="messages"> <div class="alert alert-success" id="messages">
{{ messages|safe }} {{ messages|safe }}
</div>
</p>
{% endif %}
<div class="row">
<div class="col-lg-3">
<a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">{% trans 'Return' %}</a>
</div>
</div>
</div>
</div> </div>
</div> </p>
<hr/> {% endif %}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-lg-3">
{% include '_copyright.html' %} <a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">{% trans 'Return' %}</a>
</div> </div>
</div> </div>
</div> </div>
</body> {% endblock %}
{% block custom_foot_js %}
<script> <script>
var time = '{{ interval }}'; var time = '{{ interval }}';
if (!time){ if (!time) {
time = 5; time = 5;
} else { } else {
time = parseInt(time); time = parseInt(time);
} }
function redirect_page() { function redirect_page() {
if (time >= 0) { if (time >= 0) {
var messages = '{{ messages|safe }}, <b>' + time +'</b> ...'; var messages = '{{ messages|safe }}, <b>' + time + '</b> ...';
$('#messages').html(messages); $('#messages').html(messages);
time--; time--;
setTimeout(redirect_page, 1000); setTimeout(redirect_page, 1000);
} } else {
else {
window.location.href = "{{ redirect_url }}"; window.location.href = "{{ redirect_url }}";
} }
} }
{% if auto_redirect %} {% if auto_redirect %}
window.onload = redirect_page; window.onload = redirect_page;
{% endif %} {% endif %}
</script> </script>
</html> {% endblock %}
# Generated by Django 2.2.7 on 2019-12-06 02:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0018_auto_20191202_1010'),
]
operations = [
migrations.AlterField(
model_name='replaystorage',
name='type',
field=models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('ceph', 'Ceph'), ('swift', 'Swift'), ('oss', 'OSS'), ('azure', 'Azure')], default='server', max_length=16, verbose_name='Type'),
),
]
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from ..serializers import ( from ..serializers import UserGroupSerializer
UserGroupSerializer,
UserGroupListSerializer,
UserGroupUpdateMemberSerializer,
)
from ..models import UserGroup from ..models import UserGroup
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from common.permissions import IsOrgAdmin from common.permissions import IsOrgAdmin
__all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi'] __all__ = ['UserGroupViewSet']
class UserGroupViewSet(OrgBulkModelViewSet): class UserGroupViewSet(OrgBulkModelViewSet):
model = UserGroup model = UserGroup
filter_fields = ("name",) filter_fields = ("name",)
search_fields = filter_fields search_fields = filter_fields
serializer_class = UserGroupSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = UserGroupSerializer
def get_serializer_class(self):
if self.action in ("list", 'retrieve') and \
self.request.query_params.get("display"):
return UserGroupListSerializer
return self.serializer_class
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
model = UserGroup
serializer_class = UserGroupUpdateMemberSerializer
permission_classes = (IsOrgAdmin,)
...@@ -24,7 +24,7 @@ from ..signals import post_user_create ...@@ -24,7 +24,7 @@ from ..signals import post_user_create
logger = get_logger(__name__) logger = get_logger(__name__)
__all__ = [ __all__ = [
'UserViewSet', 'UserChangePasswordApi', 'UserUpdateGroupApi', 'UserViewSet', 'UserChangePasswordApi',
'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi', 'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi',
'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi', 'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi',
] ]
...@@ -39,8 +39,10 @@ class UserQuerysetMixin: ...@@ -39,8 +39,10 @@ class UserQuerysetMixin:
class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
filter_fields = ('username', 'email', 'name', 'id') filter_fields = ('username', 'email', 'name', 'id')
search_fields = filter_fields search_fields = filter_fields
serializer_class = serializers.UserSerializer serializer_classes = {
serializer_display_class = serializers.UserDisplaySerializer 'default': serializers.UserSerializer,
'display': serializers.UserDisplaySerializer
}
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
def get_queryset(self): def get_queryset(self):
...@@ -99,11 +101,6 @@ class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView): ...@@ -99,11 +101,6 @@ class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
user.save() user.save()
class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
serializer_class = serializers.UserUpdateGroupSerializer
permission_classes = (IsOrgAdmin,)
class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView): class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
......
# -*- coding: utf-8 -*-
#
from .user import *
from .group import *
from .profile import *
This diff is collapsed.
This diff is collapsed.
...@@ -4,6 +4,7 @@ import uuid ...@@ -4,6 +4,7 @@ import uuid
from django.db import models, IntegrityError from django.db import models, IntegrityError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
__all__ = ['UserGroup'] __all__ = ['UserGroup']
...@@ -20,6 +21,10 @@ class UserGroup(OrgModelMixin): ...@@ -20,6 +21,10 @@ class UserGroup(OrgModelMixin):
def __str__(self): def __str__(self):
return self.name return self.name
@lazyproperty
def users_amount(self):
return self.users.count()
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [('org_id', 'name'),] unique_together = [('org_id', 'name'),]
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -2,3 +2,4 @@ from django.dispatch import Signal ...@@ -2,3 +2,4 @@ from django.dispatch import Signal
post_user_create = Signal(providing_args=('user',)) post_user_create = Signal(providing_args=('user',))
post_user_change_password = Signal(providing_args=('user',))
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# #
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save, m2m_changed from django.db.models.signals import m2m_changed
from common.utils import get_logger from common.utils import get_logger
from .signals import post_user_create from .signals import post_user_create
......
This diff is collapsed.
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update user group" %}{% endblock %}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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