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
|
import random
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
from rest_framework.generics import RetrieveAPIView
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
from common.utils import get_logger, get_object_or_none
|
||||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from orgs.mixins import generics
|
from orgs.mixins import generics
|
||||||
from ..models import Asset, Node
|
from ..models import Asset, Node, Platform
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..tasks import update_asset_hardware_info_manual, \
|
from ..tasks import (
|
||||||
test_asset_connectivity_manual
|
update_asset_hardware_info_manual, test_asset_connectivity_manual
|
||||||
|
)
|
||||||
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
|
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetViewSet',
|
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
||||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||||
'AssetGatewayApi',
|
'AssetGatewayApi', 'AssetPlatformViewSet',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,6 +56,34 @@ class AssetViewSet(OrgBulkModelViewSet):
|
||||||
self.set_assets_node(assets)
|
self.set_assets_node(assets)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPlatformRetrieveApi(RetrieveAPIView):
|
||||||
|
queryset = Platform.objects.all()
|
||||||
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
|
serializer_class = serializers.PlatformSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
asset_pk = self.kwargs.get('pk')
|
||||||
|
asset = get_object_or_404(Asset, pk=asset_pk)
|
||||||
|
return asset.platform
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPlatformViewSet(ModelViewSet):
|
||||||
|
queryset = Platform.objects.all()
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
serializer_class = serializers.PlatformSerializer
|
||||||
|
filterset_fields = ['name', 'base']
|
||||||
|
search_fields = ['name']
|
||||||
|
|
||||||
|
def check_object_permissions(self, request, obj):
|
||||||
|
if request.method.lower() in ['delete', 'put', 'patch'] and \
|
||||||
|
obj.internal:
|
||||||
|
self.permission_denied(
|
||||||
|
request, message={"detail": "Internal platform"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return super().check_object_permissions(request, obj)
|
||||||
|
|
||||||
|
|
||||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
Refresh asset hardware info
|
Refresh asset hardware info
|
||||||
|
|
|
@ -177,7 +177,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi):
|
||||||
if not include_assets:
|
if not include_assets:
|
||||||
return queryset
|
return queryset
|
||||||
assets = self.instance.get_assets().only(
|
assets = self.instance.get_assets().only(
|
||||||
"id", "hostname", "ip", 'platform', "os",
|
"id", "hostname", "ip", "os",
|
||||||
"org_id", "protocols",
|
"org_id", "protocols",
|
||||||
)
|
)
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
|
|
|
@ -5,3 +5,4 @@ from .label import *
|
||||||
from .user import *
|
from .user import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
from .cmd_filter import *
|
from .cmd_filter import *
|
||||||
|
from .platform import *
|
||||||
|
|
|
@ -6,13 +6,13 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from orgs.mixins.forms import OrgModelForm
|
from orgs.mixins.forms import OrgModelForm
|
||||||
|
|
||||||
from ..models import Asset, Node
|
from ..models import Asset
|
||||||
from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT
|
from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
|
'AssetCreateUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,17 +27,27 @@ class ProtocolForm(forms.Form):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssetCreateForm(OrgModelForm):
|
class AssetCreateUpdateForm(OrgModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.data:
|
self.set_platform_to_name()
|
||||||
return
|
self.set_fields_queryset()
|
||||||
|
|
||||||
|
def set_fields_queryset(self):
|
||||||
nodes_field = self.fields['nodes']
|
nodes_field = self.fields['nodes']
|
||||||
|
nodes_choices = []
|
||||||
if self.instance:
|
if self.instance:
|
||||||
nodes_field.choices = [(n.id, n.full_value) for n in
|
nodes_choices = [
|
||||||
self.instance.nodes.all()]
|
(n.id, n.full_value) for n in
|
||||||
else:
|
self.instance.nodes.all()
|
||||||
nodes_field.choices = []
|
]
|
||||||
|
nodes_field.choices = nodes_choices
|
||||||
|
|
||||||
|
def set_platform_to_name(self):
|
||||||
|
platform_field = self.fields['platform']
|
||||||
|
platform_field.to_field_name = 'name'
|
||||||
|
if self.instance:
|
||||||
|
self.initial['platform'] = self.instance.platform.name
|
||||||
|
|
||||||
def add_nodes_initial(self, node):
|
def add_nodes_initial(self, node):
|
||||||
nodes_field = self.fields['nodes']
|
nodes_field = self.fields['nodes']
|
||||||
|
@ -49,7 +59,7 @@ class AssetCreateForm(OrgModelForm):
|
||||||
fields = [
|
fields = [
|
||||||
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
|
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
|
||||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||||
'domain',
|
'domain', 'number',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'nodes': forms.SelectMultiple(attrs={
|
'nodes': forms.SelectMultiple(attrs={
|
||||||
|
@ -64,52 +74,8 @@ class AssetCreateForm(OrgModelForm):
|
||||||
'domain': forms.Select(attrs={
|
'domain': forms.Select(attrs={
|
||||||
'class': 'select2', 'data-placeholder': _('Domain')
|
'class': 'select2', 'data-placeholder': _('Domain')
|
||||||
}),
|
}),
|
||||||
}
|
'platform': forms.Select(attrs={
|
||||||
labels = {
|
'class': 'select2', 'data-placeholder': _('Platform')
|
||||||
'nodes': _("Node"),
|
|
||||||
}
|
|
||||||
help_texts = {
|
|
||||||
'hostname': GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT,
|
|
||||||
'admin_user': _(
|
|
||||||
'root or other NOPASSWD sudo privilege user existed in asset,'
|
|
||||||
'If asset is windows or other set any one, more see admin user left menu'
|
|
||||||
),
|
|
||||||
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
|
|
||||||
'domain': _("If your have some network not connect with each other, you can set domain")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUpdateForm(OrgModelForm):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
if self.data:
|
|
||||||
return
|
|
||||||
nodes_field = self.fields['nodes']
|
|
||||||
if self.instance:
|
|
||||||
nodes_field.choices = ((n.id, n.full_value) for n in
|
|
||||||
self.instance.nodes.all())
|
|
||||||
else:
|
|
||||||
nodes_field.choices = []
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Asset
|
|
||||||
fields = [
|
|
||||||
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
|
|
||||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
|
||||||
'domain',
|
|
||||||
]
|
|
||||||
widgets = {
|
|
||||||
'nodes': forms.SelectMultiple(attrs={
|
|
||||||
'class': 'nodes-select2', 'data-placeholder': _('Node')
|
|
||||||
}),
|
|
||||||
'admin_user': forms.Select(attrs={
|
|
||||||
'class': 'select2', 'data-placeholder': _('Admin user')
|
|
||||||
}),
|
|
||||||
'labels': forms.SelectMultiple(attrs={
|
|
||||||
'class': 'select2', 'data-placeholder': _('Label')
|
|
||||||
}),
|
|
||||||
'domain': forms.Select(attrs={
|
|
||||||
'class': 'select2', 'data-placeholder': _('Domain')
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
|
|
|
@ -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.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .utils import Connectivity
|
from common.fields.model import JsonDictTextField
|
||||||
|
from common.utils import lazyproperty
|
||||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||||
|
from .utils import Connectivity
|
||||||
|
|
||||||
__all__ = ['Asset', 'ProtocolsMixin']
|
__all__ = ['Asset', 'ProtocolsMixin', 'Platform']
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +39,13 @@ def default_node():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AssetManager(OrgManager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().annotate(
|
||||||
|
platform_base=models.F('platform__base')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssetQuerySet(models.QuerySet):
|
class AssetQuerySet(models.QuerySet):
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.filter(is_active=True)
|
return self.filter(is_active=True)
|
||||||
|
@ -119,6 +128,41 @@ class NodesRelationMixin:
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
class Platform(models.Model):
|
||||||
|
CHARSET_CHOICES = (
|
||||||
|
('utf8', 'UTF-8'),
|
||||||
|
('gbk', 'GBK'),
|
||||||
|
)
|
||||||
|
BASE_CHOICES = (
|
||||||
|
('Linux', 'Linux'),
|
||||||
|
('Unix', 'Unix'),
|
||||||
|
('MacOS', 'MacOS'),
|
||||||
|
('BSD', 'BSD'),
|
||||||
|
('Windows', 'Windows'),
|
||||||
|
('Other', 'Other'),
|
||||||
|
)
|
||||||
|
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
|
||||||
|
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
|
||||||
|
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
||||||
|
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
|
||||||
|
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
||||||
|
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls):
|
||||||
|
linux, created = cls.objects.get_or_create(
|
||||||
|
defaults={'name': 'Linux'}, name='Linux'
|
||||||
|
)
|
||||||
|
return linux.id
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Platform")
|
||||||
|
# ordering = ('name',)
|
||||||
|
|
||||||
|
|
||||||
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
# Important
|
# Important
|
||||||
PLATFORM_CHOICES = (
|
PLATFORM_CHOICES = (
|
||||||
|
@ -138,9 +182,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
choices=ProtocolsMixin.PROTOCOL_CHOICES,
|
choices=ProtocolsMixin.PROTOCOL_CHOICES,
|
||||||
verbose_name=_('Protocol'))
|
verbose_name=_('Protocol'))
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
|
|
||||||
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
||||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
|
||||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||||
|
@ -175,7 +218,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
|
||||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||||
_connectivity = None
|
_connectivity = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -191,19 +234,20 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
return True, warning
|
return True, warning
|
||||||
|
|
||||||
def is_windows(self):
|
def is_windows(self):
|
||||||
if self.platform in ("Windows", "Windows2016"):
|
return self.platform_base == "Windows"
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_unixlike(self):
|
def is_unixlike(self):
|
||||||
if self.platform not in ("Windows", "Windows2016", "Other"):
|
if self.platform_base not in ("Windows", "Windows2016", "Other"):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_support_ansible(self):
|
def is_support_ansible(self):
|
||||||
return self.has_protocol('ssh') and self.platform not in ("Other",)
|
return self.has_protocol('ssh') and self.platform_base not in ("Other",)
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def platform_base(self):
|
||||||
|
return self.platform.base
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cpu_info(self):
|
def cpu_info(self):
|
||||||
|
@ -264,9 +308,9 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
def as_tree_node(self, parent_node):
|
def as_tree_node(self, parent_node):
|
||||||
from common.tree import TreeNode
|
from common.tree import TreeNode
|
||||||
icon_skin = 'file'
|
icon_skin = 'file'
|
||||||
if self.platform.lower() == 'windows':
|
if self.platform_base.lower() == 'windows':
|
||||||
icon_skin = 'windows'
|
icon_skin = 'windows'
|
||||||
elif self.platform.lower() == 'linux':
|
elif self.platform_base.lower() == 'linux':
|
||||||
icon_skin = 'linux'
|
icon_skin = 'linux'
|
||||||
data = {
|
data = {
|
||||||
'id': str(self.id),
|
'id': str(self.id),
|
||||||
|
@ -283,7 +327,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
'hostname': self.hostname,
|
'hostname': self.hostname,
|
||||||
'ip': self.ip,
|
'ip': self.ip,
|
||||||
'protocols': self.protocols_as_list,
|
'protocols': self.protocols_as_list,
|
||||||
'platform': self.platform,
|
'platform': self.platform_base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from ..models import Asset, Node, Label
|
from ..models import Asset, Node, Label, Platform
|
||||||
from ..const import (
|
from ..const import (
|
||||||
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
|
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
|
||||||
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
|
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
|
||||||
|
@ -16,7 +16,8 @@ from .base import ConnectivitySerializer
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetSerializer', 'AssetSimpleSerializer',
|
'AssetSerializer', 'AssetSimpleSerializer',
|
||||||
'ProtocolsField',
|
'ProtocolsField', 'PlatformSerializer',
|
||||||
|
'AssetDetailSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +66,9 @@ class ProtocolsField(serializers.ListField):
|
||||||
|
|
||||||
|
|
||||||
class AssetSerializer(BulkOrgResourceModelSerializer):
|
class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
platform = serializers.SlugRelatedField(
|
||||||
|
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
|
||||||
|
)
|
||||||
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
||||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||||
|
|
||||||
|
@ -111,7 +115,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
queryset = queryset.prefetch_related(
|
queryset = queryset.prefetch_related(
|
||||||
Prefetch('nodes', queryset=Node.objects.all().only('id')),
|
Prefetch('nodes', queryset=Node.objects.all().only('id')),
|
||||||
Prefetch('labels', queryset=Label.objects.all().only('id')),
|
Prefetch('labels', queryset=Label.objects.all().only('id')),
|
||||||
).select_related('admin_user', 'domain')
|
).select_related('admin_user', 'domain', 'platform')
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def compatible_with_old_protocol(self, validated_data):
|
def compatible_with_old_protocol(self, validated_data):
|
||||||
|
@ -139,6 +143,21 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformSerializer(serializers.ModelSerializer):
|
||||||
|
meta = serializers.DictField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Platform
|
||||||
|
fields = [
|
||||||
|
'id', 'name', 'base', 'charset',
|
||||||
|
'internal', 'meta', 'comment'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AssetDetailSerializer(AssetSerializer):
|
||||||
|
platform = PlatformSerializer(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class AssetSimpleSerializer(serializers.ModelSerializer):
|
class AssetSimpleSerializer(serializers.ModelSerializer):
|
||||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||||
|
|
||||||
|
|
|
@ -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="wrapper wrapper-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px;overflow: auto;max-height: 500px">
|
<div class="col-sm-3" id="split-left" style="padding-left: 3px;overflow: auto;max-height: 500px">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||||
<div class="file-manager ">
|
<div class="file-manager ">
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
<div class="col-sm-9 animated fadeInRight" id="split-right">
|
||||||
<div class="mail-box-header">
|
<div class="mail-box-header">
|
||||||
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
|
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -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. '%}
|
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<div class="" style="float: right">
|
{% include '_csv_import_export.html' %}
|
||||||
<div class=" btn-group">
|
|
||||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a class=" btn_export" tabindex="0">
|
|
||||||
<span>{% trans "Export" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
|
||||||
<span>{% trans "Import" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
|
||||||
<span>{% trans "Update" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
|
@ -42,9 +21,6 @@
|
||||||
<th class="text-center">{% trans 'Name' %}</th>
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
<th class="text-center">{% trans 'Username' %}</th>
|
<th class="text-center">{% trans 'Username' %}</th>
|
||||||
<th class="text-center">{% trans 'Asset' %}</th>
|
<th class="text-center">{% trans 'Asset' %}</th>
|
||||||
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
|
|
||||||
{# <th class="text-center">{% trans 'Unreachable' %}</th>#}
|
|
||||||
{# <th class="text-center">{% trans 'Ratio' %}</th>#}
|
|
||||||
<th class="text-center">{% trans 'Comment' %}</th>
|
<th class="text-center">{% trans 'Comment' %}</th>
|
||||||
<th class="text-center">{% trans 'Action' %}</th>
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -52,8 +28,6 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% include 'assets/_admin_user_import_modal.html' %}
|
|
||||||
{% include 'assets/_admin_user_update_modal.html' %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_bottom_left %}{% endblock %}
|
{% block content_bottom_left %}{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
|
@ -80,14 +54,13 @@ function initTable() {
|
||||||
{data: "comment"}, {data: "id", orderable: false, width: "100px"}
|
{data: "comment"}, {data: "id", orderable: false, width: "100px"}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
admin_user_table = jumpserver.initServerSideDataTable(options);
|
return jumpserver.initServerSideDataTable(options);
|
||||||
return admin_user_table
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
admin_user_table = initTable();
|
||||||
|
initCsvImportExport(admin_user_table, "{% trans "Admin user" %}")
|
||||||
})
|
})
|
||||||
|
|
||||||
.on('click', '.btn_admin_user_delete', function () {
|
.on('click', '.btn_admin_user_delete', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $data_table = $("#admin_user_list_table").DataTable();
|
var $data_table = $("#admin_user_list_table").DataTable();
|
||||||
|
@ -100,69 +73,5 @@ $(document).ready(function(){
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
})
|
})
|
||||||
.on('click', '.btn_export', function(){
|
|
||||||
var admin_users = admin_user_table.selected;
|
|
||||||
var data = {
|
|
||||||
'resources': admin_users
|
|
||||||
};
|
|
||||||
var search = $("input[type='search']").val();
|
|
||||||
var props = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
success_url: "{% url 'api-assets:admin-user-list' %}",
|
|
||||||
format: "csv",
|
|
||||||
params: {
|
|
||||||
search: search
|
|
||||||
}
|
|
||||||
};
|
|
||||||
APIExportData(props);
|
|
||||||
}).on('click', '#btn_import_confirm',function () {
|
|
||||||
var url = "{% url 'api-assets:admin-user-list' %}";
|
|
||||||
var file = document.getElementById('id_file').files[0];
|
|
||||||
if(!file){
|
|
||||||
toastr.error("{% trans "Please select file" %}");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var data_table = $('#admin_user_list_table').DataTable();
|
|
||||||
APIImportData({
|
|
||||||
url: url,
|
|
||||||
method: "POST",
|
|
||||||
body: file,
|
|
||||||
data_table: data_table
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.on('click', '#download_update_template', function () {
|
|
||||||
var admin_users = admin_user_table.selected;
|
|
||||||
var data = {
|
|
||||||
'resources': admin_users
|
|
||||||
};
|
|
||||||
var search = $("input[type='search']").val();
|
|
||||||
var props = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
success_url: "{% url 'api-assets:admin-user-list' %}?format=csv&template=update",
|
|
||||||
format: 'csv',
|
|
||||||
params: {
|
|
||||||
search: search
|
|
||||||
}
|
|
||||||
};
|
|
||||||
APIExportData(props);
|
|
||||||
})
|
|
||||||
.on('click', '#btn_update_confirm', function () {
|
|
||||||
var file = document.getElementById('update_file').files[0];
|
|
||||||
if(!file){
|
|
||||||
toastr.error("{% trans "Please select file" %}");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var url = "{% url 'api-assets:admin-user-list' %}";
|
|
||||||
var data_table = $('#admin_user_list_table').DataTable();
|
|
||||||
|
|
||||||
APIImportData({
|
|
||||||
url: url,
|
|
||||||
method: "PUT",
|
|
||||||
body: file,
|
|
||||||
data_table: data_table
|
|
||||||
});
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,131 +1,52 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base_asset_tree_list.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block help_message %}
|
{% 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' %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block custom_head_css_js %}
|
{% block table_container %}
|
||||||
{# <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">#}
|
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||||
{# <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>#}
|
{% include '_csv_import_export.html' %}
|
||||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
<div class="btn-group" style="float: right">
|
||||||
<style type="text/css">
|
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||||
div#rMenu {
|
<ul class="dropdown-menu labels">
|
||||||
position:absolute;
|
{% for label in labels %}
|
||||||
visibility:hidden;
|
<li><a style="font-weight: bolder">{{ label.name }}#{{ label.value }}</a></li>
|
||||||
text-align: left;
|
{% endfor %}
|
||||||
{#top: 100%;#}
|
</ul>
|
||||||
top: 0;
|
</div>
|
||||||
left: 0;
|
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
|
||||||
z-index: 1000;
|
<thead>
|
||||||
{#float: left;#}
|
<tr>
|
||||||
padding: 0 0;
|
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||||
margin: 2px 0 0;
|
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||||
list-style: none;
|
<th class="text-center">{% trans 'IP' %}</th>
|
||||||
background-clip: padding-box;
|
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||||
}
|
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||||
.dataTables_wrapper .dataTables_processing {
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
opacity: .9;
|
</tr>
|
||||||
border: none;
|
</thead>
|
||||||
}
|
<tbody>
|
||||||
div#rMenu li{
|
</tbody>
|
||||||
margin: 1px 0;
|
</table>
|
||||||
cursor: pointer;
|
<div id="actions" class="hide">
|
||||||
list-style: none outside none;
|
<div class="input-group">
|
||||||
}
|
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||||
.dropdown a:hover {
|
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||||
background-color: #f1f1f1
|
<option value="update">{% trans 'Update selected' %}</option>
|
||||||
}
|
<option value="remove">{% trans 'Remove from this node' %}</option>
|
||||||
</style>
|
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||||
|
<option value="active">{% trans 'Active selected' %}</option>
|
||||||
{% endblock %}
|
</select>
|
||||||
|
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||||
{% block content %}
|
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||||
<div class="wrapper wrapper-content">
|
{% trans 'Submit' %}
|
||||||
<div class="row">
|
</button>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include 'assets/_asset_update_modal.html' %}
|
|
||||||
{% include 'assets/_asset_import_modal.html' %}
|
|
||||||
{% include 'assets/_asset_list_modal.html' %}
|
{% include 'assets/_asset_list_modal.html' %}
|
||||||
{% include 'assets/_node_detail_modal.html' %}
|
{% include 'assets/_node_detail_modal.html' %}
|
||||||
{% endblock %}
|
{% 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) {
|
function onNodeSelected(event, treeNode) {
|
||||||
current_node = treeNode;
|
current_node = treeNode;
|
||||||
current_node_id = treeNode.meta.node.id;
|
current_node_id = treeNode.meta.node.id;
|
||||||
|
@ -261,7 +166,8 @@ function onAssetModalConfirmAddAssetToNode(table) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
asset_table = initTable();
|
||||||
|
initCsvImportExport(asset_table, "{% trans "Asset" %}");
|
||||||
initTree();
|
initTree();
|
||||||
|
|
||||||
if(getCookie('show_current_asset') === '1'){
|
if(getCookie('show_current_asset') === '1'){
|
||||||
|
@ -282,81 +188,6 @@ $(document).ready(function(){
|
||||||
$("#asset_list_table_filter input").val(val);
|
$("#asset_list_table_filter input").val(val);
|
||||||
asset_table.search(val).draw();
|
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 () {
|
.on('click', '.btn-create-asset', function () {
|
||||||
var url = "{% url 'assets:asset-create' %}";
|
var url = "{% url 'assets:asset-create' %}";
|
||||||
if (current_node_id) {
|
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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<div class="" style="float: right">
|
{% include '_csv_import_export.html' %}
|
||||||
<div class=" btn-group">
|
|
||||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a class=" btn_export" tabindex="0">
|
|
||||||
<span>{% trans "Export" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
|
||||||
<span>{% trans "Import" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
|
||||||
<span>{% trans "Update" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
|
@ -57,8 +36,6 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% include 'assets/_system_user_import_modal.html' %}
|
|
||||||
{% include 'assets/_system_user_update_modal.html' %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
|
@ -89,14 +66,13 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
system_user_table = jumpserver.initServerSideDataTable(options);
|
return jumpserver.initServerSideDataTable(options);
|
||||||
return system_user_table
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
system_user_table = initTable();
|
||||||
|
initCsvImportExport(system_user_table, "{% trans 'System user' %}")
|
||||||
})
|
})
|
||||||
|
|
||||||
.on('click', '.btn_admin_user_delete', function () {
|
.on('click', '.btn_admin_user_delete', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $data_table = $('#cluster_list_table').DataTable();
|
var $data_table = $('#cluster_list_table').DataTable();
|
||||||
|
@ -108,125 +84,6 @@ $(document).ready(function(){
|
||||||
$data_table.ajax.reload();
|
$data_table.ajax.reload();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
})
|
})
|
||||||
|
|
||||||
.on('click', '#btn_bulk_update', function () {
|
|
||||||
var action = $('#slct_bulk_update').val();
|
|
||||||
var $data_table = $('#system_user_list_table').DataTable();
|
|
||||||
var id_list = [];
|
|
||||||
var plain_id_list = [];
|
|
||||||
$data_table.rows({selected: true}).every(function(){
|
|
||||||
id_list.push({id: this.data().id});
|
|
||||||
plain_id_list.push(this.data().id);
|
|
||||||
});
|
|
||||||
if (id_list === []) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var the_url = "{% url 'api-assets:system-user-list' %}";
|
|
||||||
function doDelete() {
|
|
||||||
swal({
|
|
||||||
title: "{% trans 'Are you sure?' %}",
|
|
||||||
text: "{% trans 'This will delete the selected System Users !!!' %}",
|
|
||||||
type: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonText: "{% trans 'Cancel' %}",
|
|
||||||
confirmButtonColor: "#DD6B55",
|
|
||||||
confirmButtonText: "{% trans 'Confirm' %}",
|
|
||||||
closeOnConfirm: false
|
|
||||||
}, function() {
|
|
||||||
var success = function() {
|
|
||||||
var msg = "{% trans 'System Users Deleted.' %}";
|
|
||||||
swal("{% trans 'System Users Delete' %}", msg, "success");
|
|
||||||
$('#system_user_list_table').DataTable().ajax.reload();
|
|
||||||
};
|
|
||||||
var fail = function() {
|
|
||||||
var msg = "{% trans 'System Users Deleting failed.' %}";
|
|
||||||
swal("{% trans 'System Users Delete' %}", msg, "error");
|
|
||||||
};
|
|
||||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
|
||||||
requestApi({url: url_delete, method: 'DELETE', success: success, error: fail});
|
|
||||||
$data_table.ajax.reload();
|
|
||||||
jumpserver.checked = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function doUpdate() {
|
|
||||||
{# TODO: bulk update the System Users #}
|
|
||||||
}
|
|
||||||
switch (action) {
|
|
||||||
case 'delete':
|
|
||||||
doDelete();
|
|
||||||
break;
|
|
||||||
case 'update':
|
|
||||||
doUpdate();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('click', '.btn_export', function () {
|
|
||||||
var system_users = system_user_table.selected;
|
|
||||||
var data = {
|
|
||||||
'resources': system_users
|
|
||||||
};
|
|
||||||
var search = $("input[type='search']").val();
|
|
||||||
var props = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
success_url: "{% url 'api-assets:system-user-list' %}",
|
|
||||||
format: "csv",
|
|
||||||
params:{
|
|
||||||
search:search
|
|
||||||
}
|
|
||||||
};
|
|
||||||
APIExportData(props);
|
|
||||||
})
|
|
||||||
.on('click', '#btn_import_confirm', function () {
|
|
||||||
var url = "{% url 'api-assets:system-user-list' %}";
|
|
||||||
var file = document.getElementById('id_file').files[0];
|
|
||||||
if(!file){
|
|
||||||
toastr.error("{% trans 'Please select file' %}");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var data_table = $('#system_user_list_table').DataTable();
|
|
||||||
APIImportData({
|
|
||||||
url: url,
|
|
||||||
method: "POST",
|
|
||||||
body: file,
|
|
||||||
data_table: data_table
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.on('click', '#download_update_template', function () {
|
|
||||||
var system_users = system_user_table.selected;
|
|
||||||
var data = {
|
|
||||||
'resources': system_users
|
|
||||||
};
|
|
||||||
var search = $("input[type='search']").val();
|
|
||||||
var props = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
success_url: "{% url 'api-assets:system-user-list' %}?format=csv&template=update",
|
|
||||||
format: "csv",
|
|
||||||
params:{
|
|
||||||
search:search
|
|
||||||
}
|
|
||||||
};
|
|
||||||
APIExportData(props);
|
|
||||||
})
|
|
||||||
.on('click', '#btn_update_confirm', function () {
|
|
||||||
var file = document.getElementById('update_file').files[0];
|
|
||||||
if(!file){
|
|
||||||
toastr.error("{% trans "Please select file" %}");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var url = "{% url 'api-assets:system-user-list' %}";
|
|
||||||
var data_table = $('#system_user_list_table').DataTable();
|
|
||||||
|
|
||||||
APIImportData({
|
|
||||||
url: url,
|
|
||||||
method: "PUT",
|
|
||||||
body: file,
|
|
||||||
data_table: data_table
|
|
||||||
});
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ app_name = 'assets'
|
||||||
|
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||||
|
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
|
||||||
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
|
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
|
||||||
router.register(r'system-users', api.SystemUserViewSet, 'system-user')
|
router.register(r'system-users', api.SystemUserViewSet, 'system-user')
|
||||||
router.register(r'labels', api.LabelViewSet, 'label')
|
router.register(r'labels', api.LabelViewSet, 'label')
|
||||||
|
@ -37,6 +38,8 @@ urlpatterns = [
|
||||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||||
path('assets/<uuid:pk>/gateway/',
|
path('assets/<uuid:pk>/gateway/',
|
||||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||||
|
path('assets/<uuid:pk>/platform/',
|
||||||
|
api.AssetPlatformRetrieveApi.as_view(), name='asset-platform-detail'),
|
||||||
|
|
||||||
path('asset-users/auth-info/',
|
path('asset-users/auth-info/',
|
||||||
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
|
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
|
||||||
|
|
|
@ -16,6 +16,11 @@ urlpatterns = [
|
||||||
# Asset user view
|
# Asset user view
|
||||||
path('asset/<uuid:pk>/asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'),
|
path('asset/<uuid:pk>/asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'),
|
||||||
|
|
||||||
|
path('platform/', views.PlatformListView.as_view(), name='platform-list'),
|
||||||
|
path('platform/create/', views.PlatformCreateView.as_view(), name='platform-create'),
|
||||||
|
path('platform/<int:pk>/', views.PlatformDetailView.as_view(), name='platform-detail'),
|
||||||
|
path('platform/<int:pk>/update/', views.PlatformUpdateView.as_view(), name='platform-update'),
|
||||||
|
|
||||||
# User asset view
|
# User asset view
|
||||||
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
|
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
from .asset import *
|
from .asset import *
|
||||||
|
from .platform import *
|
||||||
from .system_user import *
|
from .system_user import *
|
||||||
from .admin_user import *
|
from .admin_user import *
|
||||||
from .label import *
|
from .label import *
|
||||||
|
|
|
@ -74,7 +74,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
|
||||||
|
|
||||||
class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
|
class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
|
||||||
model = Asset
|
model = Asset
|
||||||
form_class = forms.AssetCreateForm
|
form_class = forms.AssetCreateUpdateForm
|
||||||
template_name = 'assets/asset_create.html'
|
template_name = 'assets/asset_create.html'
|
||||||
success_url = reverse_lazy('assets:asset-list')
|
success_url = reverse_lazy('assets:asset-list')
|
||||||
permission_classes = [IsOrgAdmin]
|
permission_classes = [IsOrgAdmin]
|
||||||
|
@ -110,7 +110,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
|
||||||
|
|
||||||
class AssetUpdateView(PermissionsMixin, UpdateView):
|
class AssetUpdateView(PermissionsMixin, UpdateView):
|
||||||
model = Asset
|
model = Asset
|
||||||
form_class = forms.AssetUpdateForm
|
form_class = forms.AssetCreateUpdateForm
|
||||||
template_name = 'assets/asset_update.html'
|
template_name = 'assets/asset_update.html'
|
||||||
success_url = reverse_lazy('assets:asset-list')
|
success_url = reverse_lazy('assets:asset-list')
|
||||||
permission_classes = [IsOrgAdmin]
|
permission_classes = [IsOrgAdmin]
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.views.generic import TemplateView, CreateView, \
|
from django.views.generic import (
|
||||||
UpdateView, DeleteView, DetailView
|
TemplateView, CreateView, UpdateView, DeleteView, DetailView
|
||||||
|
)
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.urls import reverse_lazy, reverse
|
from django.urls import reverse_lazy, reverse
|
||||||
|
|
|
@ -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 jumpserver.utils import current_request
|
||||||
from common.utils import get_request_ip, get_logger, get_syslogger
|
from common.utils import get_request_ip, get_logger, get_syslogger
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
from users.signals import post_user_change_password
|
||||||
from authentication.signals import post_auth_failed, post_auth_success
|
from authentication.signals import post_auth_failed, post_auth_success
|
||||||
from terminal.models import Session, Command
|
from terminal.models import Session, Command
|
||||||
from common.utils.encode import model_to_json
|
from common.utils.encode import model_to_json
|
||||||
|
@ -18,7 +19,7 @@ from . import models
|
||||||
from .tasks import write_login_log_async
|
from .tasks import write_login_log_async
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
sys_logger = get_syslogger("audits")
|
sys_logger = get_syslogger(__name__)
|
||||||
json_render = JSONRenderer()
|
json_render = JSONRenderer()
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ def create_operate_log(action, sender, resource):
|
||||||
logger.error("Create operate log error: {}".format(e))
|
logger.error("Create operate log error: {}".format(e))
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, dispatch_uid="my_unique_identifier")
|
@receiver(post_save)
|
||||||
def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs):
|
def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs):
|
||||||
if instance._meta.object_name == 'User' and \
|
if instance._meta.object_name == 'User' and \
|
||||||
update_fields and 'last_login' in update_fields:
|
update_fields and 'last_login' in update_fields:
|
||||||
|
@ -62,21 +63,27 @@ def on_object_created_or_update(sender, instance=None, created=False, update_fie
|
||||||
create_operate_log(action, sender, instance)
|
create_operate_log(action, sender, instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, dispatch_uid="my_unique_identifier")
|
@receiver(post_delete)
|
||||||
def on_object_delete(sender, instance=None, **kwargs):
|
def on_object_delete(sender, instance=None, **kwargs):
|
||||||
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance)
|
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
|
@receiver(post_user_change_password, sender=User)
|
||||||
def on_user_change_password(sender, instance=None, **kwargs):
|
def on_user_change_password(sender, user=None, **kwargs):
|
||||||
if hasattr(instance, '_set_password'):
|
if not current_request:
|
||||||
if not current_request or not current_request.user.is_authenticated:
|
remote_addr = '127.0.0.1'
|
||||||
return
|
change_by = 'System'
|
||||||
with transaction.atomic():
|
else:
|
||||||
models.PasswordChangeLog.objects.create(
|
remote_addr = get_request_ip(current_request)
|
||||||
user=instance, change_by=current_request.user,
|
if not current_request.user.is_authenticated:
|
||||||
remote_addr=get_request_ip(current_request),
|
change_by = str(user)
|
||||||
)
|
else:
|
||||||
|
change_by = str(current_request.user)
|
||||||
|
with transaction.atomic():
|
||||||
|
models.PasswordChangeLog.objects.create(
|
||||||
|
user=str(user), change_by=change_by,
|
||||||
|
remote_addr=remote_addr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def on_audits_log_create(sender, instance=None, **kwargs):
|
def on_audits_log_create(sender, instance=None, **kwargs):
|
||||||
|
@ -95,7 +102,7 @@ def on_audits_log_create(sender, instance=None, **kwargs):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
data = model_to_json(instance)
|
data = model_to_json(instance, indent=None)
|
||||||
msg = "{} - {}".format(category, data)
|
msg = "{} - {}".format(category, data)
|
||||||
sys_logger.info(msg)
|
sys_logger.info(msg)
|
||||||
|
|
||||||
|
|
|
@ -102,47 +102,47 @@
|
||||||
|
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
jumpserver.initStaticTable('#login_log_table');
|
jumpserver.initStaticTable('#login_log_table');
|
||||||
$('#date .input-daterange').datepicker({
|
$('#date .input-daterange').datepicker({
|
||||||
format: "yyyy-mm-dd",
|
format: "yyyy-mm-dd",
|
||||||
todayBtn: "linked",
|
todayBtn: "linked",
|
||||||
keyboardNavigation: false,
|
keyboardNavigation: false,
|
||||||
forceParse: false,
|
forceParse: false,
|
||||||
calendarWeeks: true,
|
calendarWeeks: true,
|
||||||
autoclose: true
|
autoclose: true
|
||||||
|
|
||||||
});
|
});
|
||||||
$('.select2').select2({
|
$('.select2').select2({
|
||||||
dropdownAutoWidth: true,
|
dropdownAutoWidth: true,
|
||||||
width: 'auto'
|
width: 'auto'
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.on('click', '.btn_export', function () {
|
||||||
|
var date_from = $('#id_date_from').val();
|
||||||
|
var date_to = $('#id_date_to').val();
|
||||||
|
var user = $('.select2 option:selected').val();
|
||||||
|
var keyword = $('#search').val();
|
||||||
|
$.ajax({
|
||||||
|
url: "{% url "audits:login-log-export" %}",
|
||||||
|
method: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
'date_from':date_from,
|
||||||
|
'date_to':date_to,
|
||||||
|
'user':user,
|
||||||
|
'keyword':keyword
|
||||||
|
}),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data, textStatus) {
|
||||||
|
window.open(data.redirect)
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
toastr.error('Export failed');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.on('click', '.btn_export', function () {
|
})
|
||||||
var date_from = $('#id_date_from').val();
|
</script>
|
||||||
var date_to = $('#id_date_to').val();
|
|
||||||
var user = $('.select2 option:selected').val();
|
|
||||||
var keyword = $('#search').val();
|
|
||||||
$.ajax({
|
|
||||||
url: "{% url "audits:login-log-export" %}",
|
|
||||||
method: 'POST',
|
|
||||||
data: JSON.stringify({
|
|
||||||
'date_from':date_from,
|
|
||||||
'date_to':date_to,
|
|
||||||
'user':user,
|
|
||||||
'keyword':keyword
|
|
||||||
}),
|
|
||||||
dataType: "json",
|
|
||||||
success: function (data, textStatus) {
|
|
||||||
window.open(data.redirect)
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
toastr.error('Export failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from captcha.fields import CaptchaField
|
from captcha.fields import CaptchaField
|
||||||
from django.conf import settings
|
|
||||||
from users.utils import get_login_failed_count
|
|
||||||
|
|
||||||
|
|
||||||
class UserLoginForm(forms.Form):
|
class UserLoginForm(forms.Form):
|
||||||
|
|
|
@ -27,10 +27,17 @@ class IDSpmFilterMixin:
|
||||||
|
|
||||||
class SerializerMixin:
|
class SerializerMixin:
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.request.method.lower() == 'get' and\
|
serializer_class = None
|
||||||
self.request.query_params.get('draw') \
|
if hasattr(self, 'serializer_classes') and \
|
||||||
and hasattr(self, 'serializer_display_class'):
|
isinstance(self.serializer_classes, dict):
|
||||||
return self.serializer_display_class
|
if self.action == 'list' and self.request.query_params.get('draw'):
|
||||||
|
serializer_class = self.serializer_classes.get('display')
|
||||||
|
if serializer_class is None:
|
||||||
|
serializer_class = self.serializer_classes.get(
|
||||||
|
self.action, self.serializer_classes.get('default')
|
||||||
|
)
|
||||||
|
if serializer_class:
|
||||||
|
return serializer_class
|
||||||
return super().get_serializer_class()
|
return super().get_serializer_class()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
@ -10,13 +11,13 @@ from django.db import connection
|
||||||
from django.conf import LazySettings
|
from django.conf import LazySettings
|
||||||
from django.db.utils import ProgrammingError, OperationalError
|
from django.db.utils import ProgrammingError, OperationalError
|
||||||
|
|
||||||
|
from jumpserver.utils import get_current_request
|
||||||
|
|
||||||
from common.utils import get_logger
|
|
||||||
from .local import thread_local
|
from .local import thread_local
|
||||||
from .signals import django_ready
|
from .signals import django_ready
|
||||||
|
|
||||||
pattern = re.compile(r'FROM `(\w+)`')
|
pattern = re.compile(r'FROM `(\w+)`')
|
||||||
logger = get_logger(__name__)
|
logger = logging.getLogger("jumpserver.common")
|
||||||
DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1'
|
DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1'
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,19 +51,29 @@ def on_request_finished_logging_db_query(sender, **kwargs):
|
||||||
counters['total'].time += float(time)
|
counters['total'].time += float(time)
|
||||||
|
|
||||||
counters = sorted(counters.items(), key=lambda x: x[1])
|
counters = sorted(counters.items(), key=lambda x: x[1])
|
||||||
|
if not counters:
|
||||||
|
return
|
||||||
|
method = 'GET'
|
||||||
|
path = '/Unknown'
|
||||||
|
current_request = get_current_request()
|
||||||
|
if current_request:
|
||||||
|
method = current_request.method
|
||||||
|
path = current_request.get_full_path()
|
||||||
|
logger.debug(">>> [{}] {}".format(method, path))
|
||||||
for name, counter in counters:
|
for name, counter in counters:
|
||||||
logger.debug("Query {:3} times using {:.2f}s {}".format(
|
logger.debug("Query {:3} times using {:.2f}s {}".format(
|
||||||
counter.counter, counter.time, name)
|
counter.counter, counter.time, name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(request_finished)
|
|
||||||
def on_request_finished_release_local(sender, **kwargs):
|
def on_request_finished_release_local(sender, **kwargs):
|
||||||
thread_local.__release_local__()
|
thread_local.__release_local__()
|
||||||
|
|
||||||
|
|
||||||
if settings.DEBUG and DEBUG_DB:
|
if settings.DEBUG and DEBUG_DB:
|
||||||
request_finished.connect(on_request_finished_logging_db_query)
|
request_finished.connect(on_request_finished_logging_db_query)
|
||||||
|
else:
|
||||||
|
request_finished.connect(on_request_finished_release_local)
|
||||||
|
|
||||||
|
|
||||||
@receiver(django_ready)
|
@receiver(django_ready)
|
||||||
|
|
|
@ -27,12 +27,12 @@ def combine_seq(s1, s2, callback=None):
|
||||||
return seq
|
return seq
|
||||||
|
|
||||||
|
|
||||||
def get_logger(name=None):
|
def get_logger(name=''):
|
||||||
return logging.getLogger('jumpserver.%s' % name)
|
return logging.getLogger('jumpserver.%s' % name)
|
||||||
|
|
||||||
|
|
||||||
def get_syslogger(name=None):
|
def get_syslogger(name=''):
|
||||||
return logging.getLogger('jms.%s' % name)
|
return logging.getLogger('syslog.%s' % name)
|
||||||
|
|
||||||
|
|
||||||
def timesince(dt, since='', default="just now"):
|
def timesince(dt, since='', default="just now"):
|
||||||
|
|
|
@ -184,6 +184,7 @@ class Config(dict):
|
||||||
'ASSETS_PERM_CACHE_ENABLE': False,
|
'ASSETS_PERM_CACHE_ENABLE': False,
|
||||||
'SYSLOG_ADDR': '', # '192.168.0.1:514'
|
'SYSLOG_ADDR': '', # '192.168.0.1:514'
|
||||||
'SYSLOG_FACILITY': 'user',
|
'SYSLOG_FACILITY': 'user',
|
||||||
|
'SYSLOG_SOCKTYPE': 2,
|
||||||
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
|
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
|
||||||
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
|
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
|
||||||
'FLOWER_URL': "127.0.0.1:5555",
|
'FLOWER_URL': "127.0.0.1:5555",
|
||||||
|
@ -282,10 +283,10 @@ class DynamicConfig:
|
||||||
]
|
]
|
||||||
if self.get('AUTH_LDAP'):
|
if self.get('AUTH_LDAP'):
|
||||||
backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend')
|
backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend')
|
||||||
if self.get('AUTH_OPENID'):
|
if self.static_config.get('AUTH_OPENID'):
|
||||||
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend')
|
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend')
|
||||||
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend')
|
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend')
|
||||||
if self.get('AUTH_RADIUS'):
|
if self.static_config.get('AUTH_RADIUS'):
|
||||||
backends.insert(0, 'authentication.backends.radius.RadiusBackend')
|
backends.insert(0, 'authentication.backends.radius.RadiusBackend')
|
||||||
return backends
|
return backends
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ LOGGING = {
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'jumpserver': {
|
'jumpserver': {
|
||||||
'handlers': ['console', 'file', 'syslog'],
|
'handlers': ['console', 'file'],
|
||||||
'level': LOG_LEVEL,
|
'level': LOG_LEVEL,
|
||||||
},
|
},
|
||||||
'ops.ansible_api': {
|
'ops.ansible_api': {
|
||||||
|
@ -92,7 +92,7 @@ LOGGING = {
|
||||||
'handlers': ['console', 'file'],
|
'handlers': ['console', 'file'],
|
||||||
'level': "INFO",
|
'level': "INFO",
|
||||||
},
|
},
|
||||||
'jms.audits': {
|
'syslog': {
|
||||||
'handlers': ['syslog'],
|
'handlers': ['syslog'],
|
||||||
'level': 'INFO'
|
'level': 'INFO'
|
||||||
},
|
},
|
||||||
|
@ -110,6 +110,7 @@ if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
|
||||||
'class': 'logging.handlers.SysLogHandler',
|
'class': 'logging.handlers.SysLogHandler',
|
||||||
'facility': CONFIG.SYSLOG_FACILITY,
|
'facility': CONFIG.SYSLOG_FACILITY,
|
||||||
'address': (host, int(port)),
|
'address': (host, int(port)),
|
||||||
|
'socktype': CONFIG.SYSLOG_SOCKTYPE,
|
||||||
})
|
})
|
||||||
|
|
||||||
if not os.path.isdir(LOG_DIR):
|
if not os.path.isdir(LOG_DIR):
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -58,7 +58,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="wrapper wrapper-content">
|
<div class="wrapper wrapper-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
|
<div class="col-sm-3" id="split-left" style="padding-left: 3px">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-content mailbox-content"
|
<div class="ibox-content mailbox-content"
|
||||||
style="padding-top: 0;padding-left: 1px">
|
style="padding-top: 0;padding-left: 1px">
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
<div class="col-sm-9 animated fadeInRight" id="split-right">
|
||||||
<div class="tree-toggle">
|
<div class="tree-toggle">
|
||||||
<div class="btn btn-sm btn-primary tree-toggle-btn"
|
<div class="btn btn-sm btn-primary tree-toggle-btn"
|
||||||
onclick="toggle()">
|
onclick="toggle()">
|
||||||
|
@ -87,14 +87,14 @@
|
||||||
style="height: 100%;width: 100%"></div>
|
style="height: 100%;width: 100%"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-10">
|
<div class="col-sm-10">
|
||||||
<div class="input-group"
|
<div class="input-group"
|
||||||
style="height: 100%; width: 100%">
|
style="height: 100%; width: 100%">
|
||||||
<textarea class="form-control"
|
<textarea class="form-control"
|
||||||
id="command-text"></textarea>
|
id="command-text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-2">
|
<div class="col-sm-2">
|
||||||
<select class="select2 form-control"
|
<select class="select2 form-control"
|
||||||
id="system-users-select">
|
id="system-users-select">
|
||||||
{% for s in system_users %}
|
{% for s in system_users %}
|
||||||
|
@ -199,12 +199,12 @@
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (show === 0) {
|
if (show === 0) {
|
||||||
$("#split-left").hide(500, function () {
|
$("#split-left").hide(500, function () {
|
||||||
$("#split-right").attr("class", "col-lg-12");
|
$("#split-right").attr("class", "col-sm-12");
|
||||||
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||||
show = 1;
|
show = 1;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$("#split-right").attr("class", "col-lg-9");
|
$("#split-right").attr("class", "col-sm-9");
|
||||||
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||||
$("#split-left").show(500);
|
$("#split-left").show(500);
|
||||||
show = 0;
|
show = 0;
|
||||||
|
|
|
@ -23,8 +23,10 @@ class AssetPermissionViewSet(OrgModelViewSet):
|
||||||
资产授权列表的增删改查api
|
资产授权列表的增删改查api
|
||||||
"""
|
"""
|
||||||
model = AssetPermission
|
model = AssetPermission
|
||||||
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
serializer_classes = {
|
||||||
serializer_display_class = serializers.AssetPermissionListSerializer
|
'default': serializers.AssetPermissionCreateUpdateSerializer,
|
||||||
|
'display': serializers.AssetPermissionListSerializer
|
||||||
|
}
|
||||||
filter_fields = ['name']
|
filter_fields = ['name']
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base_asset_tree_list.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
@ -12,56 +12,36 @@
|
||||||
.toggle {
|
.toggle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.detail-key {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block table_container %}
|
||||||
<div class="wrapper wrapper-content">
|
<div class="btn-group uc pull-left m-r-5">
|
||||||
<div class="row">
|
<button class="btn btn-sm btn-primary btn-create-permission">
|
||||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px;padding-right: 0">
|
{% trans "Create permission" %}
|
||||||
{% include 'assets/_node_tree.html' %}
|
</button>
|
||||||
</div>
|
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
|
||||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
<ul class="dropdown-menu">
|
||||||
<div class="tree-toggle">
|
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
|
||||||
<div class="btn btn-sm btn-primary tree-toggle-btn">
|
</ul>
|
||||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
</div>
|
||||||
</div>
|
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
|
||||||
</div>
|
<thead>
|
||||||
<div class="mail-box-header">
|
<tr>
|
||||||
<div class="btn-group uc pull-left m-r-5">
|
<th></th>
|
||||||
<button class="btn btn-sm btn-primary btn-create-permission">
|
<th>{% trans 'Name' %}</th>
|
||||||
{% trans "Create permission" %}
|
<th class="text-center">{% trans 'User' %}</th>
|
||||||
</button>
|
<th class="text-center">{% trans 'User group' %}</th>
|
||||||
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
|
<th class="text-center">{% trans 'Asset' %}</th>
|
||||||
<ul class="dropdown-menu">
|
<th class="text-center">{% trans 'Node'%}</th>
|
||||||
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
|
<th class="text-center">{% trans 'System user' %}</th>
|
||||||
</ul>
|
<th class="text-center">{% trans 'Validity' %}</th>
|
||||||
</div>
|
<th class="text-center" >{% trans 'Action' %}</th>
|
||||||
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
|
</tr>
|
||||||
<thead>
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
<th></th>
|
</tbody>
|
||||||
<th>{% trans 'Name' %}</th>
|
</table>
|
||||||
<th class="text-center">{% trans 'User' %}</th>
|
|
||||||
<th class="text-center">{% trans 'User group' %}</th>
|
|
||||||
<th class="text-center">{% trans 'Asset' %}</th>
|
|
||||||
<th class="text-center">{% trans 'Node'%}</th>
|
|
||||||
<th class="text-center">{% trans 'System user' %}</th>
|
|
||||||
<th class="text-center">{% trans 'Validity' %}</th>
|
|
||||||
<th class="text-center" >{% trans 'Action' %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include '_filter_dropdown.html' %}
|
{% include '_filter_dropdown.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -13,71 +13,74 @@ router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRela
|
||||||
router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation')
|
router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation')
|
||||||
|
|
||||||
user_permission_urlpatterns = [
|
user_permission_urlpatterns = [
|
||||||
path('users/<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
path('<uuid:pk>/assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'),
|
||||||
path('users/assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
|
path('assets/', api.UserGrantedAssetsApi.as_view(), name='my-assets'),
|
||||||
|
|
||||||
# Assets as tree
|
# Assets as tree
|
||||||
path('users/<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
|
path('<uuid:pk>/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'),
|
||||||
path('users/assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
|
path('assets/tree/', api.UserGrantedAssetsAsTreeApi.as_view(), name='my-assets-as-tree'),
|
||||||
|
|
||||||
# Nodes
|
# Nodes
|
||||||
path('users/<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
path('<uuid:pk>/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'),
|
||||||
path('users/nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
|
path('nodes/', api.UserGrantedNodesApi.as_view(), name='my-nodes'),
|
||||||
|
|
||||||
# Node children
|
# Node children
|
||||||
path('users/<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
|
path('<uuid:pk>/nodes/children/', api.UserGrantedNodesApi.as_view(), name='user-nodes-children'),
|
||||||
path('users/nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
|
path('nodes/children/', api.UserGrantedNodesApi.as_view(), name='my-nodes-children'),
|
||||||
|
|
||||||
# Node as tree
|
# Node as tree
|
||||||
path('users/<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
|
path('<uuid:pk>/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'),
|
||||||
path('users/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
|
path('nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'),
|
||||||
|
|
||||||
# Node with assets as tree
|
# Node with assets as tree
|
||||||
path('users/<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
|
path('<uuid:pk>/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'),
|
||||||
path('users/nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
|
path('nodes-with-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'),
|
||||||
|
|
||||||
# Node children as tree
|
# Node children as tree
|
||||||
path('users/<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
|
path('<uuid:pk>/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'),
|
||||||
path('users/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
|
path('nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'),
|
||||||
|
|
||||||
# Node children with assets as tree
|
# Node children with assets as tree
|
||||||
path('users/<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
|
path('<uuid:pk>/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'),
|
||||||
path('users/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
|
path('nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'),
|
||||||
|
|
||||||
# Node assets
|
# Node assets
|
||||||
path('users/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'),
|
||||||
path('users/nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
path('nodes/<uuid:node_id>/assets/', api.UserGrantedNodeAssetsApi.as_view(), name='my-node-assets'),
|
||||||
|
|
||||||
# Asset System users
|
# Asset System users
|
||||||
path('users/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'),
|
path('users/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'),
|
||||||
path('users/assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
|
path('assets/<uuid:asset_id>/system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'),
|
||||||
]
|
]
|
||||||
|
|
||||||
user_group_permission_urlpatterns = [
|
user_group_permission_urlpatterns = [
|
||||||
# 查询某个用户组授权的资产和资产组
|
# 查询某个用户组授权的资产和资产组
|
||||||
path('user-groups/<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
path('<uuid:pk>/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'),
|
||||||
path('user-groups/<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
path('<uuid:pk>/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'),
|
||||||
path('user-groups/<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
|
path('<uuid:pk>/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'),
|
||||||
path('user-groups/<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
|
path('<uuid:pk>/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'),
|
||||||
path('user-groups/<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
|
path('<uuid:pk>/nodes/<uuid:node_id>/assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'),
|
||||||
path('user-groups/<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
|
path('<uuid:pk>/assets/<uuid:asset_id>/system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'),
|
||||||
|
]
|
||||||
|
|
||||||
|
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 = [
|
asset_permission_urlpatterns = [
|
||||||
# Assets
|
# Assets
|
||||||
path('', include(user_permission_urlpatterns)),
|
path('users/', include(user_permission_urlpatterns)),
|
||||||
|
path('user-groups/', include(user_group_permission_urlpatterns)),
|
||||||
|
path('asset-permissions/', include(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'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
asset_permission_urlpatterns += router.urls
|
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="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="panel-options">
|
<div class="panel-options">
|
||||||
<ul class="nav nav-tabs">
|
{% include 'settings/_setting_tabs.html' %}
|
||||||
<li class="active">
|
|
||||||
<a href="" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="col-sm-12" style="padding-left:0">
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
|
|
|
@ -10,53 +10,33 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="panel-options">
|
<div class="panel-options">
|
||||||
<ul class="nav nav-tabs">
|
{% include 'settings/_setting_tabs.html' %}
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="col-sm-12" style="padding-left:0">
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||||
<form action="" method="post" class="form-horizontal">
|
<form action="" method="post" class="form-horizontal">
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<h3>{% trans "Create User setting" %}</h3>
|
<h3>{% trans "Create User setting" %}</h3>
|
||||||
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SUBJECT layout="horizontal" %}
|
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SUBJECT layout="horizontal" %}
|
||||||
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_HONORIFIC layout="horizontal" %}
|
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_HONORIFIC layout="horizontal" %}
|
||||||
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_BODY layout="horizontal" %}
|
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_BODY layout="horizontal" %}
|
||||||
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE layout="horizontal" %}
|
{% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE layout="horizontal" %}
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-4 col-sm-offset-2">
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,26 +10,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="panel-options">
|
<div class="panel-options">
|
||||||
<ul class="nav nav-tabs">
|
{% include 'settings/_setting_tabs.html' %}
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="col-sm-12" style="padding-left:0">
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
|
|
|
@ -10,26 +10,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="panel-options">
|
<div class="panel-options">
|
||||||
<ul class="nav nav-tabs">
|
{% include 'settings/_setting_tabs.html' %}
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="col-sm-12" style="padding-left:0">
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
|
|
|
@ -10,26 +10,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="panel-options">
|
<div class="panel-options">
|
||||||
<ul class="nav nav-tabs">
|
{% include 'settings/_setting_tabs.html' %}
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="col-sm-12" style="padding-left:0">
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
|
|
|
@ -15,30 +15,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="panel-options">
|
<div class="panel-options">
|
||||||
<ul class="nav nav-tabs">
|
{% include 'settings/_setting_tabs.html' %}
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i
|
|
||||||
class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-setting' %}" class="text-center"><i
|
|
||||||
class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:email-content-setting' %}" class="text-center"><i class="fa fa-file-text"></i> {% trans 'Email content setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i
|
|
||||||
class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li class="active">
|
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
|
|
||||||
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="col-sm-12" style="padding-left:0">
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
|
|
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 || {};
|
props = props || {};
|
||||||
var data = props.data || props.form.serializeObject();
|
var data = props.data || props.form.serializeObject();
|
||||||
var redirect_to = props.redirect_to;
|
var redirectTo = props.redirect_to || props.redirectTo;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: props.url,
|
url: props.url,
|
||||||
type: props.method || 'POST',
|
type: props.method || 'POST',
|
||||||
|
@ -185,12 +185,8 @@ function formSubmit(props) {
|
||||||
contentType: props.content_type || "application/json; charset=utf-8",
|
contentType: props.content_type || "application/json; charset=utf-8",
|
||||||
dataType: props.data_type || "json"
|
dataType: props.data_type || "json"
|
||||||
}).done(function (data, textState, jqXHR) {
|
}).done(function (data, textState, jqXHR) {
|
||||||
if (redirect_to) {
|
if (redirectTo) {
|
||||||
if (props.message) {
|
location.href = redirectTo;
|
||||||
var messages = "ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
|
|
||||||
setCookie("messages", messages)
|
|
||||||
}
|
|
||||||
location.href = redirect_to;
|
|
||||||
} else if (typeof props.success === 'function') {
|
} else if (typeof props.success === 'function') {
|
||||||
return props.success(data, textState, jqXHR);
|
return props.success(data, textState, jqXHR);
|
||||||
}
|
}
|
||||||
|
@ -254,7 +250,6 @@ function formSubmit(props) {
|
||||||
}
|
}
|
||||||
$('.has-error').get(0).scrollIntoView();
|
$('.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) {
|
function APIExportData(props) {
|
||||||
props = props || {};
|
props = props || {};
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -1081,6 +1132,7 @@ function APIImportData(props) {
|
||||||
},
|
},
|
||||||
error: function (error) {
|
error: function (error) {
|
||||||
var data = error.responseJSON;
|
var data = error.responseJSON;
|
||||||
|
console.log(data);
|
||||||
if (data instanceof Array) {
|
if (data instanceof Array) {
|
||||||
var html = '';
|
var html = '';
|
||||||
var li = '';
|
var li = '';
|
||||||
|
@ -1141,8 +1193,8 @@ function objectAttrsIsBool(obj, attrs) {
|
||||||
attrs.forEach(function (attr) {
|
attrs.forEach(function (attr) {
|
||||||
if (!obj[attr]) {
|
if (!obj[attr]) {
|
||||||
obj[attr] = false
|
obj[attr] = false
|
||||||
} else if (['on', '1'].includes(obj[attr])) {
|
} else {
|
||||||
obj[attr] = true
|
obj[attr] = ['on', '1', 'true', 'True'].includes(obj[attr]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li>
|
||||||
<li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Labels' %}</a></li>
|
<li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Labels' %}</a></li>
|
||||||
<li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li>
|
<li id="cmd-filter"><a href="{% url 'assets:cmd-filter-list' %}">{% trans 'Command filters' %}</a></li>
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
<li id="platform"><a href="{% url 'assets:platform-list' %}">{% trans 'Platform list' %}</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -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 %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
{% load i18n %}
|
||||||
<html>
|
{% block html_title %} {{ title }} {% endblock %}
|
||||||
|
{% block title %} {{ title }}{% endblock %}
|
||||||
|
|
||||||
<head>
|
{% block custom_head_css_js %}
|
||||||
<meta charset="utf-8">
|
<style>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
.passwordBox {
|
||||||
|
max-width: 660px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<title>{{ title }}</title>
|
{% block content %}
|
||||||
|
<div>
|
||||||
{% include '_head_css_js.html' %}
|
{% if errors %}
|
||||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
<p>
|
||||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
<div class="alert alert-danger">
|
||||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
{{ errors }}
|
||||||
|
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</p>
|
||||||
<hr/>
|
{% endif %}
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<p>
|
||||||
|
<div class="alert alert-success" id="messages">
|
||||||
|
{{ messages|safe }}
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-lg-3">
|
||||||
{% include '_copyright.html' %}
|
<a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">{% trans 'Return' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var time = '{{ interval }}';
|
var time = '{{ interval }}';
|
||||||
if (!time){
|
if (!time) {
|
||||||
time = 5;
|
time = 5;
|
||||||
} else {
|
} else {
|
||||||
time = parseInt(time);
|
time = parseInt(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirect_page() {
|
function redirect_page() {
|
||||||
if (time >= 0) {
|
if (time >= 0) {
|
||||||
var messages = '{{ messages|safe }}, <b>' + time +'</b> ...';
|
var messages = '{{ messages|safe }}, <b>' + time + '</b> ...';
|
||||||
$('#messages').html(messages);
|
$('#messages').html(messages);
|
||||||
time--;
|
time--;
|
||||||
setTimeout(redirect_page, 1000);
|
setTimeout(redirect_page, 1000);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
window.location.href = "{{ redirect_url }}";
|
window.location.href = "{{ redirect_url }}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{% if auto_redirect %}
|
{% if auto_redirect %}
|
||||||
window.onload = redirect_page;
|
window.onload = redirect_page;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
</html>
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from ..serializers import (
|
from ..serializers import UserGroupSerializer
|
||||||
UserGroupSerializer,
|
|
||||||
UserGroupListSerializer,
|
|
||||||
UserGroupUpdateMemberSerializer,
|
|
||||||
)
|
|
||||||
from ..models import UserGroup
|
from ..models import UserGroup
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from orgs.mixins import generics
|
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
|
__all__ = ['UserGroupViewSet']
|
||||||
|
|
||||||
|
|
||||||
class UserGroupViewSet(OrgBulkModelViewSet):
|
class UserGroupViewSet(OrgBulkModelViewSet):
|
||||||
model = UserGroup
|
model = UserGroup
|
||||||
filter_fields = ("name",)
|
filter_fields = ("name",)
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_class = UserGroupSerializer
|
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__)
|
logger = get_logger(__name__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserViewSet', 'UserChangePasswordApi', 'UserUpdateGroupApi',
|
'UserViewSet', 'UserChangePasswordApi',
|
||||||
'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi',
|
'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi',
|
||||||
'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi',
|
'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi',
|
||||||
]
|
]
|
||||||
|
@ -39,8 +39,10 @@ class UserQuerysetMixin:
|
||||||
class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
||||||
filter_fields = ('username', 'email', 'name', 'id')
|
filter_fields = ('username', 'email', 'name', 'id')
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_classes = {
|
||||||
serializer_display_class = serializers.UserDisplaySerializer
|
'default': serializers.UserSerializer,
|
||||||
|
'display': serializers.UserDisplaySerializer
|
||||||
|
}
|
||||||
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
|
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -99,11 +101,6 @@ class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
|
|
||||||
serializer_class = serializers.UserUpdateGroupSerializer
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
|
|
||||||
|
|
||||||
class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
|
class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
|
|
|
@ -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 import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from common.utils import validate_ssh_public_key
|
from common.utils import validate_ssh_public_key
|
||||||
from orgs.mixins.forms import OrgModelForm
|
from orgs.mixins.forms import OrgModelForm
|
||||||
from .models import User, UserGroup
|
from ..models import User
|
||||||
from .utils import check_password_rules, get_current_org_members
|
from ..utils import (
|
||||||
|
check_password_rules, get_current_org_members, get_source_choices
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserCheckPasswordForm(forms.Form):
|
__all__ = [
|
||||||
username = forms.CharField(label=_('Username'), max_length=100)
|
'UserCreateForm', 'UserUpdateForm', 'UserBulkUpdateForm',
|
||||||
password = forms.CharField(
|
'UserCheckOtpCodeForm', 'UserCheckPasswordForm'
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class UserCreateUpdateFormMixin(OrgModelForm):
|
class UserCreateUpdateFormMixin(OrgModelForm):
|
||||||
|
@ -157,131 +137,6 @@ class UserUpdateForm(UserCreateUpdateFormMixin):
|
||||||
pass
|
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):
|
class UserBulkUpdateForm(OrgModelForm):
|
||||||
users = forms.ModelMultipleChoiceField(
|
users = forms.ModelMultipleChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
|
@ -333,40 +188,12 @@ class UserBulkUpdateForm(OrgModelForm):
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
class UserGroupForm(OrgModelForm):
|
class UserCheckPasswordForm(forms.Form):
|
||||||
users = forms.ModelMultipleChoiceField(
|
password = forms.CharField(
|
||||||
queryset=User.objects.none(),
|
label=_('Password'), widget=forms.PasswordInput,
|
||||||
label=_("User"),
|
max_length=128, strip=False
|
||||||
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):
|
class UserCheckOtpCodeForm(forms.Form):
|
||||||
users_field = self.fields.get('users')
|
otp_code = forms.CharField(label=_('MFA code'), max_length=6)
|
||||||
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()
|
|
|
@ -4,6 +4,7 @@ import uuid
|
||||||
from django.db import models, IntegrityError
|
from django.db import models, IntegrityError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from common.utils import lazyproperty
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
|
||||||
__all__ = ['UserGroup']
|
__all__ = ['UserGroup']
|
||||||
|
@ -20,6 +21,10 @@ class UserGroup(OrgModelMixin):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def users_amount(self):
|
||||||
|
return self.users.count()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
unique_together = [('org_id', 'name'),]
|
unique_together = [('org_id', 'name'),]
|
||||||
|
|
|
@ -19,6 +19,7 @@ from django.shortcuts import reverse
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from common.utils import get_signer, date_expired_default, get_logger, lazyproperty
|
from common.utils import get_signer, date_expired_default, get_logger, lazyproperty
|
||||||
from common import fields
|
from common import fields
|
||||||
|
from ..signals import post_user_change_password
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['User']
|
__all__ = ['User']
|
||||||
|
@ -43,14 +44,10 @@ class AuthMixin:
|
||||||
self.set_password(password_raw_)
|
self.set_password(password_raw_)
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
self._set_password = True
|
|
||||||
if self.can_update_password():
|
if self.can_update_password():
|
||||||
self.date_password_last_updated = timezone.now()
|
self.date_password_last_updated = timezone.now()
|
||||||
|
post_user_change_password.send(self.__class__, user=self)
|
||||||
super().set_password(raw_password)
|
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):
|
def can_update_password(self):
|
||||||
return self.is_local
|
return self.is_local
|
||||||
|
@ -196,22 +193,22 @@ class RoleMixin:
|
||||||
def is_app(self):
|
def is_app(self):
|
||||||
return self.role == 'App'
|
return self.role == 'App'
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def user_orgs(self):
|
def user_orgs(self):
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
return Organization.get_user_user_orgs(self)
|
return Organization.get_user_user_orgs(self)
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def admin_orgs(self):
|
def admin_orgs(self):
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
return Organization.get_user_admin_orgs(self)
|
return Organization.get_user_admin_orgs(self)
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def audit_orgs(self):
|
def audit_orgs(self):
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
return Organization.get_user_audit_orgs(self)
|
return Organization.get_user_audit_orgs(self)
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def admin_or_audit_orgs(self):
|
def admin_or_audit_orgs(self):
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
return Organization.get_user_admin_or_audit_orgs(self)
|
return Organization.get_user_admin_or_audit_orgs(self)
|
||||||
|
@ -223,26 +220,26 @@ class RoleMixin:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def is_org_auditor(self):
|
def is_org_auditor(self):
|
||||||
if self.is_super_auditor or self.related_audit_orgs.exists():
|
if self.is_super_auditor or self.related_audit_orgs.exists():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def can_admin_current_org(self):
|
def can_admin_current_org(self):
|
||||||
return current_org.can_admin_by(self)
|
return current_org.can_admin_by(self)
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def can_audit_current_org(self):
|
def can_audit_current_org(self):
|
||||||
return current_org.can_audit_by(self)
|
return current_org.can_audit_by(self)
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def can_user_current_org(self):
|
def can_user_current_org(self):
|
||||||
return current_org.can_user_by(self)
|
return current_org.can_user_by(self)
|
||||||
|
|
||||||
@property
|
@lazyproperty
|
||||||
def can_admin_or_audit_current_org(self):
|
def can_admin_or_audit_current_org(self):
|
||||||
return self.can_admin_current_org or self.can_audit_current_org
|
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 rest_framework import serializers
|
||||||
|
|
||||||
from common.fields import StringManyToManyField
|
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
from django.db.models import Count
|
||||||
from ..models import User, UserGroup
|
from ..models import User, UserGroup
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserGroupSerializer', 'UserGroupListSerializer',
|
'UserGroupSerializer',
|
||||||
'UserGroupUpdateMemberSerializer',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
||||||
users = serializers.PrimaryKeyRelatedField(
|
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:
|
class Meta:
|
||||||
model = UserGroup
|
model = UserGroup
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
list_serializer_class = AdaptedBulkListSerializer
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'users', 'comment', 'date_created',
|
'id', 'name', 'users', 'users_amount', 'comment',
|
||||||
'created_by',
|
'date_created', 'created_by',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'created_by': {'label': _('Created by'), 'read_only': True}
|
'created_by': {'label': _('Created by'), 'read_only': True}
|
||||||
|
@ -47,23 +47,8 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
||||||
raise serializers.ValidationError(msg)
|
raise serializers.ValidationError(msg)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
@classmethod
|
||||||
class UserGroupListSerializer(UserGroupSerializer):
|
def setup_eager_loading(cls, queryset):
|
||||||
users = StringManyToManyField(many=True, read_only=True)
|
""" Perform necessary eager loading of data. """
|
||||||
|
queryset = queryset.annotate(users_amount=Count('users'))
|
||||||
|
return queryset
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ from common.utils import validate_ssh_public_key
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from common.serializers import AdaptedBulkListSerializer
|
from common.serializers import AdaptedBulkListSerializer
|
||||||
from common.permissions import CanUpdateDeleteUser
|
from common.permissions import CanUpdateDeleteUser
|
||||||
from ..models import User, UserGroup
|
from ..models import User
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer',
|
'UserSerializer', 'UserPKUpdateSerializer',
|
||||||
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
|
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
|
||||||
'UserProfileSerializer', 'UserDisplaySerializer',
|
'UserProfileSerializer', 'UserDisplaySerializer',
|
||||||
]
|
]
|
||||||
|
@ -123,16 +123,6 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
|
||||||
return value
|
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 ChangeUserPasswordSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -2,3 +2,4 @@ from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
post_user_create = Signal(providing_args=('user',))
|
post_user_create = Signal(providing_args=('user',))
|
||||||
|
post_user_change_password = Signal(providing_args=('user',))
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db.models.signals import post_save, m2m_changed
|
from django.db.models.signals import m2m_changed
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .signals import post_user_create
|
from .signals import post_user_create
|
||||||
|
|
|
@ -1,60 +1,20 @@
|
||||||
|
{% extends '_without_nav_base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
{% block body %}
|
||||||
<html>
|
<article>
|
||||||
<head>
|
<div class="" style="text-align: center; margin-bottom: 50px">
|
||||||
<meta charset="UTF-8">
|
<h2>
|
||||||
<title> {{ JMS_TITLE }} </title>
|
{% block small_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 %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</h2>
|
||||||
</article>
|
</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>
|
||||||
<footer>
|
<hr style="width: 500px; margin: auto; margin-top: 10px;">
|
||||||
<div class="" style="margin-top: 100px;">
|
{% block content %}
|
||||||
{% include '_copyright.html' %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</article>
|
||||||
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
{% block content %}
|
||||||
<div class="wrapper wrapper-content animated fadeInRight">
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox">
|
<div class="ibox">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'First Login' %}</h5>
|
<h5>{% trans 'First Login' %}</h5>
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content clearfix" style="background-color: #eee; border-radius:5px;">
|
<div class="content clearfix" style="background-color: #eee; border-radius:5px;">
|
||||||
<div class="row">
|
<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 %}
|
{% csrf_token %}
|
||||||
{{ wizard.management_form }}
|
{{ wizard.management_form }}
|
||||||
{#{% if wizard.form.forms %}#}
|
{#{% if wizard.form.forms %}#}
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<div class="col-lg-4">
|
<div class="col-sm-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div style="margin-top: 20px">
|
<div style="margin-top: 20px">
|
||||||
<i class="fa fa-sign-in" style="font-size: 180px;color: #e5e5e5 "></i>
|
<i class="fa fa-sign-in" style="font-size: 180px;color: #e5e5e5 "></i>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="wrapper wrapper-content animated fadeInRight">
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox">
|
<div class="ibox">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'First Login' %}</h5>
|
<h5>{% trans 'First Login' %}</h5>
|
||||||
|
|
|
@ -1,60 +1,34 @@
|
||||||
|
{% extends '_base_only_content.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<!DOCTYPE html>
|
{% load bootstrap3 %}
|
||||||
<html>
|
{% 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>
|
{% block content %}
|
||||||
<meta charset="utf-8">
|
{% if errors %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<p class="red-fonts">{{ errors }}</p>
|
||||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
{% endif %}
|
||||||
<title>{% trans 'Forgot password' %}</title>
|
<p>
|
||||||
|
{% trans 'Input your email, that will send a mail to your' %}
|
||||||
|
</p>
|
||||||
|
|
||||||
{% include '_head_css_js.html' %}
|
<div class="row">
|
||||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
<div class="col-sm-12">
|
||||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
<form role="form" class="form-horizontal" action="" method="post">
|
||||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
{% csrf_token %}
|
||||||
</head>
|
{% bootstrap_field form.email layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.captcha layout="horizontal" %}
|
||||||
<body class="gray-bg">
|
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
|
||||||
<div class="passwordBox animated fadeInDown">
|
</form>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,136 +1,80 @@
|
||||||
|
{% extends '_base_only_content.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<!DOCTYPE html>
|
{% load bootstrap3 %}
|
||||||
<html>
|
{% block html_title %}{% trans 'Reset password' %}{% endblock %}
|
||||||
|
{% block title %}{% trans 'Reset password' %}{% endblock %}
|
||||||
|
|
||||||
<head>
|
{% block content %}
|
||||||
<meta charset="utf-8">
|
<form class="m-t" role="form" method="post" action="">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% csrf_token %}
|
||||||
<title> {{ JMS_TITLE }} </title>
|
{% if errors %}
|
||||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
<p class="red-fonts">{{ errors }}</p>
|
||||||
{% include '_head_css_js.html' %}
|
{% endif %}
|
||||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
{% if not token_invalid %}
|
||||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
<div class="form-group">
|
||||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
{% bootstrap_field form.new_password %}
|
||||||
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
|
{% bootstrap_field form.confirm_password %}
|
||||||
|
{# 密码popover #}
|
||||||
</head>
|
<div id="container">
|
||||||
|
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
|
||||||
<body class="gray-bg">
|
<div class="arrow" style="left: 50%;"></div>
|
||||||
|
<h3 class="popover-title" style="display: none;"></h3>
|
||||||
<div class="loginColumns animated fadeInDown">
|
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
|
||||||
<div class="row">
|
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
|
||||||
|
<div class="popover-content"></div>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
|
||||||
<div class="row">
|
{% endif %}
|
||||||
<div class="col-md-12">
|
</form>
|
||||||
{% include '_copyright.html' %}
|
{% endblock %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</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>
|
jQuery.each(password_check_rules, function (idx, rules) {
|
||||||
<script>
|
if(rules.key === 'id_security_password_min_length'){
|
||||||
$(document).ready(function () {
|
minLength = rules.value
|
||||||
// 密码强度校验
|
}
|
||||||
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) {
|
// 初始化popover
|
||||||
if(rules.key === 'id_security_password_min_length'){
|
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
|
||||||
minLength = rules.value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化popover
|
// 监听事件
|
||||||
initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback);
|
idPassword.on('focus', function () {
|
||||||
|
idPopover.css('top', top);
|
||||||
// 监听事件
|
idPopover.css('left', left);
|
||||||
idPassword.on('focus', function () {
|
idPopover.css('display', 'block');
|
||||||
idPopover.css('top', top);
|
});
|
||||||
idPopover.css('left', left);
|
idPassword.on('blur', function () {
|
||||||
idPopover.css('display', 'block');
|
idPopover.css('display', 'none');
|
||||||
});
|
});
|
||||||
idPassword.on('blur', function () {
|
idPassword.on('keyup', function(){
|
||||||
idPopover.css('display', 'none');
|
var password = idPassword.val();
|
||||||
});
|
checkPasswordRules(password, minLength);
|
||||||
idPassword.on('keyup', function(){
|
})
|
||||||
var password = idPassword.val();
|
})
|
||||||
checkPasswordRules(password, minLength);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -244,10 +244,10 @@
|
||||||
{% for group in user_object.groups.all %}
|
{% for group in user_object.groups.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td >
|
<td >
|
||||||
<b class="bdg_group" data-gid={{ group.id }}>{{ group.name }}</b>
|
<b class="bdg_group" >{{ group.name }}</b>
|
||||||
</td>
|
</td>
|
||||||
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -307,38 +307,8 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
jumpserver.nodes_selected = {};
|
|
||||||
var usersSelect2;
|
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) {
|
function updateUserLoginReviewer(reviewers) {
|
||||||
var url = "{% url 'api-auth:login-confirm-setting-update' user_id=user_object.id %}";
|
var url = "{% url 'api-auth:login-confirm-setting-update' user_id=user_object.id %}";
|
||||||
var data = {reviewers: reviewers};
|
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() {
|
$(document).ready(function() {
|
||||||
$('.select2').select2()
|
$('.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];
|
|
||||||
});
|
|
||||||
usersSelect2 = usersSelect2Init('.users-select2')
|
usersSelect2 = usersSelect2Init('.users-select2')
|
||||||
})
|
})
|
||||||
.on('click', '#is_active', function() {
|
.on('click', '#is_active', function() {
|
||||||
|
@ -405,31 +403,11 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('click', '#btn_join_group', function() {
|
.on('click', '#btn_join_group', function() {
|
||||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
var objectsId = $("#groups_selected").val();
|
||||||
return false;
|
addObjects(objectsId);
|
||||||
}
|
|
||||||
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)
|
|
||||||
}).on('click', '.btn_leave_group', function() {
|
}).on('click', '.btn_leave_group', function() {
|
||||||
var $this = $(this);
|
var objectId = $(this).data('uid');
|
||||||
var $tr = $this.closest('tr');
|
removeObject(objectId)
|
||||||
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)
|
|
||||||
}).on('click', '#btn-reset-password', function() {
|
}).on('click', '#btn-reset-password', function() {
|
||||||
function doReset() {
|
function doReset() {
|
||||||
var the_url = '{% url "api-users:user-reset-password" pk=user_object.id %}';
|
var the_url = '{% url "api-users:user-reset-password" pk=user_object.id %}';
|
||||||
|
|
|
@ -91,9 +91,9 @@
|
||||||
|
|
||||||
{% for user in user_group.users.all %}
|
{% for user in user_group.users.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td ><b class="bdg_user" data-uid={{ user.id }}>{{ user.name }}</b></td>
|
<td ><b class="bdg_user" >{{ user.name }}</b></td>
|
||||||
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -110,73 +110,51 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
jumpserver.users_selected = {};
|
|
||||||
|
|
||||||
function updateGroupMember(users) {
|
var usersGroupsRelationUrl = "{% url 'api-users:users-groups-relation-list' %}";
|
||||||
var the_url = "{% url 'api-users:user-group-update-user' pk=user_group.id %}";
|
|
||||||
var body = {
|
function addObjects(objectsId) {
|
||||||
users: Object.assign([], users)
|
if (!objectsId || objectsId.length === 0) {
|
||||||
};
|
return
|
||||||
var success = function(data) {
|
}
|
||||||
// remove all the selected groups from select > option and rendered ul element;
|
var theUrl = usersGroupsRelationUrl;
|
||||||
$('.select2-selection__rendered').empty();
|
var body = [];
|
||||||
$('#slct_users').val('');
|
objectsId.forEach(function (v) {
|
||||||
$.map(jumpserver.users_selected, function(user_name, index) {
|
var data = {usergroup: "{{ object.id }}"};
|
||||||
$('#opt_' + index).remove();
|
data["user"] = v;
|
||||||
// change tr html of users
|
body.push(data)
|
||||||
$('.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 = {};
|
|
||||||
};
|
|
||||||
requestApi({
|
requestApi({
|
||||||
url: the_url,
|
url: theUrl,
|
||||||
body: JSON.stringify(body),
|
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 () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2()
|
$('.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]
|
|
||||||
});
|
|
||||||
usersSelect2Init('#slct_users')
|
usersSelect2Init('#slct_users')
|
||||||
}).on('click', '.btn-remove-user', function() {
|
}).on('click', '.btn-remove-user', function() {
|
||||||
var $this = $(this);
|
var objectId = $(this).data("uid");
|
||||||
var $tr = $this.closest('tr');
|
removeObject(objectId);
|
||||||
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)
|
|
||||||
}).on('click', '#btn_add_user', function() {
|
}).on('click', '#btn_add_user', function() {
|
||||||
if (Object.keys(jumpserver.users_selected).length === 0) {
|
var objectsId = $("#slct_users").val();
|
||||||
return false;
|
addObjects(objectsId);
|
||||||
}
|
|
||||||
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)
|
|
||||||
}).on('click', '.btn-delete-user-group', function () {
|
}).on('click', '.btn-delete-user-group', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = "{{ user_group.name }}";
|
var name = "{{ user_group.name }}";
|
||||||
|
@ -185,5 +163,6 @@ $(document).ready(function () {
|
||||||
var redirect_url = "{% url 'users:user-group-list' %}";
|
var redirect_url = "{% url 'users:user-group-list' %}";
|
||||||
objectDelete($this, name, the_url, redirect_url);
|
objectDelete($this, name, the_url, redirect_url);
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,28 +1,7 @@
|
||||||
{% extends '_base_list.html' %}
|
{% extends '_base_list.html' %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<div class="" style="float: right">
|
{% include '_csv_import_export.html' %}
|
||||||
<div class=" btn-group">
|
|
||||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a class=" btn_export" tabindex="0">
|
|
||||||
<span>{% trans "Export" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
|
||||||
<span>{% trans "Import" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
|
||||||
<span>{% trans "Update" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<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>
|
<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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
{% include "users/_user_groups_import_modal.html" %}
|
|
||||||
{% include "users/_user_groups_update_modal.html" %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_bottom_left %}{% endblock %}
|
{% block content_bottom_left %}{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var groups_table = 0;
|
var groupsTable = 0;
|
||||||
|
var usersAmountTpl = '<a class="group-users-amount" data-uid="ID">NUM</a>';
|
||||||
function initTable() {
|
function initTable() {
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#group_list_table'),
|
ele: $('#group_list_table'),
|
||||||
|
@ -58,14 +36,16 @@ function initTable() {
|
||||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
}},
|
}},
|
||||||
{targets: 2, createdCell: function (td, cellData, rowData) {
|
{targets: 2, createdCell: function (td, cellData, rowData) {
|
||||||
var html = createPopover(cellData);
|
var data = usersAmountTpl
|
||||||
$(td).html(html);
|
.replace("ID", rowData.id)
|
||||||
|
.replace('NUM', cellData);
|
||||||
|
$(td).html(data);
|
||||||
}},
|
}},
|
||||||
{targets: 3, createdCell: function (td, cellData) {
|
{targets: 3, createdCell: function (td, cellData) {
|
||||||
cellData = htmlEscape(cellData);
|
cellData = htmlEscape(cellData);
|
||||||
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': 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>');
|
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||||
}},
|
}},
|
||||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||||
var name = htmlEscape(rowData.name);
|
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>'
|
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',
|
ajax_url: '{% url "api-users:user-group-list" %}',
|
||||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "users", orderable: false},
|
columns: [{data: "id"}, {data: "name" }, {data: "users_amount", orderable: false},
|
||||||
{data: "comment"}, {data: "id", orderable: false, width:"100px"}],
|
{data: "comment"}, {data: "id", orderable: false, width:"100px"}],
|
||||||
order: [],
|
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
groups_table = jumpserver.initServerSideDataTable(options);
|
groupsTable = jumpserver.initServerSideDataTable(options);
|
||||||
return groups_table
|
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() {
|
$(document).ready(function() {
|
||||||
initTable()
|
groupsTable = initTable();
|
||||||
|
initCsvImportExport(groupsTable, "{% trans 'User groups' %}")
|
||||||
}).on('click', '.btn_delete_user_group', function(){
|
}).on('click', '.btn_delete_user_group', function(){
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var group_id = $this.data('gid');
|
var group_id = $this.data('gid');
|
||||||
var name = $this.data('name');
|
var name = $this.data('name');
|
||||||
var the_url = "{% url 'api-users:user-group-detail' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', group_id);
|
var the_url = "{% url 'api-users:user-group-detail' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', group_id);
|
||||||
objectDelete($this, name, the_url)
|
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(){
|
.on('click', '.group-users-amount', function () {
|
||||||
var groups = groups_table.selected;
|
var $this = $(this);
|
||||||
var data = {
|
var groupId = $(this).data("uid");
|
||||||
'resources': groups
|
getGroupUsers(groupId, function (data) {
|
||||||
};
|
var groups = [];
|
||||||
var search = $("input[type='search']").val();
|
data.forEach(function (v) {
|
||||||
var props = {
|
groups.push(v.user_display);
|
||||||
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
|
|
||||||
});
|
});
|
||||||
})
|
var popover = createPopover(groups);
|
||||||
|
$this.parent().html(popover);
|
||||||
|
$(popover).trigger('click');
|
||||||
|
})
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,28 +1,7 @@
|
||||||
{% extends '_base_list.html' %}
|
{% extends '_base_list.html' %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<div class="pull-right" >
|
{% include '_csv_import_export.html' %}
|
||||||
<div class=" btn-group">
|
|
||||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a class=" btn_export" tabindex="0">
|
|
||||||
<span>{% trans "Export" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
|
||||||
<span>{% trans "Import" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
|
||||||
<span>{% trans "Update" %}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "users/_user_import_modal.html" %}
|
|
||||||
{% include "users/_user_update_modal.html" %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_bottom_left %}{% endblock %}
|
{% block content_bottom_left %}{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
var users_table = 0;
|
var usersTable = 0;
|
||||||
function initTable() {
|
function initTable() {
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#user_list_table'),
|
ele: $('#user_list_table'),
|
||||||
|
@ -148,86 +125,17 @@ function initTable() {
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
users_table = jumpserver.initServerSideDataTable(options);
|
usersTable = jumpserver.initServerSideDataTable(options);
|
||||||
return users_table
|
return usersTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
usersTable = initTable();
|
||||||
var fields = $('#fm_user_bulk_update .form-group');
|
initCsvImportExport(usersTable, "{% trans 'User groups' %}")
|
||||||
$.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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}).on('click', '#btn_bulk_update', function(){
|
}).on('click', '#btn_bulk_update', function(){
|
||||||
var action = $('#slct_bulk_update').val();
|
var action = $('#slct_bulk_update').val();
|
||||||
var id_list = users_table.selected;
|
var id_list = usersTable.selected;
|
||||||
if (id_list.length === 0) {
|
if (id_list.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,6 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form class="" role="form" method="post" action="">
|
<form class="" role="form" method="post" action="">
|
||||||
{% csrf_token %}
|
{% 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">
|
<div class="form-input">
|
||||||
<input type="password" class="" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
<input type="password" class="" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||||
</div>
|
</div>
|
|
@ -14,7 +14,7 @@ app_name = 'users'
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
router.register(r'users', api.UserViewSet, 'user')
|
router.register(r'users', api.UserViewSet, 'user')
|
||||||
router.register(r'groups', api.UserGroupViewSet, 'user-group')
|
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 = [
|
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/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>/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>/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
|
urlpatterns += router.urls
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,10 @@ urlpatterns = [
|
||||||
path('profile/password/update/', views.UserPasswordUpdateView.as_view(), name='user-password-update'),
|
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/update/', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'),
|
||||||
path('profile/pubkey/generate/', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'),
|
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/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/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/update/', views.UserOtpUpdateView.as_view(), name='user-otp-update'),
|
||||||
path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
|
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=()):
|
def get_current_org_members(exclude=()):
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
return current_org.get_org_members(exclude=exclude)
|
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.shortcuts import render
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.shortcuts import reverse, redirect
|
from django.shortcuts import reverse, redirect
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from formtools.wizard.views import SessionWizardView
|
from formtools.wizard.views import SessionWizardView
|
||||||
|
from django.views.generic import FormView
|
||||||
|
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from common.permissions import PermissionsMixin, IsValidUser
|
from common.permissions import PermissionsMixin, IsValidUser
|
||||||
|
@ -33,22 +33,24 @@ class UserLoginView(RedirectView):
|
||||||
query_string = True
|
query_string = True
|
||||||
|
|
||||||
|
|
||||||
class UserForgotPasswordView(TemplateView):
|
class UserForgotPasswordView(FormView):
|
||||||
template_name = 'users/forgot_password.html'
|
template_name = 'users/forgot_password.html'
|
||||||
|
form_class = forms.UserForgotPasswordForm
|
||||||
|
|
||||||
def post(self, request):
|
def form_valid(self, form):
|
||||||
email = request.POST.get('email')
|
request = self.request
|
||||||
|
email = form.cleaned_data['email']
|
||||||
user = get_object_or_none(User, email=email)
|
user = get_object_or_none(User, email=email)
|
||||||
if not user:
|
if not user:
|
||||||
error = _('Email address invalid, please input again')
|
error = _('Email address invalid, please input again')
|
||||||
return self.get(request, errors=error)
|
return self.get(request, errors=error)
|
||||||
elif not user.can_update_password():
|
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)
|
return self.get(request, errors=error)
|
||||||
else:
|
else:
|
||||||
send_reset_password_mail(user)
|
send_reset_password_mail(user)
|
||||||
return HttpResponseRedirect(
|
return redirect('users:forgot-password-sendmail-success')
|
||||||
reverse('users:forgot-password-sendmail-success'))
|
|
||||||
|
|
||||||
|
|
||||||
class UserForgotPasswordSendmailSuccessView(TemplateView):
|
class UserForgotPasswordSendmailSuccessView(TemplateView):
|
||||||
|
@ -79,44 +81,47 @@ class UserResetPasswordSuccessView(TemplateView):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserResetPasswordView(TemplateView):
|
class UserResetPasswordView(FormView):
|
||||||
template_name = 'users/reset_password.html'
|
template_name = 'users/reset_password.html'
|
||||||
|
form_class = forms.UserTokenResetPasswordForm
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
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)
|
user = User.validate_reset_password_token(token)
|
||||||
if not user:
|
if not user:
|
||||||
kwargs.update({'errors': _('Token invalid or expired')})
|
context['errors'] = _('Token invalid or expired')
|
||||||
else:
|
context['token_invalid'] = True
|
||||||
check_rules = get_password_check_rules()
|
check_rules = get_password_check_rules()
|
||||||
kwargs.update({'password_check_rules': check_rules})
|
context['password_check_rules'] = check_rules
|
||||||
return super().get(request, *args, **kwargs)
|
return context
|
||||||
|
|
||||||
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'))
|
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
token = self.request.GET.get('token')
|
||||||
user = User.validate_reset_password_token(token)
|
user = User.validate_reset_password_token(token)
|
||||||
if not user:
|
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():
|
if not user.can_update_password():
|
||||||
error = _('User auth from {}, go there change password'.format(user.source))
|
errors = _('User auth from {}, go there change password'.format(user.source))
|
||||||
return self.get(request, errors=error)
|
return self.get(self.request, errors=errors)
|
||||||
|
|
||||||
|
password = form.cleaned_data['new_password']
|
||||||
is_ok = check_password_rules(password)
|
is_ok = check_password_rules(password)
|
||||||
if not is_ok:
|
if not is_ok:
|
||||||
return self.get(
|
errors = _('* Your password does not meet the requirements')
|
||||||
request,
|
return self.get(self.request, errors=errors)
|
||||||
errors=_('* Your password does not meet the requirements')
|
|
||||||
)
|
|
||||||
|
|
||||||
user.reset_password(password)
|
user.reset_password(password)
|
||||||
User.expired_reset_password_token(token)
|
User.expired_reset_password_token(token)
|
||||||
return HttpResponseRedirect(reverse('users:reset-password-success'))
|
return redirect('users:reset-password-success')
|
||||||
|
|
||||||
|
|
||||||
class UserFirstLoginView(PermissionsMixin, SessionWizardView):
|
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]]
|
choices = [(k, v) for k, v in choices if k in [0, 1]]
|
||||||
form.fields["mfa_level"].choices = choices
|
form.fields["mfa_level"].choices = choices
|
||||||
form.fields["mfa_level"].initial = self.request.user.mfa_level
|
form.fields["mfa_level"].initial = self.request.user.mfa_level
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
|
@ -31,9 +31,9 @@ __all__ = [
|
||||||
'UserProfileView',
|
'UserProfileView',
|
||||||
'UserProfileUpdateView', 'UserPasswordUpdateView',
|
'UserProfileUpdateView', 'UserPasswordUpdateView',
|
||||||
'UserPublicKeyUpdateView', 'UserPublicKeyGenerateView',
|
'UserPublicKeyUpdateView', 'UserPublicKeyGenerateView',
|
||||||
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
|
'UserCheckPasswordView', 'UserOtpEnableInstallAppView',
|
||||||
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
|
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
|
||||||
'UserOtpDisableAuthenticationView', 'UserOtpUpdateView',
|
'UserDisableMFAView', 'UserOtpUpdateView',
|
||||||
]
|
]
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -140,24 +140,10 @@ class UserPublicKeyGenerateView(PermissionsMixin, View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class UserOtpEnableAuthenticationView(FormView):
|
class UserCheckPasswordView(FormView):
|
||||||
template_name = 'users/user_password_authentication.html'
|
template_name = 'users/user_password_check.html'
|
||||||
form_class = forms.UserCheckPasswordForm
|
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):
|
def form_valid(self, form):
|
||||||
user = get_user_or_tmp_user(self.request)
|
user = get_user_or_tmp_user(self.request)
|
||||||
password = form.cleaned_data.get('password')
|
password = form.cleaned_data.get('password')
|
||||||
|
@ -165,10 +151,17 @@ class UserOtpEnableAuthenticationView(FormView):
|
||||||
if not user:
|
if not user:
|
||||||
form.add_error("password", _("Password invalid"))
|
form.add_error("password", _("Password invalid"))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
if not user.mfa_is_otp():
|
||||||
|
user.enable_mfa()
|
||||||
|
user.save()
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
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):
|
class UserOtpEnableInstallAppView(TemplateView):
|
||||||
|
@ -213,23 +206,23 @@ class UserOtpEnableBindView(TemplateView, FormView):
|
||||||
|
|
||||||
def save_otp(self, otp_secret_key):
|
def save_otp(self, otp_secret_key):
|
||||||
user = get_user_or_tmp_user(self.request)
|
user = get_user_or_tmp_user(self.request)
|
||||||
user.enable_otp()
|
user.enable_mfa()
|
||||||
user.otp_secret_key = otp_secret_key
|
user.otp_secret_key = otp_secret_key
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
class UserOtpDisableAuthenticationView(FormView):
|
class UserDisableMFAView(FormView):
|
||||||
template_name = 'users/user_otp_authentication.html'
|
template_name = 'users/user_disable_mfa.html'
|
||||||
form_class = forms.UserCheckOtpCodeForm
|
form_class = forms.UserCheckOtpCodeForm
|
||||||
success_url = reverse_lazy('users:user-otp-settings-success')
|
success_url = reverse_lazy('users:user-otp-settings-success')
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
otp_code = form.cleaned_data.get('otp_code')
|
otp_code = form.cleaned_data.get('otp_code')
|
||||||
otp_secret_key = user.otp_secret_key
|
|
||||||
|
|
||||||
if check_otp_code(otp_secret_key, otp_code):
|
valid = user.check_mfa(otp_code)
|
||||||
user.disable_otp()
|
if valid:
|
||||||
|
user.disable_mfa()
|
||||||
user.save()
|
user.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
else:
|
else:
|
||||||
|
@ -237,16 +230,13 @@ class UserOtpDisableAuthenticationView(FormView):
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
class UserOtpUpdateView(UserOtpDisableAuthenticationView):
|
class UserOtpUpdateView(UserDisableMFAView):
|
||||||
success_url = reverse_lazy('users:user-otp-enable-bind')
|
success_url = reverse_lazy('users:user-otp-enable-bind')
|
||||||
|
|
||||||
|
|
||||||
class UserOtpSettingsSuccessView(TemplateView):
|
class UserOtpSettingsSuccessView(TemplateView):
|
||||||
template_name = 'flash_message_standalone.html'
|
template_name = 'flash_message_standalone.html'
|
||||||
|
|
||||||
# def get(self, request, *args, **kwargs):
|
|
||||||
# return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
title, describe = self.get_title_describe()
|
title, describe = self.get_title_describe()
|
||||||
context = {
|
context = {
|
||||||
|
@ -265,8 +255,9 @@ class UserOtpSettingsSuccessView(TemplateView):
|
||||||
auth_logout(self.request)
|
auth_logout(self.request)
|
||||||
title = _('MFA enable success')
|
title = _('MFA enable success')
|
||||||
describe = _('MFA enable success, return login page')
|
describe = _('MFA enable success, return login page')
|
||||||
if not user.otp_enabled:
|
if not user.mfa_enabled:
|
||||||
title = _('MFA disable success')
|
title = _('MFA disable success')
|
||||||
describe = _('MFA disable success, return login page')
|
describe = _('MFA disable success, return login page')
|
||||||
|
|
||||||
return title, describe
|
return title, describe
|
||||||
|
|
||||||
|
|
2
jms
2
jms
|
@ -19,7 +19,7 @@ from daemon import pidfile
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, BASE_DIR)
|
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:
|
try:
|
||||||
from apps.jumpserver import const
|
from apps.jumpserver import const
|
||||||
|
|
Loading…
Reference in New Issue