mirror of https://github.com/jumpserver/jumpserver
Merge to master (#944)
* [Update] 修改 success message, 添加资产组时可以添加资产 * [Update] system user form add label * [Update] set default cluster * [Update] 修改一些翻译 * [Bugfix] 修复重置密码bug * [Bugfix] 默认default cluster * [Bugfix] 用户添加报错 * 修改tab样式 * [Bugfix] 修复了一些显示上的bug * 修复全选按钮在搜索后仍然选择全部的问题 * [Bugfix] 修复以下bug 1. 查看执行历史异常 2. 用户授权资产页显示message * [Update] api 返回platform, 并增加web terminal nav * [Feature] 添加setting页面 * [Feature] 添加basic settings * [Update] 修改翻译 * [Update] 修改config * [Update] 启动加载common setting * [Bugfix] 修复cluster创建的bug * [Bugfix] 修复title显示Jumpserver * [Bugfix] setting tables not found * [Bugfix] settings add option * [Feature] 添加后端paging * [Bugfix] 资产列表选择别的页会报错 * [Update] check all 只选择当前页面 * [Bugfix] user login ip * [Bugfix] for login ip * [Bugfix] 修复资产列表显示bug * [Remove] labels * [Bugfix] task运行失败,因为tasks没有设置 * [Bugfix] 读取不到prefix * [Change] 修改部分翻译 * [Update] 启用ldap移动位置 * [Update] 修改翻译 * Update README.mdpull/1053/head^2
parent
450a9495ec
commit
5c8dd5676c
31
README.md
31
README.md
|
@ -26,36 +26,7 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互
|
|||
|
||||
### Install 安装
|
||||
|
||||
1. 安装 Python3
|
||||
略
|
||||
|
||||
2. 安装依赖
|
||||
|
||||
```
|
||||
$ cd requirements && yum -y install $(cat rpm_requirements.txt) && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. 修改配置文件
|
||||
|
||||
```
|
||||
$ cp config_example.py config.py
|
||||
```
|
||||
|
||||
4. 修改表结构
|
||||
|
||||
```
|
||||
$ cd apps && python manage.py makemigrations && python manage.py migrate
|
||||
```
|
||||
|
||||
5. 运行
|
||||
|
||||
```
|
||||
$ python run_server.py
|
||||
```
|
||||
|
||||
6. 其它
|
||||
|
||||
整合luna,coco需要nginx来配合, 详见详细安装文档
|
||||
[详细安装](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
|
||||
|
||||
|
||||
### Usage 使用
|
||||
|
|
|
@ -18,9 +18,10 @@ from rest_framework.response import Response
|
|||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q, Count
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.mixins import CustomFilterMixin
|
||||
from common.utils import get_logger
|
||||
from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
|
||||
get_user_granted_assets
|
||||
|
@ -34,12 +35,16 @@ from .tasks import update_asset_hardware_info_manual, test_admin_user_connectabi
|
|||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class AssetViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
ordering_fields = ("hostname", "ip", "port", "cluster", "type", "env", "cpu_cores")
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -78,11 +83,11 @@ class UserAssetListView(generics.ListAPIView):
|
|||
return queryset
|
||||
|
||||
|
||||
class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Asset group api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
queryset = AssetGroup.objects.all()
|
||||
queryset = AssetGroup.objects.all().annotate(asset_count=Count("assets"))
|
||||
serializer_class = serializers.AssetGroupSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
@ -112,7 +117,7 @@ class GroupAddAssetsApi(generics.UpdateAPIView):
|
|||
return Response({'error': serializer.errors}, status=400)
|
||||
|
||||
|
||||
class ClusterViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class ClusterViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Cluster api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
|
@ -153,7 +158,7 @@ class ClusterAddAssetsApi(generics.UpdateAPIView):
|
|||
return Response({'error': serializer.errors}, status=400)
|
||||
|
||||
|
||||
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class AdminUserViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Admin user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
|
@ -189,7 +194,7 @@ class SystemUserViewSet(BulkModelViewSet):
|
|||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
class AssetListUpdateApi(CustomFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
Asset bulk update api
|
||||
"""
|
||||
|
|
|
@ -93,7 +93,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
|||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'groups', "cluster",
|
||||
'type', 'env', 'status',
|
||||
'type', 'env',
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
||||
|
@ -124,20 +124,25 @@ class AssetGroupForm(forms.ModelForm):
|
|||
label=_('Asset'),
|
||||
required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('instance', None):
|
||||
def __init__(self, **kwargs):
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
initial = kwargs.get('initial', {})
|
||||
initial['assets'] = kwargs['instance'].assets.all()
|
||||
super(AssetGroupForm, self).__init__(*args, **kwargs)
|
||||
initial.update({
|
||||
'assets': instance.assets.all(),
|
||||
})
|
||||
kwargs['initial'] = initial
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def _save_m2m(self):
|
||||
super(AssetGroupForm, self)._save_m2m()
|
||||
assets = self.cleaned_data['assets']
|
||||
self.instance.assets.clear()
|
||||
self.instance.assets.add(*tuple(assets))
|
||||
def save(self, commit=True):
|
||||
group = super().save(commit=commit)
|
||||
assets= self.cleaned_data['assets']
|
||||
group.assets.set(assets)
|
||||
return group
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
|
@ -253,9 +258,10 @@ class SystemUserForm(forms.ModelForm):
|
|||
# Admin user assets define, let user select, save it in form not in view
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=False, max_length=128, strip=True)
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=False,
|
||||
max_length=128, strip=True, label=_("Password"))
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False)
|
||||
private_key_file = forms.FileField(required=False, label=_("Private key"))
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
|
@ -302,8 +308,11 @@ class SystemUserForm(forms.ModelForm):
|
|||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
'cluster': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _(' Select clusters')}),
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _(' Select clusters')
|
||||
}
|
||||
),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
|
|
|
@ -18,6 +18,16 @@ __all__ = ['Asset']
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def default_cluster():
|
||||
from .cluster import Cluster
|
||||
name = "Default"
|
||||
defaults = {"name": name}
|
||||
cluster, created = Cluster.objects.get_or_create(
|
||||
defaults=defaults, name=name
|
||||
)
|
||||
return cluster.id
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
# Todo: Move them to settings
|
||||
STATUS_CHOICES = (
|
||||
|
@ -44,7 +54,7 @@ class Asset(models.Model):
|
|||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
|
||||
cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster'))
|
||||
cluster = models.ForeignKey(Cluster, related_name='assets', default=default_cluster, on_delete=models.SET_DEFAULT, verbose_name=_('Cluster'))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),)
|
||||
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),)
|
||||
|
|
|
@ -22,7 +22,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets.count()
|
||||
return obj.asset_count
|
||||
|
||||
|
||||
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
|
||||
|
@ -191,9 +191,11 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
fields = ("id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_inherited", "is_active", "system_users_join", "os",
|
||||
"platform", "comment",)
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_inherited", "is_active", "system_users_join", "os",
|
||||
"platform", "comment"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_is_inherited(obj):
|
||||
|
@ -214,8 +216,11 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
|||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
fields = ("id", "hostname", "system_users_granted", "is_inherited",
|
||||
"is_active", "system_users_join", "comment")
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_inherited", "is_active", "system_users_join",
|
||||
"os", "platform", "comment",
|
||||
)
|
||||
|
||||
|
||||
class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
from django.db.models.signals import post_save, post_init, m2m_changed, pre_save
|
||||
from django.db.models.signals import post_save, post_init
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
@ -27,16 +26,17 @@ def test_asset_conn_on_created(asset):
|
|||
|
||||
|
||||
def push_cluster_system_users_to_asset(asset):
|
||||
logger.info("Push cluster system user to asset: {}".format(asset))
|
||||
task_name = _("Push cluster system users to asset")
|
||||
system_users = asset.cluster.systemuser_set.all()
|
||||
push_system_user_util.delay(system_users, [asset], task_name)
|
||||
if asset.cluster:
|
||||
logger.info("Push cluster system user to asset: {}".format(asset))
|
||||
task_name = _("Push cluster system users to asset")
|
||||
system_users = asset.cluster.systemuser_set.all()
|
||||
push_system_user_util.delay(system_users, [asset], task_name)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
def on_asset_created(sender, instance=None, created=False, **kwargs):
|
||||
if instance and created:
|
||||
logger.info("Asset `` create signal received".format(instance))
|
||||
logger.info("Asset `{}` create signal received".format(instance))
|
||||
update_asset_hardware_info_on_created(instance)
|
||||
test_asset_conn_on_created(instance)
|
||||
push_cluster_system_users_to_asset(instance)
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b> <span class="badge"> {{ total_amount }}</span> <span class="badge badge-danger">{{ unreachable_amount }}</span></span>
|
||||
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
@ -121,7 +121,7 @@ function initTable() {
|
|||
{data: "type" }, {data: "is_connective" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
|
|
@ -1,119 +1,31 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-10">
|
||||
<div class="ibox float-e-margins">
|
||||
<div id="ibox-content" class="ibox-title">
|
||||
<h5> {{ action }}</h5>
|
||||
<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>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
|
||||
<div class="ibox-content">
|
||||
<div class="panel blank-panel">
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
<form id="groupForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<h3 class="widget-head-color-box">资产组信息</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{# <div class="hr-line-dashed"></div>#}
|
||||
{# <h3 class="widget-head-color-box">用户选择的资产</h3>#}
|
||||
{# <div class="form-group">#}
|
||||
{# <label class="col-sm-2 control-label" id="asset_on_count">已选({{ assets_count }})</label>#}
|
||||
{# <div class="col-sm-9" id="asset_sed">#}
|
||||
{# <div class="form-asset-on" id="add_asset">#}
|
||||
{# <p id="asset_on_p">#}
|
||||
{# {% for asset in assets_on_list %}#}
|
||||
{# <button name='asset_hostname' title='{{ asset.ip }}' type='button' class='btn btn-default btn-xs'>{{ asset.hostname }}</button>#}
|
||||
{# {% endfor %}#}
|
||||
{# </p>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-5">
|
||||
<button class="btn btn-white" type="reset"> 重置 </button>
|
||||
<button class="btn btn-primary" type="submit"> 提交 </button>
|
||||
<div id='box2'> </div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% block form %}
|
||||
<form id="groupForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.assets layout="horizontal" %}
|
||||
{% 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>
|
||||
</div>
|
||||
|
||||
<!-- 模态框(Modal) -->
|
||||
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content" id="box">
|
||||
<!--此部分为主体内容,将远程加载进来-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
$('.select2-system-user').select2();
|
||||
$('.select2').select2({
|
||||
closeOnSelect: false
|
||||
});
|
||||
});
|
||||
|
||||
$('#add_asset').on('click',function(){
|
||||
$('#modal').modal('show');
|
||||
});
|
||||
|
||||
$('#modal').modal({
|
||||
show: false,
|
||||
backdrop: 'static',
|
||||
keyboard: 'false',
|
||||
remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}"
|
||||
});
|
||||
$('#modal').on('show.bs.modal',function(){
|
||||
//alert('当调用show方法时,立即触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('shown.bs.modal',function(){
|
||||
//alert('当弹窗完全加载完后,再触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('hide.bs.modal',function(){
|
||||
//alert('当关闭时,立即触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('hidden.bs.modal',function(){
|
||||
//alert('当关完全关闭后,再触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('loaded.bs.modal',function(){
|
||||
//alert('当远程数据加载完毕后,再触发;')
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -17,6 +17,11 @@
|
|||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-group-update' pk=asset_group.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-del">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
@ -179,7 +184,7 @@ function initTable() {
|
|||
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
|
||||
}
|
||||
|
||||
|
@ -212,7 +217,6 @@ $(document).ready(function () {
|
|||
addAssets(assets_id);
|
||||
})
|
||||
|
||||
|
||||
.on('click', '.btn-leave-group', function () {
|
||||
var $this = $(this);
|
||||
var the_url = "{% url 'api-assets:group-update-assets' pk=asset_group.id %}";
|
||||
|
@ -225,6 +229,13 @@ $(document).ready(function () {
|
|||
assets.remove(delete_asset_id);
|
||||
var data = {"assets": assets};
|
||||
leaveGroup($this, name, the_url, data);
|
||||
}).on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ asset_group.name}}";
|
||||
var uid = "{{ asset_group.id }}";
|
||||
var the_url = '{% url "api-assets:asset-group-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
var redirect_url = "{% url 'assets:asset-group-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -34,10 +34,6 @@ $(document).ready(function(){
|
|||
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
|
||||
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-group-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_group_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Port' %}</th>
|
||||
<th class="text-center">{% trans 'Cluster' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Env' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
|
@ -71,18 +69,23 @@ function initTable() {
|
|||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||
console.log('{{ the_url }}');
|
||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 8, createdCell: function (td, cellData) {
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
$(td).html(rowData.cluster_name)
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
$(td).html(rowData.hardware_info)
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 9, createdCell: function (td, cellData) {
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
if (cellData == 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
|
@ -91,19 +94,22 @@ function initTable() {
|
|||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 10, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 8, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "cluster_name"},
|
||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"},
|
||||
{data: "is_active" }, {data: "is_connective"}, {data: "id" }],
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "cluster"},
|
||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
return jumpserver.initDataTable(options);
|
||||
return jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
|
@ -178,9 +184,15 @@ $(document).ready(function(){
|
|||
var obj = {"pk": object_id, "is_active": false};
|
||||
data.push(obj);
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
function success() {
|
||||
location.reload()
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
function doActive() {
|
||||
var data = [];
|
||||
|
@ -188,9 +200,15 @@ $(document).ready(function(){
|
|||
var obj = {"pk": object_id, "is_active": true};
|
||||
data.push(obj);
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
function success() {
|
||||
location.reload();
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
function doDelete() {
|
||||
swal({
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Cluster assets' %} <b>{{ cluster.name }} </b><span class="badge">{{ cluster.assets.all.count }}</span></span>
|
||||
<span style="float: left">{% trans 'Cluster assets' %} <b>{{ cluster.name }} </b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
@ -176,7 +176,7 @@ function initTable() {
|
|||
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
{% bootstrap_field form.contact layout="horizontal" %}
|
||||
{% bootstrap_field form.phone layout="horizontal" %}
|
||||
|
||||
<h3 class="widget-head-color-box">{% trans 'Settings' %}</h3>
|
||||
<h3 class="widget-head-color-box">{% trans 'Setting' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
{% bootstrap_field form.system_users layout="horizontal" %}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url "assets:cluster-create" %}" class="btn btn-sm btn-primary"> {% trans "Create Cluster" %} </a>
|
||||
<a href="{% url "assets:cluster-create" %}" class="btn btn-sm btn-primary"> {% trans "Create cluster" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="cluster_list_table" >
|
||||
<thead>
|
||||
|
|
|
@ -121,7 +121,7 @@ function initAssetsTable() {
|
|||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-del">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
|
@ -259,6 +264,13 @@ $(document).ready(function () {
|
|||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateSystemUserCluster(clusters);
|
||||
}).on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ system_user.name}}";
|
||||
var uid = "{{ system_user.id }}";
|
||||
var the_url = '{% url "api-assets:system-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
var redirect_url = "{% url 'assets:system-user-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Connective' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -61,16 +60,16 @@ function initTable() {
|
|||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 9, createdCell: function (td, cellData, rowData) {
|
||||
var conn_btn = '<a href="{% url "terminal:web-terminal" %}?id={{ DEFAULT_PK }}" class="btn btn-xs btn-info">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
$(td).html(conn_btn)
|
||||
}}
|
||||
{# {targets: 9, createdCell: function (td, cellData, rowData) {#}
|
||||
{# var conn_btn = '<a href="{% url "terminal:web-terminal" %}?id={{ DEFAULT_PK }}" class="btn btn-xs btn-info">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);#}
|
||||
{# $(td).html(conn_btn)#}
|
||||
{# }}#}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:user-asset-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"},
|
||||
{data: "is_active" }, {data: "is_connective"}, {data: "id"}
|
||||
{data: "is_active" }, {data: "is_connective"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
|
|
|
@ -2,20 +2,22 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import TemplateView, ListView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..models import AdminUser, Cluster
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
__all__ = ['AdminUserCreateView', 'AdminUserDetailView',
|
||||
'AdminUserDeleteView', 'AdminUserListView',
|
||||
'AdminUserUpdateView', 'AdminUserAssetsView',
|
||||
]
|
||||
__all__ = [
|
||||
'AdminUserCreateView', 'AdminUserDetailView',
|
||||
'AdminUserDeleteView', 'AdminUserListView',
|
||||
'AdminUserUpdateView', 'AdminUserAssetsView',
|
||||
]
|
||||
|
||||
|
||||
class AdminUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
|
@ -38,6 +40,7 @@ class AdminUserCreateView(AdminUserRequiredMixin,
|
|||
form_class = forms.AdminUserForm
|
||||
template_name = 'assets/admin_user_create_update.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -47,20 +50,13 @@ class AdminUserCreateView(AdminUserRequiredMixin,
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
success_message = _(
|
||||
'Create admin user <a href="{url}">{name}</a> successfully.'.format(
|
||||
url=reverse_lazy('assets:admin-user-detail',
|
||||
kwargs={'pk': self.object.pk}),
|
||||
name=self.object.name,
|
||||
))
|
||||
return success_message
|
||||
|
||||
|
||||
class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = AdminUser
|
||||
form_class = forms.AdminUserForm
|
||||
template_name = 'assets/admin_user_create_update.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -70,11 +66,6 @@ class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
success_url = reverse_lazy('assets:admin-user-detail',
|
||||
kwargs={'pk': self.object.pk})
|
||||
return success_url
|
||||
|
||||
|
||||
class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = AdminUser
|
||||
|
@ -94,7 +85,7 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
|
|||
|
||||
|
||||
class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
template_name = 'assets/admin_user_assets.html'
|
||||
context_object_name = 'admin_user'
|
||||
object = None
|
||||
|
|
|
@ -21,10 +21,11 @@ from django.core.cache import cache
|
|||
from django.utils import timezone
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger, is_uuid
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
@ -46,7 +47,6 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
|
|||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset list'),
|
||||
# 'groups': AssetGroup.objects.all(),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
@ -66,7 +66,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetCreateForm
|
||||
template_name = 'assets/asset_create.html'
|
||||
|
@ -87,9 +87,12 @@ class AssetCreateView(AdminUserRequiredMixin, CreateView):
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
model = Asset
|
||||
context_object_name = 'asset_modal_list'
|
||||
template_name = 'assets/asset_modal_list.html'
|
||||
|
@ -147,7 +150,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetUpdateForm
|
||||
template_name = 'assets/asset_update.html'
|
||||
|
@ -161,6 +164,9 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return update_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Asset
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = ['ClusterListView', 'ClusterCreateView', 'ClusterUpdateView',
|
||||
'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView']
|
||||
__all__ = [
|
||||
'ClusterListView', 'ClusterCreateView', 'ClusterUpdateView',
|
||||
'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView',
|
||||
]
|
||||
|
||||
|
||||
class ClusterListView(AdminUserRequiredMixin, TemplateView):
|
||||
|
@ -21,39 +25,40 @@ class ClusterListView(AdminUserRequiredMixin, TemplateView):
|
|||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Cluster list'),
|
||||
# 'keyword': self.request.GET.get('keyword', '')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ClusterCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class ClusterCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = Cluster
|
||||
form_class = forms.ClusterForm
|
||||
template_name = 'assets/cluster_create_update.html'
|
||||
success_url = reverse_lazy('assets:cluster-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('Create Cluster'),
|
||||
'action': _('Create cluster'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
cluster = form.save(commit=False)
|
||||
cluster.created_by = self.request.user.username or 'System'
|
||||
cluster = form.save()
|
||||
cluster.created_by = self.request.user.username
|
||||
cluster.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class ClusterUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = Cluster
|
||||
form_class = forms.ClusterForm
|
||||
template_name = 'assets/cluster_create_update.html'
|
||||
context_object_name = 'cluster'
|
||||
success_url = reverse_lazy('assets:cluster-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def form_valid(self, form):
|
||||
cluster = form.save(commit=False)
|
||||
|
|
|
@ -7,42 +7,41 @@ from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateVi
|
|||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.shortcuts import get_object_or_404, reverse, redirect
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = ['AssetGroupCreateView', 'AssetGroupDetailView',
|
||||
'AssetGroupUpdateView', 'AssetGroupListView',
|
||||
'AssetGroupDeleteView',
|
||||
]
|
||||
__all__ = [
|
||||
'AssetGroupCreateView', 'AssetGroupDetailView',
|
||||
'AssetGroupUpdateView', 'AssetGroupListView',
|
||||
'AssetGroupDeleteView',
|
||||
]
|
||||
|
||||
|
||||
class AssetGroupCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class AssetGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = AssetGroup
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset group'),
|
||||
'assets_count': 0,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupCreateView, self).get_context_data(**kwargs)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
asset_group = form.save()
|
||||
assets_id_list = self.request.POST.getlist('assets', [])
|
||||
assets = [get_object_or_404(Asset, id=int(asset_id))
|
||||
for asset_id in assets_id_list]
|
||||
asset_group.created_by = self.request.user.username or 'Admin'
|
||||
asset_group.assets.add(*tuple(assets))
|
||||
asset_group.save()
|
||||
return super(AssetGroupCreateView, self).form_valid(form)
|
||||
group = form.save()
|
||||
group.created_by = self.request.user.username
|
||||
group.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class AssetGroupListView(AdminUserRequiredMixin, TemplateView):
|
||||
|
@ -54,7 +53,6 @@ class AssetGroupListView(AdminUserRequiredMixin, TemplateView):
|
|||
'action': _('Asset group list'),
|
||||
'assets': Asset.objects.all(),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
'keyword': self.request.GET.get('keyword', '')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupListView, self).get_context_data(**kwargs)
|
||||
|
@ -77,27 +75,20 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class AssetGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = AssetGroup
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=AssetGroup.objects.all())
|
||||
return super(AssetGroupUpdateView, self).get(request, *args, **kwargs)
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets_all = self.object.assets.all()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset group'),
|
||||
'assets_on_list': assets_all,
|
||||
'assets_count': len(assets_all),
|
||||
'group_id': self.object.id,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupUpdateView, self).get_context_data(**kwargs)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect, reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.db import transaction
|
||||
from django.views.generic import TemplateView, ListView, FormView
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.views.generic.detail import DetailView
|
||||
|
||||
from ..forms import SystemUserForm, SystemUserUpdateForm, SystemUserAuthForm
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..forms import SystemUserForm, SystemUserUpdateForm
|
||||
from ..models import SystemUser, Cluster
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = ['SystemUserCreateView', 'SystemUserUpdateView',
|
||||
'SystemUserDetailView', 'SystemUserDeleteView',
|
||||
'SystemUserAssetView', 'SystemUserListView',
|
||||
]
|
||||
__all__ = [
|
||||
'SystemUserCreateView', 'SystemUserUpdateView',
|
||||
'SystemUserDetailView', 'SystemUserDeleteView',
|
||||
'SystemUserAssetView', 'SystemUserListView',
|
||||
]
|
||||
|
||||
|
||||
class SystemUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
|
@ -38,10 +37,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
|
|||
form_class = SystemUserForm
|
||||
template_name = 'assets/system_user_create.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
return super(SystemUserCreateView, self).post(request, *args, **kwargs)
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -51,20 +47,13 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse('assets:system-user-detail', kwargs={'pk': self.object.pk})
|
||||
success_message = _(
|
||||
'Create system user <a href="{url}">{name}</a> '
|
||||
'successfully.'.format(url=url, name=self.object.name)
|
||||
)
|
||||
|
||||
return success_message
|
||||
|
||||
|
||||
class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = SystemUser
|
||||
form_class = SystemUserUpdateForm
|
||||
template_name = 'assets/system_user_update.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -74,11 +63,6 @@ class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
success_url = reverse_lazy('assets:system-user-detail',
|
||||
kwargs={'pk': self.object.pk})
|
||||
return success_url
|
||||
|
||||
|
||||
class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
template_name = 'assets/system_user_detail.html'
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.views import Response
|
||||
from ldap3 import Server, Connection
|
||||
from django.core.mail import get_connection, send_mail
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from .permissions import IsSuperUser
|
||||
from .serializers import MailTestSerializer, LDAPTestSerializer
|
||||
|
||||
|
||||
class MailTestingAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = MailTestSerializer
|
||||
success_message = _("Test mail sent to {}, please check")
|
||||
|
||||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
|
||||
kwargs = {
|
||||
"host": serializer.validated_data["EMAIL_HOST"],
|
||||
"port": serializer.validated_data["EMAIL_PORT"],
|
||||
"username": serializer.validated_data["EMAIL_HOST_USER"],
|
||||
"password": serializer.validated_data["EMAIL_HOST_PASSWORD"],
|
||||
"use_ssl": serializer.validated_data["EMAIL_USE_SSL"],
|
||||
"use_tls": serializer.validated_data["EMAIL_USE_TLS"]
|
||||
}
|
||||
connection = get_connection(timeout=5, **kwargs)
|
||||
try:
|
||||
connection.open()
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=401)
|
||||
|
||||
try:
|
||||
send_mail("Test", "Test smtp setting", email_host_user,
|
||||
[email_host_user], connection=connection)
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=401)
|
||||
|
||||
return Response({"msg": self.success_message.format(email_host_user)})
|
||||
else:
|
||||
return Response({"error": str(serializer.errors)}, status=401)
|
||||
|
||||
|
||||
class LDAPTestingAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = LDAPTestSerializer
|
||||
success_message = _("Test ldap success")
|
||||
|
||||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
host = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
|
||||
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
|
||||
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
|
||||
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
|
||||
search_ou = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
|
||||
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
|
||||
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
|
||||
|
||||
print(serializer.validated_data)
|
||||
|
||||
try:
|
||||
attr_map = json.loads(attr_map)
|
||||
except json.JSONDecodeError:
|
||||
return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401)
|
||||
|
||||
server = Server(host, use_ssl=use_ssl)
|
||||
conn = Connection(server, bind_dn, password)
|
||||
try:
|
||||
conn.bind()
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=401)
|
||||
|
||||
print(search_ou)
|
||||
print(search_filter % ({"user": "*"}))
|
||||
print(attr_map.values())
|
||||
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
|
||||
attributes=list(attr_map.values()))
|
||||
if not ok:
|
||||
return Response({"error": "Search no entry matched"}, status=401)
|
||||
|
||||
users = []
|
||||
for entry in conn.entries:
|
||||
user = {}
|
||||
for attr, mapping in attr_map.items():
|
||||
if hasattr(entry, mapping):
|
||||
user[attr] = getattr(entry, mapping)
|
||||
users.append(user)
|
||||
if len(users) > 0:
|
||||
return Response({"msg": "Match {} s users".format(len(users))})
|
||||
else:
|
||||
return Response({"error": "Have user but attr mapping error"}, status=401)
|
||||
else:
|
||||
return Response({"error": str(serializer.errors)}, status=401)
|
||||
|
||||
|
||||
class DjangoSettingsAPI(APIView):
|
||||
def get(self, request):
|
||||
configs = {}
|
||||
for i in dir(settings):
|
||||
if i.isupper():
|
||||
configs[i] = str(getattr(settings, i))
|
||||
return Response(configs)
|
||||
|
|
@ -5,3 +5,9 @@ from django.apps import AppConfig
|
|||
|
||||
class CommonConfig(AppConfig):
|
||||
name = 'common'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
from .signals import django_ready
|
||||
django_ready.send(self.__class__)
|
||||
return super().ready()
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
create_success_msg = _("<b>%(name)s</b> was created successfully")
|
||||
update_success_msg = _("<b>%(name)s</b> was updated successfully")
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.utils import six
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class DictField(forms.Field):
|
||||
widget = forms.Textarea
|
||||
|
||||
def to_python(self, value):
|
||||
"""Returns a Python boolean object."""
|
||||
# Explicitly check for the string 'False', which is what a hidden field
|
||||
# will submit for False. Also check for '0', since this is what
|
||||
# RadioSelect will provide. Because bool("True") == bool('1') == True,
|
||||
# we don't need to handle that explicitly.
|
||||
if isinstance(value, six.string_types):
|
||||
try:
|
||||
print(value)
|
||||
value = json.loads(value)
|
||||
return value
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
value = {}
|
||||
return value
|
||||
|
||||
def validate(self, value):
|
||||
print(value)
|
||||
if not value and self.required:
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
|
||||
def has_changed(self, initial, data):
|
||||
# Sometimes data or initial may be a string equivalent of a boolean
|
||||
# so we should run it through to_python first to get a boolean value
|
||||
return self.to_python(initial) != self.to_python(data)
|
|
@ -0,0 +1,131 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db import transaction
|
||||
|
||||
from .models import Setting
|
||||
from .fields import DictField
|
||||
|
||||
|
||||
def to_model_value(value):
|
||||
try:
|
||||
return json.dumps(value)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
|
||||
def to_form_value(value):
|
||||
try:
|
||||
data = json.loads(value)
|
||||
if isinstance(data, dict):
|
||||
data = value
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
return ''
|
||||
|
||||
|
||||
class BaseForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
settings = Setting.objects.all()
|
||||
for name, field in self.fields.items():
|
||||
db_value = getattr(settings, name).value
|
||||
if db_value:
|
||||
field.initial = to_form_value(db_value)
|
||||
|
||||
def save(self):
|
||||
if not self.is_bound:
|
||||
raise ValueError("Form is not bound")
|
||||
|
||||
settings = Setting.objects.all()
|
||||
if self.is_valid():
|
||||
with transaction.atomic():
|
||||
for name, value in self.cleaned_data.items():
|
||||
field = self.fields[name]
|
||||
if isinstance(field.widget, forms.PasswordInput) and not value:
|
||||
continue
|
||||
if value == to_form_value(getattr(settings, name).value):
|
||||
continue
|
||||
|
||||
defaults = {
|
||||
'name': name,
|
||||
'value': to_model_value(value)
|
||||
}
|
||||
Setting.objects.update_or_create(defaults=defaults, name=name)
|
||||
else:
|
||||
raise ValueError(self.errors)
|
||||
|
||||
|
||||
class BasicSettingForm(BaseForm):
|
||||
SITE_URL = forms.URLField(
|
||||
label=_("Current SITE URL"),
|
||||
help_text="http://jumpserver.abc.com:8080"
|
||||
)
|
||||
USER_GUIDE_URL = forms.URLField(
|
||||
label=_("User Guide URL"),
|
||||
help_text=_("User first login update profile done redirect to it")
|
||||
)
|
||||
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
||||
max_length=1024, label=_("Email Subject Prefix"),
|
||||
initial="[Jumpserver] "
|
||||
)
|
||||
|
||||
|
||||
class EmailSettingForm(BaseForm):
|
||||
EMAIL_HOST = forms.CharField(
|
||||
max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org'
|
||||
)
|
||||
EMAIL_PORT = forms.CharField(max_length=5, label=_("SMTP port"), initial=25)
|
||||
EMAIL_HOST_USER = forms.CharField(
|
||||
max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org'
|
||||
)
|
||||
EMAIL_HOST_PASSWORD = forms.CharField(
|
||||
max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput,
|
||||
required=False, help_text=_("Some provider use token except password")
|
||||
)
|
||||
EMAIL_USE_SSL = forms.BooleanField(
|
||||
label=_("Use SSL"), initial=False, required=False,
|
||||
help_text=_("If SMTP port is 465, may be select")
|
||||
)
|
||||
EMAIL_USE_TLS = forms.BooleanField(
|
||||
label=_("Use TLS"), initial=False, required=False,
|
||||
help_text=_("If SMTP port is 587, may be select")
|
||||
)
|
||||
|
||||
|
||||
class LDAPSettingForm(BaseForm):
|
||||
AUTH_LDAP_SERVER_URI = forms.CharField(
|
||||
label=_("LDAP server"), initial='ldap://localhost:389'
|
||||
)
|
||||
AUTH_LDAP_BIND_DN = forms.CharField(
|
||||
label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org'
|
||||
)
|
||||
AUTH_LDAP_BIND_PASSWORD = forms.CharField(
|
||||
label=_("Password"), initial='',
|
||||
widget=forms.PasswordInput, required=False
|
||||
)
|
||||
AUTH_LDAP_SEARCH_OU = forms.CharField(
|
||||
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org'
|
||||
)
|
||||
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
|
||||
label=_("User search filter"), initial='(cn=%(user)s)'
|
||||
)
|
||||
AUTH_LDAP_USER_ATTR_MAP = DictField(
|
||||
label=_("User attr map"),
|
||||
initial=json.dumps({
|
||||
"username": "cn",
|
||||
"name": "sn",
|
||||
"email": "mail"
|
||||
})
|
||||
)
|
||||
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||
AUTH_LDAP_START_TLS = forms.BooleanField(
|
||||
label=_("Use SSL"), initial=False, required=False
|
||||
)
|
||||
AUTH_LDAP = forms.BooleanField(
|
||||
label=_("Enable LDAP Auth"), initial=False, required=False
|
||||
)
|
|
@ -1,10 +1,10 @@
|
|||
# coding: utf-8
|
||||
|
||||
import inspect
|
||||
from django.db import models
|
||||
from django.http import JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
|
||||
|
||||
class NoDeleteQuerySet(models.query.QuerySet):
|
||||
|
@ -47,8 +47,9 @@ class JSONResponseMixin(object):
|
|||
return JsonResponse(context)
|
||||
|
||||
|
||||
class IDInFilterMixin(object):
|
||||
class CustomFilterMixin(object):
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super(CustomFilterMixin, self).filter_queryset(queryset)
|
||||
id_list = self.request.query_params.get('id__in')
|
||||
if id_list:
|
||||
import json
|
||||
|
@ -113,4 +114,14 @@ class DatetimeSearchMixin:
|
|||
)
|
||||
else:
|
||||
self.date_to = timezone.now()
|
||||
return super().get(request, *args, **kwargs)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class AdminUserRequiredMixin(UserPassesTestMixin):
|
||||
def test_func(self):
|
||||
if not self.request.user.is_authenticated:
|
||||
return False
|
||||
elif not self.request.user.is_superuser:
|
||||
self.raise_exception = True
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -1,2 +1,68 @@
|
|||
from django.db import models
|
||||
import json
|
||||
|
||||
import ldap
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
||||
|
||||
class SettingQuerySet(models.QuerySet):
|
||||
def __getattr__(self, item):
|
||||
instances = self.filter(name=item)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
else:
|
||||
return Setting()
|
||||
|
||||
|
||||
class SettingManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return SettingQuerySet(self.model, using=self._db)
|
||||
|
||||
|
||||
class Setting(models.Model):
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
||||
value = models.TextField(verbose_name=_("Value"))
|
||||
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
|
||||
comment = models.TextField(verbose_name=_("Comment"))
|
||||
|
||||
objects = SettingManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def value_(self):
|
||||
try:
|
||||
return json.loads(self.value)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def refresh_all_settings(cls):
|
||||
settings_list = cls.objects.all()
|
||||
for setting in settings_list:
|
||||
setting.refresh_setting()
|
||||
|
||||
def refresh_setting(self):
|
||||
try:
|
||||
value = json.loads(self.value)
|
||||
except json.JSONDecodeError:
|
||||
return
|
||||
setattr(settings, self.name, value)
|
||||
|
||||
if self.name == "AUTH_LDAP":
|
||||
if self.value_ and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
|
||||
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
|
||||
elif not self.value_ and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
|
||||
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
|
||||
|
||||
if self.name == "AUTH_LDAP_SEARCH_FILTER":
|
||||
settings.AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||
settings.AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE,
|
||||
settings.AUTH_LDAP_SEARCH_FILTER,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = "settings"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import permissions
|
||||
|
||||
|
||||
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
||||
"""Allows access to valid user, is active and not expired"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsValidUser, self).has_permission(request, view) \
|
||||
and request.user.is_valid
|
||||
|
||||
|
||||
class IsAppUser(IsValidUser):
|
||||
"""Allows access only to app user """
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsAppUser, self).has_permission(request, view) \
|
||||
and request.user.is_app
|
||||
|
||||
|
||||
class IsSuperUser(IsValidUser):
|
||||
"""Allows access only to superuser"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsSuperUser, self).has_permission(request, view) \
|
||||
and request.user.is_superuser
|
||||
|
||||
|
||||
class IsSuperUserOrAppUser(IsValidUser):
|
||||
"""Allows access between superuser and app user"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
|
||||
and (request.user.is_superuser or request.user.is_app)
|
||||
|
||||
|
||||
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
|
||||
def has_permission(self, request, view):
|
||||
if IsValidUser.has_permission(self, request, view) \
|
||||
and request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
else:
|
||||
return IsSuperUserOrAppUser.has_permission(self, request, view)
|
||||
|
||||
|
||||
class IsCurrentUserOrReadOnly(permissions.BasePermission):
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
return obj == request.user
|
|
@ -0,0 +1,21 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class MailTestSerializer(serializers.Serializer):
|
||||
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
|
||||
EMAIL_PORT = serializers.IntegerField(default=25)
|
||||
EMAIL_HOST_USER = serializers.CharField(max_length=1024)
|
||||
EMAIL_HOST_PASSWORD = serializers.CharField()
|
||||
EMAIL_USE_SSL = serializers.BooleanField(default=False)
|
||||
EMAIL_USE_TLS = serializers.BooleanField(default=False)
|
||||
|
||||
|
||||
class LDAPTestSerializer(serializers.Serializer):
|
||||
AUTH_LDAP_SERVER_URI = serializers.CharField(max_length=1024)
|
||||
AUTH_LDAP_BIND_DN = serializers.CharField(max_length=1024)
|
||||
AUTH_LDAP_BIND_PASSWORD = serializers.CharField()
|
||||
AUTH_LDAP_SEARCH_OU = serializers.CharField()
|
||||
AUTH_LDAP_SEARCH_FILTER = serializers.CharField()
|
||||
AUTH_LDAP_USER_ATTR_MAP = serializers.CharField()
|
||||
AUTH_LDAP_START_TLS = serializers.BooleanField(required=False)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
django_ready = Signal()
|
||||
ldap_auth_enable = Signal(providing_args=["enabled"])
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_save
|
||||
from django.conf import settings
|
||||
from django.db.utils import ProgrammingError, OperationalError
|
||||
|
||||
from .models import Setting
|
||||
from .utils import get_logger
|
||||
from .signals import django_ready, ldap_auth_enable
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Setting, dispatch_uid="my_unique_identifier")
|
||||
def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
||||
logger.debug("Receive setting item change")
|
||||
logger.debug(" - refresh setting: {}".format(instance.name))
|
||||
if instance:
|
||||
instance.refresh_setting()
|
||||
|
||||
|
||||
@receiver(django_ready, dispatch_uid="my_unique_identifier")
|
||||
def refresh_all_settings_on_django_ready(sender, **kwargs):
|
||||
logger.debug("Receive django ready signal")
|
||||
logger.debug(" - fresh all settings")
|
||||
try:
|
||||
Setting.refresh_all_settings()
|
||||
except (ProgrammingError, OperationalError):
|
||||
pass
|
||||
|
||||
|
||||
@receiver(ldap_auth_enable, dispatch_uid="my_unique_identifier")
|
||||
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
|
||||
if enabled:
|
||||
logger.debug("Enable LDAP auth")
|
||||
if settings.AUTH_LDAP_BACKEND not in settings.AUTH_LDAP_BACKEND:
|
||||
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
|
||||
|
||||
else:
|
||||
logger.debug("Disable LDAP auth")
|
||||
if settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
|
||||
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% 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="" 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:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if not field.field|is_bool_field %}
|
||||
{% bootstrap_field field layout="horizontal" %}
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="col-sm-1">
|
||||
{{ field }}
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<span class="help-block" >{{ field.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on("click", ".btn-test", function () {
|
||||
var data = {};
|
||||
var form = $("form").serializeArray();
|
||||
$.each(form, function (i, field) {
|
||||
data[field.name] = field.value;
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-common:mail-testing' %}";
|
||||
|
||||
function error(message) {
|
||||
toastr.error(message)
|
||||
}
|
||||
|
||||
function success(message) {
|
||||
toastr.success(message.msg)
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(data),
|
||||
method: "POST",
|
||||
flash_message: false,
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,101 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% 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>
|
||||
<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:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if not field.field|is_bool_field %}
|
||||
{% bootstrap_field field layout="horizontal" %}
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="col-sm-1">
|
||||
{{ field }}
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<span class="help-block" >{{ field.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on("click", ".btn-test", function () {
|
||||
var data = {};
|
||||
var form = $("form").serializeArray();
|
||||
$.each(form, function (i, field) {
|
||||
data[field.name] = field.value;
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-common:mail-testing' %}";
|
||||
|
||||
function error(message) {
|
||||
toastr.error(message)
|
||||
}
|
||||
|
||||
function success(message) {
|
||||
toastr.success(message.msg)
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(data),
|
||||
method: "POST",
|
||||
flash_message: false,
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,100 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% 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>
|
||||
<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:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if not field.field|is_bool_field %}
|
||||
{% bootstrap_field field layout="horizontal" %}
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="col-sm-1">
|
||||
{{ field }}
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<span class="help-block" >{{ field.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on("click", ".btn-test", function () {
|
||||
var data = {};
|
||||
var form = $("form").serializeArray();
|
||||
$.each(form, function (i, field) {
|
||||
data[field.name] = field.value;
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-common:ldap-testing' %}";
|
||||
|
||||
function error(message) {
|
||||
toastr.error(message)
|
||||
}
|
||||
|
||||
function success(message) {
|
||||
toastr.success(message.msg)
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(data),
|
||||
method: "POST",
|
||||
flash_message: false,
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -4,6 +4,7 @@ from django import template
|
|||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.html import escape
|
||||
from django import forms
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -83,3 +84,11 @@ def time_util_with_seconds(date_from, date_to):
|
|||
return '{} h'.format(seconds//3600)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def is_bool_field(field):
|
||||
if isinstance(field, forms.BooleanField):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'common'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'),
|
||||
url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
|
||||
url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'),
|
||||
]
|
|
@ -0,0 +1,13 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .. import views
|
||||
|
||||
app_name = 'common'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'),
|
||||
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
||||
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
|
||||
]
|
|
@ -91,7 +91,7 @@ class Signer(metaclass=Singleton):
|
|||
|
||||
def date_expired_default():
|
||||
try:
|
||||
years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS)
|
||||
years = int(settings.DEFAULT_EXPIRED_YEARS)
|
||||
except TypeError:
|
||||
years = 70
|
||||
return timezone.now() + timezone.timedelta(days=365*years)
|
||||
|
|
|
@ -1,2 +1,88 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from django.views.generic import View, TemplateView
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm
|
||||
from .mixins import AdminUserRequiredMixin
|
||||
from .signals import ldap_auth_enable
|
||||
|
||||
|
||||
class BasicSettingView(AdminUserRequiredMixin, TemplateView):
|
||||
form_class = BasicSettingForm
|
||||
template_name = "common/basic_setting.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Settings'),
|
||||
'action': _('Basic setting'),
|
||||
'form': self.form_class(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def post(self, request):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
msg = _("Update setting successfully, please restart program")
|
||||
messages.success(request, msg)
|
||||
return redirect('settings:basic-setting')
|
||||
else:
|
||||
context = self.get_context_data()
|
||||
context.update({"form": form})
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
|
||||
class EmailSettingView(AdminUserRequiredMixin, TemplateView):
|
||||
form_class = EmailSettingForm
|
||||
template_name = "common/email_setting.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Settings'),
|
||||
'action': _('Email setting'),
|
||||
'form': self.form_class(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def post(self, request):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
msg = _("Update setting successfully, please restart program")
|
||||
messages.success(request, msg)
|
||||
return redirect('settings:email-setting')
|
||||
else:
|
||||
context = self.get_context_data()
|
||||
context.update({"form": form})
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
|
||||
class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
|
||||
form_class = LDAPSettingForm
|
||||
template_name = "common/ldap_setting.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Settings'),
|
||||
'action': _('LDAP setting'),
|
||||
'form': self.form_class(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def post(self, request):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
if "AUTH_LDAP" in form.cleaned_data:
|
||||
ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"])
|
||||
msg = _("Update setting successfully, please restart program")
|
||||
messages.success(request, msg)
|
||||
return redirect('settings:ldap-setting')
|
||||
else:
|
||||
context = self.get_context_data()
|
||||
context.update({"form": form})
|
||||
return render(request, self.template_name, context)
|
||||
|
|
|
@ -15,7 +15,6 @@ import sys
|
|||
|
||||
import ldap
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
|
@ -121,15 +120,6 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
|||
# Database
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
# if CONFIG.DB_ENGINE == 'sqlite':
|
||||
# DATABASES = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'django.db.backends.sqlite3',
|
||||
# 'NAME': CONFIG.DB_NAME or os.path.join(BASE_DIR, 'data', 'db.sqlite3'),
|
||||
# 'ATOMIC_REQUESTS': True,
|
||||
# }
|
||||
# }
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.{}'.format(CONFIG.DB_ENGINE),
|
||||
|
@ -284,7 +274,7 @@ EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER
|
|||
EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD
|
||||
EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL
|
||||
EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS
|
||||
EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX
|
||||
EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX or ''
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
# Use Django's standard `django.contrib.auth` permissions,
|
||||
|
@ -298,9 +288,17 @@ REST_FRAMEWORK = {
|
|||
'users.authentication.PrivateTokenAuthentication',
|
||||
'users.authentication.SessionAuthentication',
|
||||
),
|
||||
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
|
||||
'DEFAULT_FILTER_BACKENDS': (
|
||||
'django_filters.rest_framework.DjangoFilterBackend',
|
||||
'rest_framework.filters.SearchFilter',
|
||||
'rest_framework.filters.OrderingFilter',
|
||||
),
|
||||
'ORDERING_PARAM': "order",
|
||||
'SEARCH_PARAM': "search",
|
||||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
||||
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'],
|
||||
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
'PAGE_SIZE': 15
|
||||
}
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
|
@ -312,18 +310,28 @@ AUTH_USER_MODEL = 'users.User'
|
|||
|
||||
|
||||
# Auth LDAP settings
|
||||
if CONFIG.AUTH_LDAP:
|
||||
AUTHENTICATION_BACKENDS.insert(0, 'django_auth_ldap.backend.LDAPBackend')
|
||||
AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI
|
||||
AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN
|
||||
AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||
CONFIG.AUTH_LDAP_SEARCH_OU,
|
||||
ldap.SCOPE_SUBTREE,
|
||||
CONFIG.AUTH_LDAP_SEARCH_FILTER
|
||||
)
|
||||
AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS
|
||||
AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP
|
||||
AUTH_LDAP = CONFIG.AUTH_LDAP
|
||||
AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI
|
||||
AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN
|
||||
AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD
|
||||
AUTH_LDAP_SEARCH_OU = CONFIG.AUTH_LDAP_SEARCH_OU
|
||||
AUTH_LDAP_SEARCH_FILTER = CONFIG.AUTH_LDAP_SEARCH_FILTER
|
||||
AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS
|
||||
AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||
AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER,
|
||||
)
|
||||
AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||
AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||
AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||
)
|
||||
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
||||
AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
||||
|
||||
if AUTH_LDAP:
|
||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
||||
|
||||
|
||||
# Celery using redis as broker
|
||||
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
|
||||
|
@ -374,4 +382,10 @@ BOOTSTRAP3 = {
|
|||
'horizontal_field_class': 'col-md-9',
|
||||
# Set placeholder attributes to label if no placeholder is provided
|
||||
'set_placeholder': True,
|
||||
'success_css_class': '',
|
||||
}
|
||||
|
||||
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
|
||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
|
||||
DEFAULT_EXPIRED_YEARS = 70
|
||||
USER_GUIDE_URL = ""
|
||||
|
|
|
@ -19,6 +19,8 @@ urlpatterns = [
|
|||
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
||||
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')),
|
||||
url(r'^ops/', include('ops.urls.view_urls', namespace='ops')),
|
||||
url(r'^settings/', include('common.urls.view_urls', namespace='settings')),
|
||||
url(r'^common/', include('common.urls.view_urls', namespace='common')),
|
||||
|
||||
# Api url view map
|
||||
url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')),
|
||||
|
@ -26,16 +28,15 @@ urlpatterns = [
|
|||
url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||
url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
||||
url(r'^api/ops/', include('ops.urls.api_urls', namespace='api-ops')),
|
||||
url(r'^api/common/', include('common.urls.api_urls', namespace='api-common')),
|
||||
|
||||
# External apps url
|
||||
url(r'^captcha/', include('captcha.urls')),
|
||||
|
||||
]
|
||||
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
url(r'^docs/', schema_view, name="docs"),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_DIR) \
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \
|
||||
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -165,7 +165,7 @@ class AdHoc(models.Model):
|
|||
if item and isinstance(item, list):
|
||||
self._tasks = json.dumps(item)
|
||||
else:
|
||||
raise SyntaxError('Tasks should be a list')
|
||||
raise SyntaxError('Tasks should be a list: {}'.format(item))
|
||||
|
||||
@property
|
||||
def hosts(self):
|
||||
|
@ -218,8 +218,8 @@ class AdHoc(models.Model):
|
|||
history.result = raw
|
||||
history.summary = summary
|
||||
return raw, summary
|
||||
except:
|
||||
return {}, {}
|
||||
except Exception as e:
|
||||
return {}, {"dark": {"all": str(e)}, "contacted": []}
|
||||
finally:
|
||||
history.date_finished = timezone.now()
|
||||
history.timedelta = time.time() - time_start
|
||||
|
|
|
@ -43,8 +43,8 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer):
|
|||
def get_stat(obj):
|
||||
return {
|
||||
"total": len(obj.adhoc.hosts),
|
||||
"success": len(obj.summary["contacted"]),
|
||||
"failed": len(obj.summary["dark"]),
|
||||
"success": len(obj.summary.get("contacted", [])),
|
||||
"failed": len(obj.summary.get("dark", [])),
|
||||
}
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
|
|
|
@ -16,6 +16,8 @@ def update_or_create_ansible_task(
|
|||
run_as_admin=False, run_as="", become_info=None,
|
||||
created_by=None,
|
||||
):
|
||||
if not hosts or not tasks or not task_name:
|
||||
return
|
||||
|
||||
defaults = {
|
||||
'name': task_name,
|
||||
|
|
|
@ -10,7 +10,7 @@ from .hands import AdminUserRequiredMixin
|
|||
|
||||
|
||||
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
model = Task
|
||||
ordering = ('-date_created',)
|
||||
context_object_name = 'task_list'
|
||||
|
|
|
@ -351,7 +351,7 @@ class UserGroupGrantedAssetGroupsApi(ListAPIView):
|
|||
|
||||
|
||||
class ValidateUserAssetPermissionView(APIView):
|
||||
permission_classes = (IsAppUser,)
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
@staticmethod
|
||||
def get(request):
|
||||
|
|
|
@ -237,6 +237,16 @@ $(document).ready(function () {
|
|||
}).get();
|
||||
updateSystemUser(system_users);
|
||||
$tr.remove()
|
||||
}).on('click', '#is_active', function () {
|
||||
var the_url = '{% url "api-perms:asset-permission-detail" pk=asset_permission.id %}';
|
||||
var checked = $(this).prop('checked');
|
||||
var body = {
|
||||
'is_active': checked
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin
|
|||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.contrib import messages
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
|
||||
Asset, AssetGroup
|
||||
from .models import AssetPermission
|
||||
|
@ -31,46 +32,12 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class MessageMixin:
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
errors = self.object.check_system_user_in_assets()
|
||||
if errors:
|
||||
message = self.get_warning_messages(errors)
|
||||
messages.warning(self.request, message)
|
||||
else:
|
||||
message = self.get_success_message(form.cleaned_data)
|
||||
messages.success(self.request, message)
|
||||
|
||||
success_message = self.get_success_message(form.cleaned_data)
|
||||
if success_message:
|
||||
messages.success(self.request, success_message)
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def get_warning_messages(errors):
|
||||
message = "<b><i class='fa fa-warning'></i>WARNING: System user " \
|
||||
"should in behind clusters, so that " \
|
||||
"system user cat auto push to the cluster assets:</b> <br>"
|
||||
for system_user, clusters in errors.items():
|
||||
message += " >>> {}: {} ".format(system_user.name, ", ".join((cluster.name for cluster in clusters)))
|
||||
return message
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse_lazy('perms:asset-permission-detail',
|
||||
kwargs={'pk': self.object.pk})
|
||||
success_message = _(
|
||||
'Create asset permission <a href="{url}"> {name} </a> '
|
||||
'successfully.'.format(url=url, name=self.object.name))
|
||||
return success_message
|
||||
|
||||
|
||||
class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = AssetPermission
|
||||
form_class = AssetPermissionForm
|
||||
template_name = 'perms/asset_permission_create_update.html'
|
||||
success_url = reverse_lazy('perms:asset-permission-list')
|
||||
warning = None
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -80,23 +47,13 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, Cre
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse_lazy(
|
||||
'perms:asset-permission-detail',
|
||||
kwargs={'pk': self.object.pk}
|
||||
)
|
||||
success_message = _(
|
||||
'Create asset permission <a href="{url}"> {name} </a> '
|
||||
'success.'.format(url=url, name=self.object.name)
|
||||
)
|
||||
return success_message
|
||||
|
||||
|
||||
class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = AssetPermission
|
||||
form_class = AssetPermissionForm
|
||||
template_name = 'perms/asset_permission_create_update.html'
|
||||
success_url = reverse_lazy("perms:asset-permission-list")
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -106,17 +63,6 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, Upd
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse_lazy(
|
||||
'perms:asset-permission-detail',
|
||||
kwargs={'pk': self.object.pk}
|
||||
)
|
||||
success_message = _(
|
||||
'Update asset permission <a href="{url}"> {name} </a> '
|
||||
'success.'.format(url=url, name=self.object.name)
|
||||
)
|
||||
return success_message
|
||||
|
||||
|
||||
class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
|
||||
template_name = 'perms/asset_permission_detail.html'
|
||||
|
@ -147,7 +93,7 @@ class AssetPermissionUserView(AdminUserRequiredMixin,
|
|||
ListView):
|
||||
template_name = 'perms/asset_permission_user.html'
|
||||
context_object_name = 'asset_permission'
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -177,7 +123,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin,
|
|||
ListView):
|
||||
template_name = 'perms/asset_permission_asset.html'
|
||||
context_object_name = 'asset_permission'
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -322,4 +322,20 @@ div.dataTables_wrapper div.dataTables_filter {
|
|||
|
||||
.welcome-message img {
|
||||
margin: -11px 0;
|
||||
}
|
||||
|
||||
.nav.nav-tabs li.active {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.ibox-title {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.nav.nav-tabs li > a {
|
||||
max-height: 38px;
|
||||
}
|
||||
|
||||
.nav.nav-tabs li.active a {
|
||||
border: none;
|
||||
}
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -157,6 +157,11 @@ function APIUpdateAttr(props) {
|
|||
props = props || {};
|
||||
var success_message = props.success_message || '更新成功!';
|
||||
var fail_message = props.fail_message || '更新时发生未知错误.';
|
||||
var flash_message = true;
|
||||
if (props.flash_message === false){
|
||||
flash_message = false;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: props.url,
|
||||
type: props.method || "PATCH",
|
||||
|
@ -164,12 +169,16 @@ function APIUpdateAttr(props) {
|
|||
contentType: props.content_type || "application/json; charset=utf-8",
|
||||
dataType: props.data_type || "json"
|
||||
}).done(function(data, textStatue, jqXHR) {
|
||||
toastr.success(success_message);
|
||||
if (flash_message) {
|
||||
toastr.success(success_message);
|
||||
}
|
||||
if (typeof props.success === 'function') {
|
||||
return props.success(data);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
toastr.error(fail_message);
|
||||
if (flash_message) {
|
||||
toastr.error(fail_message);
|
||||
}
|
||||
if (typeof props.error === 'function') {
|
||||
return props.error(jqXHR.responseText);
|
||||
}
|
||||
|
@ -310,11 +319,130 @@ jumpserver.initDataTable = function (options) {
|
|||
if (!jumpserver.checked) {
|
||||
$(this).closest('table').find('.ipt_check').prop('checked', true);
|
||||
jumpserver.checked = true;
|
||||
table.rows().select();
|
||||
table.rows({search:'applied', page:'current'}).select();
|
||||
} else {
|
||||
$(this).closest('table').find('.ipt_check').prop('checked', false);
|
||||
jumpserver.checked = false;
|
||||
table.rows().deselect();
|
||||
table.rows({search:'applied', page:'current'}).deselect();
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
};
|
||||
|
||||
jumpserver.initServerSideDataTable = function (options) {
|
||||
// options = {
|
||||
// ele *: $('#dataTable_id'),
|
||||
// ajax_url *: '{% url 'users:user-list-api' %}',
|
||||
// columns *: [{data: ''}, ....],
|
||||
// dom: 'fltip',
|
||||
// i18n_url: '{% static "js/...../en-us.json" %}',
|
||||
// order: [[1, 'asc'], [2, 'asc'], ...],
|
||||
// buttons: ['excel', 'pdf', 'print'],
|
||||
// columnDefs: [{target: 0, createdCell: ()=>{}}, ...],
|
||||
// uc_html: '<a>header button</a>',
|
||||
// op_html: 'div.btn-group?',
|
||||
// paging: true
|
||||
// }
|
||||
var ele = options.ele || $('.dataTable');
|
||||
var columnDefs = [
|
||||
{
|
||||
targets: 0,
|
||||
orderable: false,
|
||||
createdCell: function (td, cellData) {
|
||||
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
|
||||
}
|
||||
},
|
||||
{className: 'text-center', targets: '_all'}
|
||||
];
|
||||
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
|
||||
var select = {
|
||||
style: 'multi',
|
||||
selector: 'td:first-child'
|
||||
};
|
||||
var table = ele.DataTable({
|
||||
pageLength: options.pageLength || 15,
|
||||
dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
|
||||
order: options.order || [],
|
||||
// select: options.select || 'multi',
|
||||
buttons: [],
|
||||
columnDefs: columnDefs,
|
||||
serverSide: true,
|
||||
processing: true,
|
||||
ajax: {
|
||||
url: options.ajax_url ,
|
||||
data: function (data) {
|
||||
delete data.columns;
|
||||
if (data.length !== null ){
|
||||
data.limit = data.length;
|
||||
delete data.length;
|
||||
}
|
||||
if (data.start !== null) {
|
||||
data.offset = data.start;
|
||||
delete data.start;
|
||||
}
|
||||
if (data.search !== null) {
|
||||
var search_val = data.search.value;
|
||||
data.search = search_val;
|
||||
}
|
||||
if (data.order !== null && data.order.length === 1) {
|
||||
var col = data.order[0].column;
|
||||
var order = options.columns[col].data;
|
||||
if (data.order[0].dir = "desc") {
|
||||
order = "-" + order;
|
||||
}
|
||||
data.order = order;
|
||||
}
|
||||
},
|
||||
dataFilter: function(data){
|
||||
var json = jQuery.parseJSON( data );
|
||||
json.recordsTotal = json.count;
|
||||
json.recordsFiltered = json.count;
|
||||
return JSON.stringify(json); // return JSON string
|
||||
},
|
||||
dataSrc: "results"
|
||||
},
|
||||
columns: options.columns || [],
|
||||
select: options.select || select,
|
||||
language: {
|
||||
search: "搜索",
|
||||
lengthMenu: "每页 _MENU_",
|
||||
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项",
|
||||
infoFiltered: "",
|
||||
infoEmpty: "",
|
||||
zeroRecords: "没有匹配项",
|
||||
emptyTable: "没有记录",
|
||||
paginate: {
|
||||
first: "«",
|
||||
previous: "‹",
|
||||
next: "›",
|
||||
last: "»"
|
||||
}
|
||||
},
|
||||
lengthMenu: [[15, 25, 50], [15, 25, 50]]
|
||||
});
|
||||
table.on('select', function(e, dt, type, indexes) {
|
||||
var $node = table[ type ]( indexes ).nodes().to$();
|
||||
$node.find('input.ipt_check').prop('checked', true);
|
||||
jumpserver.selected[$node.find('input.ipt_check').prop('id')] = true
|
||||
}).on('deselect', function(e, dt, type, indexes) {
|
||||
var $node = table[ type ]( indexes ).nodes().to$();
|
||||
$node.find('input.ipt_check').prop('checked', false);
|
||||
jumpserver.selected[$node.find('input.ipt_check').prop('id')] = false
|
||||
}).
|
||||
on('draw', function(){
|
||||
$('#op').html(options.op_html || '');
|
||||
$('#uc').html(options.uc_html || '');
|
||||
});
|
||||
$('.ipt_check_all').on('click', function() {
|
||||
if (!jumpserver.checked) {
|
||||
$(this).closest('table').find('.ipt_check').prop('checked', true);
|
||||
jumpserver.checked = true;
|
||||
table.rows({search:'applied', page:'current'}).select();
|
||||
} else {
|
||||
$(this).closest('table').find('.ipt_check').prop('checked', false);
|
||||
jumpserver.checked = false;
|
||||
table.rows({search:'applied', page:'current'}).deselect();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<!-- css file -->
|
||||
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/font-awesome.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/toastr/toastr.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/sweetalert/sweetalert.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/style.css' %}" rel="stylesheet">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load i18n %}
|
||||
{% block first_login_message %}
|
||||
{% if user.is_authenticated and user.is_first_login %}
|
||||
{% if request.user.is_authenticated and request.user.is_first_login %}
|
||||
<div class="alert alert-danger help-message">
|
||||
{% url 'users:user-first-login' as first_login_url %}
|
||||
{% blocktrans %}
|
||||
|
@ -10,7 +10,7 @@
|
|||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block update_public_key_message %}
|
||||
{% if user.is_authenticated and not user.is_public_key_valid %}
|
||||
{% if request.user.is_authenticated and not request.user.is_public_key_valid %}
|
||||
<div class="alert alert-danger help-message">
|
||||
{% url 'users:user-pubkey-update' as user_pubkey_update %}
|
||||
{% blocktrans %}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
{#<li id="">#}
|
||||
{# <a href="#">#}
|
||||
{# <i class="fa fa-download"></i> <span class="nav-label">{% trans 'File' %}</span><span class="fa arrow"></span>#}
|
||||
|
@ -63,8 +64,8 @@
|
|||
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
|
||||
{# </ul>#}
|
||||
{#</li>#}
|
||||
{#<li id="">#}
|
||||
{# <a href="">#}
|
||||
{# <i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>#}
|
||||
{# </a>#}
|
||||
{#</li>#}
|
||||
<li id="settings">
|
||||
<a href="{% url 'settings:basic-setting' %}">
|
||||
<i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
|
||||
</a>
|
||||
</li>
|
|
@ -8,4 +8,9 @@
|
|||
<a href="{% url 'users:user-profile' %}">
|
||||
<i class="fa fa-user" ></i> <span class="nav-label">{% trans 'Profile' %}</span><span class="label label-info pull-right"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li >
|
||||
<a href="{% url 'terminal:web-terminal' %}" target="_blank"><i class="fa fa-window-maximize"></i>
|
||||
<span class="nav-label">{% trans 'Web terminal' %}</span>
|
||||
</a>
|
||||
</li>
|
|
@ -141,7 +141,9 @@ class StatusViewSet(viewsets.ModelViewSet):
|
|||
session = serializer.save()
|
||||
return session
|
||||
else:
|
||||
msg = "session data is not valid {}".format(serializer.errors)
|
||||
msg = "session data is not valid {}: {}".format(
|
||||
serializer.errors, str(serializer.data)
|
||||
)
|
||||
logger.error(msg)
|
||||
return None
|
||||
|
||||
|
|
|
@ -13,10 +13,6 @@ RUNNING = False
|
|||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
def set_session_info_cache():
|
||||
logger.debug("")
|
||||
from .utils import get_session_asset_list, get_session_user_list, \
|
||||
|
|
|
@ -19,7 +19,7 @@ class CommandListView(DatetimeSearchMixin, ListView):
|
|||
model = Command
|
||||
template_name = "terminal/command_list.html"
|
||||
context_object_name = 'command_list'
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
command = user = asset = system_user = ""
|
||||
date_from = date_to = None
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
|||
model = Session
|
||||
template_name = 'terminal/session_list.html'
|
||||
context_object_name = 'session_list'
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = asset = system_user = ''
|
||||
date_from = date_to = None
|
||||
|
||||
|
|
|
@ -13,14 +13,14 @@ from .tasks import write_login_log_async
|
|||
from .models import User, UserGroup
|
||||
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly
|
||||
from .utils import check_user_valid, generate_token
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.mixins import CustomFilterMixin
|
||||
from common.utils import get_logger
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class UserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class UserViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
queryset = User.objects.exclude(role="App")
|
||||
# queryset = User.objects.all().exclude(role="App").order_by("date_joined")
|
||||
serializer_class = UserSerializer
|
||||
|
@ -72,7 +72,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
|
|||
user.save()
|
||||
|
||||
|
||||
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class UserGroupViewSet(CustomFilterMixin, BulkModelViewSet):
|
||||
queryset = UserGroup.objects.all()
|
||||
serializer_class = UserGroupSerializer
|
||||
|
||||
|
@ -128,7 +128,11 @@ class UserAuthApi(APIView):
|
|||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||
|
||||
if not login_ip:
|
||||
login_ip = request.META.get('HTTP_X_REAL_IP') or request.META.get("REMOTE_ADDR")
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||
if x_forwarded_for:
|
||||
login_ip = x_forwarded_for[0]
|
||||
else:
|
||||
login_ip = request.META.get("REMOTE_ADDR")
|
||||
|
||||
user, msg = check_user_valid(
|
||||
username=username, password=password,
|
||||
|
|
|
@ -113,7 +113,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
|||
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||
keyword = 'Bearer'
|
||||
model = User
|
||||
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
|
||||
expiration = settings.TOKEN_EXPIRATION or 3600
|
||||
|
||||
def authenticate(self, request):
|
||||
auth = authentication.get_authorization_header(request).split()
|
||||
|
|
|
@ -29,7 +29,7 @@ class UserCreateUpdateForm(forms.ModelForm):
|
|||
model = User
|
||||
fields = [
|
||||
'username', 'name', 'email', 'groups', 'wechat',
|
||||
'phone', 'role', 'date_expired', 'comment', 'password'
|
||||
'phone', 'role', 'date_expired', 'comment',
|
||||
]
|
||||
help_texts = {
|
||||
'username': '* required',
|
||||
|
@ -38,13 +38,16 @@ class UserCreateUpdateForm(forms.ModelForm):
|
|||
}
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Join user groups')}),
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Join user groups')
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=commit)
|
||||
password = self.cleaned_data.get('password')
|
||||
user = super().save(commit=commit)
|
||||
if password:
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
|
@ -153,7 +156,7 @@ class UserBulkUpdateForm(forms.ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['users', 'role', 'groups', 'date_expired', 'is_active']
|
||||
fields = ['users', 'role', 'groups', 'date_expired']
|
||||
widgets = {
|
||||
"groups": forms.SelectMultiple(
|
||||
attrs={
|
||||
|
@ -169,6 +172,7 @@ class UserBulkUpdateForm(forms.ModelForm):
|
|||
if self.data.get(field) is not None:
|
||||
changed_fields.append(field)
|
||||
|
||||
print(changed_fields)
|
||||
cleaned_data = {k: v for k, v in self.cleaned_data.items()
|
||||
if k in changed_fields}
|
||||
users = cleaned_data.pop('users', '')
|
||||
|
@ -183,7 +187,7 @@ class UserBulkUpdateForm(forms.ModelForm):
|
|||
|
||||
class UserGroupForm(forms.ModelForm):
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.all(),
|
||||
queryset=User.objects.exclude(role=User.ROLE_APP),
|
||||
label=_("User"),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .group import *
|
||||
from .user import *
|
||||
from .group import *
|
||||
from .authentication import *
|
||||
from .utils import *
|
||||
|
|
|
@ -20,12 +20,6 @@ class UserGroup(NoDeleteModelMixin):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.name != 'Default':
|
||||
self.users.clear()
|
||||
return super(UserGroup, self).delete()
|
||||
return True
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils import timezone
|
||||
from django.shortcuts import reverse
|
||||
|
||||
from .group import UserGroup
|
||||
from common.utils import get_signer, date_expired_default
|
||||
|
||||
|
||||
|
@ -35,7 +34,7 @@ class User(AbstractUser):
|
|||
username = models.CharField(max_length=128, unique=True, verbose_name=_('Username'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
email = models.EmailField(max_length=128, unique=True, verbose_name=_('Email'))
|
||||
groups = models.ManyToManyField(UserGroup, related_name='users', blank=True, verbose_name=_('User group'))
|
||||
groups = models.ManyToManyField('users.UserGroup', related_name='users', blank=True, verbose_name=_('User group'))
|
||||
role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role'))
|
||||
avatar = models.ImageField(upload_to="avatar", null=True, verbose_name=_('Avatar'))
|
||||
wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat'))
|
||||
|
@ -149,12 +148,7 @@ class User(AbstractUser):
|
|||
def save(self, *args, **kwargs):
|
||||
if not self.name:
|
||||
self.name = self.username
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
# Add the current user to the default group.
|
||||
if not self.groups.count():
|
||||
group = UserGroup.initial()
|
||||
self.groups.add(group)
|
||||
|
||||
@property
|
||||
def private_token(self):
|
||||
|
@ -254,6 +248,7 @@ class User(AbstractUser):
|
|||
#: Use this method initial user
|
||||
@classmethod
|
||||
def initial(cls):
|
||||
from .group import UserGroup
|
||||
user = cls(username='admin',
|
||||
email='admin@jumpserver.org',
|
||||
name=_('Administrator'),
|
||||
|
@ -269,6 +264,7 @@ class User(AbstractUser):
|
|||
from random import seed, choice
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
from .group import UserGroup
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
|
|
|
@ -2,16 +2,19 @@
|
|||
#
|
||||
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import User
|
||||
|
||||
logger = get_logger(__file__)
|
||||
on_user_created = Signal(providing_args=['user', 'request'])
|
||||
|
||||
|
||||
@receiver(on_user_created)
|
||||
def send_user_add_mail_to_user(sender, user=None, **kwargs):
|
||||
from .utils import send_user_created_mail
|
||||
logger.debug("Receive asset create signal, update asset hardware info")
|
||||
send_user_created_mail(user)
|
||||
@receiver(post_save, sender=User)
|
||||
def on_user_created(sender, instance=None, created=False, **kwargs):
|
||||
if created:
|
||||
logger.debug("Receive user `{}` create signal".format(instance.name))
|
||||
from .utils import send_user_created_mail
|
||||
logger.info(" - Sending welcome mail ...".format(instance.name))
|
||||
send_user_created_mail(instance)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> JumpServer </title>
|
||||
<title> Jumpserver </title>
|
||||
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> JumpServer </title>
|
||||
<title> Jumpserver </title>
|
||||
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||
|
|
|
@ -25,20 +25,6 @@
|
|||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.users layout="horizontal" %}
|
||||
{# <div class="form-group">#}
|
||||
{# <label for="users" class="col-sm-2 control-label">{% trans 'Users' %}</label>#}
|
||||
{# <div class="col-sm-9">#}
|
||||
{# <select name="users" id="id_users" data-placeholder="{% trans 'Select User' %}" class="select2 form-control m-b" multiple tabindex="2">#}
|
||||
{# {% for user in users %}#}
|
||||
{# {% if user.id in group_users %}#}
|
||||
{# <option value="{{ user.id }}" selected>{{ user.name }}</option>#}
|
||||
{# {% else %}#}
|
||||
{# <option value="{{ user.id }}">{{ user.name }}</option>#}
|
||||
{# {% endif %}#}
|
||||
{# {% endfor %}#}
|
||||
{# </select>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
|
@ -57,7 +43,9 @@
|
|||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
$('.select2').select2({
|
||||
closeOnSelect: false
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -161,18 +161,30 @@ $(document).ready(function(){
|
|||
var body = $.each(id_list, function(index, user_object) {
|
||||
user_object['is_active'] = false;
|
||||
});
|
||||
console.log(body);
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
function success() {
|
||||
location.reload();
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
location.reload();
|
||||
}
|
||||
function doActive() {
|
||||
var body = $.each(id_list, function(index, user_object) {
|
||||
user_object['is_active'] = true;
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
function success() {
|
||||
location.reload();
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
function doDelete() {
|
||||
swal({
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Profile' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'users:user-profile-update' %}"><i class="fa fa-edit"></i>{% trans 'Settings' %}</a>
|
||||
<a class="btn btn-outline btn-default" href="{% url 'users:user-profile-update' %}"><i class="fa fa-edit"></i>{% trans 'Setting' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -151,12 +151,12 @@ def check_user_valid(**kwargs):
|
|||
return None, _('Password or SSH public key invalid')
|
||||
|
||||
|
||||
def refresh_token(token, user, expiration=settings.CONFIG.TOKEN_EXPIRATION or 3600):
|
||||
def refresh_token(token, user, expiration=settings.TOKEN_EXPIRATION or 3600):
|
||||
cache.set(token, user.id, expiration)
|
||||
|
||||
|
||||
def generate_token(request, user):
|
||||
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
|
||||
expiration = settings.TOKEN_EXPIRATION or 3600
|
||||
remote_addr = request.META.get('REMOTE_ADDR', '')
|
||||
if not isinstance(remote_addr, bytes):
|
||||
remote_addr = remote_addr.encode("utf-8")
|
||||
|
@ -180,8 +180,10 @@ def validate_ip(ip):
|
|||
|
||||
def write_login_log(username, type='', ip='', user_agent=''):
|
||||
if not (ip and validate_ip(ip)):
|
||||
ip = '0.0.0.0'
|
||||
city = get_ip_city(ip)
|
||||
ip = ip[:15]
|
||||
city = "Unknown"
|
||||
else:
|
||||
city = get_ip_city(ip)
|
||||
LoginLog.objects.create(
|
||||
username=username, type=type,
|
||||
ip=ip, city=city, user_agent=user_agent
|
||||
|
|
|
@ -2,17 +2,15 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
from django import forms
|
||||
from django.shortcuts import reverse, redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import CreateView, UpdateView, FormMixin
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.utils import get_logger
|
||||
from perms.models import AssetPermission
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import User, UserGroup
|
||||
from ..utils import AdminUserRequiredMixin
|
||||
from .. import forms
|
||||
|
@ -39,9 +37,7 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
|
|||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
success_message = _(
|
||||
'User group <a href={url}> {name} </a> was created successfully'
|
||||
)
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -51,21 +47,13 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse_lazy(
|
||||
'users:user-group-detail',
|
||||
kwargs={'pk': self.object.id}
|
||||
)
|
||||
return self.success_message.format(
|
||||
url=url, name=self.object.name
|
||||
)
|
||||
|
||||
|
||||
class UserGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = UserGroup
|
||||
form_class = forms.UserGroupForm
|
||||
template_name = 'users/user_group_create_update.html'
|
||||
success_url = reverse_lazy('users:user-group-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
users = User.objects.all()
|
||||
|
|
|
@ -53,7 +53,11 @@ class UserLoginView(FormView):
|
|||
if not self.request.session.test_cookie_worked():
|
||||
return HttpResponse(_("Please enable cookies and try again."))
|
||||
auth_login(self.request, form.get_user())
|
||||
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
||||
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||
if x_forwarded_for:
|
||||
login_ip = x_forwarded_for[0]
|
||||
else:
|
||||
login_ip = self.request.META.get('REMOTE_ADDR', '')
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
write_login_log_async.delay(
|
||||
self.request.user.username, type='W',
|
||||
|
@ -184,7 +188,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
|||
user.is_public_key_valid = True
|
||||
user.save()
|
||||
context = {
|
||||
'user_guide_url': settings.CONFIG.USER_GUIDE_URL
|
||||
'user_guide_url': settings.USER_GUIDE_URL
|
||||
}
|
||||
return render(self.request, 'users/first_login_done.html', context)
|
||||
|
||||
|
@ -215,7 +219,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
|||
class LoginLogListView(DatetimeSearchMixin, ListView):
|
||||
template_name = 'users/login_log_list.html'
|
||||
model = LoginLog
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = keyword = ""
|
||||
date_to = date_from = None
|
||||
|
||||
|
|
|
@ -27,12 +27,14 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_logger, get_object_or_none, is_uuid
|
||||
from .. import forms
|
||||
from ..models import User, UserGroup
|
||||
from ..utils import AdminUserRequiredMixin
|
||||
from ..signals import on_user_created
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_logger, get_object_or_none, is_uuid
|
||||
|
||||
|
||||
__all__ = [
|
||||
'UserListView', 'UserCreateView', 'UserDetailView',
|
||||
|
@ -63,7 +65,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_create.html'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
success_message = _('Create user <a href="{url}">{name}</a> successfully.')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
@ -74,22 +76,16 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
user = form.save(commit=False)
|
||||
user.created_by = self.request.user.username or 'System'
|
||||
user.save()
|
||||
on_user_created.send(self.__class__, user=user)
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk})
|
||||
return self.success_message.format(
|
||||
url=url, name=self.object.name
|
||||
)
|
||||
|
||||
|
||||
class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = User
|
||||
form_class = forms.UserCreateUpdateForm
|
||||
template_name = 'users/user_update.html'
|
||||
context_object_name = 'user_object'
|
||||
success_url = reverse_lazy('users:user-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {'app': _('Users'), 'action': _('Update user')}
|
||||
|
@ -332,17 +328,10 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
|
|||
model = User
|
||||
form_class = forms.UserProfileForm
|
||||
success_url = reverse_lazy('users:user-profile')
|
||||
success_message = _('Create user <a href="{url}">{name}</a> successfully.')
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return self.request.user
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk})
|
||||
return self.success_message.format(
|
||||
url=url, name=self.object.name
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('User'),
|
||||
|
|
|
@ -17,14 +17,6 @@ class Config:
|
|||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x'
|
||||
|
||||
# How many line display every page if using django pager, default 25
|
||||
DISPLAY_PER_PAGE = 25
|
||||
|
||||
# It's used to identify your site, When we send a create mail to user, we only know login url is /login/
|
||||
# But we should know the absolute url like: http://jms.jumpserver.org/login/, so SITE_URL is
|
||||
# HTTP_PROTOCOL://HOST[:PORT]
|
||||
SITE_URL = 'http://localhost'
|
||||
|
||||
# Django security setting, if your disable debug model, you should setting that
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
|
@ -65,40 +57,6 @@ class Config:
|
|||
'port': REDIS_PORT,
|
||||
}
|
||||
|
||||
# Api token expiration when create, Jumpserver refresh time when request arrive
|
||||
TOKEN_EXPIRATION = 3600
|
||||
|
||||
# Session and csrf domain settings
|
||||
SESSION_COOKIE_AGE = 3600*24
|
||||
|
||||
# Email SMTP setting, we only support smtp send mail
|
||||
EMAIL_HOST = 'smtp.163.com'
|
||||
EMAIL_PORT = 25
|
||||
EMAIL_HOST_USER = ''
|
||||
EMAIL_HOST_PASSWORD = '' # Caution: Some SMTP server using `Authorization Code` except password
|
||||
EMAIL_USE_SSL = True if EMAIL_PORT == 465 else False
|
||||
EMAIL_USE_TLS = True if EMAIL_PORT == 587 else False
|
||||
EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
|
||||
|
||||
CAPTCHA_TEST_MODE = False
|
||||
|
||||
# You can set jumpserver usage url here, that when user submit wizard redirect to
|
||||
USER_GUIDE_URL = ''
|
||||
|
||||
# LDAP Auth settings
|
||||
AUTH_LDAP = False
|
||||
AUTH_LDAP_SERVER_URI = 'ldap://localhost:389'
|
||||
AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org'
|
||||
AUTH_LDAP_BIND_PASSWORD = ''
|
||||
AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org'
|
||||
AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)'
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"username": "cn",
|
||||
"name": "sn",
|
||||
"email": "mail"
|
||||
}
|
||||
AUTH_LDAP_START_TLS = False
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
|
Loading…
Reference in New Issue