未验证 提交 5c8dd567 编写于 作者: baltery's avatar baltery 提交者: GitHub

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.md
上级 450a9495
...@@ -26,36 +26,7 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互 ...@@ -26,36 +26,7 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互
### Install 安装 ### Install 安装
1. 安装 Python3    [详细安装](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
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来配合, 详见详细安装文档
### Usage 使用 ### Usage 使用
......
...@@ -18,9 +18,10 @@ from rest_framework.response import Response ...@@ -18,9 +18,10 @@ from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from django.shortcuts import get_object_or_404 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 common.utils import get_logger
from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
get_user_granted_assets get_user_granted_assets
...@@ -34,12 +35,16 @@ from .tasks import update_asset_hardware_info_manual, test_admin_user_connectabi ...@@ -34,12 +35,16 @@ from .tasks import update_asset_hardware_info_manual, test_admin_user_connectabi
logger = get_logger(__file__) logger = get_logger(__file__)
class AssetViewSet(IDInFilterMixin, BulkModelViewSet): class AssetViewSet(CustomFilterMixin, BulkModelViewSet):
""" """
API endpoint that allows Asset to be viewed or edited. 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() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
def get_queryset(self): def get_queryset(self):
...@@ -78,11 +83,11 @@ class UserAssetListView(generics.ListAPIView): ...@@ -78,11 +83,11 @@ class UserAssetListView(generics.ListAPIView):
return queryset return queryset
class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet): class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet):
""" """
Asset group api set, for add,delete,update,list,retrieve resource 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 serializer_class = serializers.AssetGroupSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
...@@ -112,7 +117,7 @@ class GroupAddAssetsApi(generics.UpdateAPIView): ...@@ -112,7 +117,7 @@ class GroupAddAssetsApi(generics.UpdateAPIView):
return Response({'error': serializer.errors}, status=400) 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 Cluster api set, for add,delete,update,list,retrieve resource
""" """
...@@ -153,7 +158,7 @@ class ClusterAddAssetsApi(generics.UpdateAPIView): ...@@ -153,7 +158,7 @@ class ClusterAddAssetsApi(generics.UpdateAPIView):
return Response({'error': serializer.errors}, status=400) 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 Admin user api set, for add,delete,update,list,retrieve resource
""" """
...@@ -189,7 +194,7 @@ class SystemUserViewSet(BulkModelViewSet): ...@@ -189,7 +194,7 @@ class SystemUserViewSet(BulkModelViewSet):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(CustomFilterMixin, ListBulkCreateUpdateDestroyAPIView):
""" """
Asset bulk update api Asset bulk update api
""" """
......
...@@ -93,7 +93,7 @@ class AssetBulkUpdateForm(forms.ModelForm): ...@@ -93,7 +93,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
model = Asset model = Asset
fields = [ fields = [
'assets', 'port', 'groups', "cluster", 'assets', 'port', 'groups', "cluster",
'type', 'env', 'status', 'type', 'env',
] ]
widgets = { widgets = {
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
...@@ -124,20 +124,25 @@ class AssetGroupForm(forms.ModelForm): ...@@ -124,20 +124,25 @@ class AssetGroupForm(forms.ModelForm):
label=_('Asset'), label=_('Asset'),
required=False, required=False,
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}) attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
) )
)
def __init__(self, *args, **kwargs): def __init__(self, **kwargs):
if kwargs.get('instance', None): instance = kwargs.get('instance')
if instance:
initial = kwargs.get('initial', {}) initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all() initial.update({
super(AssetGroupForm, self).__init__(*args, **kwargs) 'assets': instance.assets.all(),
})
kwargs['initial'] = initial
super().__init__(**kwargs)
def _save_m2m(self): def save(self, commit=True):
super(AssetGroupForm, self)._save_m2m() group = super().save(commit=commit)
assets = self.cleaned_data['assets'] assets= self.cleaned_data['assets']
self.instance.assets.clear() group.assets.set(assets)
self.instance.assets.add(*tuple(assets)) return group
class Meta: class Meta:
model = AssetGroup model = AssetGroup
...@@ -253,9 +258,10 @@ class SystemUserForm(forms.ModelForm): ...@@ -253,9 +258,10 @@ class SystemUserForm(forms.ModelForm):
# Admin user assets define, let user select, save it in form not in view # Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=True, required=False) auto_generate_key = forms.BooleanField(initial=True, required=False)
# Form field name can not start with `_`, so redefine it, # 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 # 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): def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save` # Because we define custom field, so we need rewrite :method: `save`
...@@ -302,8 +308,11 @@ class SystemUserForm(forms.ModelForm): ...@@ -302,8 +308,11 @@ class SystemUserForm(forms.ModelForm):
'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}),
'cluster': forms.SelectMultiple( 'cluster': forms.SelectMultiple(
attrs={'class': 'select2', attrs={
'data-placeholder': _(' Select clusters')}), 'class': 'select2',
'data-placeholder': _(' Select clusters')
}
),
} }
help_texts = { help_texts = {
'name': '* required', 'name': '* required',
......
...@@ -18,6 +18,16 @@ __all__ = ['Asset'] ...@@ -18,6 +18,16 @@ __all__ = ['Asset']
logger = logging.getLogger(__name__) 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): class Asset(models.Model):
# Todo: Move them to settings # Todo: Move them to settings
STATUS_CHOICES = ( STATUS_CHOICES = (
...@@ -44,7 +54,7 @@ class Asset(models.Model): ...@@ -44,7 +54,7 @@ class Asset(models.Model):
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
port = models.IntegerField(default=22, verbose_name=_('Port')) port = models.IntegerField(default=22, verbose_name=_('Port'))
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) 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')) 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'),) 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'),) 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): ...@@ -22,7 +22,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@staticmethod @staticmethod
def get_assets_amount(obj): def get_assets_amount(obj):
return obj.assets.count() return obj.asset_count
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
...@@ -191,9 +191,11 @@ class AssetGrantedSerializer(serializers.ModelSerializer): ...@@ -191,9 +191,11 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
class Meta(object): class Meta(object):
model = Asset model = Asset
fields = ("id", "hostname", "ip", "port", "system_users_granted", fields = (
"is_inherited", "is_active", "system_users_join", "os", "id", "hostname", "ip", "port", "system_users_granted",
"platform", "comment",) "is_inherited", "is_active", "system_users_join", "os",
"platform", "comment"
)
@staticmethod @staticmethod
def get_is_inherited(obj): def get_is_inherited(obj):
...@@ -214,8 +216,11 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer): ...@@ -214,8 +216,11 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
class Meta(object): class Meta(object):
model = Asset model = Asset
fields = ("id", "hostname", "system_users_granted", "is_inherited", fields = (
"is_active", "system_users_join", "comment") "id", "hostname", "system_users_granted",
"is_inherited", "is_active", "system_users_join",
"os", "platform", "comment",
)
class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer): class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer):
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.db.models.signals import post_save, post_init
from django.db.models.signals import post_save, post_init, m2m_changed, pre_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
...@@ -27,16 +26,17 @@ def test_asset_conn_on_created(asset): ...@@ -27,16 +26,17 @@ def test_asset_conn_on_created(asset):
def push_cluster_system_users_to_asset(asset): def push_cluster_system_users_to_asset(asset):
logger.info("Push cluster system user to asset: {}".format(asset)) if asset.cluster:
task_name = _("Push cluster system users to asset") logger.info("Push cluster system user to asset: {}".format(asset))
system_users = asset.cluster.systemuser_set.all() task_name = _("Push cluster system users to asset")
push_system_user_util.delay(system_users, [asset], task_name) 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") @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_created(sender, instance=None, created=False, **kwargs): def on_asset_created(sender, instance=None, created=False, **kwargs):
if instance and created: 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) update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance) test_asset_conn_on_created(instance)
push_cluster_system_users_to_asset(instance) push_cluster_system_users_to_asset(instance)
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
<div class="col-sm-8" style="padding-left: 0;"> <div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <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"> <div class="ibox-tools">
<a class="collapse-link"> <a class="collapse-link">
<i class="fa fa-chevron-up"></i> <i class="fa fa-chevron-up"></i>
...@@ -121,7 +121,7 @@ function initTable() { ...@@ -121,7 +121,7 @@ function initTable() {
{data: "type" }, {data: "is_connective" }], {data: "type" }, {data: "is_connective" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
$(document).ready(function () { $(document).ready(function () {
......
{% extends 'base.html' %} {% extends '_base_create_update.html' %}
{% load i18n %}
{% load static %} {% load static %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% load i18n %}
<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>
<div class="ibox-content"> {% block form %}
<div class="panel blank-panel"> <form id="groupForm" method="post" class="form-horizontal">
<div class="panel-body"> {% csrf_token %}
<div class="tab-content"> {% bootstrap_field form.name layout="horizontal" %}
<form id="groupForm" method="post" class="form-horizontal"> {% bootstrap_field form.assets layout="horizontal" %}
{% csrf_token %} {% bootstrap_field form.comment layout="horizontal" %}
<h3 class="widget-head-color-box">资产组信息</h3>
{% bootstrap_field form.name layout="horizontal" %} <div class="hr-line-dashed"></div>
{% bootstrap_field form.comment layout="horizontal" %} <div class="form-group">
{# <div class="hr-line-dashed"></div>#} <div class="col-sm-4 col-sm-offset-2">
{# <h3 class="widget-head-color-box">用户选择的资产</h3>#} <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
{# <div class="form-group">#} <button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
{# <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>
</div> </div>
</div> </div>
</div> </form>
<!-- 模态框(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>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2({
$('.select2-system-user').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> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -17,6 +17,11 @@ ...@@ -17,6 +17,11 @@
<li class="pull-right"> <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> <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>
<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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
...@@ -179,7 +184,7 @@ function initTable() { ...@@ -179,7 +184,7 @@ function initTable() {
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}], {data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
...@@ -212,7 +217,6 @@ $(document).ready(function () { ...@@ -212,7 +217,6 @@ $(document).ready(function () {
addAssets(assets_id); addAssets(assets_id);
}) })
.on('click', '.btn-leave-group', function () { .on('click', '.btn-leave-group', function () {
var $this = $(this); var $this = $(this);
var the_url = "{% url 'api-assets:group-update-assets' pk=asset_group.id %}"; var the_url = "{% url 'api-assets:group-update-assets' pk=asset_group.id %}";
...@@ -225,6 +229,13 @@ $(document).ready(function () { ...@@ -225,6 +229,13 @@ $(document).ready(function () {
assets.remove(delete_asset_id); assets.remove(delete_asset_id);
var data = {"assets": assets}; var data = {"assets": assets};
leaveGroup($this, name, the_url, data); 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(){ ...@@ -34,10 +34,6 @@ $(document).ready(function(){
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
{targets: 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) { {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 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); 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 @@ ...@@ -31,8 +31,6 @@
<th class="text-center">{% trans 'IP' %}</th> <th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Port' %}</th> <th class="text-center">{% trans 'Port' %}</th>
<th class="text-center">{% trans 'Cluster' %}</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 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th> <th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th> <th class="text-center">{% trans 'Reachable' %}</th>
...@@ -71,18 +69,23 @@ function initTable() { ...@@ -71,18 +69,23 @@ function initTable() {
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
console.log('{{ the_url }}');
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>'; var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(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) { if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>') $(td).html('<i class="fa fa-times text-danger"></i>')
} else { } else {
$(td).html('<i class="fa fa-check text-navy"></i>') $(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'){ if (cellData == 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>') $(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) { } else if (!cellData) {
...@@ -91,19 +94,22 @@ function initTable() { ...@@ -91,19 +94,22 @@ function initTable() {
$(td).html('<i class="fa fa-circle text-navy"></i>') $(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 update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
}} }}
], ],
ajax_url: '{% url "api-assets:asset-list" %}', ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "cluster_name"}, columns: [
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"}, {data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "is_active" }, {data: "is_connective"}, {data: "id" }], {data: "cluster"},
{data: "cpu_cores"}, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
return jumpserver.initDataTable(options); return jumpserver.initServerSideDataTable(options);
} }
$(document).ready(function(){ $(document).ready(function(){
...@@ -178,9 +184,15 @@ $(document).ready(function(){ ...@@ -178,9 +184,15 @@ $(document).ready(function(){
var obj = {"pk": object_id, "is_active": false}; var obj = {"pk": object_id, "is_active": false};
data.push(obj); data.push(obj);
}); });
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)}); function success() {
$data_table.ajax.reload(); location.reload()
jumpserver.checked = false; }
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
success: success
});
} }
function doActive() { function doActive() {
var data = []; var data = [];
...@@ -188,9 +200,15 @@ $(document).ready(function(){ ...@@ -188,9 +200,15 @@ $(document).ready(function(){
var obj = {"pk": object_id, "is_active": true}; var obj = {"pk": object_id, "is_active": true};
data.push(obj); data.push(obj);
}); });
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(data)}); function success() {
$data_table.ajax.reload(); location.reload();
jumpserver.checked = false; }
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
success: success
});
} }
function doDelete() { function doDelete() {
swal({ swal({
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<div class="col-sm-8" style="padding-left: 0;"> <div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <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"> <div class="ibox-tools">
<a class="collapse-link"> <a class="collapse-link">
<i class="fa fa-chevron-up"></i> <i class="fa fa-chevron-up"></i>
...@@ -176,7 +176,7 @@ function initTable() { ...@@ -176,7 +176,7 @@ function initTable() {
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}], {data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
{% bootstrap_field form.contact layout="horizontal" %} {% bootstrap_field form.contact layout="horizontal" %}
{% bootstrap_field form.phone 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.admin_user layout="horizontal" %}
{% bootstrap_field form.system_users layout="horizontal" %} {% bootstrap_field form.system_users layout="horizontal" %}
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
{% block table_search %}{% endblock %} {% block table_search %}{% endblock %}
{% block table_container %} {% block table_container %}
<div class="uc pull-left m-r-5"> <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> </div>
<table class="table table-striped table-bordered table-hover " id="cluster_list_table" > <table class="table table-striped table-bordered table-hover " id="cluster_list_table" >
<thead> <thead>
......
...@@ -121,7 +121,7 @@ function initAssetsTable() { ...@@ -121,7 +121,7 @@ function initAssetsTable() {
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }], columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
$(document).ready(function () { $(document).ready(function () {
......
...@@ -25,6 +25,11 @@ ...@@ -25,6 +25,11 @@
<li class="pull-right"> <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> <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>
<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> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
...@@ -259,6 +264,13 @@ $(document).ready(function () { ...@@ -259,6 +264,13 @@ $(document).ready(function () {
return $(this).data('gid'); return $(this).data('gid');
}).get(); }).get();
updateSystemUserCluster(clusters); 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> </script>
{% endblock %} {% endblock %}
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
<th class="text-center">{% trans 'Hardware' %}</th> <th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th> <th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Connective' %}</th> <th class="text-center">{% trans 'Connective' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -61,16 +60,16 @@ function initTable() { ...@@ -61,16 +60,16 @@ function initTable() {
$(td).html('<i class="fa fa-circle text-navy"></i>') $(td).html('<i class="fa fa-circle text-navy"></i>')
} }
}}, }},
{targets: 9, createdCell: function (td, cellData, rowData) { {# {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); {# 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) {# $(td).html(conn_btn)#}
}} {# }}#}
], ],
ajax_url: '{% url "api-assets:user-asset-list" %}', ajax_url: '{% url "api-assets:user-asset-list" %}',
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"}, {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() op_html: $('#actions').html()
}; };
......
...@@ -2,20 +2,22 @@ ...@@ -2,20 +2,22 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings 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.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.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.detail import DetailView, SingleObjectMixin
from common.const import create_success_msg, update_success_msg
from .. import forms from .. import forms
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser from ..models import AdminUser, Cluster
from ..hands import AdminUserRequiredMixin from ..hands import AdminUserRequiredMixin
__all__ = ['AdminUserCreateView', 'AdminUserDetailView', __all__ = [
'AdminUserDeleteView', 'AdminUserListView', 'AdminUserCreateView', 'AdminUserDetailView',
'AdminUserUpdateView', 'AdminUserAssetsView', 'AdminUserDeleteView', 'AdminUserListView',
] 'AdminUserUpdateView', 'AdminUserAssetsView',
]
class AdminUserListView(AdminUserRequiredMixin, TemplateView): class AdminUserListView(AdminUserRequiredMixin, TemplateView):
...@@ -38,6 +40,7 @@ class AdminUserCreateView(AdminUserRequiredMixin, ...@@ -38,6 +40,7 @@ class AdminUserCreateView(AdminUserRequiredMixin,
form_class = forms.AdminUserForm form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html' template_name = 'assets/admin_user_create_update.html'
success_url = reverse_lazy('assets:admin-user-list') success_url = reverse_lazy('assets:admin-user-list')
success_message = create_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -47,20 +50,13 @@ class AdminUserCreateView(AdminUserRequiredMixin, ...@@ -47,20 +50,13 @@ class AdminUserCreateView(AdminUserRequiredMixin,
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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, SuccessMessageMixin, UpdateView):
class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
model = AdminUser model = AdminUser
form_class = forms.AdminUserForm form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html' 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): def get_context_data(self, **kwargs):
context = { context = {
...@@ -70,11 +66,6 @@ class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -70,11 +66,6 @@ class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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): class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
model = AdminUser model = AdminUser
...@@ -94,7 +85,7 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView): ...@@ -94,7 +85,7 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): 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' template_name = 'assets/admin_user_assets.html'
context_object_name = 'admin_user' context_object_name = 'admin_user'
object = None object = None
......
...@@ -21,10 +21,11 @@ from django.core.cache import cache ...@@ -21,10 +21,11 @@ from django.core.cache import cache
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.messages.views import SuccessMessageMixin
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger, is_uuid 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 .. import forms
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
from ..hands import AdminUserRequiredMixin from ..hands import AdminUserRequiredMixin
...@@ -46,7 +47,6 @@ class AssetListView(AdminUserRequiredMixin, TemplateView): ...@@ -46,7 +47,6 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Asset list'), 'action': _('Asset list'),
# 'groups': AssetGroup.objects.all(),
'system_users': SystemUser.objects.all(), 'system_users': SystemUser.objects.all(),
} }
kwargs.update(context) kwargs.update(context)
...@@ -66,7 +66,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView): ...@@ -66,7 +66,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetCreateView(AdminUserRequiredMixin, CreateView): class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = Asset model = Asset
form_class = forms.AssetCreateForm form_class = forms.AssetCreateForm
template_name = 'assets/asset_create.html' template_name = 'assets/asset_create.html'
...@@ -87,9 +87,12 @@ class AssetCreateView(AdminUserRequiredMixin, CreateView): ...@@ -87,9 +87,12 @@ class AssetCreateView(AdminUserRequiredMixin, CreateView):
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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): class AssetModalListView(AdminUserRequiredMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
model = Asset model = Asset
context_object_name = 'asset_modal_list' context_object_name = 'asset_modal_list'
template_name = 'assets/asset_modal_list.html' template_name = 'assets/asset_modal_list.html'
...@@ -147,7 +150,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): ...@@ -147,7 +150,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetUpdateView(AdminUserRequiredMixin, UpdateView): class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = Asset model = Asset
form_class = forms.AssetUpdateForm form_class = forms.AssetUpdateForm
template_name = 'assets/asset_update.html' template_name = 'assets/asset_update.html'
...@@ -161,6 +164,9 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -161,6 +164,9 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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): class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
model = Asset model = Asset
......
# coding:utf-8 # coding:utf-8
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import TemplateView, ListView, View from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic.detail import DetailView, SingleObjectMixin 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 .. import forms
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
from ..hands import AdminUserRequiredMixin from ..hands import AdminUserRequiredMixin
__all__ = ['ClusterListView', 'ClusterCreateView', 'ClusterUpdateView', __all__ = [
'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView'] 'ClusterListView', 'ClusterCreateView', 'ClusterUpdateView',
'ClusterDetailView', 'ClusterDeleteView', 'ClusterAssetsView',
]
class ClusterListView(AdminUserRequiredMixin, TemplateView): class ClusterListView(AdminUserRequiredMixin, TemplateView):
...@@ -21,39 +25,40 @@ class ClusterListView(AdminUserRequiredMixin, TemplateView): ...@@ -21,39 +25,40 @@ class ClusterListView(AdminUserRequiredMixin, TemplateView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Cluster list'), 'action': _('Cluster list'),
# 'keyword': self.request.GET.get('keyword', '')
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class ClusterCreateView(AdminUserRequiredMixin, CreateView): class ClusterCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = Cluster model = Cluster
form_class = forms.ClusterForm form_class = forms.ClusterForm
template_name = 'assets/cluster_create_update.html' template_name = 'assets/cluster_create_update.html'
success_url = reverse_lazy('assets:cluster-list') success_url = reverse_lazy('assets:cluster-list')
success_message = create_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('assets'), 'app': _('assets'),
'action': _('Create Cluster'), 'action': _('Create cluster'),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def form_valid(self, form): def form_valid(self, form):
cluster = form.save(commit=False) cluster = form.save()
cluster.created_by = self.request.user.username or 'System' cluster.created_by = self.request.user.username
cluster.save() cluster.save()
return super().form_valid(form) return super().form_valid(form)
class ClusterUpdateView(AdminUserRequiredMixin, UpdateView): class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = Cluster model = Cluster
form_class = forms.ClusterForm form_class = forms.ClusterForm
template_name = 'assets/cluster_create_update.html' template_name = 'assets/cluster_create_update.html'
context_object_name = 'cluster' context_object_name = 'cluster'
success_url = reverse_lazy('assets:cluster-list') success_url = reverse_lazy('assets:cluster-list')
success_message = update_success_msg
def form_valid(self, form): def form_valid(self, form):
cluster = form.save(commit=False) cluster = form.save(commit=False)
......
...@@ -7,42 +7,41 @@ from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateVi ...@@ -7,42 +7,41 @@ from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateVi
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.detail import DetailView, SingleObjectMixin
from django.shortcuts import get_object_or_404, reverse, redirect 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 .. import forms
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
from ..hands import AdminUserRequiredMixin from ..hands import AdminUserRequiredMixin
__all__ = ['AssetGroupCreateView', 'AssetGroupDetailView', __all__ = [
'AssetGroupUpdateView', 'AssetGroupListView', 'AssetGroupCreateView', 'AssetGroupDetailView',
'AssetGroupDeleteView', 'AssetGroupUpdateView', 'AssetGroupListView',
] 'AssetGroupDeleteView',
]
class AssetGroupCreateView(AdminUserRequiredMixin, CreateView): class AssetGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = AssetGroup model = AssetGroup
form_class = forms.AssetGroupForm form_class = forms.AssetGroupForm
template_name = 'assets/asset_group_create.html' template_name = 'assets/asset_group_create.html'
success_url = reverse_lazy('assets:asset-group-list') success_url = reverse_lazy('assets:asset-group-list')
success_message = create_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Create asset group'), 'action': _('Create asset group'),
'assets_count': 0,
} }
kwargs.update(context) kwargs.update(context)
return super(AssetGroupCreateView, self).get_context_data(**kwargs) return super().get_context_data(**kwargs)
def form_valid(self, form): def form_valid(self, form):
asset_group = form.save() group = form.save()
assets_id_list = self.request.POST.getlist('assets', []) group.created_by = self.request.user.username
assets = [get_object_or_404(Asset, id=int(asset_id)) group.save()
for asset_id in assets_id_list] return super().form_valid(form)
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)
class AssetGroupListView(AdminUserRequiredMixin, TemplateView): class AssetGroupListView(AdminUserRequiredMixin, TemplateView):
...@@ -54,7 +53,6 @@ class AssetGroupListView(AdminUserRequiredMixin, TemplateView): ...@@ -54,7 +53,6 @@ class AssetGroupListView(AdminUserRequiredMixin, TemplateView):
'action': _('Asset group list'), 'action': _('Asset group list'),
'assets': Asset.objects.all(), 'assets': Asset.objects.all(),
'system_users': SystemUser.objects.all(), 'system_users': SystemUser.objects.all(),
'keyword': self.request.GET.get('keyword', '')
} }
kwargs.update(context) kwargs.update(context)
return super(AssetGroupListView, self).get_context_data(**kwargs) return super(AssetGroupListView, self).get_context_data(**kwargs)
...@@ -77,27 +75,20 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView): ...@@ -77,27 +75,20 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView): class AssetGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = AssetGroup model = AssetGroup
form_class = forms.AssetGroupForm form_class = forms.AssetGroupForm
template_name = 'assets/asset_group_create.html' template_name = 'assets/asset_group_create.html'
success_url = reverse_lazy('assets:asset-group-list') success_url = reverse_lazy('assets:asset-group-list')
success_message = update_success_msg
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetGroup.objects.all())
return super(AssetGroupUpdateView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
assets_all = self.object.assets.all()
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Create asset group'), 'action': _('Create asset group'),
'assets_on_list': assets_all,
'assets_count': len(assets_all),
'group_id': self.object.id,
} }
kwargs.update(context) kwargs.update(context)
return super(AssetGroupUpdateView, self).get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView): class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from django.contrib import messages
from django.shortcuts import redirect, reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.db import transaction from django.views.generic import TemplateView
from django.views.generic import TemplateView, ListView, FormView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin 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 ..models import SystemUser, Cluster
from ..hands import AdminUserRequiredMixin from ..hands import AdminUserRequiredMixin
__all__ = ['SystemUserCreateView', 'SystemUserUpdateView', __all__ = [
'SystemUserDetailView', 'SystemUserDeleteView', 'SystemUserCreateView', 'SystemUserUpdateView',
'SystemUserAssetView', 'SystemUserListView', 'SystemUserDetailView', 'SystemUserDeleteView',
] 'SystemUserAssetView', 'SystemUserListView',
]
class SystemUserListView(AdminUserRequiredMixin, TemplateView): class SystemUserListView(AdminUserRequiredMixin, TemplateView):
...@@ -38,10 +37,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi ...@@ -38,10 +37,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
form_class = SystemUserForm form_class = SystemUserForm
template_name = 'assets/system_user_create.html' template_name = 'assets/system_user_create.html'
success_url = reverse_lazy('assets:system-user-list') success_url = reverse_lazy('assets:system-user-list')
success_message = create_success_msg
@transaction.atomic
def post(self, request, *args, **kwargs):
return super(SystemUserCreateView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -51,20 +47,13 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi ...@@ -51,20 +47,13 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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 model = SystemUser
form_class = SystemUserUpdateForm form_class = SystemUserUpdateForm
template_name = 'assets/system_user_update.html' 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): def get_context_data(self, **kwargs):
context = { context = {
...@@ -74,11 +63,6 @@ class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -74,11 +63,6 @@ class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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): class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
template_name = 'assets/system_user_detail.html' template_name = 'assets/system_user_detail.html'
......
# -*- 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 ...@@ -5,3 +5,9 @@ from django.apps import AppConfig
class CommonConfig(AppConfig): class CommonConfig(AppConfig):
name = 'common' name = 'common'
def ready(self):
from . import signals_handler
from .signals import django_ready
django_ready.send(self.__class__)
return super().ready()
# -*- 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")
\ No newline at end of file
# -*- 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)
# -*- 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
)
# coding: utf-8 # coding: utf-8
import inspect
from django.db import models from django.db import models
from django.http import JsonResponse from django.http import JsonResponse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.mixins import UserPassesTestMixin
class NoDeleteQuerySet(models.query.QuerySet): class NoDeleteQuerySet(models.query.QuerySet):
...@@ -47,8 +47,9 @@ class JSONResponseMixin(object): ...@@ -47,8 +47,9 @@ class JSONResponseMixin(object):
return JsonResponse(context) return JsonResponse(context)
class IDInFilterMixin(object): class CustomFilterMixin(object):
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
queryset = super(CustomFilterMixin, self).filter_queryset(queryset)
id_list = self.request.query_params.get('id__in') id_list = self.request.query_params.get('id__in')
if id_list: if id_list:
import json import json
...@@ -113,4 +114,14 @@ class DatetimeSearchMixin: ...@@ -113,4 +114,14 @@ class DatetimeSearchMixin:
) )
else: else:
self.date_to = timezone.now() self.date_to = timezone.now()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
\ No newline at end of file
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
import json
import ldap
from django.db import models 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"
# -*- 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
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)
# -*- coding: utf-8 -*-
#
from django.dispatch import Signal
django_ready = Signal()
ldap_auth_enable = Signal(providing_args=["enabled"])
# -*- 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)
{% 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 %}
{% 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 %}
{% 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 ...@@ -4,6 +4,7 @@ from django import template
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.utils.html import escape from django.utils.html import escape
from django import forms
register = template.Library() register = template.Library()
...@@ -83,3 +84,11 @@ def time_util_with_seconds(date_from, date_to): ...@@ -83,3 +84,11 @@ def time_util_with_seconds(date_from, date_to):
return '{} h'.format(seconds//3600) return '{} h'.format(seconds//3600)
else: else:
return '' return ''
@register.filter
def is_bool_field(field):
if isinstance(field, forms.BooleanField):
return True
else:
return False
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'),
]
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): ...@@ -91,7 +91,7 @@ class Signer(metaclass=Singleton):
def date_expired_default(): def date_expired_default():
try: try:
years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS) years = int(settings.DEFAULT_EXPIRED_YEARS)
except TypeError: except TypeError:
years = 70 years = 70
return timezone.now() + timezone.timedelta(days=365*years) return timezone.now() + timezone.timedelta(days=365*years)
......
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 ...@@ -15,7 +15,6 @@ import sys
import ldap import ldap
from django_auth_ldap.config import LDAPSearch from django_auth_ldap.config import LDAPSearch
from django.urls import reverse_lazy from django.urls import reverse_lazy
...@@ -121,15 +120,6 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' ...@@ -121,15 +120,6 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
# Database # Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases # 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 = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.{}'.format(CONFIG.DB_ENGINE), 'ENGINE': 'django.db.backends.{}'.format(CONFIG.DB_ENGINE),
...@@ -284,7 +274,7 @@ EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER ...@@ -284,7 +274,7 @@ EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD
EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL
EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS
EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX or ''
REST_FRAMEWORK = { REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions, # Use Django's standard `django.contrib.auth` permissions,
...@@ -298,9 +288,17 @@ REST_FRAMEWORK = { ...@@ -298,9 +288,17 @@ REST_FRAMEWORK = {
'users.authentication.PrivateTokenAuthentication', 'users.authentication.PrivateTokenAuthentication',
'users.authentication.SessionAuthentication', '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_FORMAT': '%Y-%m-%d %H:%M:%S %z',
'DATETIME_INPUT_FORMATS': ['%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 = [ AUTHENTICATION_BACKENDS = [
...@@ -312,18 +310,28 @@ AUTH_USER_MODEL = 'users.User' ...@@ -312,18 +310,28 @@ AUTH_USER_MODEL = 'users.User'
# Auth LDAP settings # Auth LDAP settings
if CONFIG.AUTH_LDAP: AUTH_LDAP = CONFIG.AUTH_LDAP
AUTHENTICATION_BACKENDS.insert(0, 'django_auth_ldap.backend.LDAPBackend') AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI
AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN
AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD
AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD AUTH_LDAP_SEARCH_OU = CONFIG.AUTH_LDAP_SEARCH_OU
AUTH_LDAP_USER_SEARCH = LDAPSearch( AUTH_LDAP_SEARCH_FILTER = CONFIG.AUTH_LDAP_SEARCH_FILTER
CONFIG.AUTH_LDAP_SEARCH_OU, AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS
ldap.SCOPE_SUBTREE, AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP
CONFIG.AUTH_LDAP_SEARCH_FILTER AUTH_LDAP_USER_SEARCH = LDAPSearch(
) AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE, 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_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 using redis as broker
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % { CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
...@@ -374,4 +382,10 @@ BOOTSTRAP3 = { ...@@ -374,4 +382,10 @@ BOOTSTRAP3 = {
'horizontal_field_class': 'col-md-9', 'horizontal_field_class': 'col-md-9',
# Set placeholder attributes to label if no placeholder is provided # Set placeholder attributes to label if no placeholder is provided
'set_placeholder': True, '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 = [ ...@@ -19,6 +19,8 @@ urlpatterns = [
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')), url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')),
url(r'^ops/', include('ops.urls.view_urls', namespace='ops')), 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 # Api url view map
url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')), url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')),
...@@ -26,16 +28,15 @@ urlpatterns = [ ...@@ -26,16 +28,15 @@ urlpatterns = [
url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')), 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/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')),
url(r'^api/ops/', include('ops.urls.api_urls', namespace='api-ops')), 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 # External apps url
url(r'^captcha/', include('captcha.urls')), url(r'^captcha/', include('captcha.urls')),
] ]
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [ urlpatterns += [
url(r'^docs/', schema_view, name="docs"), 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) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-08 15:58+0800\n" "POT-Creation-Date: 2018-01-17 17:26+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -18,7 +18,7 @@ msgstr "" ...@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: assets/forms.py:23 assets/forms.py:53 assets/forms.py:99 perms/forms.py:37 #: assets/forms.py:23 assets/forms.py:53 assets/forms.py:99 perms/forms.py:37
#: perms/templates/perms/asset_permission_asset.html:116 users/forms.py:242 #: perms/templates/perms/asset_permission_asset.html:116 users/forms.py:246
msgid "Select asset groups" msgid "Select asset groups"
msgstr "选择资产组" msgstr "选择资产组"
...@@ -43,15 +43,15 @@ msgid "Default using cluster admin user" ...@@ -43,15 +43,15 @@ msgid "Default using cluster admin user"
msgstr "默认使用管理用户" msgstr "默认使用管理用户"
#: assets/forms.py:76 assets/forms.py:81 assets/forms.py:127 #: assets/forms.py:76 assets/forms.py:81 assets/forms.py:127
#: assets/templates/assets/asset_group_detail.html:70 perms/forms.py:34 #: assets/templates/assets/asset_group_detail.html:75 perms/forms.py:34
#: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:239 #: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:243
msgid "Select assets" msgid "Select assets"
msgstr "选择资产" msgstr "选择资产"
#: assets/forms.py:86 assets/models/asset.py:45 #: assets/forms.py:86 assets/models/asset.py:55
#: assets/templates/assets/admin_user_assets.html:61 #: assets/templates/assets/admin_user_assets.html:61
#: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/asset_group_detail.html:47 #: assets/templates/assets/asset_group_detail.html:52
#: assets/templates/assets/asset_list.html:32 #: assets/templates/assets/asset_list.html:32
#: assets/templates/assets/cluster_assets.html:53 #: assets/templates/assets/cluster_assets.html:53
#: assets/templates/assets/system_user_asset.html:54 #: assets/templates/assets/system_user_asset.html:54
...@@ -60,7 +60,7 @@ msgstr "选择资产" ...@@ -60,7 +60,7 @@ msgstr "选择资产"
msgid "Port" msgid "Port"
msgstr "端口" msgstr "端口"
#: assets/forms.py:124 assets/models/asset.py:161 #: assets/forms.py:124 assets/models/asset.py:171
#: assets/templates/assets/admin_user_list.html:24 #: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/asset_group_list.html:16 #: assets/templates/assets/asset_group_list.html:16
#: assets/templates/assets/system_user_list.html:26 perms/models.py:17 #: assets/templates/assets/system_user_list.html:26 perms/models.py:17
...@@ -76,39 +76,39 @@ msgstr "端口" ...@@ -76,39 +76,39 @@ msgstr "端口"
msgid "Asset" msgid "Asset"
msgstr "资产" msgstr "资产"
#: assets/forms.py:156 perms/forms.py:40 #: assets/forms.py:161 perms/forms.py:40
#: perms/templates/perms/asset_permission_detail.html:144 users/forms.py:245 #: perms/templates/perms/asset_permission_detail.html:144 users/forms.py:249
msgid "Select system users" msgid "Select system users"
msgstr "选择系统用户" msgstr "选择系统用户"
#: assets/forms.py:158 #: assets/forms.py:163
#: assets/templates/assets/_asset_group_bulk_update_modal.html:22 #: assets/templates/assets/_asset_group_bulk_update_modal.html:22
#: assets/templates/assets/cluster_list.html:22 #: assets/templates/assets/cluster_list.html:22
msgid "System users" msgid "System users"
msgstr "系统用户" msgstr "系统用户"
#: assets/forms.py:160 #: assets/forms.py:165
msgid "Selected system users will be create at cluster assets" msgid "Selected system users will be create at cluster assets"
msgstr "选择的系统用户将会在该集群资产上创建" msgstr "选择的系统用户将会在该集群资产上创建"
#: assets/forms.py:168 assets/forms.py:243 assets/forms.py:302 #: assets/forms.py:173 assets/forms.py:248 assets/forms.py:308
#: assets/models/cluster.py:18 assets/models/group.py:20 #: assets/models/cluster.py:18 assets/models/group.py:20
#: assets/models/user.py:28 assets/templates/assets/admin_user_detail.html:56 #: assets/models/user.py:28 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:22 #: assets/templates/assets/admin_user_list.html:22
#: assets/templates/assets/asset_group_list.html:15 #: assets/templates/assets/asset_group_list.html:15
#: assets/templates/assets/cluster_detail.html:57 #: assets/templates/assets/cluster_detail.html:57
#: assets/templates/assets/cluster_list.html:19 #: assets/templates/assets/cluster_list.html:19
#: assets/templates/assets/system_user_detail.html:53 #: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:24 ops/models.py:31 #: assets/templates/assets/system_user_list.html:24 common/models.py:25
#: ops/templates/ops/task_detail.html:56 ops/templates/ops/task_list.html:34 #: ops/models.py:31 ops/templates/ops/task_detail.html:56
#: perms/models.py:14 #: ops/templates/ops/task_list.html:34 perms/models.py:14
#: perms/templates/perms/asset_permission_create_update.html:33 #: perms/templates/perms/asset_permission_create_update.html:33
#: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:25 #: perms/templates/perms/asset_permission_list.html:25
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:14 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:14
#: terminal/models.py:118 terminal/templates/terminal/terminal_detail.html:43 #: terminal/models.py:118 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:36 users/templates/users/_select_user_modal.html:13 #: users/models/user.py:35 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:62 #: users/templates/users/user_detail.html:62
#: users/templates/users/user_granted_asset.html:81 #: users/templates/users/user_granted_asset.html:81
#: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_detail.html:55
...@@ -120,16 +120,17 @@ msgstr "选择的系统用户将会在该集群资产上创建" ...@@ -120,16 +120,17 @@ msgstr "选择的系统用户将会在该集群资产上创建"
msgid "Name" msgid "Name"
msgstr "名称" msgstr "名称"
#: assets/forms.py:174 #: assets/forms.py:179
msgid "Cluster level admin user" msgid "Cluster level admin user"
msgstr "集群级别管理用户" msgstr "集群级别管理用户"
#: assets/forms.py:195 #: assets/forms.py:200
msgid "Password or private key password" msgid "Password or private key password"
msgstr "密码或秘钥不合法" msgstr "密码或秘钥不合法"
#: assets/forms.py:196 assets/models/user.py:30 users/forms.py:16 #: assets/forms.py:201 assets/forms.py:262 assets/models/user.py:30
#: users/forms.py:24 users/templates/users/login.html:56 #: common/forms.py:107 users/forms.py:16 users/forms.py:24
#: users/templates/users/login.html:56
#: users/templates/users/reset_password.html:52 #: users/templates/users/reset_password.html:52
#: users/templates/users/user_create.html:11 #: users/templates/users/user_create.html:11
#: users/templates/users/user_password_update.html:40 #: users/templates/users/user_password_update.html:40
...@@ -138,25 +139,25 @@ msgstr "密码或秘钥不合法" ...@@ -138,25 +139,25 @@ msgstr "密码或秘钥不合法"
msgid "Password" msgid "Password"
msgstr "密码" msgstr "密码"
#: assets/forms.py:199 users/models/user.py:46 #: assets/forms.py:204 assets/forms.py:264 users/models/user.py:45
msgid "Private key" msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
#: assets/forms.py:224 assets/forms.py:284 assets/forms.py:345 #: assets/forms.py:229 assets/forms.py:290 assets/forms.py:354
msgid "Invalid private key" msgid "Invalid private key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
#: assets/forms.py:235 #: assets/forms.py:240
msgid "Password and private key file must be input one" msgid "Password and private key file must be input one"
msgstr "密码和私钥, 必须输入一个" msgstr "密码和私钥, 必须输入一个"
#: assets/forms.py:244 assets/forms.py:303 assets/models/user.py:29 #: assets/forms.py:249 assets/forms.py:309 assets/models/user.py:29
#: assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:23 #: assets/templates/assets/admin_user_list.html:23
#: assets/templates/assets/system_user_detail.html:57 #: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:25 #: assets/templates/assets/system_user_list.html:25
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:14 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:14
#: users/models/authentication.py:44 users/models/user.py:35 #: users/models/authentication.py:44 users/models/user.py:34
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:53 #: users/templates/users/login.html:53
#: users/templates/users/login_log_list.html:49 #: users/templates/users/login_log_list.html:49
...@@ -166,75 +167,75 @@ msgstr "密码和私钥, 必须输入一个" ...@@ -166,75 +167,75 @@ msgstr "密码和私钥, 必须输入一个"
msgid "Username" msgid "Username"
msgstr "用户名" msgstr "用户名"
#: assets/forms.py:291 assets/forms.py:351 #: assets/forms.py:297 assets/forms.py:360
msgid "Auth info required, private_key or password" msgid "Auth info required, private_key or password"
msgstr "密钥和密码必须填写一个" msgstr "密钥和密码必须填写一个"
#: assets/forms.py:306 #: assets/forms.py:313
msgid " Select clusters" msgid " Select clusters"
msgstr "选择集群" msgstr "选择集群"
#: assets/forms.py:311 #: assets/forms.py:320
msgid "If auto push checked, system user will be create at cluster assets" msgid "If auto push checked, system user will be create at cluster assets"
msgstr "如果选择了自动推送,系统用户将会创建在集群资产上" msgstr "如果选择了自动推送,系统用户将会创建在集群资产上"
#: assets/forms.py:312 #: assets/forms.py:321
msgid "Auto push system user to asset" msgid "Auto push system user to asset"
msgstr "自动推送系统用户到资产" msgstr "自动推送系统用户到资产"
#: assets/forms.py:313 #: assets/forms.py:322
msgid "" msgid ""
"High level will be using login asset as default, if user was granted more " "High level will be using login asset as default, if user was granted more "
"than 2 system user" "than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户" msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/models/asset.py:24 #: assets/models/asset.py:34
msgid "In use" msgid "In use"
msgstr "使用中" msgstr "使用中"
#: assets/models/asset.py:25 #: assets/models/asset.py:35
msgid "Out of use" msgid "Out of use"
msgstr "未使用" msgstr "未使用"
#: assets/models/asset.py:28 #: assets/models/asset.py:38
msgid "Server" msgid "Server"
msgstr "物理机" msgstr "物理机"
#: assets/models/asset.py:29 #: assets/models/asset.py:39
msgid "VM" msgid "VM"
msgstr "虚拟机" msgstr "虚拟机"
#: assets/models/asset.py:30 #: assets/models/asset.py:40
msgid "Switch" msgid "Switch"
msgstr "交换机" msgstr "交换机"
#: assets/models/asset.py:31 #: assets/models/asset.py:41
msgid "Router" msgid "Router"
msgstr "路由器" msgstr "路由器"
#: assets/models/asset.py:32 #: assets/models/asset.py:42
msgid "Firewall" msgid "Firewall"
msgstr "防火墙" msgstr "防火墙"
#: assets/models/asset.py:33 #: assets/models/asset.py:43
msgid "Storage" msgid "Storage"
msgstr "存储" msgstr "存储"
#: assets/models/asset.py:36 #: assets/models/asset.py:46
msgid "Production" msgid "Production"
msgstr "生产环境" msgstr "生产环境"
#: assets/models/asset.py:37 #: assets/models/asset.py:47
msgid "Development" msgid "Development"
msgstr "开发环境" msgstr "开发环境"
#: assets/models/asset.py:38 #: assets/models/asset.py:48
msgid "Testing" msgid "Testing"
msgstr "测试环境" msgstr "测试环境"
#: assets/models/asset.py:43 assets/templates/assets/admin_user_assets.html:60 #: assets/models/asset.py:53 assets/templates/assets/admin_user_assets.html:60
#: assets/templates/assets/asset_detail.html:61 #: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_group_detail.html:46 #: assets/templates/assets/asset_group_detail.html:51
#: assets/templates/assets/asset_list.html:31 #: assets/templates/assets/asset_list.html:31
#: assets/templates/assets/cluster_assets.html:52 #: assets/templates/assets/cluster_assets.html:52
#: assets/templates/assets/system_user_asset.html:53 #: assets/templates/assets/system_user_asset.html:53
...@@ -246,9 +247,9 @@ msgstr "测试环境" ...@@ -246,9 +247,9 @@ msgstr "测试环境"
msgid "IP" msgid "IP"
msgstr "IP" msgstr "IP"
#: assets/models/asset.py:44 assets/templates/assets/admin_user_assets.html:59 #: assets/models/asset.py:54 assets/templates/assets/admin_user_assets.html:59
#: assets/templates/assets/asset_detail.html:57 #: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_group_detail.html:45 #: assets/templates/assets/asset_group_detail.html:50
#: assets/templates/assets/asset_list.html:30 #: assets/templates/assets/asset_list.html:30
#: assets/templates/assets/cluster_assets.html:51 #: assets/templates/assets/cluster_assets.html:51
#: assets/templates/assets/system_user_asset.html:52 #: assets/templates/assets/system_user_asset.html:52
...@@ -259,131 +260,131 @@ msgstr "IP" ...@@ -259,131 +260,131 @@ msgstr "IP"
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:46 assets/templates/assets/asset_detail.html:213 #: assets/models/asset.py:56 assets/templates/assets/asset_detail.html:213
#: assets/views/asset.py:212 assets/views/asset.py:252 #: assets/views/asset.py:218 assets/views/asset.py:258
msgid "Asset groups" msgid "Asset groups"
msgstr "资产组" msgstr "资产组"
#: assets/models/asset.py:47 assets/models/cluster.py:40 #: assets/models/asset.py:57 assets/models/cluster.py:40
#: assets/models/user.py:215 assets/templates/assets/asset_detail.html:85 #: assets/models/user.py:219 assets/templates/assets/asset_detail.html:85
#: assets/templates/assets/asset_list.html:33 templates/_nav.html:24 #: assets/templates/assets/asset_list.html:33 templates/_nav.html:24
msgid "Cluster" msgid "Cluster"
msgstr "集群" msgstr "集群"
#: assets/models/asset.py:48 assets/templates/assets/asset_detail.html:129 #: assets/models/asset.py:58 assets/templates/assets/asset_detail.html:129
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
#: assets/models/asset.py:49 assets/templates/assets/asset_detail.html:133 #: assets/models/asset.py:59 assets/templates/assets/asset_detail.html:133
msgid "Asset type" msgid "Asset type"
msgstr "系统类型" msgstr "系统类型"
#: assets/models/asset.py:50 assets/templates/assets/asset_detail.html:137 #: assets/models/asset.py:60 assets/templates/assets/asset_detail.html:137
msgid "Asset environment" msgid "Asset environment"
msgstr "资产环境" msgstr "资产环境"
#: assets/models/asset.py:51 assets/templates/assets/asset_detail.html:125 #: assets/models/asset.py:61 assets/templates/assets/asset_detail.html:125
msgid "Asset status" msgid "Asset status"
msgstr "资产状态" msgstr "资产状态"
#: assets/models/asset.py:54 assets/models/cluster.py:19 #: assets/models/asset.py:64 assets/models/cluster.py:19
#: assets/models/user.py:186 assets/templates/assets/asset_detail.html:73 #: assets/models/user.py:190 assets/templates/assets/asset_detail.html:73
#: assets/templates/assets/cluster_list.html:20 templates/_nav.html:25 #: assets/templates/assets/cluster_list.html:20 templates/_nav.html:25
msgid "Admin user" msgid "Admin user"
msgstr "管理用户" msgstr "管理用户"
#: assets/models/asset.py:57 assets/templates/assets/asset_detail.html:65 #: assets/models/asset.py:67 assets/templates/assets/asset_detail.html:65
msgid "Public IP" msgid "Public IP"
msgstr "公网IP" msgstr "公网IP"
#: assets/models/asset.py:58 #: assets/models/asset.py:68
msgid "Remote control card IP" msgid "Remote control card IP"
msgstr "远控卡IP" msgstr "远控卡IP"
#: assets/models/asset.py:59 assets/templates/assets/asset_detail.html:89 #: assets/models/asset.py:69 assets/templates/assets/asset_detail.html:89
msgid "Cabinet number" msgid "Cabinet number"
msgstr "机柜编号" msgstr "机柜编号"
#: assets/models/asset.py:60 assets/templates/assets/asset_detail.html:93 #: assets/models/asset.py:70 assets/templates/assets/asset_detail.html:93
msgid "Cabinet position" msgid "Cabinet position"
msgstr "机柜层号" msgstr "机柜层号"
#: assets/models/asset.py:61 assets/templates/assets/asset_detail.html:145 #: assets/models/asset.py:71 assets/templates/assets/asset_detail.html:145
msgid "Asset number" msgid "Asset number"
msgstr "资产编号" msgstr "资产编号"
#: assets/models/asset.py:64 assets/templates/assets/asset_detail.html:97 #: assets/models/asset.py:74 assets/templates/assets/asset_detail.html:97
msgid "Vendor" msgid "Vendor"
msgstr "制造商" msgstr "制造商"
#: assets/models/asset.py:65 assets/templates/assets/asset_detail.html:101 #: assets/models/asset.py:75 assets/templates/assets/asset_detail.html:101
msgid "Model" msgid "Model"
msgstr "型号" msgstr "型号"
#: assets/models/asset.py:66 assets/templates/assets/asset_detail.html:141 #: assets/models/asset.py:76 assets/templates/assets/asset_detail.html:141
msgid "Serial number" msgid "Serial number"
msgstr "序列号" msgstr "序列号"
#: assets/models/asset.py:68 #: assets/models/asset.py:78
msgid "CPU model" msgid "CPU model"
msgstr "CPU型号" msgstr "CPU型号"
#: assets/models/asset.py:69 #: assets/models/asset.py:79
msgid "CPU count" msgid "CPU count"
msgstr "CPU数量" msgstr "CPU数量"
#: assets/models/asset.py:70 #: assets/models/asset.py:80
msgid "CPU cores" msgid "CPU cores"
msgstr "CPU核数" msgstr "CPU核数"
#: assets/models/asset.py:71 assets/templates/assets/asset_detail.html:109 #: assets/models/asset.py:81 assets/templates/assets/asset_detail.html:109
msgid "Memory" msgid "Memory"
msgstr "内存" msgstr "内存"
#: assets/models/asset.py:72 #: assets/models/asset.py:82
msgid "Disk total" msgid "Disk total"
msgstr "硬盘大小" msgstr "硬盘大小"
#: assets/models/asset.py:73 #: assets/models/asset.py:83
msgid "Disk info" msgid "Disk info"
msgstr "硬盘信息" msgstr "硬盘信息"
#: assets/models/asset.py:75 assets/templates/assets/asset_detail.html:117 #: assets/models/asset.py:85 assets/templates/assets/asset_detail.html:117
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
#: assets/models/asset.py:76 assets/templates/assets/asset_detail.html:121 #: assets/models/asset.py:86 assets/templates/assets/asset_detail.html:121
msgid "OS" msgid "OS"
msgstr "操作系统" msgstr "操作系统"
#: assets/models/asset.py:77 #: assets/models/asset.py:87
msgid "OS version" msgid "OS version"
msgstr "系统版本" msgstr "系统版本"
#: assets/models/asset.py:78 #: assets/models/asset.py:88
msgid "OS arch" msgid "OS arch"
msgstr "系统架构" msgstr "系统架构"
#: assets/models/asset.py:79 #: assets/models/asset.py:89
msgid "Hostname raw" msgid "Hostname raw"
msgstr "主机名原始" msgstr "主机名原始"
#: assets/models/asset.py:81 assets/models/cluster.py:28 #: assets/models/asset.py:91 assets/models/cluster.py:28
#: assets/models/group.py:21 assets/models/user.py:36 #: assets/models/group.py:21 assets/models/user.py:36
#: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:149 #: assets/templates/assets/asset_detail.html:149
#: assets/templates/assets/cluster_detail.html:93 #: assets/templates/assets/cluster_detail.html:93
#: assets/templates/assets/system_user_detail.html:91 #: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:22 #: ops/templates/ops/adhoc_detail.html:86 perms/models.py:22
#: perms/templates/perms/asset_permission_detail.html:94 #: perms/templates/perms/asset_permission_detail.html:94
#: users/models/user.py:51 users/templates/users/user_detail.html:98 #: users/models/user.py:50 users/templates/users/user_detail.html:98
msgid "Created by" msgid "Created by"
msgstr "创建者" msgstr "创建者"
#: assets/models/asset.py:82 assets/models/cluster.py:26 #: assets/models/asset.py:92 assets/models/cluster.py:26
#: assets/models/group.py:22 assets/templates/assets/admin_user_detail.html:64 #: assets/models/group.py:22 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/cluster_detail.html:89 #: assets/templates/assets/cluster_detail.html:89
#: assets/templates/assets/system_user_detail.html:87 #: assets/templates/assets/system_user_detail.html:92
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:60 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:60
#: perms/models.py:23 perms/templates/perms/asset_permission_detail.html:90 #: perms/models.py:23 perms/templates/perms/asset_permission_detail.html:90
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
...@@ -391,19 +392,19 @@ msgstr "创建者" ...@@ -391,19 +392,19 @@ msgstr "创建者"
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
#: assets/models/asset.py:83 assets/models/cluster.py:29 #: assets/models/asset.py:93 assets/models/cluster.py:29
#: assets/models/group.py:23 assets/models/user.py:33 #: assets/models/group.py:23 assets/models/user.py:33
#: assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:28 #: assets/templates/assets/admin_user_list.html:28
#: assets/templates/assets/asset_detail.html:157 #: assets/templates/assets/asset_detail.html:157
#: assets/templates/assets/asset_group_list.html:17 #: assets/templates/assets/asset_group_list.html:17
#: assets/templates/assets/cluster_detail.html:97 #: assets/templates/assets/cluster_detail.html:97
#: assets/templates/assets/system_user_detail.html:95 #: assets/templates/assets/system_user_detail.html:100
#: assets/templates/assets/system_user_list.html:30 ops/models.py:37 #: assets/templates/assets/system_user_list.html:30 common/models.py:28
#: perms/models.py:24 perms/templates/perms/asset_permission_detail.html:98 #: ops/models.py:37 perms/models.py:24
#: terminal/models.py:22 terminal/templates/terminal/terminal_detail.html:63 #: perms/templates/perms/asset_permission_detail.html:98 terminal/models.py:22
#: users/models/group.py:15 users/models/user.py:48 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
#: users/templates/users/user_detail.html:110 #: users/models/user.py:47 users/templates/users/user_detail.html:110
#: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14 #: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:118 #: users/templates/users/user_profile.html:118
...@@ -419,7 +420,7 @@ msgid "Contact" ...@@ -419,7 +420,7 @@ msgid "Contact"
msgstr "联系人" msgstr "联系人"
#: assets/models/cluster.py:22 assets/templates/assets/cluster_detail.html:69 #: assets/models/cluster.py:22 assets/templates/assets/cluster_detail.html:69
#: users/models/user.py:42 users/templates/users/user_detail.html:75 #: users/models/user.py:41 users/templates/users/user_detail.html:75
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
...@@ -443,7 +444,7 @@ msgstr "运营商" ...@@ -443,7 +444,7 @@ msgstr "运营商"
msgid "Default" msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 users/models/user.py:259 #: assets/models/cluster.py:36 users/models/user.py:258
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
...@@ -468,29 +469,29 @@ msgstr "ssh密钥" ...@@ -468,29 +469,29 @@ msgstr "ssh密钥"
msgid "SSH public key" msgid "SSH public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: assets/models/user.py:216 #: assets/models/user.py:220
msgid "Priority" msgid "Priority"
msgstr "优先级" msgstr "优先级"
#: assets/models/user.py:217 assets/templates/assets/system_user_detail.html:61 #: assets/models/user.py:221 assets/templates/assets/system_user_detail.html:66
msgid "Protocol" msgid "Protocol"
msgstr "协议" msgstr "协议"
#: assets/models/user.py:218 assets/templates/assets/_system_user.html:59 #: assets/models/user.py:222 assets/templates/assets/_system_user.html:59
#: assets/templates/assets/system_user_detail.html:113 #: assets/templates/assets/system_user_detail.html:118
#: assets/templates/assets/system_user_update.html:11 #: assets/templates/assets/system_user_update.html:11
msgid "Auto push" msgid "Auto push"
msgstr "自动推送" msgstr "自动推送"
#: assets/models/user.py:219 assets/templates/assets/system_user_detail.html:65 #: assets/models/user.py:223 assets/templates/assets/system_user_detail.html:70
msgid "Sudo" msgid "Sudo"
msgstr "Sudo" msgstr "Sudo"
#: assets/models/user.py:220 assets/templates/assets/system_user_detail.html:70 #: assets/models/user.py:224 assets/templates/assets/system_user_detail.html:75
msgid "Shell" msgid "Shell"
msgstr "Shell" msgstr "Shell"
#: assets/models/user.py:265 perms/models.py:19 #: assets/models/user.py:269 perms/models.py:19
#: perms/templates/perms/asset_permission_detail.html:136 #: perms/templates/perms/asset_permission_detail.html:136
#: perms/templates/perms/asset_permission_list.html:30 templates/_nav.html:26 #: perms/templates/perms/asset_permission_list.html:30 templates/_nav.html:26
#: terminal/backends/command/models.py:12 terminal/models.py:94 #: terminal/backends/command/models.py:12 terminal/models.py:94
...@@ -552,11 +553,11 @@ msgstr "测试系统用户可连接性: {}" ...@@ -552,11 +553,11 @@ msgstr "测试系统用户可连接性: {}"
msgid "Test system user connectability period: {}" msgid "Test system user connectability period: {}"
msgstr "定期测试系统用户可连接性: {}" msgstr "定期测试系统用户可连接性: {}"
#: assets/tasks.py:372 #: assets/tasks.py:376
msgid "Push system user to cluster assets: {}" msgid "Push system user to cluster assets: {}"
msgstr "推送系统用户到资产: {}" msgstr "推送系统用户到资产: {}"
#: assets/tasks.py:393 #: assets/tasks.py:397
msgid "Push cluster system users to assets period: {}" msgid "Push cluster system users to assets period: {}"
msgstr "定期推送集群系统用户到资产: {}" msgstr "定期推送集群系统用户到资产: {}"
...@@ -570,16 +571,16 @@ msgstr "仅修改你需要更新的字段" ...@@ -570,16 +571,16 @@ msgstr "仅修改你需要更新的字段"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:12 #: assets/templates/assets/_asset_group_bulk_update_modal.html:12
#: assets/templates/assets/system_user_asset.html:21 #: assets/templates/assets/system_user_asset.html:21
#: assets/views/admin_user.py:27 assets/views/admin_user.py:44 #: assets/views/admin_user.py:29 assets/views/admin_user.py:47
#: assets/views/admin_user.py:67 assets/views/admin_user.py:88 #: assets/views/admin_user.py:63 assets/views/admin_user.py:79
#: assets/views/admin_user.py:115 assets/views/asset.py:47 #: assets/views/admin_user.py:106 assets/views/asset.py:48
#: assets/views/asset.py:61 assets/views/asset.py:84 assets/views/asset.py:141 #: assets/views/asset.py:61 assets/views/asset.py:84 assets/views/asset.py:144
#: assets/views/asset.py:158 assets/views/asset.py:179 #: assets/views/asset.py:161 assets/views/asset.py:185
#: assets/views/cluster.py:22 assets/views/cluster.py:80 #: assets/views/cluster.py:26 assets/views/cluster.py:85
#: assets/views/cluster.py:97 assets/views/group.py:30 assets/views/group.py:53 #: assets/views/cluster.py:102 assets/views/group.py:34
#: assets/views/group.py:71 assets/views/group.py:93 #: assets/views/group.py:52 assets/views/group.py:69 assets/views/group.py:87
#: assets/views/system_user.py:29 assets/views/system_user.py:48 #: assets/views/system_user.py:28 assets/views/system_user.py:44
#: assets/views/system_user.py:71 assets/views/system_user.py:91 #: assets/views/system_user.py:60 assets/views/system_user.py:75
#: templates/_nav.html:19 #: templates/_nav.html:19
msgid "Assets" msgid "Assets"
msgstr "资产管理" msgstr "资产管理"
...@@ -620,7 +621,7 @@ msgstr "如果设置了id,则会使用该行信息更新该id的资产" ...@@ -620,7 +621,7 @@ msgstr "如果设置了id,则会使用该行信息更新该id的资产"
#: assets/templates/assets/_system_user.html:16 #: assets/templates/assets/_system_user.html:16
#: assets/templates/assets/system_user_list.html:16 #: assets/templates/assets/system_user_list.html:16
#: assets/views/system_user.py:49 #: assets/views/system_user.py:45
msgid "Create system user" msgid "Create system user"
msgstr "创建系统用户" msgstr "创建系统用户"
...@@ -657,8 +658,12 @@ msgstr "其它" ...@@ -657,8 +658,12 @@ msgstr "其它"
#: assets/templates/assets/admin_user_create_update.html:45 #: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23 #: assets/templates/assets/asset_bulk_update.html:23
#: assets/templates/assets/asset_create.html:40 #: assets/templates/assets/asset_create.html:40
#: assets/templates/assets/asset_group_create.html:16
#: assets/templates/assets/asset_update.html:55 #: assets/templates/assets/asset_update.html:55
#: assets/templates/assets/cluster_create_update.html:54 #: assets/templates/assets/cluster_create_update.html:54
#: common/templates/common/basic_setting.html:55
#: common/templates/common/email_setting.html:56
#: common/templates/common/ldap_setting.html:56
#: perms/templates/perms/asset_permission_create_update.html:67 #: perms/templates/perms/asset_permission_create_update.html:67
#: terminal/templates/terminal/terminal_update.html:45 #: terminal/templates/terminal/terminal_update.html:45
#: users/templates/users/_user.html:49 #: users/templates/users/_user.html:49
...@@ -675,9 +680,13 @@ msgstr "重置" ...@@ -675,9 +680,13 @@ msgstr "重置"
#: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:41 #: assets/templates/assets/asset_create.html:41
#: assets/templates/assets/asset_list.html:55 #: assets/templates/assets/asset_group_create.html:17
#: assets/templates/assets/asset_list.html:53
#: assets/templates/assets/asset_update.html:56 #: assets/templates/assets/asset_update.html:56
#: assets/templates/assets/cluster_create_update.html:55 #: assets/templates/assets/cluster_create_update.html:55
#: common/templates/common/basic_setting.html:56
#: common/templates/common/email_setting.html:57
#: common/templates/common/ldap_setting.html:57
#: perms/templates/perms/asset_permission_create_update.html:68 #: perms/templates/perms/asset_permission_create_update.html:68
#: terminal/templates/terminal/terminal_update.html:46 #: terminal/templates/terminal/terminal_update.html:46
#: users/templates/users/_user.html:50 #: users/templates/users/_user.html:50
...@@ -711,15 +720,62 @@ msgstr "详情" ...@@ -711,15 +720,62 @@ msgstr "详情"
msgid "Assets list" msgid "Assets list"
msgstr "资产列表" msgstr "资产列表"
#: assets/templates/assets/admin_user_assets.html:24
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:83
#: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_group_detail.html:18
#: assets/templates/assets/asset_group_detail.html:177
#: assets/templates/assets/asset_group_list.html:38
#: assets/templates/assets/asset_list.html:98
#: assets/templates/assets/cluster_assets.html:170
#: assets/templates/assets/cluster_detail.html:25
#: assets/templates/assets/cluster_list.html:43
#: assets/templates/assets/system_user_asset.html:25
#: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:84
#: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:73
#: terminal/templates/terminal/terminal_detail.html:16
#: terminal/templates/terminal/terminal_list.html:71
#: users/templates/users/user_detail.html:25
#: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:39
#: users/templates/users/user_list.html:76
msgid "Update"
msgstr "更新"
#: assets/templates/assets/admin_user_assets.html:28
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:84
#: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_group_detail.html:22
#: assets/templates/assets/asset_group_list.html:39
#: assets/templates/assets/asset_list.html:99
#: assets/templates/assets/cluster_detail.html:29
#: assets/templates/assets/cluster_list.html:44
#: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:85
#: ops/templates/ops/task_list.html:71
#: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:74
#: terminal/templates/terminal/terminal_list.html:73
#: users/templates/users/user_detail.html:29
#: users/templates/users/user_group_detail.html:32
#: users/templates/users/user_group_list.html:41
#: users/templates/users/user_list.html:80
#: users/templates/users/user_list.html:84
msgid "Delete"
msgstr "删除"
#: assets/templates/assets/admin_user_assets.html:37 #: assets/templates/assets/admin_user_assets.html:37
#: assets/templates/assets/asset_group_detail.html:26 #: assets/templates/assets/asset_group_detail.html:31
#: perms/templates/perms/asset_permission_asset.html:35 #: perms/templates/perms/asset_permission_asset.html:35
msgid "Asset list of " msgid "Asset list of "
msgstr "资产列表" msgstr "资产列表"
#: assets/templates/assets/admin_user_assets.html:62 #: assets/templates/assets/admin_user_assets.html:62
#: assets/templates/assets/asset_group_detail.html:48 #: assets/templates/assets/asset_group_detail.html:53
#: assets/templates/assets/asset_list.html:34
#: assets/templates/assets/cluster_assets.html:54 #: assets/templates/assets/cluster_assets.html:54
#: assets/templates/assets/user_asset_list.html:22 #: assets/templates/assets/user_asset_list.html:22
#: users/templates/users/login_log_list.html:50 #: users/templates/users/login_log_list.html:50
...@@ -729,7 +785,7 @@ msgstr "类型" ...@@ -729,7 +785,7 @@ msgstr "类型"
#: assets/templates/assets/admin_user_assets.html:63 #: assets/templates/assets/admin_user_assets.html:63
#: assets/templates/assets/admin_user_list.html:25 #: assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/asset_detail.html:376 #: assets/templates/assets/asset_detail.html:376
#: assets/templates/assets/asset_list.html:38 #: assets/templates/assets/asset_list.html:36
#: assets/templates/assets/system_user_asset.html:55 #: assets/templates/assets/system_user_asset.html:55
#: assets/templates/assets/system_user_list.html:27 #: assets/templates/assets/system_user_list.html:27
msgid "Reachable" msgid "Reachable"
...@@ -738,7 +794,7 @@ msgstr "可连接" ...@@ -738,7 +794,7 @@ msgstr "可连接"
#: assets/templates/assets/admin_user_assets.html:75 #: assets/templates/assets/admin_user_assets.html:75
#: assets/templates/assets/cluster_assets.html:68 #: assets/templates/assets/cluster_assets.html:68
#: assets/templates/assets/system_user_asset.html:67 #: assets/templates/assets/system_user_asset.html:67
#: assets/templates/assets/system_user_detail.html:107 #: assets/templates/assets/system_user_detail.html:112
#: perms/templates/perms/asset_permission_detail.html:110 #: perms/templates/perms/asset_permission_detail.html:110
msgid "Quick update" msgid "Quick update"
msgstr "快速更新" msgstr "快速更新"
...@@ -760,7 +816,7 @@ msgstr "任务已下发,查看左侧资产状态" ...@@ -760,7 +816,7 @@ msgstr "任务已下发,查看左侧资产状态"
#: assets/templates/assets/admin_user_create_update.html:16 #: assets/templates/assets/admin_user_create_update.html:16
#: assets/templates/assets/admin_user_list.html:14 #: assets/templates/assets/admin_user_list.html:14
#: assets/views/admin_user.py:45 #: assets/views/admin_user.py:48
msgid "Create admin user" msgid "Create admin user"
msgstr "创建管理用户" msgstr "创建管理用户"
...@@ -770,19 +826,19 @@ msgstr "使用集群管理用户" ...@@ -770,19 +826,19 @@ msgstr "使用集群管理用户"
#: assets/templates/assets/admin_user_detail.html:101 #: assets/templates/assets/admin_user_detail.html:101
#: assets/templates/assets/asset_detail.html:230 #: assets/templates/assets/asset_detail.html:230
#: assets/templates/assets/asset_group_list.html:85 #: assets/templates/assets/asset_group_list.html:81
#: assets/templates/assets/asset_list.html:202 #: assets/templates/assets/asset_list.html:220
#: assets/templates/assets/cluster_assets.html:104 #: assets/templates/assets/cluster_assets.html:104
#: assets/templates/assets/cluster_list.html:89 #: assets/templates/assets/cluster_list.html:89
#: assets/templates/assets/system_user_detail.html:159 #: assets/templates/assets/system_user_detail.html:164
#: assets/templates/assets/system_user_list.html:134 templates/_modal.html:16 #: assets/templates/assets/system_user_list.html:134 templates/_modal.html:16
#: terminal/templates/terminal/session_detail.html:108 #: terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:338 #: users/templates/users/user_detail.html:338
#: users/templates/users/user_detail.html:363 #: users/templates/users/user_detail.html:363
#: users/templates/users/user_detail.html:386 #: users/templates/users/user_detail.html:386
#: users/templates/users/user_group_create_update.html:46 #: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:82 #: users/templates/users/user_group_list.html:82
#: users/templates/users/user_list.html:184 #: users/templates/users/user_list.html:196
#: users/templates/users/user_profile.html:181 #: users/templates/users/user_profile.html:181
msgid "Confirm" msgid "Confirm"
msgstr "确认" msgstr "确认"
...@@ -800,13 +856,12 @@ msgid "Ratio" ...@@ -800,13 +856,12 @@ msgid "Ratio"
msgstr "比例" msgstr "比例"
#: assets/templates/assets/admin_user_list.html:29 #: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/asset_group_detail.html:50 #: assets/templates/assets/asset_group_detail.html:55
#: assets/templates/assets/asset_group_list.html:18 #: assets/templates/assets/asset_group_list.html:18
#: assets/templates/assets/asset_list.html:39 #: assets/templates/assets/asset_list.html:37
#: assets/templates/assets/cluster_assets.html:56 #: assets/templates/assets/cluster_assets.html:56
#: assets/templates/assets/cluster_list.html:23 #: assets/templates/assets/cluster_list.html:23
#: assets/templates/assets/system_user_list.html:31 #: assets/templates/assets/system_user_list.html:31
#: assets/templates/assets/user_asset_list.html:27
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:61 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:61
#: ops/templates/ops/task_history.html:62 ops/templates/ops/task_list.html:41 #: ops/templates/ops/task_history.html:62 ops/templates/ops/task_list.html:41
#: perms/templates/perms/asset_permission_list.html:32 #: perms/templates/perms/asset_permission_list.html:32
...@@ -817,41 +872,13 @@ msgstr "比例" ...@@ -817,41 +872,13 @@ msgstr "比例"
msgid "Action" msgid "Action"
msgstr "动作" msgstr "动作"
#: assets/templates/assets/admin_user_list.html:83
#: assets/templates/assets/asset_group_detail.html:172
#: assets/templates/assets/asset_group_list.html:42
#: assets/templates/assets/asset_list.html:95
#: assets/templates/assets/cluster_assets.html:170
#: assets/templates/assets/cluster_list.html:43
#: assets/templates/assets/system_user_list.html:84
#: perms/templates/perms/asset_permission_list.html:73
#: terminal/templates/terminal/terminal_list.html:71
#: users/templates/users/user_group_list.html:39
#: users/templates/users/user_list.html:76
msgid "Update"
msgstr "更新"
#: assets/templates/assets/admin_user_list.html:84
#: assets/templates/assets/asset_group_list.html:43
#: assets/templates/assets/asset_list.html:96
#: assets/templates/assets/cluster_list.html:44
#: assets/templates/assets/system_user_list.html:85
#: ops/templates/ops/task_list.html:70
#: perms/templates/perms/asset_permission_list.html:74
#: terminal/templates/terminal/terminal_list.html:73
#: users/templates/users/user_group_list.html:41
#: users/templates/users/user_list.html:80
#: users/templates/users/user_list.html:84
msgid "Delete"
msgstr "删除"
#: assets/templates/assets/asset_create.html:28 #: assets/templates/assets/asset_create.html:28
#: assets/templates/assets/asset_update.html:33 #: assets/templates/assets/asset_update.html:33
msgid "Group" msgid "Group"
msgstr "组" msgstr "组"
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:180 #: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:186
#: assets/views/cluster.py:98 #: assets/views/cluster.py:103
msgid "Asset detail" msgid "Asset detail"
msgstr "资产详情" msgstr "资产详情"
...@@ -881,7 +908,7 @@ msgid "Quick modify" ...@@ -881,7 +908,7 @@ msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:175 #: assets/templates/assets/asset_detail.html:175
#: assets/templates/assets/asset_list.html:37 #: assets/templates/assets/asset_list.html:35
#: assets/templates/assets/user_asset_list.html:25 perms/models.py:20 #: assets/templates/assets/user_asset_list.html:25 perms/models.py:20
#: perms/templates/perms/asset_permission_create_update.html:47 #: perms/templates/perms/asset_permission_create_update.html:47
#: perms/templates/perms/asset_permission_detail.html:116 #: perms/templates/perms/asset_permission_detail.html:116
...@@ -914,17 +941,17 @@ msgstr "更新成功" ...@@ -914,17 +941,17 @@ msgstr "更新成功"
msgid "Group assets" msgid "Group assets"
msgstr "组下资产" msgstr "组下资产"
#: assets/templates/assets/asset_group_detail.html:49 #: assets/templates/assets/asset_group_detail.html:54
#: assets/templates/assets/cluster_assets.html:55 #: assets/templates/assets/cluster_assets.html:55
#: terminal/templates/terminal/terminal_list.html:35 #: terminal/templates/terminal/terminal_list.html:35
msgid "Alive" msgid "Alive"
msgstr "在线" msgstr "在线"
#: assets/templates/assets/asset_group_detail.html:62 #: assets/templates/assets/asset_group_detail.html:67
msgid "Add assets to this group" msgid "Add assets to this group"
msgstr "添加资产到该组" msgstr "添加资产到该组"
#: assets/templates/assets/asset_group_detail.html:79 #: assets/templates/assets/asset_group_detail.html:84
#: perms/templates/perms/asset_permission_asset.html:97 #: perms/templates/perms/asset_permission_asset.html:97
#: perms/templates/perms/asset_permission_detail.html:153 #: perms/templates/perms/asset_permission_detail.html:153
#: perms/templates/perms/asset_permission_user.html:97 #: perms/templates/perms/asset_permission_user.html:97
...@@ -933,49 +960,49 @@ msgstr "添加资产到该组" ...@@ -933,49 +960,49 @@ msgstr "添加资产到该组"
msgid "Add" msgid "Add"
msgstr "添加" msgstr "添加"
#: assets/templates/assets/asset_group_detail.html:173 #: assets/templates/assets/asset_group_detail.html:178
msgid "Remove" msgid "Remove"
msgstr "移除" msgstr "移除"
#: assets/templates/assets/asset_group_list.html:7 assets/views/group.py:31 #: assets/templates/assets/asset_group_list.html:7 assets/views/group.py:35
#: assets/views/group.py:94 #: assets/views/group.py:88
msgid "Create asset group" msgid "Create asset group"
msgstr "创建资产组" msgstr "创建资产组"
#: assets/templates/assets/asset_group_list.html:80 #: assets/templates/assets/asset_group_list.html:76
#: assets/templates/assets/asset_list.html:197 #: assets/templates/assets/asset_list.html:215
#: assets/templates/assets/cluster_list.html:84 #: assets/templates/assets/cluster_list.html:84
#: assets/templates/assets/system_user_list.html:129 #: assets/templates/assets/system_user_list.html:129
#: users/templates/users/user_detail.html:333 #: users/templates/users/user_detail.html:333
#: users/templates/users/user_detail.html:358 #: users/templates/users/user_detail.html:358
#: users/templates/users/user_group_list.html:77 #: users/templates/users/user_group_list.html:77
#: users/templates/users/user_list.html:179 #: users/templates/users/user_list.html:191
msgid "Are you sure?" msgid "Are you sure?"
msgstr "你确认吗?" msgstr "你确认吗?"
#: assets/templates/assets/asset_group_list.html:81 #: assets/templates/assets/asset_group_list.html:77
#: users/templates/users/user_group_list.html:78 #: users/templates/users/user_group_list.html:78
msgid "This will delete the selected groups !!!" msgid "This will delete the selected groups !!!"
msgstr "删除选择组" msgstr "删除选择组"
#: assets/templates/assets/asset_group_list.html:89 #: assets/templates/assets/asset_group_list.html:85
msgid "Group deleted" msgid "Group deleted"
msgstr "组已被删除" msgstr "组已被删除"
#: assets/templates/assets/asset_group_list.html:90 #: assets/templates/assets/asset_group_list.html:86
#: assets/templates/assets/asset_group_list.html:95 #: assets/templates/assets/asset_group_list.html:91
msgid "Group Delete" msgid "Group Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_group_list.html:94 #: assets/templates/assets/asset_group_list.html:90
msgid "Group deleting failed." msgid "Group deleting failed."
msgstr "删除失败" msgstr "删除失败"
#: assets/templates/assets/asset_group_list.html:157 #: assets/templates/assets/asset_group_list.html:153
msgid "The selected asset groups has been updated successfully." msgid "The selected asset groups has been updated successfully."
msgstr "更新成功" msgstr "更新成功"
#: assets/templates/assets/asset_group_list.html:158 #: assets/templates/assets/asset_group_list.html:154
msgid "AssetGroup Updated" msgid "AssetGroup Updated"
msgstr "资产组更新" msgstr "资产组更新"
...@@ -993,52 +1020,47 @@ msgstr "导出" ...@@ -993,52 +1020,47 @@ msgstr "导出"
msgid "Create asset" msgid "Create asset"
msgstr "创建资产" msgstr "创建资产"
#: assets/templates/assets/asset_list.html:35 #: assets/templates/assets/asset_list.html:34
#: assets/templates/assets/user_asset_list.html:23
msgid "Env"
msgstr "环境"
#: assets/templates/assets/asset_list.html:36
#: assets/templates/assets/user_asset_list.html:24 #: assets/templates/assets/user_asset_list.html:24
msgid "Hardware" msgid "Hardware"
msgstr "硬件" msgstr "硬件"
#: assets/templates/assets/asset_list.html:48 #: assets/templates/assets/asset_list.html:46
#: users/templates/users/user_list.html:37 #: users/templates/users/user_list.html:37
msgid "Delete selected" msgid "Delete selected"
msgstr "批量删除" msgstr "批量删除"
#: assets/templates/assets/asset_list.html:49 #: assets/templates/assets/asset_list.html:47
#: users/templates/users/user_list.html:38 #: users/templates/users/user_list.html:38
msgid "Update selected" msgid "Update selected"
msgstr "批量更新" msgstr "批量更新"
#: assets/templates/assets/asset_list.html:50 #: assets/templates/assets/asset_list.html:48
#: users/templates/users/user_list.html:39 #: users/templates/users/user_list.html:39
msgid "Deactive selected" msgid "Deactive selected"
msgstr "禁用所选" msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:51 #: assets/templates/assets/asset_list.html:49
#: users/templates/users/user_list.html:40 #: users/templates/users/user_list.html:40
msgid "Active selected" msgid "Active selected"
msgstr "激活所选" msgstr "激活所选"
#: assets/templates/assets/asset_list.html:198 #: assets/templates/assets/asset_list.html:216
msgid "This will delete the selected assets !!!" msgid "This will delete the selected assets !!!"
msgstr "删除选择资产" msgstr "删除选择资产"
# msgid "Deleted!" # msgid "Deleted!"
# msgstr "删除" # msgstr "删除"
#: assets/templates/assets/asset_list.html:206 #: assets/templates/assets/asset_list.html:224
msgid "Asset Deleted." msgid "Asset Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/asset_list.html:207 #: assets/templates/assets/asset_list.html:225
#: assets/templates/assets/asset_list.html:212 #: assets/templates/assets/asset_list.html:230
msgid "Asset Delete" msgid "Asset Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_list.html:211 #: assets/templates/assets/asset_list.html:229
msgid "Asset Deleting failed." msgid "Asset Deleting failed."
msgstr "删除失败" msgstr "删除失败"
...@@ -1062,6 +1084,7 @@ msgid "Test assets connective" ...@@ -1062,6 +1084,7 @@ msgid "Test assets connective"
msgstr "测试资产可连接性" msgstr "测试资产可连接性"
#: assets/templates/assets/cluster_assets.html:77 #: assets/templates/assets/cluster_assets.html:77
#: ops/templates/ops/task_list.html:70
msgid "Run" msgid "Run"
msgstr "执行" msgstr "执行"
...@@ -1079,13 +1102,14 @@ msgid "Task has been send, seen left assets status" ...@@ -1079,13 +1102,14 @@ msgid "Task has been send, seen left assets status"
msgstr "任务已下发,查看左侧资产状态" msgstr "任务已下发,查看左侧资产状态"
#: assets/templates/assets/cluster_create_update.html:41 #: assets/templates/assets/cluster_create_update.html:41
#: users/templates/users/reset_password.html:57
#: users/templates/users/user_profile.html:20 #: users/templates/users/user_profile.html:20
msgid "Settings" msgid "Setting"
msgstr "设置" msgstr "设置"
#: assets/templates/assets/cluster_list.html:11 assets/views/cluster.py:39 #: assets/templates/assets/cluster_list.html:11 assets/views/cluster.py:43
msgid "Create Cluster" msgid "Create cluster"
msgstr "创建Cluster" msgstr "创建集群"
#: assets/templates/assets/cluster_list.html:21 #: assets/templates/assets/cluster_list.html:21
#: users/templates/users/_select_user_modal.html:17 #: users/templates/users/_select_user_modal.html:17
...@@ -1139,19 +1163,19 @@ msgstr "任务已下发,查看ops任务列表" ...@@ -1139,19 +1163,19 @@ msgstr "任务已下发,查看ops任务列表"
msgid "Attached assets" msgid "Attached assets"
msgstr "关联的资产" msgstr "关联的资产"
#: assets/templates/assets/system_user_detail.html:76 #: assets/templates/assets/system_user_detail.html:81
msgid "Home" msgid "Home"
msgstr "家目录" msgstr "家目录"
#: assets/templates/assets/system_user_detail.html:82 #: assets/templates/assets/system_user_detail.html:87
msgid "Uid" msgid "Uid"
msgstr "Uid" msgstr "Uid"
#: assets/templates/assets/system_user_detail.html:142 #: assets/templates/assets/system_user_detail.html:147
msgid "Clusters" msgid "Clusters"
msgstr "集群" msgstr "集群"
#: assets/templates/assets/system_user_detail.html:150 #: assets/templates/assets/system_user_detail.html:155
msgid "Add to cluster" msgid "Add to cluster"
msgstr "添加到集群" msgstr "添加到集群"
...@@ -1172,93 +1196,177 @@ msgstr "删除系统用户" ...@@ -1172,93 +1196,177 @@ msgstr "删除系统用户"
msgid "System Users Deleting failed." msgid "System Users Deleting failed."
msgstr "系统用户删除失败" msgstr "系统用户删除失败"
#: assets/templates/assets/user_asset_list.html:23
msgid "Env"
msgstr "环境"
#: assets/templates/assets/user_asset_list.html:26 #: assets/templates/assets/user_asset_list.html:26
msgid "Connective" msgid "Connective"
msgstr "连接性" msgstr "连接性"
#: assets/templates/assets/user_asset_list.html:65 #: assets/views/admin_user.py:30
msgid "Connect"
msgstr "连接"
#: assets/views/admin_user.py:28
msgid "Admin user list" msgid "Admin user list"
msgstr "管理用户列表" msgstr "管理用户列表"
#: assets/views/admin_user.py:52 #: assets/views/admin_user.py:64
#, python-brace-format
msgid "Create admin user <a href=\"{url}\">{name}</a> successfully."
msgstr "创建管理用户 <a href=\"{url}\">{name}</a> 成功"
#: assets/views/admin_user.py:68
msgid "Update admin user" msgid "Update admin user"
msgstr "更新管理用户" msgstr "更新管理用户"
#: assets/views/admin_user.py:89 assets/views/admin_user.py:116 #: assets/views/admin_user.py:80 assets/views/admin_user.py:107
msgid "Admin user detail" msgid "Admin user detail"
msgstr "管理用户详情" msgstr "管理用户详情"
#: assets/views/asset.py:48 assets/views/asset.py:62 #: assets/views/asset.py:49 assets/views/asset.py:62
msgid "Asset list" msgid "Asset list"
msgstr "资产列表" msgstr "资产列表"
#: assets/views/asset.py:142 #: assets/views/asset.py:145
msgid "Bulk update asset" msgid "Bulk update asset"
msgstr "批量更新资产" msgstr "批量更新资产"
#: assets/views/asset.py:159 #: assets/views/asset.py:162
msgid "Update asset" msgid "Update asset"
msgstr "编辑资产" msgstr "编辑资产"
#: assets/views/asset.py:292 #: assets/views/asset.py:298
msgid "already exists" msgid "already exists"
msgstr "已经存在" msgstr "已经存在"
#: assets/views/cluster.py:23 #: assets/views/cluster.py:27
msgid "Cluster list" msgid "Cluster list"
msgstr "集群列表" msgstr "集群列表"
#: assets/views/cluster.py:38 assets/views/cluster.py:65 #: assets/views/cluster.py:42 assets/views/cluster.py:70
#: assets/views/system_user.py:112 #: assets/views/system_user.py:96
msgid "assets" msgid "assets"
msgstr "资产管理" msgstr "资产管理"
#: assets/views/cluster.py:66 #: assets/views/cluster.py:71
msgid "Update Cluster" msgid "Update Cluster"
msgstr "更新Cluster" msgstr "更新Cluster"
#: assets/views/cluster.py:81 #: assets/views/cluster.py:86
msgid "Cluster detail" msgid "Cluster detail"
msgstr "集群详情" msgstr "集群详情"
#: assets/views/group.py:54 #: assets/views/group.py:53
msgid "Asset group list" msgid "Asset group list"
msgstr "资产组列表" msgstr "资产组列表"
#: assets/views/group.py:72 #: assets/views/group.py:70
msgid "Asset group detail" msgid "Asset group detail"
msgstr "资产组详情" msgstr "资产组详情"
#: assets/views/system_user.py:30 #: assets/views/system_user.py:29
msgid "System user list" msgid "System user list"
msgstr "系统用户列表" msgstr "系统用户列表"
#: assets/views/system_user.py:57 #: assets/views/system_user.py:61
#, python-brace-format
msgid "Create system user <a href=\"{url}\">{name}</a> successfully."
msgstr "创建系统用户 <a href=\"{url}\">{name}</a> 成功"
#: assets/views/system_user.py:72
msgid "Update system user" msgid "Update system user"
msgstr "更新系统用户" msgstr "更新系统用户"
#: assets/views/system_user.py:92 #: assets/views/system_user.py:76
msgid "System user detail" msgid "System user detail"
msgstr "系统用户详情" msgstr "系统用户详情"
#: assets/views/system_user.py:113 #: assets/views/system_user.py:97
msgid "System user asset" msgid "System user asset"
msgstr "系统用户集群资产" msgstr "系统用户集群资产"
#: common/api.py:19
msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查"
#: common/api.py:53
msgid "Test ldap success"
msgstr "连接LDAP成功"
#: common/const.py:6
#, python-format
msgid "<b>%(name)s</b> was created successfully"
msgstr "<b>%(name)s</b> 创建成功"
#: common/const.py:7
#, python-format
msgid "<b>%(name)s</b> was updated successfully"
msgstr "<b>%(name)s</b> 更新成功"
#: common/forms.py:64
msgid "Current SITE URL"
msgstr "当前站点URL"
#: common/forms.py:68
msgid "User Guide URL"
msgstr "用户向导URL"
#: common/forms.py:69
msgid "User first login update profile done redirect to it"
msgstr "用户第一次登录,修改profile后重定向到地址"
#: common/forms.py:72
msgid "Email Subject Prefix"
msgstr "Email主题前缀"
#: common/forms.py:79
msgid "SMTP host"
msgstr "SMTP主机"
#: common/forms.py:81
msgid "SMTP port"
msgstr "SMTP端口"
#: common/forms.py:83
msgid "SMTP user"
msgstr "SMTP账号"
#: common/forms.py:86
msgid "SMTP password"
msgstr "SMTP密码"
#: common/forms.py:87
msgid "Some provider use token except password"
msgstr "一些邮件提供商需要输入的是Token"
#: common/forms.py:90 common/forms.py:127
msgid "Use SSL"
msgstr "使用SSL"
#: common/forms.py:91
msgid "If SMTP port is 465, may be select"
msgstr "如果SMTP端口是465,通常需要启用SSL"
#: common/forms.py:94
msgid "Use TLS"
msgstr "使用TLS"
#: common/forms.py:95
msgid "If SMTP port is 587, may be select"
msgstr "如果SMTP端口是587,通常需要启用TLS"
#: common/forms.py:101
msgid "LDAP server"
msgstr "LDAP地址"
#: common/forms.py:104
msgid "Bind DN"
msgstr "绑定DN"
#: common/forms.py:111
msgid "User OU"
msgstr "用户OU"
#: common/forms.py:114
msgid "User search filter"
msgstr "用户过滤器"
#: common/forms.py:117
msgid "User attr map"
msgstr "LDAP属性映射"
#: common/forms.py:130
msgid "Enable LDAP Auth"
msgstr "开启LDAP认证"
#: common/mixins.py:29 #: common/mixins.py:29
msgid "is discard" msgid "is discard"
msgstr "" msgstr ""
...@@ -1267,6 +1375,46 @@ msgstr "" ...@@ -1267,6 +1375,46 @@ msgstr ""
msgid "discard time" msgid "discard time"
msgstr "" msgstr ""
#: common/models.py:26
msgid "Value"
msgstr "值"
#: common/models.py:27
msgid "Enabled"
msgstr "启用"
#: common/templates/common/basic_setting.html:15
#: common/templates/common/email_setting.html:15
#: common/templates/common/ldap_setting.html:15 common/views.py:18
msgid "Basic setting"
msgstr "基本设置"
#: common/templates/common/basic_setting.html:18
#: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_setting.html:18 common/views.py:44
msgid "Email setting"
msgstr "邮件设置"
#: common/templates/common/basic_setting.html:21
#: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_setting.html:21 common/views.py:70
msgid "LDAP setting"
msgstr "LDAP设置"
#: common/templates/common/email_setting.html:55
#: common/templates/common/ldap_setting.html:55
msgid "Test connection"
msgstr "测试连接"
#: common/views.py:17 common/views.py:43 common/views.py:69
#: templates/_nav.html:69
msgid "Settings"
msgstr "系统设置"
#: common/views.py:28 common/views.py:54 common/views.py:82
msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序"
#: ops/models.py:32 #: ops/models.py:32
msgid "Interval" msgid "Interval"
msgstr "间隔" msgstr "间隔"
...@@ -1517,6 +1665,10 @@ msgstr "成功" ...@@ -1517,6 +1665,10 @@ msgstr "成功"
msgid "Date" msgid "Date"
msgstr "日期" msgstr "日期"
#: ops/templates/ops/task_list.html:125
msgid "Task start: "
msgstr "任务开始: "
#: ops/views.py:36 ops/views.py:52 ops/views.py:65 ops/views.py:78 #: ops/views.py:36 ops/views.py:52 ops/views.py:65 ops/views.py:78
#: ops/views.py:91 ops/views.py:104 ops/views.py:117 #: ops/views.py:91 ops/views.py:104 ops/views.py:117
msgid "Ops" msgid "Ops"
...@@ -1530,8 +1682,8 @@ msgstr "任务列表" ...@@ -1530,8 +1682,8 @@ msgstr "任务列表"
msgid "Task run history" msgid "Task run history"
msgstr "执行历史" msgstr "执行历史"
#: perms/forms.py:16 users/forms.py:144 users/forms.py:149 users/forms.py:161 #: perms/forms.py:16 users/forms.py:147 users/forms.py:152 users/forms.py:164
#: users/forms.py:191 #: users/forms.py:195
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
...@@ -1542,9 +1694,9 @@ msgstr "选择用户" ...@@ -1542,9 +1694,9 @@ msgstr "选择用户"
#: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:187 #: terminal/templates/terminal/session_list.html:71 users/forms.py:191
#: users/models/user.py:31 users/templates/users/user_group_detail.html:78 #: users/models/user.py:30 users/templates/users/user_group_detail.html:78
#: users/views/user.py:348 #: users/views/user.py:337
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
...@@ -1570,7 +1722,7 @@ msgid "" ...@@ -1570,7 +1722,7 @@ msgid ""
msgstr "资产 {}(组 {}) 所在集群 {} 不包含系统用户 [{}] 请检查\n" msgstr "资产 {}(组 {}) 所在集群 {} 不包含系统用户 [{}] 请检查\n"
#: perms/models.py:16 perms/templates/perms/asset_permission_list.html:27 #: perms/models.py:16 perms/templates/perms/asset_permission_list.html:27
#: templates/_nav.html:13 users/models/user.py:38 #: templates/_nav.html:13 users/models/user.py:37
#: users/templates/users/_select_user_modal.html:16 #: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:178 #: users/templates/users/user_detail.html:178
#: users/templates/users/user_list.html:26 #: users/templates/users/user_list.html:26
...@@ -1578,7 +1730,7 @@ msgid "User group" ...@@ -1578,7 +1730,7 @@ msgid "User group"
msgstr "用户组" msgstr "用户组"
#: perms/models.py:21 perms/templates/perms/asset_permission_detail.html:86 #: perms/models.py:21 perms/templates/perms/asset_permission_detail.html:86
#: users/models/user.py:50 users/templates/users/user_detail.html:94 #: users/models/user.py:49 users/templates/users/user_detail.html:94
#: users/templates/users/user_profile.html:96 #: users/templates/users/user_profile.html:96
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
...@@ -1658,47 +1810,32 @@ msgstr "选择用户" ...@@ -1658,47 +1810,32 @@ msgstr "选择用户"
msgid "Add user group to asset permission" msgid "Add user group to asset permission"
msgstr "添加用户组" msgstr "添加用户组"
#: perms/views.py:27 perms/views.py:77 perms/views.py:103 perms/views.py:128 #: perms/views.py:28 perms/views.py:44 perms/views.py:60 perms/views.py:74
#: perms/views.py:165 perms/views.py:195 templates/_nav.html:30 #: perms/views.py:111 perms/views.py:141 templates/_nav.html:30
msgid "Perms" msgid "Perms"
msgstr "权限管理" msgstr "权限管理"
#: perms/views.py:28 #: perms/views.py:29
msgid "Asset permission list" msgid "Asset permission list"
msgstr "资产授权列表" msgstr "资产授权列表"
#: perms/views.py:63 #: perms/views.py:45
#, python-brace-format
msgid "Create asset permission <a href=\"{url}\"> {name} </a> successfully."
msgstr "创建授权 <a href=\"{url}\"> {name} </a> 成功"
#: perms/views.py:78
msgid "Create asset permission" msgid "Create asset permission"
msgstr "创建权限规则" msgstr "创建权限规则"
#: perms/views.py:89 #: perms/views.py:61
#, python-brace-format
msgid "Create asset permission <a href=\"{url}\"> {name} </a> success."
msgstr "创建授权 <a href=\"{url}\"> {name} </a> 成功"
#: perms/views.py:104
msgid "Update asset permission" msgid "Update asset permission"
msgstr "更新资产授权" msgstr "更新资产授权"
#: perms/views.py:115 #: perms/views.py:75
#, python-brace-format
msgid "Update asset permission <a href=\"{url}\"> {name} </a> success."
msgstr "更新授权 <a href=\"{url}\"> {name} </a> 成功"
#: perms/views.py:129
msgid "Asset permission detail" msgid "Asset permission detail"
msgstr "资产授权详情" msgstr "资产授权详情"
#: perms/views.py:166 #: perms/views.py:112
msgid "Asset permission user list" msgid "Asset permission user list"
msgstr "资产授权包含用户" msgstr "资产授权包含用户"
#: perms/views.py:196 #: perms/views.py:142
msgid "Asset permission asset list" msgid "Asset permission asset list"
msgstr "资产组授权包含资产" msgstr "资产组授权包含资产"
...@@ -1716,32 +1853,28 @@ msgstr "帮助" ...@@ -1716,32 +1853,28 @@ msgstr "帮助"
#: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57 #: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:323 #: users/templates/users/user_pubkey_update.html:37 users/views/user.py:319
msgid "Profile" msgid "Profile"
msgstr "个人信息" msgstr "个人信息"
#: templates/_header_bar.html:34 #: templates/_header_bar.html:37
msgid "Profile settings"
msgstr "个人信息设置"
#: templates/_header_bar.html:38
msgid "Admin page" msgid "Admin page"
msgstr "管理页面" msgstr "管理页面"
#: templates/_header_bar.html:40 #: templates/_header_bar.html:39
msgid "User page" msgid "User page"
msgstr "用户页面" msgstr "用户页面"
#: templates/_header_bar.html:43 #: templates/_header_bar.html:42
msgid "Logout" msgid "Logout"
msgstr "注销登录" msgstr "注销登录"
#: templates/_header_bar.html:47 users/templates/users/login.html:42 #: templates/_header_bar.html:46 users/templates/users/login.html:42
#: users/templates/users/login.html:61 #: users/templates/users/login.html:61
msgid "Login" msgid "Login"
msgstr "登录" msgstr "登录"
#: templates/_header_bar.html:60 templates/_nav.html:4 #: templates/_header_bar.html:59 templates/_nav.html:4
msgid "Dashboard" msgid "Dashboard"
msgstr "仪表盘" msgstr "仪表盘"
...@@ -1775,11 +1908,11 @@ msgstr "" ...@@ -1775,11 +1908,11 @@ msgstr ""
msgid "Close" msgid "Close"
msgstr "关闭" msgstr "关闭"
#: templates/_nav.html:9 users/views/group.py:30 users/views/group.py:48 #: templates/_nav.html:9 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:74 users/views/group.py:91 users/views/login.py:193 #: users/views/group.py:62 users/views/group.py:79 users/views/login.py:197
#: users/views/login.py:242 users/views/user.py:55 users/views/user.py:70 #: users/views/login.py:246 users/views/user.py:57 users/views/user.py:72
#: users/views/user.py:95 users/views/user.py:151 users/views/user.py:308 #: users/views/user.py:91 users/views/user.py:147 users/views/user.py:304
#: users/views/user.py:322 users/views/user.py:366 users/views/user.py:388 #: users/views/user.py:318 users/views/user.py:355 users/views/user.py:377
msgid "Users" msgid "Users"
msgstr "用户管理" msgstr "用户管理"
...@@ -1806,7 +1939,7 @@ msgstr "任务" ...@@ -1806,7 +1939,7 @@ msgstr "任务"
#: terminal/views/terminal.py:31 terminal/views/terminal.py:46 #: terminal/views/terminal.py:31 terminal/views/terminal.py:46
#: terminal/views/terminal.py:58 #: terminal/views/terminal.py:58
msgid "Terminal" msgid "Terminal"
msgstr "终端" msgstr "终端管理"
#: templates/_nav.html:51 #: templates/_nav.html:51
msgid "Session online" msgid "Session online"
...@@ -1828,6 +1961,10 @@ msgstr "命令" ...@@ -1828,6 +1961,10 @@ msgstr "命令"
msgid "My assets" msgid "My assets"
msgstr "我的资产" msgstr "我的资产"
#: templates/_nav_user.html:14
msgid "Web terminal"
msgstr "Web终端"
#: templates/captcha/image.html:3 #: templates/captcha/image.html:3
msgid "Play CAPTCHA as audio file" msgid "Play CAPTCHA as audio file"
msgstr "语言播放验证码" msgstr "语言播放验证码"
...@@ -2084,47 +2221,47 @@ msgstr "" ...@@ -2084,47 +2221,47 @@ msgstr ""
msgid "Invalid token or cache refreshed." msgid "Invalid token or cache refreshed."
msgstr "" msgstr ""
#: users/forms.py:42 users/templates/users/user_detail.html:186 #: users/forms.py:43 users/templates/users/user_detail.html:186
msgid "Join user groups" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
#: users/forms.py:71 #: users/forms.py:74
msgid "Old password" msgid "Old password"
msgstr "原来密码" msgstr "原来密码"
#: users/forms.py:76 #: users/forms.py:79
msgid "New password" msgid "New password"
msgstr "新密码" msgstr "新密码"
#: users/forms.py:81 #: users/forms.py:84
msgid "Confirm password" msgid "Confirm password"
msgstr "确认密码" msgstr "确认密码"
#: users/forms.py:91 #: users/forms.py:94
msgid "Old password error" msgid "Old password error"
msgstr "原来密码错误" msgstr "原来密码错误"
#: users/forms.py:99 #: users/forms.py:102
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms.py:111 #: users/forms.py:114
msgid "ssh public key" msgid "ssh public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:112 #: users/forms.py:115
msgid "ssh-rsa AAAA..." msgid "ssh-rsa AAAA..."
msgstr "" msgstr ""
#: users/forms.py:113 #: users/forms.py:116
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里" msgstr "复制你的公钥到这里"
#: users/forms.py:126 #: users/forms.py:129
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms.py:130 users/serializers.py:42 #: users/forms.py:133 users/serializers.py:42
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
...@@ -2152,46 +2289,46 @@ msgstr "Agent" ...@@ -2152,46 +2289,46 @@ msgstr "Agent"
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
#: users/models/user.py:30 users/models/user.py:255 #: users/models/user.py:29 users/models/user.py:254
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:32 #: users/models/user.py:31
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: users/models/user.py:37 users/templates/users/user_detail.html:70 #: users/models/user.py:36 users/templates/users/user_detail.html:70
#: users/templates/users/user_profile.html:59 #: users/templates/users/user_profile.html:59
msgid "Email" msgid "Email"
msgstr "邮件" msgstr "邮件"
#: users/models/user.py:39 users/templates/users/_select_user_modal.html:15 #: users/models/user.py:38 users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:86 #: users/templates/users/user_detail.html:86
#: users/templates/users/user_list.html:25 #: users/templates/users/user_list.html:25
#: users/templates/users/user_profile.html:55 #: users/templates/users/user_profile.html:55
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
#: users/models/user.py:40 #: users/models/user.py:39
msgid "Avatar" msgid "Avatar"
msgstr "头像" msgstr "头像"
#: users/models/user.py:41 users/templates/users/user_detail.html:81 #: users/models/user.py:40 users/templates/users/user_detail.html:81
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:43 #: users/models/user.py:42
msgid "Enable OTP" msgid "Enable OTP"
msgstr "二次验证" msgstr "二次验证"
#: users/models/user.py:47 users/templates/users/user_password_update.html:43 #: users/models/user.py:46 users/templates/users/user_password_update.html:43
#: users/templates/users/user_profile.html:71 #: users/templates/users/user_profile.html:71
#: users/templates/users/user_profile_update.html:43 #: users/templates/users/user_profile_update.html:43
#: users/templates/users/user_pubkey_update.html:43 #: users/templates/users/user_pubkey_update.html:43
msgid "Public key" msgid "Public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/models/user.py:258 #: users/models/user.py:257
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
...@@ -2288,12 +2425,8 @@ msgstr "重置密码" ...@@ -2288,12 +2425,8 @@ msgstr "重置密码"
msgid "Password again" msgid "Password again"
msgstr "再次输入密码" msgstr "再次输入密码"
#: users/templates/users/reset_password.html:57
msgid "Setting"
msgstr "设置"
#: users/templates/users/user_create.html:4 #: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:70 #: users/templates/users/user_list.html:16 users/views/user.py:72
msgid "Create user" msgid "Create user"
msgstr "创建用户" msgstr "创建用户"
...@@ -2304,7 +2437,7 @@ msgstr "生成重置密码连接,通过邮件发送给用户" ...@@ -2304,7 +2437,7 @@ msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19 #: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 #: users/templates/users/user_granted_asset.html:18
#: users/templates/users/user_group_granted_asset.html:18 #: users/templates/users/user_group_granted_asset.html:18
#: users/views/user.py:152 #: users/views/user.py:148
msgid "User detail" msgid "User detail"
msgstr "用户详情" msgstr "用户详情"
...@@ -2379,11 +2512,11 @@ msgstr "授权资产" ...@@ -2379,11 +2512,11 @@ msgstr "授权资产"
msgid "Asset groups granted of " msgid "Asset groups granted of "
msgstr "授权资产组" msgstr "授权资产组"
#: users/templates/users/user_group_create_update.html:45 #: users/templates/users/user_group_create_update.html:31
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#: users/templates/users/user_group_detail.html:22 users/views/group.py:92 #: users/templates/users/user_group_detail.html:22 users/views/group.py:80
msgid "User group detail" msgid "User group detail"
msgstr "资产组详情" msgstr "资产组详情"
...@@ -2395,7 +2528,7 @@ msgstr "添加用户" ...@@ -2395,7 +2528,7 @@ msgstr "添加用户"
msgid "Valid" msgid "Valid"
msgstr "可用" msgstr "可用"
#: users/templates/users/user_group_list.html:5 users/views/group.py:49 #: users/templates/users/user_group_list.html:5 users/views/group.py:45
msgid "Create user group" msgid "Create user group"
msgstr "创建用户组" msgstr "创建用户组"
...@@ -2412,20 +2545,20 @@ msgstr "用户组删除" ...@@ -2412,20 +2545,20 @@ msgstr "用户组删除"
msgid "UserGroup Deleting failed." msgid "UserGroup Deleting failed."
msgstr "用户组删除失败" msgstr "用户组删除失败"
#: users/templates/users/user_list.html:180 #: users/templates/users/user_list.html:192
msgid "This will delete the selected users !!!" msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!" msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:188 #: users/templates/users/user_list.html:200
msgid "User Deleted." msgid "User Deleted."
msgstr "已被删除" msgstr "已被删除"
#: users/templates/users/user_list.html:189 #: users/templates/users/user_list.html:201
#: users/templates/users/user_list.html:194 #: users/templates/users/user_list.html:206
msgid "User Delete" msgid "User Delete"
msgstr "删除" msgstr "删除"
#: users/templates/users/user_list.html:193 #: users/templates/users/user_list.html:205
msgid "User Deleting failed." msgid "User Deleting failed."
msgstr "用户删除失败" msgstr "用户删除失败"
...@@ -2433,8 +2566,8 @@ msgstr "用户删除失败" ...@@ -2433,8 +2566,8 @@ msgstr "用户删除失败"
msgid "OTP" msgid "OTP"
msgstr "" msgstr ""
#: users/templates/users/user_profile.html:100 users/views/user.py:181 #: users/templates/users/user_profile.html:100 users/views/user.py:177
#: users/views/user.py:233 #: users/views/user.py:229
msgid "User groups" msgid "User groups"
msgstr "用户组" msgstr "用户组"
...@@ -2458,7 +2591,7 @@ msgstr "指纹" ...@@ -2458,7 +2591,7 @@ msgstr "指纹"
msgid "Update public key" msgid "Update public key"
msgstr "更新密钥" msgstr "更新密钥"
#: users/templates/users/user_update.html:4 users/views/user.py:95 #: users/templates/users/user_update.html:4 users/views/user.py:91
msgid "Update user" msgid "Update user"
msgstr "编辑用户" msgstr "编辑用户"
...@@ -2592,17 +2725,11 @@ msgstr "禁用或失效" ...@@ -2592,17 +2725,11 @@ msgstr "禁用或失效"
msgid "Password or SSH public key invalid" msgid "Password or SSH public key invalid"
msgstr "密码或秘钥不合法" msgstr "密码或秘钥不合法"
#: users/views/group.py:31 #: users/views/group.py:29
msgid "User group list" msgid "User group list"
msgstr "用户组列表" msgstr "用户组列表"
#: users/views/group.py:43 #: users/views/group.py:63
#, fuzzy, python-brace-format
#| msgid "Create user <a href=\"{url}\">{name}</a> successfully."
msgid "User group <a href={url}> {name} </a> was created successfully"
msgstr "创建用户 <a href=\"{url}\">{name}</a> 成功"
#: users/views/group.py:75
msgid "Update user group" msgid "Update user group"
msgstr "编辑用户组" msgstr "编辑用户组"
...@@ -2610,82 +2737,78 @@ msgstr "编辑用户组" ...@@ -2610,82 +2737,78 @@ msgstr "编辑用户组"
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:83 #: users/views/login.py:87
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: users/views/login.py:84 #: users/views/login.py:88
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:100 #: users/views/login.py:104
msgid "Email address invalid, please input again" msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入" msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:113 #: users/views/login.py:117
msgid "Send reset password message" msgid "Send reset password message"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/views/login.py:114 #: users/views/login.py:118
msgid "Send reset password mail success, login your mail box and follow it " msgid "Send reset password mail success, login your mail box and follow it "
msgstr "" msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:128 #: users/views/login.py:132
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/views/login.py:129 #: users/views/login.py:133
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:146 users/views/login.py:159 #: users/views/login.py:150 users/views/login.py:163
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/login.py:155 #: users/views/login.py:159
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" msgstr "密码不一致"
#: users/views/login.py:193 #: users/views/login.py:197
msgid "First login" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/login.py:243 #: users/views/login.py:247
msgid "Login log list" msgid "Login log list"
msgstr "登录日志" msgstr "登录日志"
#: users/views/user.py:56 #: users/views/user.py:58
msgid "User list" msgid "User list"
msgstr "用户列表" msgstr "用户列表"
#: users/views/user.py:66 users/views/user.py:335 #: users/views/user.py:101
#, python-brace-format
msgid "Create user <a href=\"{url}\">{name}</a> successfully."
msgstr "创建用户 <a href=\"{url}\">{name}</a> 成功"
#: users/views/user.py:105
msgid "Bulk update user success" msgid "Bulk update user success"
msgstr "批量更新用户成功" msgstr "批量更新用户成功"
#: users/views/user.py:210 #: users/views/user.py:206
msgid "Invalid file." msgid "Invalid file."
msgstr "文件不合法" msgstr "文件不合法"
#: users/views/user.py:309 #: users/views/user.py:305
msgid "User granted assets" msgid "User granted assets"
msgstr "用户授权资产" msgstr "用户授权资产"
#: users/views/user.py:349 #: users/views/user.py:338
msgid "Profile setting" msgid "Profile setting"
msgstr "个人信息设置" msgstr "个人信息设置"
#: users/views/user.py:367 #: users/views/user.py:356
msgid "Password update" msgid "Password update"
msgstr "密码更新" msgstr "密码更新"
#: users/views/user.py:389 #: users/views/user.py:378
msgid "Public key update" msgid "Public key update"
msgstr "秘钥更新" msgstr "秘钥更新"
#~ msgid "Connect"
#~ msgstr "连接"
...@@ -165,7 +165,7 @@ class AdHoc(models.Model): ...@@ -165,7 +165,7 @@ class AdHoc(models.Model):
if item and isinstance(item, list): if item and isinstance(item, list):
self._tasks = json.dumps(item) self._tasks = json.dumps(item)
else: else:
raise SyntaxError('Tasks should be a list') raise SyntaxError('Tasks should be a list: {}'.format(item))
@property @property
def hosts(self): def hosts(self):
...@@ -218,8 +218,8 @@ class AdHoc(models.Model): ...@@ -218,8 +218,8 @@ class AdHoc(models.Model):
history.result = raw history.result = raw
history.summary = summary history.summary = summary
return raw, summary return raw, summary
except: except Exception as e:
return {}, {} return {}, {"dark": {"all": str(e)}, "contacted": []}
finally: finally:
history.date_finished = timezone.now() history.date_finished = timezone.now()
history.timedelta = time.time() - time_start history.timedelta = time.time() - time_start
......
...@@ -43,8 +43,8 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer): ...@@ -43,8 +43,8 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer):
def get_stat(obj): def get_stat(obj):
return { return {
"total": len(obj.adhoc.hosts), "total": len(obj.adhoc.hosts),
"success": len(obj.summary["contacted"]), "success": len(obj.summary.get("contacted", [])),
"failed": len(obj.summary["dark"]), "failed": len(obj.summary.get("dark", [])),
} }
def get_field_names(self, declared_fields, info): def get_field_names(self, declared_fields, info):
......
...@@ -16,6 +16,8 @@ def update_or_create_ansible_task( ...@@ -16,6 +16,8 @@ def update_or_create_ansible_task(
run_as_admin=False, run_as="", become_info=None, run_as_admin=False, run_as="", become_info=None,
created_by=None, created_by=None,
): ):
if not hosts or not tasks or not task_name:
return
defaults = { defaults = {
'name': task_name, 'name': task_name,
......
...@@ -10,7 +10,7 @@ from .hands import AdminUserRequiredMixin ...@@ -10,7 +10,7 @@ from .hands import AdminUserRequiredMixin
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
model = Task model = Task
ordering = ('-date_created',) ordering = ('-date_created',)
context_object_name = 'task_list' context_object_name = 'task_list'
......
...@@ -351,7 +351,7 @@ class UserGroupGrantedAssetGroupsApi(ListAPIView): ...@@ -351,7 +351,7 @@ class UserGroupGrantedAssetGroupsApi(ListAPIView):
class ValidateUserAssetPermissionView(APIView): class ValidateUserAssetPermissionView(APIView):
permission_classes = (IsAppUser,) permission_classes = (IsSuperUserOrAppUser,)
@staticmethod @staticmethod
def get(request): def get(request):
......
...@@ -237,6 +237,16 @@ $(document).ready(function () { ...@@ -237,6 +237,16 @@ $(document).ready(function () {
}).get(); }).get();
updateSystemUser(system_users); updateSystemUser(system_users);
$tr.remove() $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> </script>
{% endblock %} {% endblock %}
...@@ -11,6 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin ...@@ -11,6 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.detail import DetailView, SingleObjectMixin
from django.contrib import messages from django.contrib import messages
from common.const import create_success_msg, update_success_msg
from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \ from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
Asset, AssetGroup Asset, AssetGroup
from .models import AssetPermission from .models import AssetPermission
...@@ -31,46 +32,12 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView): ...@@ -31,46 +32,12 @@ class AssetPermissionListView(AdminUserRequiredMixin, ListView):
return super().get_context_data(**kwargs) 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): class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = AssetPermission model = AssetPermission
form_class = AssetPermissionForm form_class = AssetPermissionForm
template_name = 'perms/asset_permission_create_update.html' template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy('perms:asset-permission-list') success_url = reverse_lazy('perms:asset-permission-list')
warning = None success_message = create_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -80,23 +47,13 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, Cre ...@@ -80,23 +47,13 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, Cre
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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): class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = AssetPermission model = AssetPermission
form_class = AssetPermissionForm form_class = AssetPermissionForm
template_name = 'perms/asset_permission_create_update.html' template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy("perms:asset-permission-list") success_url = reverse_lazy("perms:asset-permission-list")
success_message = update_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -106,17 +63,6 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, Upd ...@@ -106,17 +63,6 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, Upd
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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): class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
template_name = 'perms/asset_permission_detail.html' template_name = 'perms/asset_permission_detail.html'
...@@ -147,7 +93,7 @@ class AssetPermissionUserView(AdminUserRequiredMixin, ...@@ -147,7 +93,7 @@ class AssetPermissionUserView(AdminUserRequiredMixin,
ListView): ListView):
template_name = 'perms/asset_permission_user.html' template_name = 'perms/asset_permission_user.html'
context_object_name = 'asset_permission' context_object_name = 'asset_permission'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
object = None object = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
...@@ -177,7 +123,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin, ...@@ -177,7 +123,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin,
ListView): ListView):
template_name = 'perms/asset_permission_asset.html' template_name = 'perms/asset_permission_asset.html'
context_object_name = 'asset_permission' context_object_name = 'asset_permission'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
object = None object = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
......
/*!
* Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.2.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
.fa {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* makes the font 33% larger relative to the icon container */
.fa-lg {
font-size: 1.33333333em;
line-height: 0.75em;
vertical-align: -15%;
}
.fa-2x {
font-size: 2em;
}
.fa-3x {
font-size: 3em;
}
.fa-4x {
font-size: 4em;
}
.fa-5x {
font-size: 5em;
}
.fa-fw {
width: 1.28571429em;
text-align: center;
}
.fa-ul {
padding-left: 0;
margin-left: 2.14285714em;
list-style-type: none;
}
.fa-ul > li {
position: relative;
}
.fa-li {
position: absolute;
left: -2.14285714em;
width: 2.14285714em;
top: 0.14285714em;
text-align: center;
}
.fa-li.fa-lg {
left: -1.85714286em;
}
.fa-border {
padding: .2em .25em .15em;
border: solid 0.08em #eeeeee;
border-radius: .1em;
}
.pull-right {
float: right;
}
.pull-left {
float: left;
}
.fa.pull-left {
margin-right: .3em;
}
.fa.pull-right {
margin-left: .3em;
}
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
.fa-rotate-90 {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.fa-rotate-180 {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.fa-rotate-270 {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.fa-flip-horizontal {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.fa-flip-vertical {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
}
:root .fa-rotate-90,
:root .fa-rotate-180,
:root .fa-rotate-270,
:root .fa-flip-horizontal,
:root .fa-flip-vertical {
filter: none;
}
.fa-stack {
position: relative;
display: inline-block;
width: 2em;
height: 2em;
line-height: 2em;
vertical-align: middle;
}
.fa-stack-1x,
.fa-stack-2x {
position: absolute;
left: 0;
width: 100%;
text-align: center;
}
.fa-stack-1x {
line-height: inherit;
}
.fa-stack-2x {
font-size: 2em;
}
.fa-inverse {
color: #ffffff;
}
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.fa-glass:before {
content: "\f000";
}
.fa-music:before {
content: "\f001";
}
.fa-search:before {
content: "\f002";
}
.fa-envelope-o:before {
content: "\f003";
}
.fa-heart:before {
content: "\f004";
}
.fa-star:before {
content: "\f005";
}
.fa-star-o:before {
content: "\f006";
}
.fa-user:before {
content: "\f007";
}
.fa-film:before {
content: "\f008";
}
.fa-th-large:before {
content: "\f009";
}
.fa-th:before {
content: "\f00a";
}
.fa-th-list:before {
content: "\f00b";
}
.fa-check:before {
content: "\f00c";
}
.fa-remove:before,
.fa-close:before,
.fa-times:before {
content: "\f00d";
}
.fa-search-plus:before {
content: "\f00e";
}
.fa-search-minus:before {
content: "\f010";
}
.fa-power-off:before {
content: "\f011";
}
.fa-signal:before {
content: "\f012";
}
.fa-gear:before,
.fa-cog:before {
content: "\f013";
}
.fa-trash-o:before {
content: "\f014";
}
.fa-home:before {
content: "\f015";
}
.fa-file-o:before {
content: "\f016";
}
.fa-clock-o:before {
content: "\f017";
}
.fa-road:before {
content: "\f018";
}
.fa-download:before {
content: "\f019";
}
.fa-arrow-circle-o-down:before {
content: "\f01a";
}
.fa-arrow-circle-o-up:before {
content: "\f01b";
}
.fa-inbox:before {
content: "\f01c";
}
.fa-play-circle-o:before {
content: "\f01d";
}
.fa-rotate-right:before,
.fa-repeat:before {
content: "\f01e";
}
.fa-refresh:before {
content: "\f021";
}
.fa-list-alt:before {
content: "\f022";
}
.fa-lock:before {
content: "\f023";
}
.fa-flag:before {
content: "\f024";
}
.fa-headphones:before {
content: "\f025";
}
.fa-volume-off:before {
content: "\f026";
}
.fa-volume-down:before {
content: "\f027";
}
.fa-volume-up:before {
content: "\f028";
}
.fa-qrcode:before {
content: "\f029";
}
.fa-barcode:before {
content: "\f02a";
}
.fa-tag:before {
content: "\f02b";
}
.fa-tags:before {
content: "\f02c";
}
.fa-book:before {
content: "\f02d";
}
.fa-bookmark:before {
content: "\f02e";
}
.fa-print:before {
content: "\f02f";
}
.fa-camera:before {
content: "\f030";
}
.fa-font:before {
content: "\f031";
}
.fa-bold:before {
content: "\f032";
}
.fa-italic:before {
content: "\f033";
}
.fa-text-height:before {
content: "\f034";
}
.fa-text-width:before {
content: "\f035";
}
.fa-align-left:before {
content: "\f036";
}
.fa-align-center:before {
content: "\f037";
}
.fa-align-right:before {
content: "\f038";
}
.fa-align-justify:before {
content: "\f039";
}
.fa-list:before {
content: "\f03a";
}
.fa-dedent:before,
.fa-outdent:before {
content: "\f03b";
}
.fa-indent:before {
content: "\f03c";
}
.fa-video-camera:before {
content: "\f03d";
}
.fa-photo:before,
.fa-image:before,
.fa-picture-o:before {
content: "\f03e";
}
.fa-pencil:before {
content: "\f040";
}
.fa-map-marker:before {
content: "\f041";
}
.fa-adjust:before {
content: "\f042";
}
.fa-tint:before {
content: "\f043";
}
.fa-edit:before,
.fa-pencil-square-o:before {
content: "\f044";
}
.fa-share-square-o:before {
content: "\f045";
}
.fa-check-square-o:before {
content: "\f046";
}
.fa-arrows:before {
content: "\f047";
}
.fa-step-backward:before {
content: "\f048";
}
.fa-fast-backward:before {
content: "\f049";
}
.fa-backward:before {
content: "\f04a";
}
.fa-play:before {
content: "\f04b";
}
.fa-pause:before {
content: "\f04c";
}
.fa-stop:before {
content: "\f04d";
}
.fa-forward:before {
content: "\f04e";
}
.fa-fast-forward:before {
content: "\f050";
}
.fa-step-forward:before {
content: "\f051";
}
.fa-eject:before {
content: "\f052";
}
.fa-chevron-left:before {
content: "\f053";
}
.fa-chevron-right:before {
content: "\f054";
}
.fa-plus-circle:before {
content: "\f055";
}
.fa-minus-circle:before {
content: "\f056";
}
.fa-times-circle:before {
content: "\f057";
}
.fa-check-circle:before {
content: "\f058";
}
.fa-question-circle:before {
content: "\f059";
}
.fa-info-circle:before {
content: "\f05a";
}
.fa-crosshairs:before {
content: "\f05b";
}
.fa-times-circle-o:before {
content: "\f05c";
}
.fa-check-circle-o:before {
content: "\f05d";
}
.fa-ban:before {
content: "\f05e";
}
.fa-arrow-left:before {
content: "\f060";
}
.fa-arrow-right:before {
content: "\f061";
}
.fa-arrow-up:before {
content: "\f062";
}
.fa-arrow-down:before {
content: "\f063";
}
.fa-mail-forward:before,
.fa-share:before {
content: "\f064";
}
.fa-expand:before {
content: "\f065";
}
.fa-compress:before {
content: "\f066";
}
.fa-plus:before {
content: "\f067";
}
.fa-minus:before {
content: "\f068";
}
.fa-asterisk:before {
content: "\f069";
}
.fa-exclamation-circle:before {
content: "\f06a";
}
.fa-gift:before {
content: "\f06b";
}
.fa-leaf:before {
content: "\f06c";
}
.fa-fire:before {
content: "\f06d";
}
.fa-eye:before {
content: "\f06e";
}
.fa-eye-slash:before {
content: "\f070";
}
.fa-warning:before,
.fa-exclamation-triangle:before {
content: "\f071";
}
.fa-plane:before {
content: "\f072";
}
.fa-calendar:before {
content: "\f073";
}
.fa-random:before {
content: "\f074";
}
.fa-comment:before {
content: "\f075";
}
.fa-magnet:before {
content: "\f076";
}
.fa-chevron-up:before {
content: "\f077";
}
.fa-chevron-down:before {
content: "\f078";
}
.fa-retweet:before {
content: "\f079";
}
.fa-shopping-cart:before {
content: "\f07a";
}
.fa-folder:before {
content: "\f07b";
}
.fa-folder-open:before {
content: "\f07c";
}
.fa-arrows-v:before {
content: "\f07d";
}
.fa-arrows-h:before {
content: "\f07e";
}
.fa-bar-chart-o:before,
.fa-bar-chart:before {
content: "\f080";
}
.fa-twitter-square:before {
content: "\f081";
}
.fa-facebook-square:before {
content: "\f082";
}
.fa-camera-retro:before {
content: "\f083";
}
.fa-key:before {
content: "\f084";
}
.fa-gears:before,
.fa-cogs:before {
content: "\f085";
}
.fa-comments:before {
content: "\f086";
}
.fa-thumbs-o-up:before {
content: "\f087";
}
.fa-thumbs-o-down:before {
content: "\f088";
}
.fa-star-half:before {
content: "\f089";
}
.fa-heart-o:before {
content: "\f08a";
}
.fa-sign-out:before {
content: "\f08b";
}
.fa-linkedin-square:before {
content: "\f08c";
}
.fa-thumb-tack:before {
content: "\f08d";
}
.fa-external-link:before {
content: "\f08e";
}
.fa-sign-in:before {
content: "\f090";
}
.fa-trophy:before {
content: "\f091";
}
.fa-github-square:before {
content: "\f092";
}
.fa-upload:before {
content: "\f093";
}
.fa-lemon-o:before {
content: "\f094";
}
.fa-phone:before {
content: "\f095";
}
.fa-square-o:before {
content: "\f096";
}
.fa-bookmark-o:before {
content: "\f097";
}
.fa-phone-square:before {
content: "\f098";
}
.fa-twitter:before {
content: "\f099";
}
.fa-facebook:before {
content: "\f09a";
}
.fa-github:before {
content: "\f09b";
}
.fa-unlock:before {
content: "\f09c";
}
.fa-credit-card:before {
content: "\f09d";
}
.fa-rss:before {
content: "\f09e";
}
.fa-hdd-o:before {
content: "\f0a0";
}
.fa-bullhorn:before {
content: "\f0a1";
}
.fa-bell:before {
content: "\f0f3";
}
.fa-certificate:before {
content: "\f0a3";
}
.fa-hand-o-right:before {
content: "\f0a4";
}
.fa-hand-o-left:before {
content: "\f0a5";
}
.fa-hand-o-up:before {
content: "\f0a6";
}
.fa-hand-o-down:before {
content: "\f0a7";
}
.fa-arrow-circle-left:before {
content: "\f0a8";
}
.fa-arrow-circle-right:before {
content: "\f0a9";
}
.fa-arrow-circle-up:before {
content: "\f0aa";
}
.fa-arrow-circle-down:before {
content: "\f0ab";
}
.fa-globe:before {
content: "\f0ac";
}
.fa-wrench:before {
content: "\f0ad";
}
.fa-tasks:before {
content: "\f0ae";
}
.fa-filter:before {
content: "\f0b0";
}
.fa-briefcase:before {
content: "\f0b1";
}
.fa-arrows-alt:before {
content: "\f0b2";
}
.fa-group:before,
.fa-users:before {
content: "\f0c0";
}
.fa-chain:before,
.fa-link:before {
content: "\f0c1";
}
.fa-cloud:before {
content: "\f0c2";
}
.fa-flask:before {
content: "\f0c3";
}
.fa-cut:before,
.fa-scissors:before {
content: "\f0c4";
}
.fa-copy:before,
.fa-files-o:before {
content: "\f0c5";
}
.fa-paperclip:before {
content: "\f0c6";
}
.fa-save:before,
.fa-floppy-o:before {
content: "\f0c7";
}
.fa-square:before {
content: "\f0c8";
}
.fa-navicon:before,
.fa-reorder:before,
.fa-bars:before {
content: "\f0c9";
}
.fa-list-ul:before {
content: "\f0ca";
}
.fa-list-ol:before {
content: "\f0cb";
}
.fa-strikethrough:before {
content: "\f0cc";
}
.fa-underline:before {
content: "\f0cd";
}
.fa-table:before {
content: "\f0ce";
}
.fa-magic:before {
content: "\f0d0";
}
.fa-truck:before {
content: "\f0d1";
}
.fa-pinterest:before {
content: "\f0d2";
}
.fa-pinterest-square:before {
content: "\f0d3";
}
.fa-google-plus-square:before {
content: "\f0d4";
}
.fa-google-plus:before {
content: "\f0d5";
}
.fa-money:before {
content: "\f0d6";
}
.fa-caret-down:before {
content: "\f0d7";
}
.fa-caret-up:before {
content: "\f0d8";
}
.fa-caret-left:before {
content: "\f0d9";
}
.fa-caret-right:before {
content: "\f0da";
}
.fa-columns:before {
content: "\f0db";
}
.fa-unsorted:before,
.fa-sort:before {
content: "\f0dc";
}
.fa-sort-down:before,
.fa-sort-desc:before {
content: "\f0dd";
}
.fa-sort-up:before,
.fa-sort-asc:before {
content: "\f0de";
}
.fa-envelope:before {
content: "\f0e0";
}
.fa-linkedin:before {
content: "\f0e1";
}
.fa-rotate-left:before,
.fa-undo:before {
content: "\f0e2";
}
.fa-legal:before,
.fa-gavel:before {
content: "\f0e3";
}
.fa-dashboard:before,
.fa-tachometer:before {
content: "\f0e4";
}
.fa-comment-o:before {
content: "\f0e5";
}
.fa-comments-o:before {
content: "\f0e6";
}
.fa-flash:before,
.fa-bolt:before {
content: "\f0e7";
}
.fa-sitemap:before {
content: "\f0e8";
}
.fa-umbrella:before {
content: "\f0e9";
}
.fa-paste:before,
.fa-clipboard:before {
content: "\f0ea";
}
.fa-lightbulb-o:before {
content: "\f0eb";
}
.fa-exchange:before {
content: "\f0ec";
}
.fa-cloud-download:before {
content: "\f0ed";
}
.fa-cloud-upload:before {
content: "\f0ee";
}
.fa-user-md:before {
content: "\f0f0";
}
.fa-stethoscope:before {
content: "\f0f1";
}
.fa-suitcase:before {
content: "\f0f2";
}
.fa-bell-o:before {
content: "\f0a2";
}
.fa-coffee:before {
content: "\f0f4";
}
.fa-cutlery:before {
content: "\f0f5";
}
.fa-file-text-o:before {
content: "\f0f6";
}
.fa-building-o:before {
content: "\f0f7";
}
.fa-hospital-o:before {
content: "\f0f8";
}
.fa-ambulance:before {
content: "\f0f9";
}
.fa-medkit:before {
content: "\f0fa";
}
.fa-fighter-jet:before {
content: "\f0fb";
}
.fa-beer:before {
content: "\f0fc";
}
.fa-h-square:before {
content: "\f0fd";
}
.fa-plus-square:before {
content: "\f0fe";
}
.fa-angle-double-left:before {
content: "\f100";
}
.fa-angle-double-right:before {
content: "\f101";
}
.fa-angle-double-up:before {
content: "\f102";
}
.fa-angle-double-down:before {
content: "\f103";
}
.fa-angle-left:before {
content: "\f104";
}
.fa-angle-right:before {
content: "\f105";
}
.fa-angle-up:before {
content: "\f106";
}
.fa-angle-down:before {
content: "\f107";
}
.fa-desktop:before {
content: "\f108";
}
.fa-laptop:before {
content: "\f109";
}
.fa-tablet:before {
content: "\f10a";
}
.fa-mobile-phone:before,
.fa-mobile:before {
content: "\f10b";
}
.fa-circle-o:before {
content: "\f10c";
}
.fa-quote-left:before {
content: "\f10d";
}
.fa-quote-right:before {
content: "\f10e";
}
.fa-spinner:before {
content: "\f110";
}
.fa-circle:before {
content: "\f111";
}
.fa-mail-reply:before,
.fa-reply:before {
content: "\f112";
}
.fa-github-alt:before {
content: "\f113";
}
.fa-folder-o:before {
content: "\f114";
}
.fa-folder-open-o:before {
content: "\f115";
}
.fa-smile-o:before {
content: "\f118";
}
.fa-frown-o:before {
content: "\f119";
}
.fa-meh-o:before {
content: "\f11a";
}
.fa-gamepad:before {
content: "\f11b";
}
.fa-keyboard-o:before {
content: "\f11c";
}
.fa-flag-o:before {
content: "\f11d";
}
.fa-flag-checkered:before {
content: "\f11e";
}
.fa-terminal:before {
content: "\f120";
}
.fa-code:before {
content: "\f121";
}
.fa-mail-reply-all:before,
.fa-reply-all:before {
content: "\f122";
}
.fa-star-half-empty:before,
.fa-star-half-full:before,
.fa-star-half-o:before {
content: "\f123";
}
.fa-location-arrow:before {
content: "\f124";
}
.fa-crop:before {
content: "\f125";
}
.fa-code-fork:before {
content: "\f126";
}
.fa-unlink:before,
.fa-chain-broken:before {
content: "\f127";
}
.fa-question:before {
content: "\f128";
}
.fa-info:before {
content: "\f129";
}
.fa-exclamation:before {
content: "\f12a";
}
.fa-superscript:before {
content: "\f12b";
}
.fa-subscript:before {
content: "\f12c";
}
.fa-eraser:before {
content: "\f12d";
}
.fa-puzzle-piece:before {
content: "\f12e";
}
.fa-microphone:before {
content: "\f130";
}
.fa-microphone-slash:before {
content: "\f131";
}
.fa-shield:before {
content: "\f132";
}
.fa-calendar-o:before {
content: "\f133";
}
.fa-fire-extinguisher:before {
content: "\f134";
}
.fa-rocket:before {
content: "\f135";
}
.fa-maxcdn:before {
content: "\f136";
}
.fa-chevron-circle-left:before {
content: "\f137";
}
.fa-chevron-circle-right:before {
content: "\f138";
}
.fa-chevron-circle-up:before {
content: "\f139";
}
.fa-chevron-circle-down:before {
content: "\f13a";
}
.fa-html5:before {
content: "\f13b";
}
.fa-css3:before {
content: "\f13c";
}
.fa-anchor:before {
content: "\f13d";
}
.fa-unlock-alt:before {
content: "\f13e";
}
.fa-bullseye:before {
content: "\f140";
}
.fa-ellipsis-h:before {
content: "\f141";
}
.fa-ellipsis-v:before {
content: "\f142";
}
.fa-rss-square:before {
content: "\f143";
}
.fa-play-circle:before {
content: "\f144";
}
.fa-ticket:before {
content: "\f145";
}
.fa-minus-square:before {
content: "\f146";
}
.fa-minus-square-o:before {
content: "\f147";
}
.fa-level-up:before {
content: "\f148";
}
.fa-level-down:before {
content: "\f149";
}
.fa-check-square:before {
content: "\f14a";
}
.fa-pencil-square:before {
content: "\f14b";
}
.fa-external-link-square:before {
content: "\f14c";
}
.fa-share-square:before {
content: "\f14d";
}
.fa-compass:before {
content: "\f14e";
}
.fa-toggle-down:before,
.fa-caret-square-o-down:before {
content: "\f150";
}
.fa-toggle-up:before,
.fa-caret-square-o-up:before {
content: "\f151";
}
.fa-toggle-right:before,
.fa-caret-square-o-right:before {
content: "\f152";
}
.fa-euro:before,
.fa-eur:before {
content: "\f153";
}
.fa-gbp:before {
content: "\f154";
}
.fa-dollar:before,
.fa-usd:before {
content: "\f155";
}
.fa-rupee:before,
.fa-inr:before {
content: "\f156";
}
.fa-cny:before,
.fa-rmb:before,
.fa-yen:before,
.fa-jpy:before {
content: "\f157";
}
.fa-ruble:before,
.fa-rouble:before,
.fa-rub:before {
content: "\f158";
}
.fa-won:before,
.fa-krw:before {
content: "\f159";
}
.fa-bitcoin:before,
.fa-btc:before {
content: "\f15a";
}
.fa-file:before {
content: "\f15b";
}
.fa-file-text:before {
content: "\f15c";
}
.fa-sort-alpha-asc:before {
content: "\f15d";
}
.fa-sort-alpha-desc:before {
content: "\f15e";
}
.fa-sort-amount-asc:before {
content: "\f160";
}
.fa-sort-amount-desc:before {
content: "\f161";
}
.fa-sort-numeric-asc:before {
content: "\f162";
}
.fa-sort-numeric-desc:before {
content: "\f163";
}
.fa-thumbs-up:before {
content: "\f164";
}
.fa-thumbs-down:before {
content: "\f165";
}
.fa-youtube-square:before {
content: "\f166";
}
.fa-youtube:before {
content: "\f167";
}
.fa-xing:before {
content: "\f168";
}
.fa-xing-square:before {
content: "\f169";
}
.fa-youtube-play:before {
content: "\f16a";
}
.fa-dropbox:before {
content: "\f16b";
}
.fa-stack-overflow:before {
content: "\f16c";
}
.fa-instagram:before {
content: "\f16d";
}
.fa-flickr:before {
content: "\f16e";
}
.fa-adn:before {
content: "\f170";
}
.fa-bitbucket:before {
content: "\f171";
}
.fa-bitbucket-square:before {
content: "\f172";
}
.fa-tumblr:before {
content: "\f173";
}
.fa-tumblr-square:before {
content: "\f174";
}
.fa-long-arrow-down:before {
content: "\f175";
}
.fa-long-arrow-up:before {
content: "\f176";
}
.fa-long-arrow-left:before {
content: "\f177";
}
.fa-long-arrow-right:before {
content: "\f178";
}
.fa-apple:before {
content: "\f179";
}
.fa-windows:before {
content: "\f17a";
}
.fa-android:before {
content: "\f17b";
}
.fa-linux:before {
content: "\f17c";
}
.fa-dribbble:before {
content: "\f17d";
}
.fa-skype:before {
content: "\f17e";
}
.fa-foursquare:before {
content: "\f180";
}
.fa-trello:before {
content: "\f181";
}
.fa-female:before {
content: "\f182";
}
.fa-male:before {
content: "\f183";
}
.fa-gittip:before {
content: "\f184";
}
.fa-sun-o:before {
content: "\f185";
}
.fa-moon-o:before {
content: "\f186";
}
.fa-archive:before {
content: "\f187";
}
.fa-bug:before {
content: "\f188";
}
.fa-vk:before {
content: "\f189";
}
.fa-weibo:before {
content: "\f18a";
}
.fa-renren:before {
content: "\f18b";
}
.fa-pagelines:before {
content: "\f18c";
}
.fa-stack-exchange:before {
content: "\f18d";
}
.fa-arrow-circle-o-right:before {
content: "\f18e";
}
.fa-arrow-circle-o-left:before {
content: "\f190";
}
.fa-toggle-left:before,
.fa-caret-square-o-left:before {
content: "\f191";
}
.fa-dot-circle-o:before {
content: "\f192";
}
.fa-wheelchair:before {
content: "\f193";
}
.fa-vimeo-square:before {
content: "\f194";
}
.fa-turkish-lira:before,
.fa-try:before {
content: "\f195";
}
.fa-plus-square-o:before {
content: "\f196";
}
.fa-space-shuttle:before {
content: "\f197";
}
.fa-slack:before {
content: "\f198";
}
.fa-envelope-square:before {
content: "\f199";
}
.fa-wordpress:before {
content: "\f19a";
}
.fa-openid:before {
content: "\f19b";
}
.fa-institution:before,
.fa-bank:before,
.fa-university:before {
content: "\f19c";
}
.fa-mortar-board:before,
.fa-graduation-cap:before {
content: "\f19d";
}
.fa-yahoo:before {
content: "\f19e";
}
.fa-google:before {
content: "\f1a0";
}
.fa-reddit:before {
content: "\f1a1";
}
.fa-reddit-square:before {
content: "\f1a2";
}
.fa-stumbleupon-circle:before {
content: "\f1a3";
}
.fa-stumbleupon:before {
content: "\f1a4";
}
.fa-delicious:before {
content: "\f1a5";
}
.fa-digg:before {
content: "\f1a6";
}
.fa-pied-piper:before {
content: "\f1a7";
}
.fa-pied-piper-alt:before {
content: "\f1a8";
}
.fa-drupal:before {
content: "\f1a9";
}
.fa-joomla:before {
content: "\f1aa";
}
.fa-language:before {
content: "\f1ab";
}
.fa-fax:before {
content: "\f1ac";
}
.fa-building:before {
content: "\f1ad";
}
.fa-child:before {
content: "\f1ae";
}
.fa-paw:before {
content: "\f1b0";
}
.fa-spoon:before {
content: "\f1b1";
}
.fa-cube:before {
content: "\f1b2";
}
.fa-cubes:before {
content: "\f1b3";
}
.fa-behance:before {
content: "\f1b4";
}
.fa-behance-square:before {
content: "\f1b5";
}
.fa-steam:before {
content: "\f1b6";
}
.fa-steam-square:before {
content: "\f1b7";
}
.fa-recycle:before {
content: "\f1b8";
}
.fa-automobile:before,
.fa-car:before {
content: "\f1b9";
}
.fa-cab:before,
.fa-taxi:before {
content: "\f1ba";
}
.fa-tree:before {
content: "\f1bb";
}
.fa-spotify:before {
content: "\f1bc";
}
.fa-deviantart:before {
content: "\f1bd";
}
.fa-soundcloud:before {
content: "\f1be";
}
.fa-database:before {
content: "\f1c0";
}
.fa-file-pdf-o:before {
content: "\f1c1";
}
.fa-file-word-o:before {
content: "\f1c2";
}
.fa-file-excel-o:before {
content: "\f1c3";
}
.fa-file-powerpoint-o:before {
content: "\f1c4";
}
.fa-file-photo-o:before,
.fa-file-picture-o:before,
.fa-file-image-o:before {
content: "\f1c5";
}
.fa-file-zip-o:before,
.fa-file-archive-o:before {
content: "\f1c6";
}
.fa-file-sound-o:before,
.fa-file-audio-o:before {
content: "\f1c7";
}
.fa-file-movie-o:before,
.fa-file-video-o:before {
content: "\f1c8";
}
.fa-file-code-o:before {
content: "\f1c9";
}
.fa-vine:before {
content: "\f1ca";
}
.fa-codepen:before {
content: "\f1cb";
}
.fa-jsfiddle:before {
content: "\f1cc";
}
.fa-life-bouy:before,
.fa-life-buoy:before,
.fa-life-saver:before,
.fa-support:before,
.fa-life-ring:before {
content: "\f1cd";
}
.fa-circle-o-notch:before {
content: "\f1ce";
}
.fa-ra:before,
.fa-rebel:before {
content: "\f1d0";
}
.fa-ge:before,
.fa-empire:before {
content: "\f1d1";
}
.fa-git-square:before {
content: "\f1d2";
}
.fa-git:before {
content: "\f1d3";
}
.fa-hacker-news:before {
content: "\f1d4";
}
.fa-tencent-weibo:before {
content: "\f1d5";
}
.fa-qq:before {
content: "\f1d6";
}
.fa-wechat:before,
.fa-weixin:before {
content: "\f1d7";
}
.fa-send:before,
.fa-paper-plane:before {
content: "\f1d8";
}
.fa-send-o:before,
.fa-paper-plane-o:before {
content: "\f1d9";
}
.fa-history:before {
content: "\f1da";
}
.fa-circle-thin:before {
content: "\f1db";
}
.fa-header:before {
content: "\f1dc";
}
.fa-paragraph:before {
content: "\f1dd";
}
.fa-sliders:before {
content: "\f1de";
}
.fa-share-alt:before {
content: "\f1e0";
}
.fa-share-alt-square:before {
content: "\f1e1";
}
.fa-bomb:before {
content: "\f1e2";
}
.fa-soccer-ball-o:before,
.fa-futbol-o:before {
content: "\f1e3";
}
.fa-tty:before {
content: "\f1e4";
}
.fa-binoculars:before {
content: "\f1e5";
}
.fa-plug:before {
content: "\f1e6";
}
.fa-slideshare:before {
content: "\f1e7";
}
.fa-twitch:before {
content: "\f1e8";
}
.fa-yelp:before {
content: "\f1e9";
}
.fa-newspaper-o:before {
content: "\f1ea";
}
.fa-wifi:before {
content: "\f1eb";
}
.fa-calculator:before {
content: "\f1ec";
}
.fa-paypal:before {
content: "\f1ed";
}
.fa-google-wallet:before {
content: "\f1ee";
}
.fa-cc-visa:before {
content: "\f1f0";
}
.fa-cc-mastercard:before {
content: "\f1f1";
}
.fa-cc-discover:before {
content: "\f1f2";
}
.fa-cc-amex:before {
content: "\f1f3";
}
.fa-cc-paypal:before {
content: "\f1f4";
}
.fa-cc-stripe:before {
content: "\f1f5";
}
.fa-bell-slash:before {
content: "\f1f6";
}
.fa-bell-slash-o:before {
content: "\f1f7";
}
.fa-trash:before {
content: "\f1f8";
}
.fa-copyright:before {
content: "\f1f9";
}
.fa-at:before {
content: "\f1fa";
}
.fa-eyedropper:before {
content: "\f1fb";
}
.fa-paint-brush:before {
content: "\f1fc";
}
.fa-birthday-cake:before {
content: "\f1fd";
}
.fa-area-chart:before {
content: "\f1fe";
}
.fa-pie-chart:before {
content: "\f200";
}
.fa-line-chart:before {
content: "\f201";
}
.fa-lastfm:before {
content: "\f202";
}
.fa-lastfm-square:before {
content: "\f203";
}
.fa-toggle-off:before {
content: "\f204";
}
.fa-toggle-on:before {
content: "\f205";
}
.fa-bicycle:before {
content: "\f206";
}
.fa-bus:before {
content: "\f207";
}
.fa-ioxhost:before {
content: "\f208";
}
.fa-angellist:before {
content: "\f209";
}
.fa-cc:before {
content: "\f20a";
}
.fa-shekel:before,
.fa-sheqel:before,
.fa-ils:before {
content: "\f20b";
}
.fa-meanpath:before {
content: "\f20c";
}
/*! /*!
* Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.2.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"} */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
\ No newline at end of file
...@@ -322,4 +322,20 @@ div.dataTables_wrapper div.dataTables_filter { ...@@ -322,4 +322,20 @@ div.dataTables_wrapper div.dataTables_filter {
.welcome-message img { .welcome-message img {
margin: -11px 0; 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;
} }
\ No newline at end of file
无法预览此类型文件
无法预览此类型文件
因为 它太大了无法显示 source diff 。你可以改为 查看blob
无法预览此类型文件
无法预览此类型文件
无法预览此类型文件
...@@ -157,6 +157,11 @@ function APIUpdateAttr(props) { ...@@ -157,6 +157,11 @@ function APIUpdateAttr(props) {
props = props || {}; props = props || {};
var success_message = props.success_message || '更新成功!'; var success_message = props.success_message || '更新成功!';
var fail_message = props.fail_message || '更新时发生未知错误.'; var fail_message = props.fail_message || '更新时发生未知错误.';
var flash_message = true;
if (props.flash_message === false){
flash_message = false;
}
$.ajax({ $.ajax({
url: props.url, url: props.url,
type: props.method || "PATCH", type: props.method || "PATCH",
...@@ -164,12 +169,16 @@ function APIUpdateAttr(props) { ...@@ -164,12 +169,16 @@ function APIUpdateAttr(props) {
contentType: props.content_type || "application/json; charset=utf-8", contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json" dataType: props.data_type || "json"
}).done(function(data, textStatue, jqXHR) { }).done(function(data, textStatue, jqXHR) {
toastr.success(success_message); if (flash_message) {
toastr.success(success_message);
}
if (typeof props.success === 'function') { if (typeof props.success === 'function') {
return props.success(data); return props.success(data);
} }
}).fail(function(jqXHR, textStatus, errorThrown) { }).fail(function(jqXHR, textStatus, errorThrown) {
toastr.error(fail_message); if (flash_message) {
toastr.error(fail_message);
}
if (typeof props.error === 'function') { if (typeof props.error === 'function') {
return props.error(jqXHR.responseText); return props.error(jqXHR.responseText);
} }
...@@ -310,11 +319,130 @@ jumpserver.initDataTable = function (options) { ...@@ -310,11 +319,130 @@ jumpserver.initDataTable = function (options) {
if (!jumpserver.checked) { if (!jumpserver.checked) {
$(this).closest('table').find('.ipt_check').prop('checked', true); $(this).closest('table').find('.ipt_check').prop('checked', true);
jumpserver.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({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 { } else {
$(this).closest('table').find('.ipt_check').prop('checked', false); $(this).closest('table').find('.ipt_check').prop('checked', false);
jumpserver.checked = false; jumpserver.checked = false;
table.rows().deselect(); table.rows({search:'applied', page:'current'}).deselect();
} }
}); });
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<!-- css file --> <!-- css file -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <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/toastr/toastr.min.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/sweetalert/sweetalert.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/sweetalert/sweetalert.css' %}" rel="stylesheet">
<link href="{% static 'css/style.css' %}" rel="stylesheet"> <link href="{% static 'css/style.css' %}" rel="stylesheet">
......
{% load i18n %} {% load i18n %}
{% block first_login_message %} {% 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"> <div class="alert alert-danger help-message">
{% url 'users:user-first-login' as first_login_url %} {% url 'users:user-first-login' as first_login_url %}
{% blocktrans %} {% blocktrans %}
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block update_public_key_message %} {% 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"> <div class="alert alert-danger help-message">
{% url 'users:user-pubkey-update' as user_pubkey_update %} {% url 'users:user-pubkey-update' as user_pubkey_update %}
{% blocktrans %} {% blocktrans %}
......
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
</ul> </ul>
</li> </li>
{#<li id="">#} {#<li id="">#}
{# <a href="#">#} {# <a href="#">#}
{# <i class="fa fa-download"></i> <span class="nav-label">{% trans 'File' %}</span><span class="fa arrow"></span>#} {# <i class="fa fa-download"></i> <span class="nav-label">{% trans 'File' %}</span><span class="fa arrow"></span>#}
...@@ -63,8 +64,8 @@ ...@@ -63,8 +64,8 @@
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#} {# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
{# </ul>#} {# </ul>#}
{#</li>#} {#</li>#}
{#<li id="">#} <li id="settings">
{# <a href="">#} <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>#} <i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
{# </a>#} </a>
{#</li>#} </li>
\ No newline at end of file \ No newline at end of file
...@@ -8,4 +8,9 @@ ...@@ -8,4 +8,9 @@
<a href="{% url 'users:user-profile' %}"> <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> <i class="fa fa-user" ></i> <span class="nav-label">{% trans 'Profile' %}</span><span class="label label-info pull-right"></span>
</a> </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> </li>
\ No newline at end of file
...@@ -141,7 +141,9 @@ class StatusViewSet(viewsets.ModelViewSet): ...@@ -141,7 +141,9 @@ class StatusViewSet(viewsets.ModelViewSet):
session = serializer.save() session = serializer.save()
return session return session
else: 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) logger.error(msg)
return None return None
......
...@@ -13,10 +13,6 @@ RUNNING = False ...@@ -13,10 +13,6 @@ RUNNING = False
logger = get_logger(__file__) 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(): def set_session_info_cache():
logger.debug("") logger.debug("")
from .utils import get_session_asset_list, get_session_user_list, \ from .utils import get_session_asset_list, get_session_user_list, \
......
...@@ -19,7 +19,7 @@ class CommandListView(DatetimeSearchMixin, ListView): ...@@ -19,7 +19,7 @@ class CommandListView(DatetimeSearchMixin, ListView):
model = Command model = Command
template_name = "terminal/command_list.html" template_name = "terminal/command_list.html"
context_object_name = 'command_list' context_object_name = 'command_list'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
command = user = asset = system_user = "" command = user = asset = system_user = ""
date_from = date_to = None date_from = date_to = None
......
...@@ -26,7 +26,7 @@ class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -26,7 +26,7 @@ class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
model = Session model = Session
template_name = 'terminal/session_list.html' template_name = 'terminal/session_list.html'
context_object_name = 'session_list' context_object_name = 'session_list'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
user = asset = system_user = '' user = asset = system_user = ''
date_from = date_to = None date_from = date_to = None
......
...@@ -13,14 +13,14 @@ from .tasks import write_login_log_async ...@@ -13,14 +13,14 @@ from .tasks import write_login_log_async
from .models import User, UserGroup from .models import User, UserGroup
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly
from .utils import check_user_valid, generate_token from .utils import check_user_valid, generate_token
from common.mixins import IDInFilterMixin from common.mixins import CustomFilterMixin
from common.utils import get_logger from common.utils import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class UserViewSet(IDInFilterMixin, BulkModelViewSet): class UserViewSet(CustomFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App") queryset = User.objects.exclude(role="App")
# queryset = User.objects.all().exclude(role="App").order_by("date_joined") # queryset = User.objects.all().exclude(role="App").order_by("date_joined")
serializer_class = UserSerializer serializer_class = UserSerializer
...@@ -72,7 +72,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): ...@@ -72,7 +72,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user.save() user.save()
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): class UserGroupViewSet(CustomFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all() queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer serializer_class = UserGroupSerializer
...@@ -128,7 +128,11 @@ class UserAuthApi(APIView): ...@@ -128,7 +128,11 @@ class UserAuthApi(APIView):
user_agent = request.data.get('HTTP_USER_AGENT', '') user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip: 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( user, msg = check_user_valid(
username=username, password=password, username=username, password=password,
......
...@@ -113,7 +113,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): ...@@ -113,7 +113,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
class AccessTokenAuthentication(authentication.BaseAuthentication): class AccessTokenAuthentication(authentication.BaseAuthentication):
keyword = 'Bearer' keyword = 'Bearer'
model = User model = User
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 expiration = settings.TOKEN_EXPIRATION or 3600
def authenticate(self, request): def authenticate(self, request):
auth = authentication.get_authorization_header(request).split() auth = authentication.get_authorization_header(request).split()
......
...@@ -29,7 +29,7 @@ class UserCreateUpdateForm(forms.ModelForm): ...@@ -29,7 +29,7 @@ class UserCreateUpdateForm(forms.ModelForm):
model = User model = User
fields = [ fields = [
'username', 'name', 'email', 'groups', 'wechat', 'username', 'name', 'email', 'groups', 'wechat',
'phone', 'role', 'date_expired', 'comment', 'password' 'phone', 'role', 'date_expired', 'comment',
] ]
help_texts = { help_texts = {
'username': '* required', 'username': '* required',
...@@ -38,13 +38,16 @@ class UserCreateUpdateForm(forms.ModelForm): ...@@ -38,13 +38,16 @@ class UserCreateUpdateForm(forms.ModelForm):
} }
widgets = { widgets = {
'groups': forms.SelectMultiple( 'groups': forms.SelectMultiple(
attrs={'class': 'select2', attrs={
'data-placeholder': _('Join user groups')}), 'class': 'select2',
'data-placeholder': _('Join user groups')
}
),
} }
def save(self, commit=True): def save(self, commit=True):
user = super().save(commit=commit)
password = self.cleaned_data.get('password') password = self.cleaned_data.get('password')
user = super().save(commit=commit)
if password: if password:
user.set_password(password) user.set_password(password)
user.save() user.save()
...@@ -153,7 +156,7 @@ class UserBulkUpdateForm(forms.ModelForm): ...@@ -153,7 +156,7 @@ class UserBulkUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['users', 'role', 'groups', 'date_expired', 'is_active'] fields = ['users', 'role', 'groups', 'date_expired']
widgets = { widgets = {
"groups": forms.SelectMultiple( "groups": forms.SelectMultiple(
attrs={ attrs={
...@@ -169,6 +172,7 @@ class UserBulkUpdateForm(forms.ModelForm): ...@@ -169,6 +172,7 @@ class UserBulkUpdateForm(forms.ModelForm):
if self.data.get(field) is not None: if self.data.get(field) is not None:
changed_fields.append(field) changed_fields.append(field)
print(changed_fields)
cleaned_data = {k: v for k, v in self.cleaned_data.items() cleaned_data = {k: v for k, v in self.cleaned_data.items()
if k in changed_fields} if k in changed_fields}
users = cleaned_data.pop('users', '') users = cleaned_data.pop('users', '')
...@@ -183,7 +187,7 @@ class UserBulkUpdateForm(forms.ModelForm): ...@@ -183,7 +187,7 @@ class UserBulkUpdateForm(forms.ModelForm):
class UserGroupForm(forms.ModelForm): class UserGroupForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(), queryset=User.objects.exclude(role=User.ROLE_APP),
label=_("User"), label=_("User"),
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={ attrs={
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .group import *
from .user import * from .user import *
from .group import *
from .authentication import * from .authentication import *
from .utils import * from .utils import *
...@@ -20,12 +20,6 @@ class UserGroup(NoDeleteModelMixin): ...@@ -20,12 +20,6 @@ class UserGroup(NoDeleteModelMixin):
def __str__(self): def __str__(self):
return self.name 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: class Meta:
ordering = ['name'] ordering = ['name']
......
...@@ -13,7 +13,6 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -13,7 +13,6 @@ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.shortcuts import reverse from django.shortcuts import reverse
from .group import UserGroup
from common.utils import get_signer, date_expired_default from common.utils import get_signer, date_expired_default
...@@ -35,7 +34,7 @@ class User(AbstractUser): ...@@ -35,7 +34,7 @@ class User(AbstractUser):
username = models.CharField(max_length=128, unique=True, verbose_name=_('Username')) username = models.CharField(max_length=128, unique=True, verbose_name=_('Username'))
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
email = models.EmailField(max_length=128, unique=True, verbose_name=_('Email')) 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')) 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')) avatar = models.ImageField(upload_to="avatar", null=True, verbose_name=_('Avatar'))
wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat')) wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat'))
...@@ -149,12 +148,7 @@ class User(AbstractUser): ...@@ -149,12 +148,7 @@ class User(AbstractUser):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.name: if not self.name:
self.name = self.username self.name = self.username
super().save(*args, **kwargs) 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 @property
def private_token(self): def private_token(self):
...@@ -254,6 +248,7 @@ class User(AbstractUser): ...@@ -254,6 +248,7 @@ class User(AbstractUser):
#: Use this method initial user #: Use this method initial user
@classmethod @classmethod
def initial(cls): def initial(cls):
from .group import UserGroup
user = cls(username='admin', user = cls(username='admin',
email='admin@jumpserver.org', email='admin@jumpserver.org',
name=_('Administrator'), name=_('Administrator'),
...@@ -269,6 +264,7 @@ class User(AbstractUser): ...@@ -269,6 +264,7 @@ class User(AbstractUser):
from random import seed, choice from random import seed, choice
import forgery_py import forgery_py
from django.db import IntegrityError from django.db import IntegrityError
from .group import UserGroup
seed() seed()
for i in range(count): for i in range(count):
......
...@@ -2,16 +2,19 @@ ...@@ -2,16 +2,19 @@
# #
from django.dispatch import Signal, receiver from django.dispatch import Signal, receiver
from django.db.models.signals import post_save
from common.utils import get_logger from common.utils import get_logger
from .models import User
logger = get_logger(__file__) logger = get_logger(__file__)
on_user_created = Signal(providing_args=['user', 'request'])
@receiver(on_user_created) @receiver(post_save, sender=User)
def send_user_add_mail_to_user(sender, user=None, **kwargs): def on_user_created(sender, instance=None, created=False, **kwargs):
from .utils import send_user_created_mail if created:
logger.debug("Receive asset create signal, update asset hardware info") logger.debug("Receive user `{}` create signal".format(instance.name))
send_user_created_mail(user) from .utils import send_user_created_mail
logger.info(" - Sending welcome mail ...".format(instance.name))
send_user_created_mail(instance)
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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"> <link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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"> <link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
......
...@@ -25,20 +25,6 @@ ...@@ -25,20 +25,6 @@
{% csrf_token %} {% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %} {% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.users 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" %} {% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group"> <div class="form-group">
<div class="col-sm-4 col-sm-offset-2"> <div class="col-sm-4 col-sm-offset-2">
...@@ -57,7 +43,9 @@ ...@@ -57,7 +43,9 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2({
closeOnSelect: false
});
}) })
</script> </script>
{% endblock %} {% endblock %}
...@@ -161,18 +161,30 @@ $(document).ready(function(){ ...@@ -161,18 +161,30 @@ $(document).ready(function(){
var body = $.each(id_list, function(index, user_object) { var body = $.each(id_list, function(index, user_object) {
user_object['is_active'] = false; user_object['is_active'] = false;
}); });
console.log(body); function success() {
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)}); location.reload();
$data_table.ajax.reload(); }
jumpserver.checked = false; APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(body),
success: success
});
location.reload();
} }
function doActive() { function doActive() {
var body = $.each(id_list, function(index, user_object) { var body = $.each(id_list, function(index, user_object) {
user_object['is_active'] = true; user_object['is_active'] = true;
}); });
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)}); function success() {
$data_table.ajax.reload(); location.reload();
jumpserver.checked = false; }
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(body),
success: success
});
} }
function doDelete() { function doDelete() {
swal({ swal({
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Profile' %} </a> <a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Profile' %} </a>
</li> </li>
<li class="pull-right"> <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> </li>
</ul> </ul>
</div> </div>
......
...@@ -151,12 +151,12 @@ def check_user_valid(**kwargs): ...@@ -151,12 +151,12 @@ def check_user_valid(**kwargs):
return None, _('Password or SSH public key invalid') 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) cache.set(token, user.id, expiration)
def generate_token(request, user): 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', '') remote_addr = request.META.get('REMOTE_ADDR', '')
if not isinstance(remote_addr, bytes): if not isinstance(remote_addr, bytes):
remote_addr = remote_addr.encode("utf-8") remote_addr = remote_addr.encode("utf-8")
...@@ -180,8 +180,10 @@ def validate_ip(ip): ...@@ -180,8 +180,10 @@ def validate_ip(ip):
def write_login_log(username, type='', ip='', user_agent=''): def write_login_log(username, type='', ip='', user_agent=''):
if not (ip and validate_ip(ip)): if not (ip and validate_ip(ip)):
ip = '0.0.0.0' ip = ip[:15]
city = get_ip_city(ip) city = "Unknown"
else:
city = get_ip_city(ip)
LoginLog.objects.create( LoginLog.objects.create(
username=username, type=type, username=username, type=type,
ip=ip, city=city, user_agent=user_agent ip=ip, city=city, user_agent=user_agent
......
...@@ -2,17 +2,15 @@ ...@@ -2,17 +2,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms from django import forms
from django.shortcuts import reverse, redirect
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import ListView
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic.edit import CreateView, UpdateView, FormMixin from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.detail import DetailView
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from common.utils import get_logger 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 ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin from ..utils import AdminUserRequiredMixin
from .. import forms from .. import forms
...@@ -39,9 +37,7 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie ...@@ -39,9 +37,7 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
form_class = forms.UserGroupForm form_class = forms.UserGroupForm
template_name = 'users/user_group_create_update.html' template_name = 'users/user_group_create_update.html'
success_url = reverse_lazy('users:user-group-list') success_url = reverse_lazy('users:user-group-list')
success_message = _( success_message = create_success_msg
'User group <a href={url}> {name} </a> was created successfully'
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -51,21 +47,13 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie ...@@ -51,21 +47,13 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) 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, SuccessMessageMixin, UpdateView):
class UserGroupUpdateView(AdminUserRequiredMixin, UpdateView):
model = UserGroup model = UserGroup
form_class = forms.UserGroupForm form_class = forms.UserGroupForm
template_name = 'users/user_group_create_update.html' template_name = 'users/user_group_create_update.html'
success_url = reverse_lazy('users:user-group-list') success_url = reverse_lazy('users:user-group-list')
success_message = update_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
users = User.objects.all() users = User.objects.all()
......
...@@ -53,7 +53,11 @@ class UserLoginView(FormView): ...@@ -53,7 +53,11 @@ class UserLoginView(FormView):
if not self.request.session.test_cookie_worked(): if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again.")) return HttpResponse(_("Please enable cookies and try again."))
auth_login(self.request, form.get_user()) 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', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay( write_login_log_async.delay(
self.request.user.username, type='W', self.request.user.username, type='W',
...@@ -184,7 +188,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): ...@@ -184,7 +188,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
user.is_public_key_valid = True user.is_public_key_valid = True
user.save() user.save()
context = { 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) return render(self.request, 'users/first_login_done.html', context)
...@@ -215,7 +219,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): ...@@ -215,7 +219,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
class LoginLogListView(DatetimeSearchMixin, ListView): class LoginLogListView(DatetimeSearchMixin, ListView):
template_name = 'users/login_log_list.html' template_name = 'users/login_log_list.html'
model = LoginLog model = LoginLog
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
user = keyword = "" user = keyword = ""
date_to = date_from = None date_to = date_from = None
......
...@@ -27,12 +27,14 @@ from django.views.generic.detail import DetailView, SingleObjectMixin ...@@ -27,12 +27,14 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth import logout as auth_logout 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 .. import forms
from ..models import User, UserGroup from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin from ..utils import AdminUserRequiredMixin
from ..signals import on_user_created from ..signals import on_user_created
from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid
__all__ = [ __all__ = [
'UserListView', 'UserCreateView', 'UserDetailView', 'UserListView', 'UserCreateView', 'UserDetailView',
...@@ -63,7 +65,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): ...@@ -63,7 +65,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
form_class = forms.UserCreateUpdateForm form_class = forms.UserCreateUpdateForm
template_name = 'users/user_create.html' template_name = 'users/user_create.html'
success_url = reverse_lazy('users:user-list') 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
...@@ -74,22 +76,16 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): ...@@ -74,22 +76,16 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
user = form.save(commit=False) user = form.save(commit=False)
user.created_by = self.request.user.username or 'System' user.created_by = self.request.user.username or 'System'
user.save() user.save()
on_user_created.send(self.__class__, user=user)
return super().form_valid(form) 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 model = User
form_class = forms.UserCreateUpdateForm form_class = forms.UserCreateUpdateForm
template_name = 'users/user_update.html' template_name = 'users/user_update.html'
context_object_name = 'user_object' context_object_name = 'user_object'
success_url = reverse_lazy('users:user-list') success_url = reverse_lazy('users:user-list')
success_message = update_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = {'app': _('Users'), 'action': _('Update user')} context = {'app': _('Users'), 'action': _('Update user')}
...@@ -332,17 +328,10 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView): ...@@ -332,17 +328,10 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
model = User model = User
form_class = forms.UserProfileForm form_class = forms.UserProfileForm
success_url = reverse_lazy('users:user-profile') success_url = reverse_lazy('users:user-profile')
success_message = _('Create user <a href="{url}">{name}</a> successfully.')
def get_object(self, queryset=None): def get_object(self, queryset=None):
return self.request.user 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): def get_context_data(self, **kwargs):
context = { context = {
'app': _('User'), 'app': _('User'),
......
...@@ -17,14 +17,6 @@ class Config: ...@@ -17,14 +17,6 @@ class Config:
# SECURITY WARNING: keep the secret key used in production secret! # 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' 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 # Django security setting, if your disable debug model, you should setting that
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
...@@ -65,40 +57,6 @@ class Config: ...@@ -65,40 +57,6 @@ class Config:
'port': REDIS_PORT, '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): def __init__(self):
pass pass
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册