mirror of https://github.com/jumpserver/jumpserver
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,避免重复计算 - 修改用户组详情页,影响 - 用户组增加删除用户pull/3550/head
parent
4ac4b517f4
commit
e1919d0a62
|
@ -4,24 +4,27 @@
|
|||
import random
|
||||
|
||||
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 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 import generics
|
||||
from ..models import Asset, Node
|
||||
from ..models import Asset, Node, Platform
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
test_asset_connectivity_manual
|
||||
from ..tasks import (
|
||||
update_asset_hardware_info_manual, test_asset_connectivity_manual
|
||||
)
|
||||
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet',
|
||||
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||
'AssetGatewayApi',
|
||||
'AssetGatewayApi', 'AssetPlatformViewSet',
|
||||
]
|
||||
|
||||
|
||||
|
@ -53,6 +56,34 @@ class AssetViewSet(OrgBulkModelViewSet):
|
|||
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):
|
||||
"""
|
||||
Refresh asset hardware info
|
||||
|
|
|
@ -177,7 +177,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi):
|
|||
if not include_assets:
|
||||
return queryset
|
||||
assets = self.instance.get_assets().only(
|
||||
"id", "hostname", "ip", 'platform', "os",
|
||||
"id", "hostname", "ip", "os",
|
||||
"org_id", "protocols",
|
||||
)
|
||||
for asset in assets:
|
||||
|
|
|
@ -5,3 +5,4 @@ from .label import *
|
|||
from .user import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .platform import *
|
||||
|
|
|
@ -6,13 +6,13 @@ from django.utils.translation import gettext_lazy as _
|
|||
from common.utils import get_logger
|
||||
from orgs.mixins.forms import OrgModelForm
|
||||
|
||||
from ..models import Asset, Node
|
||||
from ..models import Asset
|
||||
from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
|
||||
'AssetCreateUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
|
||||
]
|
||||
|
||||
|
||||
|
@ -27,17 +27,27 @@ class ProtocolForm(forms.Form):
|
|||
)
|
||||
|
||||
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
class AssetCreateUpdateForm(OrgModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.data:
|
||||
return
|
||||
self.set_platform_to_name()
|
||||
self.set_fields_queryset()
|
||||
|
||||
def set_fields_queryset(self):
|
||||
nodes_field = self.fields['nodes']
|
||||
nodes_choices = []
|
||||
if self.instance:
|
||||
nodes_field.choices = [(n.id, n.full_value) for n in
|
||||
self.instance.nodes.all()]
|
||||
else:
|
||||
nodes_field.choices = []
|
||||
nodes_choices = [
|
||||
(n.id, n.full_value) for n in
|
||||
self.instance.nodes.all()
|
||||
]
|
||||
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):
|
||||
nodes_field = self.fields['nodes']
|
||||
|
@ -49,7 +59,7 @@ class AssetCreateForm(OrgModelForm):
|
|||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
'domain',
|
||||
'domain', 'number',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
|
@ -64,52 +74,8 @@ class AssetCreateForm(OrgModelForm):
|
|||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
}
|
||||
labels = {
|
||||
'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')
|
||||
'platform': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Platform')
|
||||
}),
|
||||
}
|
||||
labels = {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# -*- 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")
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# 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)
|
||||
]
|
|
@ -0,0 +1,47 @@
|
|||
# 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
|
|||
from django.db import models
|
||||
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 .utils import Connectivity
|
||||
|
||||
__all__ = ['Asset', 'ProtocolsMixin']
|
||||
__all__ = ['Asset', 'ProtocolsMixin', 'Platform']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -37,6 +39,13 @@ def default_node():
|
|||
return None
|
||||
|
||||
|
||||
class AssetManager(OrgManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().annotate(
|
||||
platform_base=models.F('platform__base')
|
||||
)
|
||||
|
||||
|
||||
class AssetQuerySet(models.QuerySet):
|
||||
def active(self):
|
||||
return self.filter(is_active=True)
|
||||
|
@ -119,6 +128,41 @@ class NodesRelationMixin:
|
|||
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):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
|
@ -138,9 +182,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
choices=ProtocolsMixin.PROTOCOL_CHOICES,
|
||||
verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
|
||||
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)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
@ -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'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
||||
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||
_connectivity = None
|
||||
|
||||
def __str__(self):
|
||||
|
@ -191,19 +234,20 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
return True, warning
|
||||
|
||||
def is_windows(self):
|
||||
if self.platform in ("Windows", "Windows2016"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return self.platform_base == "Windows"
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows", "Windows2016", "Other"):
|
||||
if self.platform_base not in ("Windows", "Windows2016", "Other"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
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
|
||||
def cpu_info(self):
|
||||
|
@ -264,9 +308,9 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
def as_tree_node(self, parent_node):
|
||||
from common.tree import TreeNode
|
||||
icon_skin = 'file'
|
||||
if self.platform.lower() == 'windows':
|
||||
if self.platform_base.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
elif self.platform.lower() == 'linux':
|
||||
elif self.platform_base.lower() == 'linux':
|
||||
icon_skin = 'linux'
|
||||
data = {
|
||||
'id': str(self.id),
|
||||
|
@ -283,7 +327,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'protocols': self.protocols_as_list,
|
||||
'platform': self.platform,
|
||||
'platform': self.platform_base,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from ..models import Asset, Node, Label
|
||||
from ..models import Asset, Node, Label, Platform
|
||||
from ..const import (
|
||||
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
|
||||
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
|
||||
|
@ -16,7 +16,8 @@ from .base import ConnectivitySerializer
|
|||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetSimpleSerializer',
|
||||
'ProtocolsField',
|
||||
'ProtocolsField', 'PlatformSerializer',
|
||||
'AssetDetailSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -65,6 +66,9 @@ class ProtocolsField(serializers.ListField):
|
|||
|
||||
|
||||
class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||
platform = serializers.SlugRelatedField(
|
||||
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
|
||||
)
|
||||
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
|
@ -111,7 +115,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
queryset = queryset.prefetch_related(
|
||||
Prefetch('nodes', queryset=Node.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
|
||||
|
||||
def compatible_with_old_protocol(self, validated_data):
|
||||
|
@ -139,6 +143,21 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
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):
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{% 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 %}
|
|
@ -1,4 +0,0 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update admin user" %}{% endblock %}
|
|
@ -1,6 +0,0 @@
|
|||
{% 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 @@
|
|||
|
||||
<div class="wrapper wrapper-content">
|
||||
<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-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager ">
|
||||
|
@ -37,7 +37,7 @@
|
|||
</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">
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
|
||||
<thead>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{% 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 %}
|
|
@ -1,4 +0,0 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update system user" %}{% endblock %}
|
|
@ -5,28 +5,7 @@
|
|||
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
|
||||
{% endblock %}
|
||||
{% block table_search %}
|
||||
<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>
|
||||
<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>
|
||||
{% include '_csv_import_export.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
|
@ -42,9 +21,6 @@
|
|||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</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 'Action' %}</th>
|
||||
</tr>
|
||||
|
@ -52,8 +28,6 @@
|
|||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_admin_user_import_modal.html' %}
|
||||
{% include 'assets/_admin_user_update_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
|
@ -80,14 +54,13 @@ function initTable() {
|
|||
{data: "comment"}, {data: "id", orderable: false, width: "100px"}
|
||||
]
|
||||
};
|
||||
admin_user_table = jumpserver.initServerSideDataTable(options);
|
||||
return admin_user_table
|
||||
return jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
admin_user_table = initTable();
|
||||
initCsvImportExport(admin_user_table, "{% trans "Admin user" %}")
|
||||
})
|
||||
|
||||
.on('click', '.btn_admin_user_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $("#admin_user_list_table").DataTable();
|
||||
|
@ -100,69 +73,5 @@ $(document).ready(function(){
|
|||
}, 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>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,131 +1,52 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base_asset_tree_list.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block help_message %}
|
||||
{# <div class="alert alert-info help-message">#}
|
||||
{# <button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>#}
|
||||
{# 左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产#}
|
||||
{% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %}
|
||||
{# </div>#}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
{# <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">#}
|
||||
{# <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>#}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
div#rMenu {
|
||||
position:absolute;
|
||||
visibility:hidden;
|
||||
text-align: left;
|
||||
{#top: 100%;#}
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
{#float: left;#}
|
||||
padding: 0 0;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_processing {
|
||||
opacity: .9;
|
||||
border: none;
|
||||
}
|
||||
div#rMenu li{
|
||||
margin: 1px 0;
|
||||
cursor: pointer;
|
||||
list-style: none outside none;
|
||||
}
|
||||
.dropdown a:hover {
|
||||
background-color: #f1f1f1
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px;padding-right: 0">
|
||||
{% include 'assets/_node_tree.html' %}
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="tree-toggle" style="z-index: 9999">
|
||||
<div class="btn btn-sm btn-primary tree-toggle-btn">
|
||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mail-box-header">
|
||||
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||
<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>
|
||||
<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>
|
||||
<div class="btn-group" style="float: right">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu labels">
|
||||
{% for label in labels %}
|
||||
<li><a style="font-weight: bolder">{{ label.name }}#{{ label.value }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="remove">{% trans 'Remove from this node' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||
{% include '_csv_import_export.html' %}
|
||||
<div class="btn-group" style="float: right">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu labels">
|
||||
{% for label in labels %}
|
||||
<li><a style="font-weight: bolder">{{ label.name }}#{{ label.value }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="remove">{% trans 'Remove from this node' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'assets/_asset_update_modal.html' %}
|
||||
{% include 'assets/_asset_import_modal.html' %}
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
{% include 'assets/_node_detail_modal.html' %}
|
||||
{% endblock %}
|
||||
|
@ -205,22 +126,6 @@ function initTree() {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#split-left").hide(500, function () {
|
||||
$("#split-right").attr("class", "col-lg-12");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||
show = 1;
|
||||
});
|
||||
} else {
|
||||
$("#split-right").attr("class", "col-lg-9");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||
$("#split-left").show(500);
|
||||
show = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function onNodeSelected(event, treeNode) {
|
||||
current_node = treeNode;
|
||||
current_node_id = treeNode.meta.node.id;
|
||||
|
@ -261,7 +166,8 @@ function onAssetModalConfirmAddAssetToNode(table) {
|
|||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
asset_table = initTable();
|
||||
initCsvImportExport(asset_table, "{% trans "Asset" %}");
|
||||
initTree();
|
||||
|
||||
if(getCookie('show_current_asset') === '1'){
|
||||
|
@ -282,81 +188,6 @@ $(document).ready(function(){
|
|||
$("#asset_list_table_filter input").val(val);
|
||||
asset_table.search(val).draw();
|
||||
})
|
||||
.on('click', '.btn_export', function () {
|
||||
var assets = asset_table.selected;
|
||||
var data = {
|
||||
'resources': assets
|
||||
};
|
||||
var search = $("input[type='search']").val();
|
||||
var props = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success_url: "{% url 'api-assets:asset-list' %}",
|
||||
format: 'csv',
|
||||
params: {
|
||||
search: search,
|
||||
node_id: current_node_id || '',
|
||||
show_current_asset: getCookie('show_current_asset')
|
||||
}
|
||||
};
|
||||
APIExportData(props);
|
||||
})
|
||||
.on('click', '#btn_import_confirm', function () {
|
||||
var file = document.getElementById('id_file').files[0];
|
||||
if(!file){
|
||||
toastr.error("{% trans "Please select file" %}");
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:asset-list' %}";
|
||||
if (current_node_id){
|
||||
url = setUrlParam(url, 'node_id', current_node_id);
|
||||
}
|
||||
var data_table = $('#asset_list_table').DataTable();
|
||||
|
||||
APIImportData({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: file,
|
||||
data_table: data_table
|
||||
});
|
||||
})
|
||||
.on('click', '#download_update_template', function () {
|
||||
var assets = asset_table.selected;
|
||||
var data = {
|
||||
'resources': assets
|
||||
};
|
||||
var search = $("input[type='search']").val();
|
||||
var props = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success_url: "{% url 'api-assets:asset-list' %}?format=csv&template=update",
|
||||
format: 'csv',
|
||||
params: {
|
||||
search: search,
|
||||
node_id: current_node_id || ''
|
||||
}
|
||||
};
|
||||
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:asset-list' %}";
|
||||
if (current_node_id){
|
||||
url = setUrlParam(url, 'node_id', current_node_id);
|
||||
}
|
||||
var data_table = $('#asset_list_table').DataTable();
|
||||
|
||||
APIImportData({
|
||||
url: url,
|
||||
method: "PUT",
|
||||
body: file,
|
||||
data_table: data_table
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-create-asset', function () {
|
||||
var url = "{% url 'assets:asset-create' %}";
|
||||
if (current_node_id) {
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
{% 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 %}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,75 @@
|
|||
{% 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 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
<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>
|
||||
<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>
|
||||
{% include '_csv_import_export.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
|
@ -57,8 +36,6 @@
|
|||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_system_user_import_modal.html' %}
|
||||
{% include 'assets/_system_user_update_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
@ -89,14 +66,13 @@ function initTable() {
|
|||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
system_user_table = jumpserver.initServerSideDataTable(options);
|
||||
return system_user_table
|
||||
return jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
system_user_table = initTable();
|
||||
initCsvImportExport(system_user_table, "{% trans 'System user' %}")
|
||||
})
|
||||
|
||||
.on('click', '.btn_admin_user_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#cluster_list_table').DataTable();
|
||||
|
@ -108,125 +84,6 @@ $(document).ready(function(){
|
|||
$data_table.ajax.reload();
|
||||
}, 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>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ app_name = 'assets'
|
|||
|
||||
router = BulkRouter()
|
||||
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'system-users', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'labels', api.LabelViewSet, 'label')
|
||||
|
@ -37,6 +38,8 @@ urlpatterns = [
|
|||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
path('assets/<uuid:pk>/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/',
|
||||
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
|
||||
|
|
|
@ -16,6 +16,11 @@ urlpatterns = [
|
|||
# Asset user view
|
||||
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
|
||||
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# coding:utf-8
|
||||
from .asset import *
|
||||
from .platform import *
|
||||
from .system_user import *
|
||||
from .admin_user import *
|
||||
from .label import *
|
||||
|
|
|
@ -74,7 +74,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
|
|||
|
||||
class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetCreateForm
|
||||
form_class = forms.AssetCreateUpdateForm
|
||||
template_name = 'assets/asset_create.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
@ -110,7 +110,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
|
|||
|
||||
class AssetUpdateView(PermissionsMixin, UpdateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetUpdateForm
|
||||
form_class = forms.AssetCreateUpdateForm
|
||||
template_name = 'assets/asset_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.views.generic import TemplateView, CreateView, \
|
||||
UpdateView, DeleteView, DetailView
|
||||
from django.views.generic import (
|
||||
TemplateView, CreateView, UpdateView, DeleteView, DetailView
|
||||
)
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
# -*- 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
|
|||
from jumpserver.utils import current_request
|
||||
from common.utils import get_request_ip, get_logger, get_syslogger
|
||||
from users.models import User
|
||||
from users.signals import post_user_change_password
|
||||
from authentication.signals import post_auth_failed, post_auth_success
|
||||
from terminal.models import Session, Command
|
||||
from common.utils.encode import model_to_json
|
||||
|
@ -18,7 +19,7 @@ from . import models
|
|||
from .tasks import write_login_log_async
|
||||
|
||||
logger = get_logger(__name__)
|
||||
sys_logger = get_syslogger("audits")
|
||||
sys_logger = get_syslogger(__name__)
|
||||
json_render = JSONRenderer()
|
||||
|
||||
|
||||
|
@ -50,7 +51,7 @@ def create_operate_log(action, sender, resource):
|
|||
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):
|
||||
if instance._meta.object_name == 'User' and \
|
||||
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
|
|||
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):
|
||||
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
|
||||
def on_user_change_password(sender, instance=None, **kwargs):
|
||||
if hasattr(instance, '_set_password'):
|
||||
if not current_request or not current_request.user.is_authenticated:
|
||||
return
|
||||
with transaction.atomic():
|
||||
models.PasswordChangeLog.objects.create(
|
||||
user=instance, change_by=current_request.user,
|
||||
remote_addr=get_request_ip(current_request),
|
||||
)
|
||||
@receiver(post_user_change_password, sender=User)
|
||||
def on_user_change_password(sender, user=None, **kwargs):
|
||||
if not current_request:
|
||||
remote_addr = '127.0.0.1'
|
||||
change_by = 'System'
|
||||
else:
|
||||
remote_addr = get_request_ip(current_request)
|
||||
if not current_request.user.is_authenticated:
|
||||
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):
|
||||
|
@ -95,7 +102,7 @@ def on_audits_log_create(sender, instance=None, **kwargs):
|
|||
else:
|
||||
return
|
||||
|
||||
data = model_to_json(instance)
|
||||
data = model_to_json(instance, indent=None)
|
||||
msg = "{} - {}".format(category, data)
|
||||
sys_logger.info(msg)
|
||||
|
||||
|
|
|
@ -102,47 +102,47 @@
|
|||
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
jumpserver.initStaticTable('#login_log_table');
|
||||
$('#date .input-daterange').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
jumpserver.initStaticTable('#login_log_table');
|
||||
$('#date .input-daterange').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
|
||||
});
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true,
|
||||
width: 'auto'
|
||||
});
|
||||
});
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true,
|
||||
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();
|
||||
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>
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -2,11 +2,8 @@
|
|||
#
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from captcha.fields import CaptchaField
|
||||
from django.conf import settings
|
||||
from users.utils import get_login_failed_count
|
||||
|
||||
|
||||
class UserLoginForm(forms.Form):
|
||||
|
|
|
@ -27,10 +27,17 @@ class IDSpmFilterMixin:
|
|||
|
||||
class SerializerMixin:
|
||||
def get_serializer_class(self):
|
||||
if self.request.method.lower() == 'get' and\
|
||||
self.request.query_params.get('draw') \
|
||||
and hasattr(self, 'serializer_display_class'):
|
||||
return self.serializer_display_class
|
||||
serializer_class = None
|
||||
if hasattr(self, 'serializer_classes') and \
|
||||
isinstance(self.serializer_classes, dict):
|
||||
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()
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from django.conf import settings
|
||||
from django.dispatch import receiver
|
||||
|
@ -10,13 +11,13 @@ from django.db import connection
|
|||
from django.conf import LazySettings
|
||||
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 .signals import django_ready
|
||||
|
||||
pattern = re.compile(r'FROM `(\w+)`')
|
||||
logger = get_logger(__name__)
|
||||
logger = logging.getLogger("jumpserver.common")
|
||||
DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1'
|
||||
|
||||
|
||||
|
@ -50,19 +51,29 @@ def on_request_finished_logging_db_query(sender, **kwargs):
|
|||
counters['total'].time += float(time)
|
||||
|
||||
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:
|
||||
logger.debug("Query {:3} times using {:.2f}s {}".format(
|
||||
counter.counter, counter.time, name)
|
||||
)
|
||||
|
||||
|
||||
@receiver(request_finished)
|
||||
def on_request_finished_release_local(sender, **kwargs):
|
||||
thread_local.__release_local__()
|
||||
|
||||
|
||||
if settings.DEBUG and DEBUG_DB:
|
||||
request_finished.connect(on_request_finished_logging_db_query)
|
||||
else:
|
||||
request_finished.connect(on_request_finished_release_local)
|
||||
|
||||
|
||||
@receiver(django_ready)
|
||||
|
|
|
@ -27,12 +27,12 @@ def combine_seq(s1, s2, callback=None):
|
|||
return seq
|
||||
|
||||
|
||||
def get_logger(name=None):
|
||||
def get_logger(name=''):
|
||||
return logging.getLogger('jumpserver.%s' % name)
|
||||
|
||||
|
||||
def get_syslogger(name=None):
|
||||
return logging.getLogger('jms.%s' % name)
|
||||
def get_syslogger(name=''):
|
||||
return logging.getLogger('syslog.%s' % name)
|
||||
|
||||
|
||||
def timesince(dt, since='', default="just now"):
|
||||
|
|
|
@ -184,6 +184,7 @@ class Config(dict):
|
|||
'ASSETS_PERM_CACHE_ENABLE': False,
|
||||
'SYSLOG_ADDR': '', # '192.168.0.1:514'
|
||||
'SYSLOG_FACILITY': 'user',
|
||||
'SYSLOG_SOCKTYPE': 2,
|
||||
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
|
||||
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
|
||||
'FLOWER_URL': "127.0.0.1:5555",
|
||||
|
@ -282,10 +283,10 @@ class DynamicConfig:
|
|||
]
|
||||
if self.get('AUTH_LDAP'):
|
||||
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.OpenIDAuthorizationCodeBackend')
|
||||
if self.get('AUTH_RADIUS'):
|
||||
if self.static_config.get('AUTH_RADIUS'):
|
||||
backends.insert(0, 'authentication.backends.radius.RadiusBackend')
|
||||
return backends
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ LOGGING = {
|
|||
'propagate': False,
|
||||
},
|
||||
'jumpserver': {
|
||||
'handlers': ['console', 'file', 'syslog'],
|
||||
'handlers': ['console', 'file'],
|
||||
'level': LOG_LEVEL,
|
||||
},
|
||||
'ops.ansible_api': {
|
||||
|
@ -92,7 +92,7 @@ LOGGING = {
|
|||
'handlers': ['console', 'file'],
|
||||
'level': "INFO",
|
||||
},
|
||||
'jms.audits': {
|
||||
'syslog': {
|
||||
'handlers': ['syslog'],
|
||||
'level': 'INFO'
|
||||
},
|
||||
|
@ -110,6 +110,7 @@ if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
|
|||
'class': 'logging.handlers.SysLogHandler',
|
||||
'facility': CONFIG.SYSLOG_FACILITY,
|
||||
'address': (host, int(port)),
|
||||
'socktype': CONFIG.SYSLOG_SOCKTYPE,
|
||||
})
|
||||
|
||||
if not os.path.isdir(LOG_DIR):
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -58,7 +58,7 @@
|
|||
{% block content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
<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-content mailbox-content"
|
||||
style="padding-top: 0;padding-left: 1px">
|
||||
|
@ -71,7 +71,7 @@
|
|||
</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="btn btn-sm btn-primary tree-toggle-btn"
|
||||
onclick="toggle()">
|
||||
|
@ -87,14 +87,14 @@
|
|||
style="height: 100%;width: 100%"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-10">
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group"
|
||||
style="height: 100%; width: 100%">
|
||||
<textarea class="form-control"
|
||||
id="command-text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<div class="col-sm-2">
|
||||
<select class="select2 form-control"
|
||||
id="system-users-select">
|
||||
{% for s in system_users %}
|
||||
|
@ -199,12 +199,12 @@
|
|||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#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");
|
||||
show = 1;
|
||||
});
|
||||
} 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");
|
||||
$("#split-left").show(500);
|
||||
show = 0;
|
||||
|
|
|
@ -23,8 +23,10 @@ class AssetPermissionViewSet(OrgModelViewSet):
|
|||
资产授权列表的增删改查api
|
||||
"""
|
||||
model = AssetPermission
|
||||
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
||||
serializer_display_class = serializers.AssetPermissionListSerializer
|
||||
serializer_classes = {
|
||||
'default': serializers.AssetPermissionCreateUpdateSerializer,
|
||||
'display': serializers.AssetPermissionListSerializer
|
||||
}
|
||||
filter_fields = ['name']
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base_asset_tree_list.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
|
@ -12,56 +12,36 @@
|
|||
.toggle {
|
||||
cursor: pointer;
|
||||
}
|
||||
.detail-key {
|
||||
width: 70px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px;padding-right: 0">
|
||||
{% include 'assets/_node_tree.html' %}
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="tree-toggle">
|
||||
<div class="btn btn-sm btn-primary tree-toggle-btn">
|
||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mail-box-header">
|
||||
<div class="btn-group uc pull-left m-r-5">
|
||||
<button class="btn btn-sm btn-primary btn-create-permission">
|
||||
{% trans "Create permission" %}
|
||||
</button>
|
||||
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<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>
|
||||
|
||||
{% block table_container %}
|
||||
<div class="btn-group uc pull-left m-r-5">
|
||||
<button class="btn btn-sm btn-primary btn-create-permission">
|
||||
{% trans "Create permission" %}
|
||||
</button>
|
||||
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<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>
|
||||
{% include '_filter_dropdown.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
||||
user_permission_urlpatterns = [
|
||||
path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
|
||||
path('<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
||||
path('assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
|
||||
|
||||
# Assets as tree
|
||||
path('users/<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('<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
|
||||
path('assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
|
||||
|
||||
# Nodes
|
||||
path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||
path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
|
||||
path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||
path('nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
|
||||
|
||||
# Node children
|
||||
path('users/<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('<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
|
||||
path('nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
|
||||
|
||||
# Node as tree
|
||||
path('users/<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('<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
|
||||
path('nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-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('users/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
|
||||
path('<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-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
|
||||
path('users/<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('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
|
||||
path('nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-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('users/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-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('nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
|
||||
|
||||
# Node assets
|
||||
path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||
path('users/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||
path('nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||
|
||||
# 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 = [
|
||||
# 查询某个用户组授权的资产和资产组
|
||||
path('user-groups/<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('user-groups/<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('user-groups/<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/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||
path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
||||
path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
|
||||
path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
|
||||
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
|
||||
path('<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
|
||||
]
|
||||
|
||||
permission_urlpatterns = [
|
||||
# 授权规则中授权的资产
|
||||
path('<uuid:pk>/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'),
|
||||
path('<uuid:pk>/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'),
|
||||
|
||||
# 验证用户是否有某个资产和系统用户的权限
|
||||
path('user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
|
||||
path('user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
|
||||
|
||||
# 刷新缓存
|
||||
path('cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
|
||||
]
|
||||
|
||||
asset_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('asset-permissions/<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('asset-permissions/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('users/', include(user_permission_urlpatterns)),
|
||||
path('user-groups/', include(user_group_permission_urlpatterns)),
|
||||
path('asset-permissions/', include(permission_urlpatterns)),
|
||||
]
|
||||
|
||||
asset_permission_urlpatterns += router.urls
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
{% 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 @@
|
|||
<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="" 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>
|
||||
{% include 'settings/_setting_tabs.html' %}
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
|
|
|
@ -10,53 +10,33 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<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>
|
||||
{% include 'settings/_setting_tabs.html' %}
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
|
||||
<h3>{% trans "Create User setting" %}</h3>
|
||||
{% 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_BODY layout="horizontal" %}
|
||||
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans "Create User setting" %}</h3>
|
||||
{% 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_BODY layout="horizontal" %}
|
||||
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE 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 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>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,26 +10,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<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>
|
||||
{% include 'settings/_setting_tabs.html' %}
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
|
|
|
@ -10,26 +10,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<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>
|
||||
{% include 'settings/_setting_tabs.html' %}
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
|
|
|
@ -10,26 +10,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<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>
|
||||
{% include 'settings/_setting_tabs.html' %}
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
|
|
|
@ -15,30 +15,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<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>
|
||||
{% include 'settings/_setting_tabs.html' %}
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -177,7 +177,7 @@ function formSubmit(props) {
|
|||
*/
|
||||
props = props || {};
|
||||
var data = props.data || props.form.serializeObject();
|
||||
var redirect_to = props.redirect_to;
|
||||
var redirectTo = props.redirect_to || props.redirectTo;
|
||||
$.ajax({
|
||||
url: props.url,
|
||||
type: props.method || 'POST',
|
||||
|
@ -185,12 +185,8 @@ function formSubmit(props) {
|
|||
contentType: props.content_type || "application/json; charset=utf-8",
|
||||
dataType: props.data_type || "json"
|
||||
}).done(function (data, textState, jqXHR) {
|
||||
if (redirect_to) {
|
||||
if (props.message) {
|
||||
var messages = "ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
|
||||
setCookie("messages", messages)
|
||||
}
|
||||
location.href = redirect_to;
|
||||
if (redirectTo) {
|
||||
location.href = redirectTo;
|
||||
} else if (typeof props.success === 'function') {
|
||||
return props.success(data, textState, jqXHR);
|
||||
}
|
||||
|
@ -254,7 +250,6 @@ function formSubmit(props) {
|
|||
}
|
||||
$('.has-error').get(0).scrollIntoView();
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
props = props || {};
|
||||
$.ajax({
|
||||
|
@ -1081,6 +1132,7 @@ function APIImportData(props) {
|
|||
},
|
||||
error: function (error) {
|
||||
var data = error.responseJSON;
|
||||
console.log(data);
|
||||
if (data instanceof Array) {
|
||||
var html = '';
|
||||
var li = '';
|
||||
|
@ -1141,8 +1193,8 @@ function objectAttrsIsBool(obj, attrs) {
|
|||
attrs.forEach(function (attr) {
|
||||
if (!obj[attr]) {
|
||||
obj[attr] = false
|
||||
} else if (['on', '1'].includes(obj[attr])) {
|
||||
obj[attr] = true
|
||||
} else {
|
||||
obj[attr] = ['on', '1', 'true', 'True'].includes(obj[attr]);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* 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);
|
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* 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}});
|
|
@ -0,0 +1 @@
|
|||
!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});
|
|
@ -0,0 +1,55 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,47 @@
|
|||
{% 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>
|
|
@ -0,0 +1,62 @@
|
|||
{% 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>
|
|
@ -0,0 +1,52 @@
|
|||
{% 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 %}
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{% 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 @@
|
|||
<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="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>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
{% 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>
|
||||
|
|
@ -1,83 +1,64 @@
|
|||
{% load i18n %}
|
||||
{% extends '_base_only_content.html' %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
{% load i18n %}
|
||||
{% 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">
|
||||
{% block custom_head_css_js %}
|
||||
<style>
|
||||
.passwordBox {
|
||||
max-width: 660px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
<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>
|
||||
|
||||
<body class="gray-bg">
|
||||
<div class="passwordBox2 animated fadeInDown">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<img src="{{ LOGO_URL }}" style="margin: auto" width="82" height="82">
|
||||
<h2 style="display: inline">
|
||||
{{ JMS_TITLE }}
|
||||
</h2>
|
||||
</div>
|
||||
{% if errors %}
|
||||
<p>
|
||||
<div class="alert alert-danger">
|
||||
{{ errors }}
|
||||
</div>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
<p>
|
||||
<div class="alert alert-success" id="messages">
|
||||
{{ 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>
|
||||
{% block content %}
|
||||
<div>
|
||||
{% if errors %}
|
||||
<p>
|
||||
<div class="alert alert-danger">
|
||||
{{ errors }}
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
<p>
|
||||
<div class="alert alert-success" id="messages">
|
||||
{{ messages|safe }}
|
||||
</div>
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{% include '_copyright.html' %}
|
||||
<div class="col-lg-3">
|
||||
<a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">{% trans 'Return' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var time = '{{ interval }}';
|
||||
if (!time){
|
||||
if (!time) {
|
||||
time = 5;
|
||||
} else {
|
||||
time = parseInt(time);
|
||||
}
|
||||
|
||||
function redirect_page() {
|
||||
if (time >= 0) {
|
||||
var messages = '{{ messages|safe }}, <b>' + time +'</b> ...';
|
||||
var messages = '{{ messages|safe }}, <b>' + time + '</b> ...';
|
||||
$('#messages').html(messages);
|
||||
time--;
|
||||
setTimeout(redirect_page, 1000);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
window.location.href = "{{ redirect_url }}";
|
||||
}
|
||||
}
|
||||
{% if auto_redirect %}
|
||||
window.onload = redirect_page;
|
||||
window.onload = redirect_page;
|
||||
{% endif %}
|
||||
</script>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
|
@ -1,35 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..serializers import (
|
||||
UserGroupSerializer,
|
||||
UserGroupListSerializer,
|
||||
UserGroupUpdateMemberSerializer,
|
||||
)
|
||||
from ..serializers import UserGroupSerializer
|
||||
from ..models import UserGroup
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from common.permissions import IsOrgAdmin
|
||||
|
||||
|
||||
__all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
|
||||
__all__ = ['UserGroupViewSet']
|
||||
|
||||
|
||||
class UserGroupViewSet(OrgBulkModelViewSet):
|
||||
model = UserGroup
|
||||
filter_fields = ("name",)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = UserGroupSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
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
|
|||
|
||||
logger = get_logger(__name__)
|
||||
__all__ = [
|
||||
'UserViewSet', 'UserChangePasswordApi', 'UserUpdateGroupApi',
|
||||
'UserViewSet', 'UserChangePasswordApi',
|
||||
'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi',
|
||||
'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi',
|
||||
]
|
||||
|
@ -39,8 +39,10 @@ class UserQuerysetMixin:
|
|||
class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
||||
filter_fields = ('username', 'email', 'name', 'id')
|
||||
search_fields = filter_fields
|
||||
serializer_class = serializers.UserSerializer
|
||||
serializer_display_class = serializers.UserDisplaySerializer
|
||||
serializer_classes = {
|
||||
'default': serializers.UserSerializer,
|
||||
'display': serializers.UserDisplaySerializer
|
||||
}
|
||||
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -99,11 +101,6 @@ class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
|
|||
user.save()
|
||||
|
||||
|
||||
class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
|
||||
serializer_class = serializers.UserUpdateGroupSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = serializers.UserSerializer
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .user import *
|
||||
from .group import *
|
||||
from .profile import *
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from orgs.mixins.forms import OrgModelForm
|
||||
from ..models import User, UserGroup
|
||||
|
||||
__all__ = ['UserGroupForm']
|
||||
|
||||
|
||||
class UserGroupForm(OrgModelForm):
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.none(),
|
||||
label=_("User"),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'users-select2',
|
||||
'data-placeholder': _('Select users')
|
||||
}
|
||||
),
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_fields_queryset()
|
||||
|
||||
def set_fields_queryset(self):
|
||||
users_field = self.fields.get('users')
|
||||
if self.instance:
|
||||
users_field.initial = self.instance.users.all()
|
||||
users_field.queryset = self.instance.users.all()
|
||||
else:
|
||||
users_field.queryset = User.objects.none()
|
||||
|
||||
def save(self, commit=True):
|
||||
raise Exception("Save by restful api")
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
fields = [
|
||||
'name', 'users', 'comment',
|
||||
]
|
|
@ -0,0 +1,152 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from captcha.fields import CaptchaField
|
||||
|
||||
from common.utils import validate_ssh_public_key
|
||||
from ..models import User
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserProfileForm', 'UserMFAForm', 'UserFirstLoginFinishForm',
|
||||
'UserPasswordForm', 'UserPublicKeyForm', 'FileForm',
|
||||
'UserTokenResetPasswordForm', 'UserForgotPasswordForm',
|
||||
]
|
||||
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
username = forms.CharField(disabled=True, label=_("Username"))
|
||||
name = forms.CharField(disabled=True, label=_("Name"))
|
||||
email = forms.CharField(disabled=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'username', 'name', 'email',
|
||||
'wechat', 'phone',
|
||||
]
|
||||
|
||||
|
||||
UserProfileForm.verbose_name = _("Profile")
|
||||
|
||||
|
||||
class UserMFAForm(forms.ModelForm):
|
||||
|
||||
mfa_description = _(
|
||||
'When enabled, '
|
||||
'you will enter the MFA binding process the next time you log in. '
|
||||
'you can also directly bind in '
|
||||
'"personal information -> quick modification -> change MFA Settings"!')
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['mfa_level']
|
||||
widgets = {'mfa_level': forms.RadioSelect()}
|
||||
help_texts = {
|
||||
'mfa_level': _('* Enable MFA authentication '
|
||||
'to make the account more secure.'),
|
||||
}
|
||||
|
||||
|
||||
UserMFAForm.verbose_name = _("MFA")
|
||||
|
||||
|
||||
class UserFirstLoginFinishForm(forms.Form):
|
||||
finish_description = _(
|
||||
'In order to protect you and your company, '
|
||||
'please keep your account, '
|
||||
'password and key sensitive information properly. '
|
||||
'(for example: setting complex password, enabling MFA authentication)'
|
||||
)
|
||||
|
||||
|
||||
UserFirstLoginFinishForm.verbose_name = _("Finish")
|
||||
|
||||
|
||||
class UserTokenResetPasswordForm(forms.Form):
|
||||
new_password = forms.CharField(
|
||||
min_length=5, max_length=128,
|
||||
widget=forms.PasswordInput,
|
||||
label=_("New password")
|
||||
)
|
||||
confirm_password = forms.CharField(
|
||||
min_length=5, max_length=128,
|
||||
widget=forms.PasswordInput,
|
||||
label=_("Confirm password")
|
||||
)
|
||||
|
||||
def clean_confirm_password(self):
|
||||
new_password = self.cleaned_data['new_password']
|
||||
confirm_password = self.cleaned_data['confirm_password']
|
||||
|
||||
if new_password != confirm_password:
|
||||
raise forms.ValidationError(_('Password does not match'))
|
||||
return confirm_password
|
||||
|
||||
|
||||
class UserForgotPasswordForm(forms.Form):
|
||||
email = forms.EmailField(label=_("Email"))
|
||||
captcha = CaptchaField(label=_("Captcha"))
|
||||
|
||||
|
||||
class UserPasswordForm(UserTokenResetPasswordForm):
|
||||
old_password = forms.CharField(
|
||||
max_length=128, widget=forms.PasswordInput,
|
||||
label=_("Old password")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.instance = kwargs.pop('instance')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_old_password(self):
|
||||
old_password = self.cleaned_data['old_password']
|
||||
if not self.instance.check_password(old_password):
|
||||
raise forms.ValidationError(_('Old password error'))
|
||||
return old_password
|
||||
|
||||
def save(self):
|
||||
password = self.cleaned_data['new_password']
|
||||
self.instance.reset_password(new_password=password)
|
||||
return self.instance
|
||||
|
||||
|
||||
class UserPublicKeyForm(forms.Form):
|
||||
pubkey_description = _('Automatically configure and download the SSH key')
|
||||
public_key = forms.CharField(
|
||||
label=_('ssh public key'), max_length=5000, required=False,
|
||||
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
|
||||
help_text=_('Paste your id_rsa.pub here.')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'instance' in kwargs:
|
||||
self.instance = kwargs.pop('instance')
|
||||
else:
|
||||
self.instance = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_public_key(self):
|
||||
public_key = self.cleaned_data['public_key']
|
||||
if self.instance.public_key and public_key == self.instance.public_key:
|
||||
msg = _('Public key should not be the same as your old one.')
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
if public_key and not validate_ssh_public_key(public_key):
|
||||
raise forms.ValidationError(_('Not a valid ssh public key'))
|
||||
return public_key
|
||||
|
||||
def save(self):
|
||||
public_key = self.cleaned_data['public_key']
|
||||
if public_key:
|
||||
self.instance.public_key = public_key
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
|
||||
|
||||
UserPublicKeyForm.verbose_name = _("Public key")
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField()
|
|
@ -1,39 +1,19 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import validate_ssh_public_key
|
||||
from orgs.mixins.forms import OrgModelForm
|
||||
from .models import User, UserGroup
|
||||
from .utils import check_password_rules, get_current_org_members
|
||||
from ..models import User
|
||||
from ..utils import (
|
||||
check_password_rules, get_current_org_members, get_source_choices
|
||||
)
|
||||
|
||||
|
||||
class UserCheckPasswordForm(forms.Form):
|
||||
username = forms.CharField(label=_('Username'), max_length=100)
|
||||
password = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput,
|
||||
max_length=128, strip=False
|
||||
)
|
||||
|
||||
|
||||
class UserCheckOtpCodeForm(forms.Form):
|
||||
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
|
||||
|
||||
|
||||
def get_source_choices():
|
||||
choices_all = dict(User.SOURCE_CHOICES)
|
||||
choices = [
|
||||
(User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]),
|
||||
]
|
||||
if settings.AUTH_LDAP:
|
||||
choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP]))
|
||||
if settings.AUTH_OPENID:
|
||||
choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID]))
|
||||
if settings.AUTH_RADIUS:
|
||||
choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS]))
|
||||
return choices
|
||||
__all__ = [
|
||||
'UserCreateForm', 'UserUpdateForm', 'UserBulkUpdateForm',
|
||||
'UserCheckOtpCodeForm', 'UserCheckPasswordForm'
|
||||
]
|
||||
|
||||
|
||||
class UserCreateUpdateFormMixin(OrgModelForm):
|
||||
|
@ -157,131 +137,6 @@ class UserUpdateForm(UserCreateUpdateFormMixin):
|
|||
pass
|
||||
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
username = forms.CharField(disabled=True, label=_("Username"))
|
||||
name = forms.CharField(disabled=True, label=_("Name"))
|
||||
email = forms.CharField(disabled=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'username', 'name', 'email',
|
||||
'wechat', 'phone',
|
||||
]
|
||||
|
||||
|
||||
UserProfileForm.verbose_name = _("Profile")
|
||||
|
||||
|
||||
class UserMFAForm(forms.ModelForm):
|
||||
|
||||
mfa_description = _(
|
||||
'When enabled, '
|
||||
'you will enter the MFA binding process the next time you log in. '
|
||||
'you can also directly bind in '
|
||||
'"personal information -> quick modification -> change MFA Settings"!')
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['mfa_level']
|
||||
widgets = {'mfa_level': forms.RadioSelect()}
|
||||
help_texts = {
|
||||
'mfa_level': _('* Enable MFA authentication '
|
||||
'to make the account more secure.'),
|
||||
}
|
||||
|
||||
|
||||
UserMFAForm.verbose_name = _("MFA")
|
||||
|
||||
|
||||
class UserFirstLoginFinishForm(forms.Form):
|
||||
finish_description = _(
|
||||
'In order to protect you and your company, '
|
||||
'please keep your account, '
|
||||
'password and key sensitive information properly. '
|
||||
'(for example: setting complex password, enabling MFA authentication)'
|
||||
)
|
||||
|
||||
|
||||
UserFirstLoginFinishForm.verbose_name = _("Finish")
|
||||
|
||||
|
||||
class UserPasswordForm(forms.Form):
|
||||
old_password = forms.CharField(
|
||||
max_length=128, widget=forms.PasswordInput,
|
||||
label=_("Old password")
|
||||
)
|
||||
new_password = forms.CharField(
|
||||
min_length=5, max_length=128,
|
||||
widget=forms.PasswordInput,
|
||||
label=_("New password")
|
||||
)
|
||||
confirm_password = forms.CharField(
|
||||
min_length=5, max_length=128,
|
||||
widget=forms.PasswordInput,
|
||||
label=_("Confirm password")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.instance = kwargs.pop('instance')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_old_password(self):
|
||||
old_password = self.cleaned_data['old_password']
|
||||
if not self.instance.check_password(old_password):
|
||||
raise forms.ValidationError(_('Old password error'))
|
||||
return old_password
|
||||
|
||||
def clean_confirm_password(self):
|
||||
new_password = self.cleaned_data['new_password']
|
||||
confirm_password = self.cleaned_data['confirm_password']
|
||||
|
||||
if new_password != confirm_password:
|
||||
raise forms.ValidationError(_('Password does not match'))
|
||||
return confirm_password
|
||||
|
||||
def save(self):
|
||||
password = self.cleaned_data['new_password']
|
||||
self.instance.reset_password(new_password=password)
|
||||
return self.instance
|
||||
|
||||
|
||||
class UserPublicKeyForm(forms.Form):
|
||||
pubkey_description = _('Automatically configure and download the SSH key')
|
||||
public_key = forms.CharField(
|
||||
label=_('ssh public key'), max_length=5000, required=False,
|
||||
widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}),
|
||||
help_text=_('Paste your id_rsa.pub here.')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'instance' in kwargs:
|
||||
self.instance = kwargs.pop('instance')
|
||||
else:
|
||||
self.instance = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_public_key(self):
|
||||
public_key = self.cleaned_data['public_key']
|
||||
if self.instance.public_key and public_key == self.instance.public_key:
|
||||
msg = _('Public key should not be the same as your old one.')
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
if public_key and not validate_ssh_public_key(public_key):
|
||||
raise forms.ValidationError(_('Not a valid ssh public key'))
|
||||
return public_key
|
||||
|
||||
def save(self):
|
||||
public_key = self.cleaned_data['public_key']
|
||||
if public_key:
|
||||
self.instance.public_key = public_key
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
|
||||
|
||||
UserPublicKeyForm.verbose_name = _("Public key")
|
||||
|
||||
|
||||
class UserBulkUpdateForm(OrgModelForm):
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
required=True,
|
||||
|
@ -333,40 +188,12 @@ class UserBulkUpdateForm(OrgModelForm):
|
|||
return users
|
||||
|
||||
|
||||
class UserGroupForm(OrgModelForm):
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.none(),
|
||||
label=_("User"),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'users-select2',
|
||||
'data-placeholder': _('Select users')
|
||||
}
|
||||
),
|
||||
required=False,
|
||||
class UserCheckPasswordForm(forms.Form):
|
||||
password = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput,
|
||||
max_length=128, strip=False
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_fields_queryset()
|
||||
|
||||
def set_fields_queryset(self):
|
||||
users_field = self.fields.get('users')
|
||||
if self.instance:
|
||||
users_field.initial = self.instance.users.all()
|
||||
users_field.queryset = self.instance.users.all()
|
||||
else:
|
||||
users_field.queryset = User.objects.none()
|
||||
|
||||
def save(self, commit=True):
|
||||
raise Exception("Save by restful api")
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
fields = [
|
||||
'name', 'users', 'comment',
|
||||
]
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField()
|
||||
class UserCheckOtpCodeForm(forms.Form):
|
||||
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
|
|
@ -4,6 +4,7 @@ import uuid
|
|||
from django.db import models, IntegrityError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
__all__ = ['UserGroup']
|
||||
|
@ -20,6 +21,10 @@ class UserGroup(OrgModelMixin):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@lazyproperty
|
||||
def users_amount(self):
|
||||
return self.users.count()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('org_id', 'name'),]
|
||||
|
|
|
@ -19,6 +19,7 @@ from django.shortcuts import reverse
|
|||
from orgs.utils import current_org
|
||||
from common.utils import get_signer, date_expired_default, get_logger, lazyproperty
|
||||
from common import fields
|
||||
from ..signals import post_user_change_password
|
||||
|
||||
|
||||
__all__ = ['User']
|
||||
|
@ -43,14 +44,10 @@ class AuthMixin:
|
|||
self.set_password(password_raw_)
|
||||
|
||||
def set_password(self, raw_password):
|
||||
self._set_password = True
|
||||
if self.can_update_password():
|
||||
self.date_password_last_updated = timezone.now()
|
||||
post_user_change_password.send(self.__class__, user=self)
|
||||
super().set_password(raw_password)
|
||||
else:
|
||||
error = _("User auth from {}, go there change password").format(
|
||||
self.source)
|
||||
raise PermissionError(error)
|
||||
|
||||
def can_update_password(self):
|
||||
return self.is_local
|
||||
|
@ -196,22 +193,22 @@ class RoleMixin:
|
|||
def is_app(self):
|
||||
return self.role == 'App'
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def user_orgs(self):
|
||||
from orgs.models import Organization
|
||||
return Organization.get_user_user_orgs(self)
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def admin_orgs(self):
|
||||
from orgs.models import Organization
|
||||
return Organization.get_user_admin_orgs(self)
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def audit_orgs(self):
|
||||
from orgs.models import Organization
|
||||
return Organization.get_user_audit_orgs(self)
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def admin_or_audit_orgs(self):
|
||||
from orgs.models import Organization
|
||||
return Organization.get_user_admin_or_audit_orgs(self)
|
||||
|
@ -223,26 +220,26 @@ class RoleMixin:
|
|||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def is_org_auditor(self):
|
||||
if self.is_super_auditor or self.related_audit_orgs.exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def can_admin_current_org(self):
|
||||
return current_org.can_admin_by(self)
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def can_audit_current_org(self):
|
||||
return current_org.can_audit_by(self)
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def can_user_current_org(self):
|
||||
return current_org.can_user_by(self)
|
||||
|
||||
@property
|
||||
@lazyproperty
|
||||
def can_admin_or_audit_current_org(self):
|
||||
return self.can_admin_current_org or self.can_audit_current_org
|
||||
|
||||
|
|
|
@ -4,29 +4,29 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import StringManyToManyField
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from django.db.models import Count
|
||||
from ..models import User, UserGroup
|
||||
from .. import utils
|
||||
|
||||
__all__ = [
|
||||
'UserGroupSerializer', 'UserGroupListSerializer',
|
||||
'UserGroupUpdateMemberSerializer',
|
||||
'UserGroupSerializer',
|
||||
]
|
||||
|
||||
|
||||
class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
||||
users = serializers.PrimaryKeyRelatedField(
|
||||
required=False, many=True, queryset=User.objects, label=_('User')
|
||||
required=False, many=True, queryset=User.objects, label=_('User'),
|
||||
write_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'users', 'comment', 'date_created',
|
||||
'created_by',
|
||||
'id', 'name', 'users', 'users_amount', 'comment',
|
||||
'date_created', 'created_by',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'created_by': {'label': _('Created by'), 'read_only': True}
|
||||
|
@ -47,23 +47,8 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
|||
raise serializers.ValidationError(msg)
|
||||
return users
|
||||
|
||||
|
||||
class UserGroupListSerializer(UserGroupSerializer):
|
||||
users = StringManyToManyField(many=True, read_only=True)
|
||||
|
||||
|
||||
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
|
||||
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
|
||||
|
||||
class Meta:
|
||||
model = UserGroup
|
||||
fields = ['id', 'users']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_fields_queryset()
|
||||
|
||||
def set_fields_queryset(self):
|
||||
users_field = self.fields['users']
|
||||
users_field.child_relation.queryset = utils.get_current_org_members()
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.annotate(users_amount=Count('users'))
|
||||
return queryset
|
||||
|
|
|
@ -7,11 +7,11 @@ from common.utils import validate_ssh_public_key
|
|||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.permissions import CanUpdateDeleteUser
|
||||
from ..models import User, UserGroup
|
||||
from ..models import User
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer',
|
||||
'UserSerializer', 'UserPKUpdateSerializer',
|
||||
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
|
||||
'UserProfileSerializer', 'UserDisplaySerializer',
|
||||
]
|
||||
|
@ -123,16 +123,6 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
|
|||
return value
|
||||
|
||||
|
||||
class UserUpdateGroupSerializer(serializers.ModelSerializer):
|
||||
groups = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset=UserGroup.objects
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'groups']
|
||||
|
||||
|
||||
class ChangeUserPasswordSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -2,3 +2,4 @@ from django.dispatch import Signal
|
|||
|
||||
|
||||
post_user_create = Signal(providing_args=('user',))
|
||||
post_user_change_password = Signal(providing_args=('user',))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
|
||||
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 .signals import post_user_create
|
||||
|
|
|
@ -1,60 +1,20 @@
|
|||
{% extends '_without_nav_base.html' %}
|
||||
{% 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 src="{% static "js/plugins/qrcode/qrcode.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>
|
||||
|
||||
<!--内容-->
|
||||
<article>
|
||||
<div class="" style="text-align: center; margin-bottom: 50px">
|
||||
<h2>
|
||||
{% block small_title %}
|
||||
{% endblock %}
|
||||
</h2>
|
||||
</div>
|
||||
<div >
|
||||
<div class="verify">{% trans 'Security token validation' %} {% trans 'Account' %} <span>{{ user.username }}</span> {% trans 'Follow these steps to complete the binding operation' %}</div>
|
||||
<hr style="width: 500px; margin: auto; margin-top: 10px;">
|
||||
{% block content %}
|
||||
{% block body %}
|
||||
<article>
|
||||
<div class="" style="text-align: center; margin-bottom: 50px">
|
||||
<h2>
|
||||
{% block small_title %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
<footer>
|
||||
<div class="" style="margin-top: 100px;">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</h2>
|
||||
</div>
|
||||
<div >
|
||||
<div class="verify">{% trans 'Security token validation' %} {% trans 'Account' %} <span>{{ user.username }}</span> {% trans 'Follow these steps to complete the binding operation' %}</div>
|
||||
<hr style="width: 500px; margin: auto; margin-top: 10px;">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{% extends '_import_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Import user groups" %}{% endblock %}
|
||||
|
||||
{% block import_modal_download_template_url %}{% url "api-users:user-group-list" %}{% endblock %}
|
|
@ -1,4 +0,0 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update user group" %}{% endblock %}
|
|
@ -1,6 +0,0 @@
|
|||
{% extends '_import_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Import users" %}{% endblock %}
|
||||
|
||||
{% block import_modal_download_template_url %}{% url "api-users:user-list" %}{% endblock %}
|
|
@ -1,4 +0,0 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update user" %}{% endblock %}
|
|
@ -13,7 +13,7 @@
|
|||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'First Login' %}</h5>
|
||||
|
@ -55,7 +55,7 @@
|
|||
</div>
|
||||
<div class="content clearfix" style="background-color: #eee; border-radius:5px;">
|
||||
<div class="row">
|
||||
<form action="" method="post" class="form col-lg-8 p-m" id="fl_form" style="padding-left: 40px;">
|
||||
<form action="" method="post" class="form col-sm-8 p-m" id="fl_form" style="padding-left: 40px;">
|
||||
{% csrf_token %}
|
||||
{{ wizard.management_form }}
|
||||
{#{% if wizard.form.forms %}#}
|
||||
|
@ -88,7 +88,7 @@
|
|||
{% endif %}
|
||||
|
||||
</form>
|
||||
<div class="col-lg-4">
|
||||
<div class="col-sm-4">
|
||||
<div class="text-center">
|
||||
<div style="margin-top: 20px">
|
||||
<i class="fa fa-sign-in" style="font-size: 180px;color: #e5e5e5 "></i>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'First Login' %}</h5>
|
||||
|
|
|
@ -1,60 +1,34 @@
|
|||
{% extends '_base_only_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<style>
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block html_title %}{% trans 'Forgot password' %}{% endblock %}
|
||||
{% block title %} {% trans 'Forgot password' %}?{% endblock %}
|
||||
|
||||
<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>{% trans 'Forgot password' %}</title>
|
||||
{% block content %}
|
||||
{% if errors %}
|
||||
<p class="red-fonts">{{ errors }}</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
{% trans 'Input your email, that will send a mail to your' %}
|
||||
</p>
|
||||
|
||||
{% 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>
|
||||
|
||||
<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="82" height="82">
|
||||
<h2 class="font-bold" style="display: inline">{% trans 'Forgot password' %} ?</h2>
|
||||
<h1></h1>
|
||||
{% if errors %}
|
||||
<p class="red-fonts">{{ errors }}</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
{% trans 'Input your email, that will send a mail to your' %}
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<form class="m-t" role="form" action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<input type="email" name="email" class="form-control" placeholder="Email address" required="">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<form role="form" class="form-horizontal" action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.email layout="horizontal" %}
|
||||
{% bootstrap_field form.captcha layout="horizontal" %}
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,136 +1,80 @@
|
|||
{% extends '_base_only_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
{% load bootstrap3 %}
|
||||
{% block html_title %}{% trans 'Reset password' %}{% endblock %}
|
||||
{% block title %}{% trans 'Reset password' %}{% endblock %}
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> {{ JMS_TITLE }} </title>
|
||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
||||
{% 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>
|
||||
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="gray-bg">
|
||||
|
||||
<div class="loginColumns animated fadeInDown">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
|
||||
|
||||
<p>
|
||||
{% trans 'Jumpserver is an open source desktop system developed using Python and Django that helps Internet businesses with efficient users, assets, permissions, and audit management' %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% trans 'We are from all over the world, we have great admiration and worship for the spirit of open source, we have endless pursuit for perfection, neatness and elegance' %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% trans 'We focus on automatic operation and maintenance, and strive to build an easy-to-use, stable, safe and automatic board hopping machine, which is our unremitting pursuit and power' %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<small>{% trans 'Always young, always with tears in my eyes. Stay foolish Stay hungry' %}</small>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ibox-content">
|
||||
<div><img src="{{ LOGO_URL }}" width="82" height="82"> <span class="font-bold text-center" style="font-size: 32px; font-family: inherit">{% trans 'Reset password' %}</span></div>
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
{% if errors %}
|
||||
<p class="red-fonts">{{ errors }}</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<input type="password" id="id_new_password" class="form-control" name="password" placeholder="{% trans 'Password' %}" required="">
|
||||
{# 密码popover #}
|
||||
<div id="container">
|
||||
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
|
||||
<div class="arrow" style="left: 50%;"></div>
|
||||
<h3 class="popover-title" style="display: none;"></h3>
|
||||
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
|
||||
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
|
||||
<div class="popover-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" id="id_confirm_password" class="form-control" name="password-confirm" placeholder="{% trans 'Password again' %}" required="">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
|
||||
|
||||
<a href="#">
|
||||
<small>Forgot password?</small>
|
||||
</a>
|
||||
|
||||
<p class="text-muted text-center">
|
||||
</p>
|
||||
</form>
|
||||
<p class="m-t">
|
||||
</p>
|
||||
{% block content %}
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
{% if errors %}
|
||||
<p class="red-fonts">{{ errors }}</p>
|
||||
{% endif %}
|
||||
{% if not token_invalid %}
|
||||
<div class="form-group">
|
||||
{% bootstrap_field form.new_password %}
|
||||
{% bootstrap_field form.confirm_password %}
|
||||
{# 密码popover #}
|
||||
<div id="container">
|
||||
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
|
||||
<div class="arrow" style="left: 50%;"></div>
|
||||
<h3 class="popover-title" style="display: none;"></h3>
|
||||
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
|
||||
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
|
||||
<div class="popover-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
{% block custom_foot_js %}
|
||||
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// 密码强度校验
|
||||
var el = $('#id_password_rules'),
|
||||
idPassword = $('#id_new_password'),
|
||||
idPopover = $('#popover777'),
|
||||
container = $('#container'),
|
||||
progress = $('#id_progress'),
|
||||
password_check_rules = {{ password_check_rules|safe }},
|
||||
minLength = 6,
|
||||
top = 146, left = 170,
|
||||
i18n_fallback = {
|
||||
"veryWeak": "{% trans 'Very weak' %}",
|
||||
"weak": "{% trans 'Weak' %}",
|
||||
"normal": "{% trans 'Normal' %}",
|
||||
"medium": "{% trans 'Medium' %}",
|
||||
"strong": "{% trans 'Strong' %}",
|
||||
"veryStrong": "{% trans 'Very strong' %}"
|
||||
};
|
||||
|
||||
</html>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// 密码强度校验
|
||||
var el = $('#id_password_rules'),
|
||||
idPassword = $('#id_new_password'),
|
||||
idPopover = $('#popover777'),
|
||||
container = $('#container'),
|
||||
progress = $('#id_progress'),
|
||||
password_check_rules = {{ password_check_rules|safe }},
|
||||
minLength = 6,
|
||||
top = 146, left = 170,
|
||||
i18n_fallback = {
|
||||
"veryWeak": "{% trans 'Very weak' %}",
|
||||
"weak": "{% trans 'Weak' %}",
|
||||
"normal": "{% trans 'Normal' %}",
|
||||
"medium": "{% trans 'Medium' %}",
|
||||
"strong": "{% trans 'Strong' %}",
|
||||
"veryStrong": "{% trans 'Very strong' %}"
|
||||
};
|
||||
jQuery.each(password_check_rules, function (idx, rules) {
|
||||
if(rules.key === 'id_security_password_min_length'){
|
||||
minLength = rules.value
|
||||
}
|
||||
});
|
||||
|
||||
jQuery.each(password_check_rules, function (idx, rules) {
|
||||
if(rules.key === 'id_security_password_min_length'){
|
||||
minLength = rules.value
|
||||
}
|
||||
});
|
||||
// 初始化popover
|
||||
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
|
||||
|
||||
// 初始化popover
|
||||
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
|
||||
|
||||
// 监听事件
|
||||
idPassword.on('focus', function () {
|
||||
idPopover.css('top', top);
|
||||
idPopover.css('left', left);
|
||||
idPopover.css('display', 'block');
|
||||
});
|
||||
idPassword.on('blur', function () {
|
||||
idPopover.css('display', 'none');
|
||||
});
|
||||
idPassword.on('keyup', function(){
|
||||
var password = idPassword.val();
|
||||
checkPasswordRules(password, minLength);
|
||||
})
|
||||
})
|
||||
// 监听事件
|
||||
idPassword.on('focus', function () {
|
||||
idPopover.css('top', top);
|
||||
idPopover.css('left', left);
|
||||
idPopover.css('display', 'block');
|
||||
});
|
||||
idPassword.on('blur', function () {
|
||||
idPopover.css('display', 'none');
|
||||
});
|
||||
idPassword.on('keyup', function(){
|
||||
var password = idPassword.val();
|
||||
checkPasswordRules(password, minLength);
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -244,10 +244,10 @@
|
|||
{% for group in user_object.groups.all %}
|
||||
<tr>
|
||||
<td >
|
||||
<b class="bdg_group" data-gid={{ group.id }}>{{ group.name }}</b>
|
||||
<b class="bdg_group" >{{ group.name }}</b>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button>
|
||||
<button class="btn btn-danger pull-right btn-xs btn_leave_group" data-uid={{ group.id }} type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -307,38 +307,8 @@
|
|||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.nodes_selected = {};
|
||||
var usersSelect2;
|
||||
|
||||
function updateUserGroups(groups) {
|
||||
var the_url = "{% url 'api-users:user-update-group' pk=user_object.id %}";
|
||||
var body = {
|
||||
groups: Object.assign([], groups)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#groups_selected').val('');
|
||||
$.map(jumpserver.nodes_selected, function(group_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('.group_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_group" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.nodes_selected = {};
|
||||
};
|
||||
requestApi({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
function updateUserLoginReviewer(reviewers) {
|
||||
var url = "{% url 'api-auth:login-confirm-setting-update' user_id=user_object.id %}";
|
||||
var data = {reviewers: reviewers};
|
||||
|
@ -352,16 +322,44 @@ function updateUserLoginReviewer(reviewers) {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
var usersGroupsRelationUrl = "{% url 'api-users:users-groups-relation-list' %}";
|
||||
|
||||
function addObjects(objectsId) {
|
||||
if (!objectsId || objectsId.length === 0) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersGroupsRelationUrl;
|
||||
var body = [];
|
||||
objectsId.forEach(function (v) {
|
||||
var data = {user: "{{ object.id }}"};
|
||||
data["usergroup"] = v;
|
||||
body.push(data)
|
||||
});
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
success: reloadPage
|
||||
});
|
||||
}
|
||||
|
||||
function removeObject(objectId) {
|
||||
if (!objectId) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersGroupsRelationUrl;
|
||||
theUrl = setUrlParam(theUrl, 'user', "{{ object.id }}");
|
||||
theUrl = setUrlParam(theUrl, 'usergroup', objectId);
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: "DELETE",
|
||||
success: reloadPage
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
$('.select2').select2();
|
||||
usersSelect2 = usersSelect2Init('.users-select2')
|
||||
})
|
||||
.on('click', '#is_active', function() {
|
||||
|
@ -405,31 +403,11 @@ $(document).ready(function() {
|
|||
});
|
||||
})
|
||||
.on('click', '#btn_join_group', function() {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var groups = $('.bdg_group').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
groups.push(index);
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
updateUserGroups(groups)
|
||||
var objectsId = $("#groups_selected").val();
|
||||
addObjects(objectsId);
|
||||
}).on('click', '.btn_leave_group', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_group');
|
||||
var gid = $badge.data('gid');
|
||||
var group_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var groups = $('.bdg_group').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateUserGroups(groups)
|
||||
var objectId = $(this).data('uid');
|
||||
removeObject(objectId)
|
||||
}).on('click', '#btn-reset-password', function() {
|
||||
function doReset() {
|
||||
var the_url = '{% url "api-users:user-reset-password" pk=user_object.id %}';
|
||||
|
|
|
@ -91,9 +91,9 @@
|
|||
|
||||
{% for user in user_group.users.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_user" data-uid={{ user.id }}>{{ user.name }}</b></td>
|
||||
<td ><b class="bdg_user" >{{ user.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-remove-user" type="button"><i class="fa fa-minus"></i></button>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-remove-user" data-uid={{ user.id }} type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -110,73 +110,51 @@
|
|||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.users_selected = {};
|
||||
|
||||
function updateGroupMember(users) {
|
||||
var the_url = "{% url 'api-users:user-group-update-user' pk=user_group.id %}";
|
||||
var body = {
|
||||
users: Object.assign([], users)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#slct_users').val('');
|
||||
$.map(jumpserver.users_selected, function(user_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of users
|
||||
$('.user_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_user" data-uid="' + index + '">' + user_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-user" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.selected_groups
|
||||
jumpserver.users_selected = {};
|
||||
};
|
||||
var usersGroupsRelationUrl = "{% url 'api-users:users-groups-relation-list' %}";
|
||||
|
||||
function addObjects(objectsId) {
|
||||
if (!objectsId || objectsId.length === 0) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersGroupsRelationUrl;
|
||||
var body = [];
|
||||
objectsId.forEach(function (v) {
|
||||
var data = {usergroup: "{{ object.id }}"};
|
||||
data["user"] = v;
|
||||
body.push(data)
|
||||
});
|
||||
requestApi({
|
||||
url: the_url,
|
||||
url: theUrl,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
method: "POST",
|
||||
success: reloadPage
|
||||
});
|
||||
}
|
||||
|
||||
function removeObject(objectId, type) {
|
||||
if (!objectId) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersGroupsRelationUrl;
|
||||
theUrl = setUrlParam(theUrl, 'usergroup', "{{ object.id }}");
|
||||
theUrl = setUrlParam(theUrl, 'user', objectId);
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: "DELETE",
|
||||
success: reloadPage
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.users_selected[data.id] = data.text;
|
||||
}).on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.users_selected[data.id]
|
||||
});
|
||||
$('.select2').select2();
|
||||
usersSelect2Init('#slct_users')
|
||||
}).on('click', '.btn-remove-user', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_user');
|
||||
var uid = $badge.data('uid');
|
||||
var user_name = $badge.html() || $badge.text();
|
||||
$('#slct_users').append(
|
||||
'<option value="' + uid + '" id="opt_' + uid + '">' + user_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var users = $('.bdg_user').map(function() {
|
||||
return $(this).data('uid');
|
||||
}).get();
|
||||
updateGroupMember(users)
|
||||
var objectId = $(this).data("uid");
|
||||
removeObject(objectId);
|
||||
}).on('click', '#btn_add_user', function() {
|
||||
if (Object.keys(jumpserver.users_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var users = $('.bdg_user').map(function() {
|
||||
return $(this).data('uid');
|
||||
}).get();
|
||||
$.map(jumpserver.users_selected, function(value, index) {
|
||||
users.push(index);
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
updateGroupMember(users)
|
||||
var objectsId = $("#slct_users").val();
|
||||
addObjects(objectsId);
|
||||
}).on('click', '.btn-delete-user-group', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ user_group.name }}";
|
||||
|
@ -185,5 +163,6 @@ $(document).ready(function () {
|
|||
var redirect_url = "{% url 'users:user-group-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,28 +1,7 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
<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>
|
||||
<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>
|
||||
{% include '_csv_import_export.html' %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="pull-left m-r-5"><a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary ">{% trans "Create user group" %}</a></div>
|
||||
|
@ -39,14 +18,13 @@
|
|||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
{% include "users/_user_groups_import_modal.html" %}
|
||||
{% include "users/_user_groups_update_modal.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var groups_table = 0;
|
||||
var groupsTable = 0;
|
||||
var usersAmountTpl = '<a class="group-users-amount" data-uid="ID">NUM</a>';
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#group_list_table'),
|
||||
|
@ -58,14 +36,16 @@ function initTable() {
|
|||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 2, createdCell: function (td, cellData, rowData) {
|
||||
var html = createPopover(cellData);
|
||||
$(td).html(html);
|
||||
var data = usersAmountTpl
|
||||
.replace("ID", rowData.id)
|
||||
.replace('NUM', cellData);
|
||||
$(td).html(data);
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var name = htmlEscape(rowData.name);
|
||||
var update_btn = '<a href="{% url "users:user-group-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
|
||||
|
@ -80,129 +60,53 @@ function initTable() {
|
|||
}
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-users:user-group-list" %}?display=1',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "users", orderable: false},
|
||||
ajax_url: '{% url "api-users:user-group-list" %}',
|
||||
columns: [{data: "id"}, {data: "name" }, {data: "users_amount", orderable: false},
|
||||
{data: "comment"}, {data: "id", orderable: false, width:"100px"}],
|
||||
order: [],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
groups_table = jumpserver.initServerSideDataTable(options);
|
||||
return groups_table
|
||||
groupsTable = jumpserver.initServerSideDataTable(options);
|
||||
return groupsTable
|
||||
}
|
||||
|
||||
var usersGroupsRelationUrl = "{% url 'api-users:users-groups-relation-list' %}";
|
||||
|
||||
function getGroupUsers(groupId, callback) {
|
||||
var theUrl = setUrlParam(usersGroupsRelationUrl, "usergroup", groupId);
|
||||
if (!callback) {
|
||||
callback = function (data) {
|
||||
console.log(data)
|
||||
}
|
||||
}
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: "GET",
|
||||
success: callback,
|
||||
flash_message: false,
|
||||
})
|
||||
}
|
||||
$(document).ready(function() {
|
||||
initTable()
|
||||
|
||||
groupsTable = initTable();
|
||||
initCsvImportExport(groupsTable, "{% trans 'User groups' %}")
|
||||
}).on('click', '.btn_delete_user_group', function(){
|
||||
var $this = $(this);
|
||||
var group_id = $this.data('gid');
|
||||
var name = $this.data('name');
|
||||
var the_url = "{% url 'api-users:user-group-detail' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', group_id);
|
||||
objectDelete($this, name, the_url)
|
||||
}).on('click', '#btn_bulk_update', function(){
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#group_list_table').DataTable()
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (plain_id_list === []) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-users:user-group-list' %}";
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected groups !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "{% trans 'Cancel' %}",
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'UserGroups Deleted.' %}";
|
||||
swal("{% trans 'UserGroups Delete' %}", msg, "success");
|
||||
$data_table.ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'UserGroup Deleting failed.' %}";
|
||||
swal("{% trans 'UserGroups 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});
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
switch(action) {
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}).on('click', '.btn_export', function(){
|
||||
var groups = groups_table.selected;
|
||||
var data = {
|
||||
'resources': groups
|
||||
};
|
||||
var search = $("input[type='search']").val();
|
||||
var props = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success_url: "{% url 'api-users:user-group-list' %}",
|
||||
format: "csv",
|
||||
params: {
|
||||
search: search
|
||||
}
|
||||
};
|
||||
APIExportData(props);
|
||||
}).on('click', '#btn_import_confirm',function () {
|
||||
var url = "{% url 'api-users:user-group-list' %}";
|
||||
var file = document.getElementById('id_file').files[0];
|
||||
if(!file){
|
||||
toastr.error("{% trans "Please select file" %}");
|
||||
return
|
||||
}
|
||||
var data_table = $('#group_list_table').DataTable();
|
||||
APIImportData({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: file,
|
||||
data_table: data_table
|
||||
});
|
||||
})
|
||||
.on('click', '#download_update_template', function(){
|
||||
var groups = groups_table.selected;
|
||||
var data = {
|
||||
'resources': groups
|
||||
};
|
||||
var search = $("input[type='search']").val();
|
||||
var props = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success_url: "{% url 'api-users:user-group-list' %}?format=csv&template=update",
|
||||
format: "csv",
|
||||
params: {
|
||||
search: search
|
||||
}
|
||||
};
|
||||
APIExportData(props);
|
||||
}).on('click', '#btn_update_confirm',function () {
|
||||
var url = "{% url 'api-users:user-group-list' %}";
|
||||
var file = document.getElementById('update_file').files[0];
|
||||
if(!file){
|
||||
toastr.error("{% trans "Please select file" %}");
|
||||
return
|
||||
}
|
||||
var data_table = $('#group_list_table').DataTable();
|
||||
APIImportData({
|
||||
url: url,
|
||||
method: "PUT",
|
||||
body: file,
|
||||
data_table: data_table
|
||||
.on('click', '.group-users-amount', function () {
|
||||
var $this = $(this);
|
||||
var groupId = $(this).data("uid");
|
||||
getGroupUsers(groupId, function (data) {
|
||||
var groups = [];
|
||||
data.forEach(function (v) {
|
||||
groups.push(v.user_display);
|
||||
});
|
||||
})
|
||||
|
||||
var popover = createPopover(groups);
|
||||
$this.parent().html(popover);
|
||||
$(popover).trigger('click');
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,28 +1,7 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
<div class="pull-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>
|
||||
<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>
|
||||
{% include '_csv_import_export.html' %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>
|
||||
|
@ -63,14 +42,12 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "users/_user_import_modal.html" %}
|
||||
{% include "users/_user_update_modal.html" %}
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script>
|
||||
var users_table = 0;
|
||||
var usersTable = 0;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#user_list_table'),
|
||||
|
@ -148,86 +125,17 @@ function initTable() {
|
|||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
users_table = jumpserver.initServerSideDataTable(options);
|
||||
return users_table
|
||||
usersTable = jumpserver.initServerSideDataTable(options);
|
||||
return usersTable
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
var fields = $('#fm_user_bulk_update .form-group');
|
||||
$.each(fields, function (index, value) {
|
||||
console.log(value)
|
||||
});
|
||||
$('.btn_export').click(function () {
|
||||
var users = users_table.selected;
|
||||
var data = {
|
||||
'resources': users
|
||||
};
|
||||
var search = $("input[type='search']").val();
|
||||
var props = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success_url: "{% url 'api-users:user-list' %}",
|
||||
format: 'csv',
|
||||
params: {
|
||||
search: search
|
||||
}
|
||||
};
|
||||
APIExportData(props);
|
||||
});
|
||||
|
||||
$('#btn_import_confirm').click(function() {
|
||||
var url = "{% url 'api-users:user-list' %}";
|
||||
var file = document.getElementById('id_file').files[0];
|
||||
if(!file){
|
||||
toastr.error("{% trans "Please select file" %}");
|
||||
return
|
||||
}
|
||||
var data_table = $('#user_list_table').DataTable();
|
||||
APIImportData({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: file,
|
||||
data_table: data_table
|
||||
});
|
||||
});
|
||||
$('#download_update_template').click(function () {
|
||||
var users = users_table.selected;
|
||||
var data = {
|
||||
'resources': users
|
||||
};
|
||||
var search = $("input[type='search']").val();
|
||||
var props = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success_url: "{% url 'api-users:user-list' %}?format=csv&template=update",
|
||||
format: 'csv',
|
||||
params: {
|
||||
search: search
|
||||
}
|
||||
};
|
||||
APIExportData(props);
|
||||
});
|
||||
$('#btn_update_confirm').click(function() {
|
||||
var url = "{% url 'api-users:user-list' %}";
|
||||
var file = document.getElementById('update_file').files[0];
|
||||
if(!file){
|
||||
toastr.error("{% trans "Please select file" %}");
|
||||
return
|
||||
}
|
||||
var data_table = $('#user_list_table').DataTable();
|
||||
APIImportData({
|
||||
url: url,
|
||||
method: "PUT",
|
||||
body: file,
|
||||
data_table: data_table
|
||||
});
|
||||
});
|
||||
|
||||
usersTable = initTable();
|
||||
initCsvImportExport(usersTable, "{% trans 'User groups' %}")
|
||||
}).on('click', '#btn_bulk_update', function(){
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var id_list = users_table.selected;
|
||||
var id_list = usersTable.selected;
|
||||
if (id_list.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,6 @@
|
|||
{% block content %}
|
||||
<form class="" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-input">
|
||||
<input type="text" class="" name="{{ form.username.html_name }}" value="{{ form.username.value }}" readonly="readonly" required="">
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<input type="password" class="" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
</div>
|
|
@ -14,7 +14,7 @@ app_name = 'users'
|
|||
router = BulkRouter()
|
||||
router.register(r'users', api.UserViewSet, 'user')
|
||||
router.register(r'groups', api.UserGroupViewSet, 'user-group')
|
||||
router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'user-group-relation')
|
||||
router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -28,8 +28,6 @@ urlpatterns = [
|
|||
path('users/<uuid:pk>/pubkey/reset/', api.UserResetPKApi.as_view(), name='user-public-key-reset'),
|
||||
path('users/<uuid:pk>/pubkey/update/', api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
|
||||
path('users/<uuid:pk>/unblock/', api.UserUnblockPKApi.as_view(), name='user-unblock'),
|
||||
path('users/<uuid:pk>/groups/', api.UserUpdateGroupApi.as_view(), name='user-update-group'),
|
||||
path('groups/<uuid:pk>/users/', api.UserGroupUpdateUserApi.as_view(), name='user-group-update-user'),
|
||||
]
|
||||
urlpatterns += router.urls
|
||||
|
||||
|
|
|
@ -20,10 +20,10 @@ urlpatterns = [
|
|||
path('profile/password/update/', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
|
||||
path('profile/pubkey/update/', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
|
||||
path('profile/pubkey/generate/', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
|
||||
path('profile/otp/enable/authentication/', views.UserOtpEnableAuthenticationView.as_view(), name='user-otp-enable-authentication'),
|
||||
path('profile/otp/enable/authentication/', views.UserCheckPasswordView.as_view(), name='user-otp-enable-authentication'),
|
||||
path('profile/otp/enable/install-app/', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'),
|
||||
path('profile/otp/enable/bind/', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'),
|
||||
path('profile/otp/disable/authentication/', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'),
|
||||
path('profile/otp/disable/authentication/', views.UserDisableMFAView.as_view(), name='user-otp-disable-authentication'),
|
||||
path('profile/otp/update/', views.UserOtpUpdateView.as_view(), name='user-otp-update'),
|
||||
path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
|
||||
|
||||
|
|
|
@ -329,3 +329,18 @@ def construct_user_email(username, email):
|
|||
def get_current_org_members(exclude=()):
|
||||
from orgs.utils import current_org
|
||||
return current_org.get_org_members(exclude=exclude)
|
||||
|
||||
|
||||
def get_source_choices():
|
||||
from .models import User
|
||||
choices_all = dict(User.SOURCE_CHOICES)
|
||||
choices = [
|
||||
(User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]),
|
||||
]
|
||||
if settings.AUTH_LDAP:
|
||||
choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP]))
|
||||
if settings.AUTH_OPENID:
|
||||
choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID]))
|
||||
if settings.AUTH_RADIUS:
|
||||
choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS]))
|
||||
return choices
|
||||
|
|
|
@ -4,13 +4,13 @@ from __future__ import unicode_literals
|
|||
from django.shortcuts import render
|
||||
from django.views.generic import RedirectView
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
from django.views.generic import FormView
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from common.permissions import PermissionsMixin, IsValidUser
|
||||
|
@ -33,22 +33,24 @@ class UserLoginView(RedirectView):
|
|||
query_string = True
|
||||
|
||||
|
||||
class UserForgotPasswordView(TemplateView):
|
||||
class UserForgotPasswordView(FormView):
|
||||
template_name = 'users/forgot_password.html'
|
||||
form_class = forms.UserForgotPasswordForm
|
||||
|
||||
def post(self, request):
|
||||
email = request.POST.get('email')
|
||||
def form_valid(self, form):
|
||||
request = self.request
|
||||
email = form.cleaned_data['email']
|
||||
user = get_object_or_none(User, email=email)
|
||||
if not user:
|
||||
error = _('Email address invalid, please input again')
|
||||
return self.get(request, errors=error)
|
||||
elif not user.can_update_password():
|
||||
error = _('User auth from {}, go there change password'.format(user.source))
|
||||
error = _('User auth from {}, go there change password'.format(
|
||||
user.source))
|
||||
return self.get(request, errors=error)
|
||||
else:
|
||||
send_reset_password_mail(user)
|
||||
return HttpResponseRedirect(
|
||||
reverse('users:forgot-password-sendmail-success'))
|
||||
return redirect('users:forgot-password-sendmail-success')
|
||||
|
||||
|
||||
class UserForgotPasswordSendmailSuccessView(TemplateView):
|
||||
|
@ -79,44 +81,47 @@ class UserResetPasswordSuccessView(TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserResetPasswordView(TemplateView):
|
||||
class UserResetPasswordView(FormView):
|
||||
template_name = 'users/reset_password.html'
|
||||
form_class = forms.UserTokenResetPasswordForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
token = request.GET.get('token', '')
|
||||
context = self.get_context_data(**kwargs)
|
||||
errors = kwargs.get('errors')
|
||||
if errors:
|
||||
context['errors'] = errors
|
||||
return self.render_to_response(context)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
token = self.request.GET.get('token', '')
|
||||
user = User.validate_reset_password_token(token)
|
||||
if not user:
|
||||
kwargs.update({'errors': _('Token invalid or expired')})
|
||||
else:
|
||||
check_rules = get_password_check_rules()
|
||||
kwargs.update({'password_check_rules': check_rules})
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
password = request.POST.get('password')
|
||||
password_confirm = request.POST.get('password-confirm')
|
||||
token = request.GET.get('token')
|
||||
|
||||
if password != password_confirm:
|
||||
return self.get(request, errors=_('Password not same'))
|
||||
context['errors'] = _('Token invalid or expired')
|
||||
context['token_invalid'] = True
|
||||
check_rules = get_password_check_rules()
|
||||
context['password_check_rules'] = check_rules
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
token = self.request.GET.get('token')
|
||||
user = User.validate_reset_password_token(token)
|
||||
if not user:
|
||||
return self.get(request, errors=_('Token invalid or expired'))
|
||||
return self.get(self.request, errors=_('Token invalid or expired'))
|
||||
|
||||
if not user.can_update_password():
|
||||
error = _('User auth from {}, go there change password'.format(user.source))
|
||||
return self.get(request, errors=error)
|
||||
errors = _('User auth from {}, go there change password'.format(user.source))
|
||||
return self.get(self.request, errors=errors)
|
||||
|
||||
password = form.cleaned_data['new_password']
|
||||
is_ok = check_password_rules(password)
|
||||
if not is_ok:
|
||||
return self.get(
|
||||
request,
|
||||
errors=_('* Your password does not meet the requirements')
|
||||
)
|
||||
errors = _('* Your password does not meet the requirements')
|
||||
return self.get(self.request, errors=errors)
|
||||
|
||||
user.reset_password(password)
|
||||
User.expired_reset_password_token(token)
|
||||
return HttpResponseRedirect(reverse('users:reset-password-success'))
|
||||
return redirect('users:reset-password-success')
|
||||
|
||||
|
||||
class UserFirstLoginView(PermissionsMixin, SessionWizardView):
|
||||
|
@ -177,5 +182,4 @@ class UserFirstLoginView(PermissionsMixin, SessionWizardView):
|
|||
choices = [(k, v) for k, v in choices if k in [0, 1]]
|
||||
form.fields["mfa_level"].choices = choices
|
||||
form.fields["mfa_level"].initial = self.request.user.mfa_level
|
||||
|
||||
return form
|
||||
|
|
|
@ -31,9 +31,9 @@ __all__ = [
|
|||
'UserProfileView',
|
||||
'UserProfileUpdateView', 'UserPasswordUpdateView',
|
||||
'UserPublicKeyUpdateView', 'UserPublicKeyGenerateView',
|
||||
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
|
||||
'UserCheckPasswordView', 'UserOtpEnableInstallAppView',
|
||||
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
|
||||
'UserOtpDisableAuthenticationView', 'UserOtpUpdateView',
|
||||
'UserDisableMFAView', 'UserOtpUpdateView',
|
||||
]
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -140,24 +140,10 @@ class UserPublicKeyGenerateView(PermissionsMixin, View):
|
|||
return response
|
||||
|
||||
|
||||
class UserOtpEnableAuthenticationView(FormView):
|
||||
template_name = 'users/user_password_authentication.html'
|
||||
class UserCheckPasswordView(FormView):
|
||||
template_name = 'users/user_password_check.html'
|
||||
form_class = forms.UserCheckPasswordForm
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
form = super().get_form(form_class=form_class)
|
||||
form['username'].initial = user.username
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
context = {
|
||||
'user': user
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
password = form.cleaned_data.get('password')
|
||||
|
@ -165,10 +151,17 @@ class UserOtpEnableAuthenticationView(FormView):
|
|||
if not user:
|
||||
form.add_error("password", _("Password invalid"))
|
||||
return self.form_invalid(form)
|
||||
if not user.mfa_is_otp():
|
||||
user.enable_mfa()
|
||||
user.save()
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('users:user-otp-enable-install-app')
|
||||
if settings.OTP_IN_RADIUS:
|
||||
success_url = reverse_lazy('users:user-otp-settings-success')
|
||||
else:
|
||||
success_url = reverse('users:user-otp-enable-install-app')
|
||||
return success_url
|
||||
|
||||
|
||||
class UserOtpEnableInstallAppView(TemplateView):
|
||||
|
@ -213,23 +206,23 @@ class UserOtpEnableBindView(TemplateView, FormView):
|
|||
|
||||
def save_otp(self, otp_secret_key):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
user.enable_otp()
|
||||
user.enable_mfa()
|
||||
user.otp_secret_key = otp_secret_key
|
||||
user.save()
|
||||
|
||||
|
||||
class UserOtpDisableAuthenticationView(FormView):
|
||||
template_name = 'users/user_otp_authentication.html'
|
||||
class UserDisableMFAView(FormView):
|
||||
template_name = 'users/user_disable_mfa.html'
|
||||
form_class = forms.UserCheckOtpCodeForm
|
||||
success_url = reverse_lazy('users:user-otp-settings-success')
|
||||
|
||||
def form_valid(self, form):
|
||||
user = self.request.user
|
||||
otp_code = form.cleaned_data.get('otp_code')
|
||||
otp_secret_key = user.otp_secret_key
|
||||
|
||||
if check_otp_code(otp_secret_key, otp_code):
|
||||
user.disable_otp()
|
||||
valid = user.check_mfa(otp_code)
|
||||
if valid:
|
||||
user.disable_mfa()
|
||||
user.save()
|
||||
return super().form_valid(form)
|
||||
else:
|
||||
|
@ -237,16 +230,13 @@ class UserOtpDisableAuthenticationView(FormView):
|
|||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class UserOtpUpdateView(UserOtpDisableAuthenticationView):
|
||||
class UserOtpUpdateView(UserDisableMFAView):
|
||||
success_url = reverse_lazy('users:user-otp-enable-bind')
|
||||
|
||||
|
||||
class UserOtpSettingsSuccessView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
# def get(self, request, *args, **kwargs):
|
||||
# return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
title, describe = self.get_title_describe()
|
||||
context = {
|
||||
|
@ -265,8 +255,9 @@ class UserOtpSettingsSuccessView(TemplateView):
|
|||
auth_logout(self.request)
|
||||
title = _('MFA enable success')
|
||||
describe = _('MFA enable success, return login page')
|
||||
if not user.otp_enabled:
|
||||
if not user.mfa_enabled:
|
||||
title = _('MFA disable success')
|
||||
describe = _('MFA disable success, return login page')
|
||||
|
||||
return title, describe
|
||||
|
||||
|
|
2
jms
2
jms
|
@ -19,7 +19,7 @@ from daemon import pidfile
|
|||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
|
||||
try:
|
||||
from apps.jumpserver import const
|
||||
|
|
Loading…
Reference in New Issue